Mercurial > hg > batmud > maputils
view src/colormap.c @ 2833:d0e186348cb2 default tip
Add mention of soft level limitation to 'Eightleg woods'.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sun, 26 May 2024 20:33:53 +0300 |
parents | f3c5cd908d6a |
children |
line wrap: on
line source
/* * Convert BatMUD ASCII map to different formats * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2006-2022 Tecnic Software productions (TNSP) */ #include "libmaputils.h" #include "th_args.h" #include "th_string.h" #define SET_MAX_COLORS (256) #define SET_MAX_STRLEN (1024) typedef struct { char *fmtName; char *fmtDescription; bool supBackColor; void (*putTagMarker) (FILE *fh, const char *str, const int color, const int marker); void (*putTagLocation) (FILE *fh, const char *str, const int color); void (*putTagLocationEnd) (FILE *fh, const char *str); void (*putTagStart) (FILE *fh, const int color); void (*putTagEnd) (FILE *fh); bool (*putTagEOL) (FILE *fh); void (*putFileStart) (FILE *fh); void (*putFileEnd) (FILE *fh); int (*putString) (const char *str, FILE *fh); } CMapOutFormat; char *inFilename = NULL, *outFilename = NULL; char *optXHTMLTagName = NULL, *optMapTitle = NULL, *optUrchinFile = NULL; char *optUseJS = NULL; bool optPerformAnalysis = false, optUseOldFormat = false, optCheatMode = false, optNoHeaders = false, optPosGlue = false, optCityFormat = false; int optOutputFormat = 0, optBackColor = 0; /* Arguments */ static const th_optarg optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 'q', "quiet", "Be quiet", OPT_NONE }, { 3, 'T', "html-tag", "XHTML tag used for map", OPT_ARGREQ }, { 4, 'a', "analysis", "Perform statistical analysis", OPT_NONE }, { 6, 'O', "old-format", "Input using old symbols/colors", OPT_NONE }, { 7, 'o', "output", "Output filename", OPT_ARGREQ }, { 8, 'f', "format", "Specify output format", OPT_ARGREQ }, { 9, 't', "title", "Map title", OPT_ARGREQ }, { 10,'C', "cheat-mode", "Use cheating in HTML", OPT_NONE }, { 11,'n', "no-headers", "Do not output headers/footers", OPT_NONE }, { 12,'P', "pos-glue", "Generate JavaScript, etc. for posglue", OPT_NONE }, { 13,'c', "city-format", "Input is a city map", OPT_NONE }, { 14,'u', "urchin-file", "Specify urchin file", OPT_ARGREQ }, { 15,'J', "use-js", "Include javascript file", OPT_ARGREQ }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); /* * ANSI output format functions */ void putTagStartANSI(FILE *fh, const int c) { fputc(0x1b, fh); if (c < 0) { fprintf(fh, "[0;%d;30m", 10 + mapColors[-c-1].ansi); } else { fprintf(fh, "[0;%d;%dm", mapColors[c].ansiAttr, mapColors[c].ansi); } } void putTagEndANSI(FILE *fh) { (void) fh; /* fputc(0x1b, fh); fprintf(fh, "[0m"); */ } bool putEOLANSI(FILE *fh) { fprintf(fh, "\n"); return true; } void putFileStartANSI(FILE *fh) { fputc(0x1b, fh); fprintf(fh, "[0m"); if (optMapTitle) { fprintf(fh, "%s\n", optMapTitle); } } void putFileEndANSI(FILE *fh) { fputc(0x1b, fh); fprintf(fh, "[0m"); } /* * ASCII output format functions */ void putTagStartText(FILE *fh, const int c) { (void) fh; (void) c; // fputc(0xa7, fh); } void putTagEndText(FILE *fh) { (void) fh; // fprintf(fh, "$"); } bool putEOLText(FILE *fh) { fprintf(fh, "\n"); return false; } void putFileStartText(FILE *fh) { if (optMapTitle) { fprintf(fh, "%s\n", optMapTitle); } } /* * XHTML+CSS output format functions */ #define XHTML_LOCLABEL "label" #define XHTML_LOCMARKER "marker" void putColorClassXHTML(FILE *fh, const int color) { int q = 'a' + color; if (optCheatMode) fprintf(fh, "class=%c>", q); else fprintf(fh, "class=\"%c\">", q); } void putTagMarkerXHTML(FILE *fh, const char *locID, const int color, const int marker) { fprintf(fh, "<i class=\"" XHTML_LOCMARKER " %c\" id=\"%s\">%c", 'a' + color, locID, marker); } void putTagLocationXHTML(FILE *fh, const char *locID, const int color) { fprintf(fh, "<i class=\"" XHTML_LOCLABEL "\"><a id=\"%s\" ", locID); putColorClassXHTML(fh, color); } void putTagLocationEndXHTML(FILE *fh, const char *locID) { (void) locID; fprintf(fh, "</a>"); } void putTagStartXHTML(FILE *fh, const int c) { fprintf(fh, "<i "); putColorClassXHTML(fh, c); } void putTagEndXHTML(FILE *fh) { fprintf(fh, "</i>"); } bool putEOLXHTML(FILE *fh) { fprintf(fh, "\n"); return false; } void putFileGenericHTML(FILE *fh) { char buf[32]; if (optUseJS != NULL) { fprintf(fh, " <script type=\"text/javascript\" src=\"%s\"></script>\n", optUseJS); } fprintf(fh, " <style type=\"text/css\">\n" " <!--\n" " %s." XHTML_LOCLABEL " { background: white; color: black; %s}\n" " body { background: black; color: %s; font-family: Arial, Verdana, sans-serif; }\n" " h2 { color: white; font-size: 14pt; }\n" " pre { font-size: 10pt; }\n" " %s { text-decoration: none; font-style: normal; }\n", optXHTMLTagName, (optPosGlue) ? "position: relative; " : "", muColorToCSSColor(buf, sizeof(buf), optBackColor), optXHTMLTagName ); muPrintHTMLcolors(fh, optXHTMLTagName, NULL, NULL); muPrintHTMLcolors(fh, "a", "background", " color: black;"); if (optPosGlue) { fprintf(fh, " div.controls {\n" " position: fixed;\n" " top: 0.5em;\n" " right: 1em;\n" " width: 25em;\n" " height: 6em;\n" " margin: 0;\n" " padding: 0.5em;\n" " z-index: 10;\n" " background: #bbb;\n" " color: black;\n" // " border: 2px solid gray;\n" " font-size: 10pt;\n" " }\n" " i." XHTML_LOCLABEL " a {\n" " position: absolute;\n" " top: 0px;\n" " left: 0px;\n" " z-index: 2;\n" " border: 2px solid black;\n" " border-radius: 0.35em;\n" " padding: 0.15em;\n" " box-shadow: 2px 2px 4px black;\n" " }\n" " div.sbtitle {\n" " font-size: 1.5em;\n" " font-weight: bold;\n" " }\n" " i.label a.nactive {\n" " border: 2px solid rgba(100,0,0, 0.5);\n" " background: rgba(255,0,0, 0.7);\n" " color: rgba(255,255,255, 0.8);\n" " text-shadow: 1px 1px 1px #000;\n" " font-weight: bold;\n" " transform: scale(1.1);\n" " }\n" " i.marker.nactive {\n" " animation: pulse 1s ease infinite;\n" " }\n" " @keyframes pulse {\n" " 0%% { background: #000; }\n" " 50%% { background: #f00; }\n" " 100%% { background: #000; }\n" " }\n" " #smap {\n" " position: absolute;\n" " top: 0;\n" " bottom: 0;\n" " left: 0;\n" " right: 0;\n" " overflow: auto;\n" " }\n" ); } fprintf(fh, " -->\n" " </style>\n" ); if (optUrchinFile && muCopyFileToStream(fh, optUrchinFile) < 0) { THERR("Error copying urchin file '%s' to output!\n", optUrchinFile); } fprintf(fh, "</head>\n"); if (optUseJS != NULL) { fprintf(fh, "<body onload=\"mapOnLoad();\">\n"); } else { fprintf(fh, "<body>\n"); } if (!optPosGlue) { if (optMapTitle) { fprintf(fh, "<h2>"); fputse(optMapTitle, fh); fprintf(fh, "</h2>\n"); } } else { fprintf(fh, "<div id=\"sbox\" class=\"controls\">\n" " <div class=\"sbtitle\">"); fputse(optMapTitle, fh); fprintf(fh, "</div>\n" " <select id=\"slocation\" autofocus=\"autofocus\">\n" "@LOCATIONS@\n" " </select>\n" " <div id=\"sbuttons\">\n" " <input id=\"slabels\" type=\"checkbox\"%s><label for=\"slabels\">Labels</label>\n" " <input id=\"spanning\" type=\"checkbox\"%s><label for=\"spanning\">Enable drag panning</label>\n" " </div>\n" "</div>\n", (1 ? " checked=\"checked\"" : ""), (1 ? " checked=\"checked\"" : "") ); } fprintf(fh, "<div id=\"smap\"><pre>\n" ); } void putFileStartXHTML(FILE *fh) { muPrintHTMLhead(fh, optMapTitle, false); putFileGenericHTML(fh); } void putFileEndXHTML(FILE *fh) { // XHTML document end tags fprintf(fh, "</pre></div>\n" "</body>\n" "</html>\n" ); } /* * XHTML+CSS output format functions */ void putFileStartHTML5(FILE *fh) { muPrintHTMLhead(fh, optMapTitle, true); putFileGenericHTML(fh); } /* Process a normal format input */ void checkEndTag(FILE *fh, const CMapOutFormat *fmt, const int prevColor) { if (prevColor != -1 && (!fmt->supBackColor || prevColor != optBackColor)) fmt->putTagEnd(fh); } bool getTagStr(FILE *fh, char *tmpStr, const size_t len, const int endch) { size_t i; int mch = EOF; for (i = 0; i + 1 < len && (mch = fgetc(fh)) != EOF;) { if (mch == endch) break; else tmpStr[i++] = mch; } tmpStr[i] = 0; if (mch == EOF) { THERR("Unexpected end of file.\n"); return false; } else if (mch != endch) { THERR("No end tag 0x%02x found.\n", endch); return false; } return true; } bool processData(FILE *inFile, FILE *outFile, const CMapOutFormat *fmt) { int k, currColor, prevColor; currColor = prevColor = -1; while ((k = fgetc(inFile)) != EOF) { if (k == '\n') { if (fmt->putTagEOL(outFile)) currColor = -1; } else if (k == 0xfb) { char tmpStr[SET_MAX_STRLEN]; int mch; // Location marker mode checkEndTag(outFile, fmt, prevColor); // Get location marker tag if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) return false; // Get marker character mch = fgetc(inFile); k = fgetc(inFile); if (k != 0xfe) { THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); return false; } currColor = muGetMapPieceColor(mch, optUseOldFormat, optCityFormat); if (fmt->putTagMarker != NULL) fmt->putTagMarker(outFile, tmpStr, currColor, mch); else { if (fmt->putTagStart) fmt->putTagStart(outFile, currColor); fprintf(outFile, "%c", mch); } currColor = prevColor = -2; } else if (k == 0xff) { char tmpStr[SET_MAX_STRLEN], tmpStr2[SET_MAX_STRLEN]; int mcol; // Location title mode checkEndTag(outFile, fmt, prevColor); // Get location marker tag if (!getTagStr(inFile, tmpStr, SET_MAX_STRLEN, 0xfc)) return false; // Get color mcol = fgetc(inFile); if (mcol == EOF) { THERR("Unexpected end of file (location tag colour).\n"); return false; } // Get location name if (!getTagStr(inFile, tmpStr2, SET_MAX_STRLEN, 0xfe)) { THERR("Expected location tag '%s' end, but did not find one.\n", tmpStr); return false; } if (!fmt->putTagLocation && fmt->putTagStart) fmt->putTagStart(outFile, -mcol - 1); if (fmt->putTagLocation) fmt->putTagLocation(outFile, tmpStr, mcol); if (fmt->putString) fmt->putString(tmpStr2, outFile); else fputs(tmpStr2, outFile); if (fmt->putTagLocationEnd) fmt->putTagLocationEnd(outFile, tmpStr); currColor = prevColor = -2; } else { currColor = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); if (currColor != prevColor) { checkEndTag(outFile, fmt, prevColor); if ((!fmt->supBackColor || currColor != optBackColor) && fmt->putTagStart) fmt->putTagStart(outFile, currColor); fprintf(outFile, "%c", k); } else { fprintf(outFile, "%c", k); } } prevColor = currColor; } checkEndTag(outFile, fmt, prevColor); return true; } /* Get a symbol */ char getSymbol(int i, bool useOld) { if (useOld) return mapPieces[i].oldSymbol; else return mapPieces[i].symbol; } /* List of output formats */ static const CMapOutFormat outputFormats[] = { { "xhtml", "XHTML+CSS", true, putTagMarkerXHTML, putTagLocationXHTML, putTagLocationEndXHTML, putTagStartXHTML, putTagEndXHTML, putEOLXHTML, putFileStartXHTML, putFileEndXHTML, fputse, }, { "html5", "HTML5+CSS", true, putTagMarkerXHTML, putTagLocationXHTML, putTagLocationEndXHTML, putTagStartXHTML, putTagEndXHTML, putEOLXHTML, putFileStartHTML5, putFileEndXHTML, fputse, }, { "ansi", "ANSI text", false, NULL, NULL, NULL, putTagStartANSI, putTagEndANSI, putEOLANSI, putFileStartANSI, putFileEndANSI, NULL, }, { "text", "Plain ASCII text", false, NULL, NULL, NULL, putTagStartText, putTagEndText, putEOLText, putFileStartText, NULL, NULL, }, }; static const int noutputFormats = sizeof(outputFormats) / sizeof(outputFormats[0]); void argShowHelp(void) { th_print_banner(stdout, th_prog_name, "[options] <input mapfile>"); th_args_help(stdout, optList, optListN, 0, 80 - 2); fprintf(stdout, "\nAvailable OUTPUT formats:\n"); for (int i = 0; i < noutputFormats; i++) { fprintf(stdout, " %-8s - %s %s\n", outputFormats[i].fmtName, outputFormats[i].fmtDescription, (i == optOutputFormat) ? "(default)" : "" ); } fprintf(stdout, "\n"); } bool argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: th_verbosity++; break; case 2: th_verbosity = -1; break; case 3: { char *tmp = th_strdup_trim(optArg, TH_TRIM_BOTH); if (tmp == NULL) { THERR("Can't set HTML tag to empty string.\n"); return false; } th_free(optXHTMLTagName); optXHTMLTagName = tmp; THMSG(2, "HTML tag set to '%s'.\n", optXHTMLTagName); } break; case 4: optPerformAnalysis = true; THMSG(2, "Analysis enabled.\n"); break; case 6: optUseOldFormat = true; THMSG(2, "Input is using old map symbols/colors.\n"); break; case 7: outFilename = optArg; THMSG(2, "Output file set to '%s'.\n", outFilename); break; case 8: { int i, n; // Match format from the list for (i = 0, n = -1; i < noutputFormats && n < 0; i++) if (th_strcasecmp(optArg, outputFormats[i].fmtName) == 0) n = i; if (n < 0) { THERR("Invalid output format '%s'.\n", optArg); return false; } optOutputFormat = n; THMSG(2, "Output format set to '%s'.\n", optArg); } break; case 9: optMapTitle = optArg; THMSG(2, "Map title string set to '%s'.\n", optMapTitle); break; case 10: optCheatMode = true; THMSG(2, "HTML cheats mode enabled!\n"); break; case 11: optNoHeaders = true; THMSG(2, "Not outputting headers/footers\n"); break; case 12: optPosGlue = true; THMSG(2, "Generating JavaScript junk for positioning glue\n"); break; case 13: optCityFormat = true; THMSG(2, "Input is handled as a city map\n"); break; case 14: optUrchinFile = optArg; THMSG(2, "Urchin filename set to '%s'.\n", optUrchinFile); break; case 15: optUseJS = optArg; THMSG(2, "Including Javascript bits.\n"); break; default: THERR("Unknown option '%s'.\n", currArg); return false; } return true; } bool argHandleFile(char *currArg) { if (!inFilename) inFilename = currArg; else { THERR("Too many input map files specified!\n"); return false; } return true; } /* Main program */ int main(int argc, char *argv[]) { int res = 0; FILE *inFile = NULL, *outFile = NULL; const CMapOutFormat *fmt = NULL; // Initialize th_init("colormap", "ASCII map colorizer", "0.5", NULL, NULL); th_verbosity = 0; optXHTMLTagName = th_strdup("i"); // Parse arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, OPTH_BAILOUT)) { res = 1; goto out; } if (inFilename == NULL) { argShowHelp(); THERR("Nothing to do.\n"); res = 1; goto out; } // Do statistical analysis, if needed if (optPerformAnalysis) { int k, c, p, i, tMax, fMax, tn, fn; int colChangesTo[SET_MAX_COLORS]; int colChangesFrom[SET_MAX_COLORS]; THMSG(1, "Performing statistical frequency analysis ...\n"); THMSG(2, "Reading '%s' for analysis data ...\n", inFilename); if ((inFile = fopen(inFilename, "rb")) == NULL) { THERR("Error opening input file '%s'!\n", inFilename); res = -9; goto out; } // Initialize counters memset(colChangesTo, 0, sizeof(colChangesTo)); memset(colChangesFrom, 0, sizeof(colChangesFrom)); // Read data, keeping statistics of colour change frequencies c = p = -1; while ((k = fgetc(inFile)) != EOF) { if (k != '\n' && k != ' ' && k < 0xfb) { c = muGetMapPieceColor(k, optUseOldFormat, optCityFormat); if (c != p && c >= 0 && c < SET_MAX_COLORS) { colChangesTo[c]++; if (p >= 0 && p < SET_MAX_COLORS) colChangesFrom[p]++; } } p = c; } fclose(inFile); inFile = NULL; // Find highest frequency THMSG(2, "Computing results.\n"); tMax = fMax = tn = fn = -1; for (i = 0; i < SET_MAX_COLORS; i++) { if (colChangesTo[i] > tMax) { tMax = colChangesTo[i]; tn = i; } if (colChangesFrom[i] > fMax) { fMax = colChangesFrom[i]; fn = i; } } THMSG(2, "tMax=%d, tn=%d -- fMax=%d, fn=%d\n", tMax, tn, fMax, fn); if (tMax > fMax) optBackColor = tn; else optBackColor = fn; THMSG(1, "Using colour %d as 'background' color.\n", optBackColor); } // Open input file if ((inFile = fopen(inFilename, "rb")) == NULL) { THERR("Error opening input file '%s'!\n", inFilename); goto out; } // Open output file if (outFilename == NULL) outFile = stdout; else if ((outFile = fopen(outFilename, "wb")) == NULL) { THERR("Error opening output file '%s'!\n", outFilename); goto out; } // Okay, let's process the shit fmt = &outputFormats[optOutputFormat]; THMSG(1, "Converting to '%s' ...\n", fmt->fmtName); if (!optNoHeaders && fmt->putFileStart) fmt->putFileStart(outFile); processData(inFile, outFile, fmt); if (!optNoHeaders && fmt->putFileEnd) fmt->putFileEnd(outFile); THMSG(1, "Done.\n"); out: if (outFile != NULL) fclose(outFile); if (inFile != NULL) fclose(inFile); th_free(optXHTMLTagName); return res; }