Mercurial > hg > batmud > maputils
view src/mkloc.c @ 2470:d0aad04c3e61
th-libs now uses stdbool.h if possible, so we need to rename all
BOOL/TRUE/FALSE to bool/true/false.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 07 Dec 2022 13:23:46 +0200 |
parents | ad34b6c78af2 |
children | 84abab47bb82 |
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-2022 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 }; // 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; 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 }, }; 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; default: THERR("Unknown option '%s'.\n", currArg); return false; } return true; } int locPrintType(FILE *outFile, LocMarker *loc, bool adjust, int (*func)(const char *, FILE *), 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) { 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 *tmp = lp->locations[i]; len = strlen(tmp->names[0].name); if (optLabelType) len += locPrintType(NULL, tmp, true, NULL, false); // Compute text location switch (tmp->align) { case LOCD_LEFTDOWN: yc = tmp->yc + 1; x0 = tmp->xc - len; break; case LOCD_LEFT: yc = tmp->yc - 1; x0 = tmp->xc - len; break; case LOCD_DOWN: yc = tmp->yc + 1; x0 = tmp->xc + 1; break; case LOCD_NONE: default: yc = tmp->yc - 1; x0 = tmp->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 tmp->xc = x0; tmp->yc = yc; } } /* Check for adjacent markers */ int checkForAdjacent(MapLocations *world, int cx, int cy, int mask) { int x, y, n; for (y = -1; y <= 1; y++) for (x = -1; x <= 1; x++) { if (!(y == 0 && x == 0) && (n = locFindByCoords(world, cx + x, cy + y, true)) >= 0) { 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(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++; } 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 *l) { for (int i = 0; i < l->nlocations; i++) { LocMarker *loc = l->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(l->locations, l->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, MapBlock *map, 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 *tmp = lp->locations[n]; char chm = dp[xc]; switch (tmp->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 (tmp->flags & LOCF_INVALID) chm = '$'; break; } fputc(0xfb, outFile); fprintf(outFile, "mloc%d_%d", tmp->ox + 1, tmp->oy + 1); fputc(0xfc, outFile); fputc(chm, outFile); fputc(0xfe, outFile); } else if (!optNoLabels && (n = locFindByCoords(lp, xc, yc, false)) >= 0) { LocMarker *tmp = lp->locations[n]; if ((tmp->flags & LOCF_INVIS) == 0) { int col = col_light_white; fputc(0xff, outFile); fprintf(outFile, "loc%d_%d", tmp->ox + 1, tmp->oy + 1); 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; } if (tmp->flags & LOCF_CLOSED) col = col_light_red; fputc(col, outFile); if (optLabelType) { xc += locPrintType(outFile, tmp, !optNoAdjust, NULL, false); } fputs(tmp->names[0].name, outFile); fputc(0xfe, outFile); if (!optNoAdjust) xc += strlen(tmp->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, MapLocations *lp) { assert(l != NULL); fprintf(outFile, "<option value=\"\">-</option>\n"); for (int i = 0; i < lp->nlocations; i++) { LocMarker *tmp = lp->locations[i]; if (tmp->flags & LOCF_INVIS) continue; fprintf(outFile, "<option value=\"loc%d_%d\">", tmp->ox + 1, tmp->oy + 1); locPrintType(outFile, tmp, false, fputse, true); fprintf(outFile, "</option>\n"); } } /* Output generated locations into given file stream */ void printLocNameEsc(FILE *outFile, LocName *name) { fputs(locGetAreaCreatorRole(name->flags, false), outFile); fputsesc2(name->name, outFile); } 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 *tmp = lp->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].name); } } fprintf(outFile, "%d\t; %d\t; %d", tmp->ox + 1, tmp->oy + 1, tmp->align); 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_CLOSED) fputc('!', outFile); if (tmp->flags & LOCF_INSTANCED) fputc('I', outFile); if (tmp->flags & LOCF_INVIS) fputc('-', outFile); fprintf(outFile, "\t;"); printLocNameEsc(outFile, &tmp->names[0]); for (int n = 1; n < tmp->nnames; n++) { fprintf(outFile, "|"); printLocNameEsc(outFile, &tmp->names[n]); } fprintf(outFile, ";"); if (tmp->ncreators > 0) { printLocNameEsc(outFile, &tmp->creators[0]); for (int n = 1; n < tmp->ncreators; n++) { fprintf(outFile, ","); printLocNameEsc(outFile, &tmp->creators[n]); } } fprintf(outFile, ";"); if (tmp->valid) { fprintf(outFile, LOC_TIMEFMT, tmp->added.day, tmp->added.month, tmp->added.year); } fprintf(outFile, ";"); if (tmp->uri) fputsesc2(tmp->uri, outFile); fprintf(outFile, ";"); if (tmp->freeform) fputsesc2(tmp->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) { int 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 (int i = 0; i < lp->nlocations; i++) { LocMarker *tmp = lp->locations[i]; int xc, yc, leftMove; char *cs; // Is location visible? if (tmp->flags & LOCF_INVIS) continue; leftMove = ((float) strlen(tmp->names[0].name)) * optFontScale; switch (tmp->align) { case LOCD_LEFTDOWN: yc = tmp->yc + optUnitSize*3.0f; xc = tmp->xc - leftMove; break; case LOCD_LEFT: yc = tmp->yc - optUnitSize; xc = tmp->xc - leftMove; break; case LOCD_DOWN: yc = tmp->yc + optUnitSize*3.0f; xc = tmp->xc + optUnitSize; break; case LOCD_NONE: default: yc = tmp->yc - optUnitSize; xc = tmp->xc + optUnitSize; break; } // Determine colour switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_CITY: cs = "'#880000'"; 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; } if (tmp->flags & LOCF_CLOSED) cs = "'#ff0000'"; numTotal++; // Location marker fprintf(outFile, "\t-fill black -draw \"circle %d,%d %d,%d\" ", tmp->xc, tmp->yc, (int) (tmp->xc + optUnitSize), (int) (tmp->yc + optUnitSize)); fprintf(outFile, "-fill %s -draw \"circle %d,%d %d,%d\" ", cs, tmp->xc, tmp->yc, (int) (tmp->xc + optUnitSize * 0.90f), (int) (tmp->yc + optUnitSize * 0.90f)); // Location description text if (!optNoLabels) { fprintf(outFile, "-fill %s -box '#00000080' -draw \"text %d,%d '", cs, xc, yc); fputsesc3(tmp->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 char *addQuestLink(char **buf, size_t *bufSize, size_t *bufLen, char *ptr, char *start, 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, LocMarker *tmp, int (*fpr)(FILE *, const char *fmt, ...), int (*fps)(const char *, FILE *)) { if (tmp->uri != NULL) { fpr(outFile, "<b><a target=\"_blank\" href=\"%s\">", tmp->uri); locPrintType(outFile, tmp, false, fps, true); fpr(outFile, "</a></b><br>"); } else { fpr(outFile, "<b>"); locPrintType(outFile, tmp, false, fps, true); fpr(outFile, "</b><br>"); } // Alternative names, if any if (tmp->nnames > 1) { fpr(outFile, "Also known as <i>"); for (int n = 1; n < tmp->nnames; n++) { fps(tmp->names[n].name, outFile); if (tmp->names[n].flags & NAME_ORIG) fprintf(outFile, " (*)"); if (n < tmp->nnames - 1) fprintf(outFile, " ; "); } fpr(outFile, "</i>.<br>"); } // Added to game timestamp if (tmp->valid) { fpr(outFile, "Added " LOC_TIMEFMT ".<br>", tmp->added.day, tmp->added.month, tmp->added.year); } // Creator names or societies if (tmp->ncreators > 0) { if (tmp->flags & LOCF_M_PCITY) { fprintf(outFile, "Societies: "); for (int n = 0; n < tmp->ncreators; n++) { fps(tmp->creators[n].name, outFile); if (n < tmp->ncreators - 1) fprintf(outFile, ", "); } } else { fprintf(outFile, "Creators: "); for (int n = 0; n < tmp->ncreators; n++) { char *info = "", *sinfo = ""; switch (tmp->creators[n].flags) { case CREATOR_ORIG: info = " (O)"; sinfo = "Original coder"; break; case CREATOR_RECODER: info = " (R)"; sinfo = "Re-coder"; break; case CREATOR_MAINTAINER: info = " (M)"; sinfo = "Maintainer"; break; case CREATOR_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>", tmp->creators[n].name, sinfo, tmp->creators[n].name, info); if (n < tmp->ncreators - 1) fprintf(outFile, ", "); } } fps(".<br>", outFile); } // Print out freeform information field if (tmp->freeform) { char *ptr = tmp->freeform; char *buf = NULL; size_t bufLen = 0, bufSize = 0; while (*ptr != 0) { 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 *tmp = lp->locations[i]; // Skip disabled / invisible locations if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; // Coordinates and label direction/alignment fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\"", tmp->ox, tmp->oy, tmp->align); // Primary location name fprintf(outFile, " name=\""); locPrintType(outFile, tmp, false, fputse, false); fprintf(outFile, "\""); // HTML fprintf(outFile, " html=\""); outputGMapsHTML(outFile, tmp, fprintfe, fputse); fprintf(outFile, "\""); // Flags fprintf(outFile, " flags=\"%d\"", tmp->flags); // Continent name if (tmp->file != NULL && tmp->file->continent != NULL) { fprintf(outFile, " continent=\""); fputse(tmp->file->continent, outFile); fprintf(outFile, "\""); } // Type of the marker fprintf(outFile, " type=\"%s\"", locGetTypeName(tmp->flags)); // Added to game timestamp if (tmp->valid) { fprintf(outFile, " added=\"" LOC_TIMEFMT "\"", tmp->added.day, tmp->added.month, tmp->added.year); } fprintf(outFile, ">"); // Location alternative names if (tmp->nnames > 1) { fprintf(outFile, "<altnames>"); for (int n = 1; n < tmp->nnames; n++) { const char *tmps = locGetAreaNameType(tmp->names[n].flags, true); fprintf(outFile, "<name"); if (tmps != NULL) fprintf(outFile, " type=\"original\""); fprintf(outFile, ">"); fputse(tmp->names[n].name, outFile); fprintf(outFile, "</name>"); } fprintf(outFile, "</altnames>"); } // Creators or secret societies if (tmp->ncreators > 0) { if (tmp->flags & LOCF_M_PCITY) { fprintf(outFile, "<societies>"); for (int n = 0; n < tmp->ncreators; n++) { fprintf(outFile, "<name>"); fputse(tmp->creators[n].name, outFile); fprintf(outFile, "</name>"); } fprintf(outFile, "</societies>"); } else { fprintf(outFile, "<creators>"); for (int n = 0; n < tmp->ncreators; n++) { fprintf(outFile, "<creator role=\"%s\">", locGetAreaCreatorRole(tmp->creators[n].flags, true)); fputse(tmp->creators[n].name, outFile); fprintf(outFile, "</creator>"); } fprintf(outFile, "</creators>"); } } 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 *tmp = lp->locations[i]; // Skip disabled / invisible locations if (tmp->flags & (LOCF_INVIS | LOCF_INVALID)) continue; // Print out coordinates etc. fprintf(outFile, "{\"x\":%d,\"y\":%d,\"labeldir\":%d,\"name\":\"", tmp->ox, tmp->oy, tmp->align); // Location name locPrintType(outFile, tmp, false, fputsesc1, false); fprintf(outFile, "\",\"html\":\""); outputGMapsHTML(outFile, tmp, fprintfesc1, fputsesc1); // Flags fprintf(outFile, "\",\"flags\":%d", tmp->flags); // Continent name if (tmp->file != NULL && tmp->file->continent != NULL) { fprintf(outFile, ",\"continent\":\""); fputsesc1(tmp->file->continent, outFile); fprintf(outFile, "\""); } fprintf(outFile, "}%s\n", (i < lp->nlocations - 1) ? "," : ""); } fprintf(outFile, "]\n"); } int main(int argc, char *argv[]) { FILE *outFile = 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)) goto exit; // Check the mode and arguments if (optInFilename == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) { THERR("Map file required for location update mode!\n"); goto exit; } if (optOutput == OUTFMT_LOCFILE && noptLocFiles < 0 && !optGetUpdateLoc) { THERR("Location file or location update mode required for location file output!\n"); goto exit; } if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && noptLocFiles < 0) { THERR("Location file required for script or MapLoc HTML output!\n"); goto exit; } if (optInFilename == NULL && optOutput == OUTFMT_MAP) { THERR("Mapfile required for map generation.\n"); goto exit; } // 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"); goto exit; } THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->width, worldMap->height); } // Read location info for (int i = 0; i <= noptLocFiles; i++) { LocFileInfo *f = &optLocFiles[i]; FILE *inFile; if (optOutput == OUTFMT_GMAPS && f->continent == NULL) { THERR("Required continent name not set for #%d '%s'.\n", i, f->filename); goto exit; } THMSG(2, "Reading location info '%s'\n", f->filename); if ((inFile = fopen(f->filename, "rb")) == NULL) { THERR("Could not open location file '%s' for reading.\n", f->filename); goto exit; } if (!locParseLocStream(inFile, f, &worldLoc, f->xoffs, f->yoffs)) goto exit; fclose(inFile); } // 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 *tmp = worldLoc.locations[i]; tmp->xc = ((float) tmp->xc) * optScale; tmp->yc = ((float) tmp->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); goto exit; } // 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; } exit: if (outFile != NULL) fclose(outFile); mapBlockFree(worldMap); locFreeMapLocations(&worldLoc); return 0; }