view src/mkcitymap.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 7677cfceded0
children
line wrap: on
line source

/*
 * Create interactive XHTML+CSS+JS map from input
 * 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"


char    *optDestFilename = NULL,
        *optMapFilename = NULL,
        *optLocFilename = NULL,
        *optHeadFilename = NULL,
        *optMapTitle = NULL;
bool    optUseOldFormat = false;


/* Arguments
 */
static const th_optarg optList[] =
{
    { 0, '?', "help",           "Show this help", OPT_NONE },
    { 1, 'v', "verbose",        "Be more verbose", OPT_NONE },
    { 2, 'o', "output",         "Output filename", OPT_ARGREQ },
    { 3, 't', "title",          "Map title", OPT_ARGREQ },
    { 4, 'h', "header-file",    "Specify extra <head> data in file", OPT_ARGREQ },
    { 5, 'O', "old-format",     "Input using old symbols/colors", OPT_NONE },
};

const int optListN = sizeof(optList) / sizeof(optList[0]);


void argShowHelp(void)
{
    th_print_banner(stdout, th_prog_name,
        "[options] <mapfile> <locfile>");

    th_args_help(stdout, optList, optListN, 0, 80 - 2);
}


bool argHandleOpt(const int optN, char *optArg, char *currArg)
{
    switch (optN)
    {
    case 0:
        argShowHelp();
        exit(0);
        break;

    case 1:
        th_verbosity++;
        break;

    case 2:
        optDestFilename = optArg;
        THMSG(2, "Output file set to '%s'.\n", optDestFilename);
        break;

    case 3:
        optMapTitle = optArg;
        THMSG(2, "Map title string set to '%s'.\n", optMapTitle);
        break;

    case 4:
        optHeadFilename = optArg;
        THMSG(2, "Head filename set to '%s'.\n", optHeadFilename);
        break;

    case 5:
        optUseOldFormat = true;
        THMSG(2, "Input is using old map symbols/colors.\n");
        break;


    default:
        THERR("Unknown option '%s'.\n", currArg);
        return false;
    }

    return true;
}


bool argHandleFile(char *currArg)
{
    if (!optMapFilename)
        optMapFilename = currArg;
    else
    if (!optLocFilename)
        optLocFilename = currArg;
    else
    {
        THERR("Too many filenames specified!\n");
        return false;
    }

    return true;
}


void outputHTMLHeader(FILE *outFile, const MapLocations *locs)
{
    (void) locs;

    muPrintHTMLhead(outFile, optMapTitle, true);

    if (optHeadFilename != NULL &&
        muCopyFileToStream(outFile, optHeadFilename) < 0)
    {
        THERR("Error copying urchin file '%s' to output.\n", optHeadFilename);
    }

    fprintf(outFile, " <style type=\"text/css\">\n");
    muPrintHTMLcolors(outFile, "span", NULL, NULL);
    fprintf(outFile, " </style>\n");


    fprintf(outFile,
        "</head>\n"
        "<body onload=\"mapOnLoad();\">\n"
        );

    if (optMapTitle)
    {
        fprintf(outFile, "<h1>");
        fputse(optMapTitle, outFile);
        fprintf(outFile, "</h1>\n");
    }

    fprintf(outFile,
        "<div id=\"smap\" class=\"%s\">"
        "<pre class=\"map\">",
        optUseOldFormat ? "old" : "new"
        );
}


const char *getCityLocationType(const LocMarker *marker)
{
    switch (marker->flags & LOCF_M_MASK)
    {
//        case LOCF_M_CITY:   return "special";
        case LOCF_M_PCITY:  return "gov";
        default:
            switch (marker->flags & LOCF_T_MASK)
            {
                case LOCF_T_SHRINE:  return "shop";
                case LOCF_T_GUILD:   return "guild";
/*
                case LOCF_T_SS:      return "ss";
                case LOCF_T_MONSTER: return "monster";
                case LOCF_T_TRAINER: return "trainer";
                case LOCF_T_FORT:    return "fort";
*/
            }
            break;
    }

    return "default";
}


static char *getExtraNameData(const LocMarker *marker)
{
    return th_strdup_printf(
        "%s",
        (marker->flags & LOCF_CLOSED) ? " [CLOSED]" : ""
        );
}


static int compareLocation(const void *pp1, const void *pp2)
{
    const LocMarker
        *vp1 = *(const LocMarker **) pp1,
        *vp2 = *(const LocMarker **) pp2;

    if (vp1->flags == vp2->flags)
        return th_strcasecmp(vp1->names[0].name, vp2->names[0].name);
    else
        return (vp1->flags & LOCF_MASK) - (vp2->flags & LOCF_MASK);
}


void outputHTMLFooter(FILE *outFile, MapLocations *locs)
{
    char *tmps;

    fprintf(outFile,
    "</pre>"
    "</div>\n"
    "<div id=\"loclist\">\n"
    );

    qsort(locs->locations, locs->nlocations, sizeof(LocMarker *), compareLocation);

    for (int n = 0; n < locs->nlocations; n++)
    {
        const LocMarker *marker = locs->locations[n];
        if (marker->flags & LOCF_INVIS)
            continue;

        if ((tmps = getExtraNameData(marker)) == NULL)
            return;

        fprintf(outFile,
            "<a class=\"item %s%s lt%s lt%d\" id=\"listloc%d_%d\" href=\"#%d_%d\" "
            "data-id=\"%d_%d\" data-info=\"%s\" data-tt-title=\"%s%s\" ",
            "ltprimary",
            marker->nnames > 1 ? " ltsubitems" : "",
            getCityLocationType(marker),
            marker->align,
            marker->xc, marker->yc,
            marker->xc, marker->yc,
            marker->xc, marker->yc,
            marker->freeform ? "true" : "false",
            marker->names[0].name, tmps);

        if (marker->nnames > 1)
        {
            fprintf(outFile, "data-tt-names=\"");
            for (int i = 1; i < marker->nnames; i++)
            {
                fputse(marker->names[i].name, outFile);
                fprintf(outFile, "%s",
                    i + 1 < marker->nnames ? " ; " : "");
            }
            fprintf(outFile, "\" ");
        }

        if (marker->freeform)
        {
            fprintf(outFile, "data-tt-freeform=\"");
            fputse(marker->freeform, outFile);
            fprintf(outFile, "\"");
        }

        fprintf(outFile, ">");
        fputse(marker->names[0].name, outFile);
        fputse(tmps, outFile);
        fprintf(outFile, "</a>\n");

        for (int i = 1; i < marker->nnames; i++)
        {
            fprintf(outFile,
                "  <a class=\"item %s lt%s lt%d\" href=\"#%d_%d\" "
                "data-id=\"%d_%d\">",
                "ltsecondary",
                getCityLocationType(marker),
                marker->align,
                marker->xc, marker->yc,
                marker->xc, marker->yc);

            fputse(marker->names[i].name, outFile);
            fprintf(outFile, "</a>\n");
        }

        th_free(tmps);
    }

    fprintf(outFile,
    "</div>\n"
    "</body>\n"
    "</html>\n"
    );
}


void outputMapHTML(FILE *outFile, const MapBlock *map, const MapLocations *locs)
{
    int n, p = -1;
    bool span = false;

    for (int yc = 0; yc < map->height; yc++)
    {
        const unsigned char *dp = map->data + (map->scansize * yc);
        for (int xc = 0; xc < map->width; xc++)
        {
            if ((n = locFindByCoords(locs, xc, yc, true)) >= 0)
            {
                LocMarker *marker = locs->locations[n];
                unsigned char chr;
                int color;

                if (span)
                    fprintf(outFile, "</span>");

                if (marker->uri != NULL)
                {
                    // If URI/URL is set, parse piece color from it
                    char *endptr = NULL;
                    color = strtol(marker->uri, &endptr, 10);
                    if (color < 0 || color >= nmapColors)
                        color = 0;

                    // If there is extra character after color number,
                    // use it as the map patch
                    if (endptr != NULL && *endptr != 0)
                        chr = *endptr;
                    else
                        chr = *dp;
                }
                else
                {
                    // If old format, use map piece color
                    if (optUseOldFormat)
                        color = muGetMapPieceColor(*dp, optUseOldFormat, true);
                    else
                    // Otherwise use "link" color
                        color = col_light_red;

                    chr = *dp;
                }

                if (marker->flags & LOCF_INVIS)
                {
                    fprintf(outFile,
                        "<span class=\"%c\">%c</span>",
                        'a' + color, chr);
                }
                else
                {
                    fprintf(outFile,
                    "<span class=\"%c\">"
                    "<a class=\"loc\" id=\"maploc%d_%d\" data-id=\"%d_%d\" "
                    "href=\"#%d_%d\">%c</a></span>",
                    'a' + color,
                    marker->xc, marker->yc,
                    marker->xc, marker->yc,
                    marker->xc, marker->yc,
                    chr);
                }
                span = false;
                p = -1;
            }
            else
            {
                if (p != *dp)
                {
                    int color = muGetMapPieceColor(*dp, optUseOldFormat, true);
                    if (span) fprintf(outFile, "</span>");
                    fprintf(outFile, "<span class=\"%c\">",
                        'a' + color);
                    span = true;
                }
                fputc(*dp, outFile);
                p = *dp;
            }
            dp++;
        }
        if (span) fprintf(outFile, "</span>\n");
        p = -1;
        span = false;
    }
}


int main(int argc, char *argv[])
{
    int res = 0;
    MapBlock *map = NULL;
    FILE *outFile = NULL, *inFile = NULL;
    LocFileInfo info;
    MapLocations locations;

    memset(&info, 0, sizeof(info));
    memset(&locations, 0, sizeof(locations));

    th_init("mkcitymap", "ASCII citymap converter", "0.5", NULL, NULL);
    th_verbosity = 0;

    // Parse arguments
    if (!th_args_process(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, OPTH_BAILOUT))
    {
        res = 1;
        goto out;
    }

    if (optMapFilename == NULL || optLocFilename == NULL)
    {
        argShowHelp();
        THERR("You need to specify at least map and loc filenames.\n");
        res = 1;
        goto out;
    }

    // Parse location file
    locSetFileInfo(&info, optLocFilename, NULL);
    if ((inFile = fopen(info.filename, "rb")) == NULL)
    {
        THERR("Could not open location file '%s' for reading.\n",
            info.filename);
        res = -3;
        goto out;
    }

    if (!locParseLocStream(inFile, &info, &locations, info.xoffs, info.yoffs))
    {
        res = -4;
        goto out;
    }


    // Open mapfile
    if ((map = mapBlockParseFile(optMapFilename, false)) == NULL)
    {
        THERR("Error parsing/opening mapfile '%s'.\n",
            optMapFilename);
        res = -5;
        goto out;
    }

    if (optDestFilename == NULL)
        outFile = stdout;
    else
    if ((outFile = fopen(optDestFilename, "wb")) == NULL)
    {
        THERR("Error opening output file '%s'!\n",
            optDestFilename);
        res = -6;
        goto out;
    }

    // Output data
    outputHTMLHeader(outFile, &locations);
    outputMapHTML(outFile, map, &locations);
    outputHTMLFooter(outFile, &locations);


out:
    // Close input and output files
    if (outFile != NULL && outFile != stdout)
        fclose(outFile);

    if (inFile != NULL)
        fclose(inFile);

    mapBlockFree(map);
    locFreeMapLocations(&locations);
    locFreeFileInfo(&info);

    return res;
}