view src/libmaputils.c @ 2696:b294c2efea35

Add another color option to citymaps.
author Matti Hamalainen <ccr@tnsp.org>
date Sun, 03 Mar 2024 22:09:43 +0200
parents d0aad04c3e61
children
line wrap: on
line source

/*
 * maputils - Generic functions/tables for maputils package
 * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
 * (C) Copyright 2006-2022 Tecnic Software productions (TNSP)
 */
#include "libmaputils.h"


const MapColor mapColors[] =
{
    { 0x00, 0x00, 0x00, 30,    ANSI_OFF,  false }, // col_black
    { 0x00, 0x00, 0xaa, 34,    ANSI_OFF,  false }, // col_blue
    { 0xaa, 0x00, 0x00, 31,    ANSI_OFF,  false }, // col_red
    { 0xaa, 0xaa, 0x00, 33,    ANSI_OFF,  false }, // col_yellow
    { 0x00, 0xaa, 0x00, 32,    ANSI_OFF,  false }, // col_green
    { 0xaa, 0xaa, 0xaa, 37,    ANSI_OFF,  false }, // col_white
    { 0x00, 0xff, 0xff, 36,    ANSI_OFF,  false }, // col_cyan
    { 0x77, 0x00, 0x77, 35,    ANSI_OFF,  false }, // col_magenta

    { 0x77, 0x77, 0x77, 30,    ANSI_BOLD, false }, // col_light_black
    { 0x00, 0x00, 0xff, 34,    ANSI_BOLD, false }, // col_light_blue
    { 0xee, 0x00, 0x00, 31,    ANSI_BOLD, false }, // col_light_red
    { 0xff, 0xff, 0x00, 33,    ANSI_BOLD, false }, // col_light_yellow
    { 0x00, 0xff, 0x00, 32,    ANSI_BOLD, false }, // col_light_green
    { 0xff, 0xff, 0xff, 37,    ANSI_BOLD, false }, // col_light_white
    { 0x00, 0xff, 0xff, 36,    ANSI_BOLD, false }, // col_light_cyan
    { 0xff, 0x00, 0xff, 35,    ANSI_BOLD, false }, // col_light_magenta

    // extra colors, not in ANSI
    { 0xff, 0x00, 0x00, 31,    ANSI_BOLD, true  }, // col_very_light_red
    { 0x33, 0x33, 0x33, 30,    ANSI_OFF,  true  }, // col_very_dark_gray
};

const int nmapColors = sizeof(mapColors) / sizeof(mapColors[0]);


const MapPiece mapPieces[] =
{
    { '!', "Mountain Peak",     0xcc,0xff,0xff, col_white,          -1, },
    { '#', "Ruins",             0x88,0x88,0x88, col_light_black,    -1, },
    { '%', "Special Location",  0xff,0xff,0xff, col_light_white,    -1, },
    { '+', "Crossing",          0x33,0x33,0x33, col_light_black,    -1, },
    { '-', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
    { '|', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
    { '/', "Road",              0x33,0x33,0x33, col_light_black,    -1, },
    { '\\',"Road",              0x33,0x33,0x33, col_light_black,    -1, },
    { '.', "Plains",            0x55,0x92,0x00, col_green,          -1, },
    { '=', "Bridge",            0x33,0x33,0x33, col_light_black,    -1, },
    { '?', "Scenic Location",   0xff,0xff,0xff, col_light_white,    -1, },
    { '@', "Flowing Lava",      0xff,0x99,0x3f, col_very_light_red, -1, },
    { 'C', "Player City",       0x88,0x88,0x88, col_light_black,    -1, },
    { 'F', "Deep Forest",       0x00,0x88,0x00, col_green,          -1, },
    { 'H', "Highlands",         0x66,0x3f,0x00, col_magenta,        -1, },
    { 'L', "Lava Lake",         0xff,0x50,0x00, col_very_light_red, -1, },
    { 'R', "Deep River",        0x33,0x66,0xff, col_blue,           -1, },
    { 'V', "Volcano",           0xff,0x33,0x00, col_red,            -1, },
    { '^', "Mountain",          0x71,0x82,0x92, col_light_magenta,  -1, },
    { 'b', "Beach",             0xcf,0xc4,0xa5, col_yellow,         -1, },
    { 'c', "City",              0x88,0x88,0x88, col_light_black,    -1, },
    { 'd', "Desert",            0xee,0xaa,0x22, col_yellow,         -1, },
    { 'f', "Forest",            0x00,0xb6,0x00, col_light_green,    -1, },
    { 'h', "Hills",             0x99,0x66,0x00, col_magenta,        -1, },
    { 'i', "Ice",               0xee,0xee,0xff, col_light_blue,     -1, },
    { 'j', "Jungle",            0x13,0x96,0x36, col_green,          -1, },
    { 'l', "Lake",              0x21,0x33,0xcc, col_light_blue,     -1, },
    { 'r', "River",             0x66,0x99,0xff, col_light_blue,     -1, },
    { 's', "Swamp",             0x9d,0xa8,0x0a, col_light_red,      -1, },
    { 't', "Tundra",            0x61,0xc3,0xa2, col_white,          -1, },
    { 'v', "Valley",            0x22,0xdd,0x22, col_light_green,    -1, },
    { 'w', "Waterfall",         0x77,0xaa,0xff, col_light_cyan,     -1, },
    { 'x', "Badlands",          0x8a,0x83,0x60, col_light_red,      -1, },
    { 'y', "Fields",            0xa7,0xcc,0x14, col_yellow,         -1, },
    { 'z', "Shore",             0xa7,0xcc,0x14, col_light_yellow,   -1, },
    { ',', "Muddy Trail",       0x8c,0x57,0x38, col_light_yellow,   -1, },
    { '&', "Monster",           0xff,0x00,0x00, col_light_red,      -1, },
#ifndef SECRET_MAP_DATA_FORMAT
    { 'S', "Shallows",          0x44,0xcc,0xcc, col_light_cyan,     -1, },
    { '~', "Sea",               0x11,0x88,0xdd, col_blue,           -1, },
#else
    { '~', "Sea 1",             0x00,0x11,0x88, col_blue,           -1, },
    { '"', "Sea 2",             0x11,0x22,0x99, col_blue,           -1, },
    { '\'',"Sea 3",             0x11,0x33,0xaa, col_blue,           -1, },
    { '`', "Shallows?",         0x11,0x66,0xdd, col_blue,           -1, },
    { 'p', "Plains?",           0x55,0x92,0x00, col_green,          -1, },
    { 'S', "Swamp?",            0x77,0x77,0x33, col_yellow,         -1, },
#endif
    { -1 , "Road",              0x33,0x33,0x33, col_light_black,    '.', },
    { -1 , "Plains",            0x00,0xff,0x00, col_light_green,    'p', },
    { -1 , "Highlands",         0x77,0x00,0x77, col_magenta,        'i', },
};

const int nmapPieces = sizeof(mapPieces) / sizeof(mapPieces[0]);


const MapPiece mapCityPieces[] =
{
    { '.', "Street",            0xaa,0xaa,0x00, col_yellow,         -1, },
    { '-', "Door/Gate",         0xff,0xff,0x00, col_light_yellow,   -1, },
    { '|', "Door/Gate",         0xff,0xff,0x00, col_light_yellow,   -1, },
    { '=', "Bridge/Gate",       0x33,0x33,0x33, col_yellow,         -1, },
    { '*', "Fountain",          0x00,0x00,0xff, col_light_blue,     -1, },
    { '@', "???",               0xff,0xff,0x00, col_light_yellow,   -1, },
    { '$', "Tree",              0x00,0xaa,0x00, col_green,          -1, },
    { '#', "Wall",              0x33,0x33,0x33, col_light_black,    -1, },
    { '"', "Grass",             0x00,0x88,0x00, col_green,          -1, },
    { ':', "???",               0x00,0x00,0x00, col_light_white,    -1, },
    { 'z', "Shore",             0xa7,0xcc,0x14, col_yellow,         -1, },
    { ',', "Lawn",              0x00,0xcc,0x00, col_green,          -1, },

    // Old versions
    {  -1, "Street",            0x00,0x00,0x00, col_white,          '.', },
    {  -1, "Door/Gate",         0x00,0x00,0x00, col_yellow,         '-', },
    {  -1, "Door/Gate",         0x00,0x00,0x00, col_yellow,         '|', },
    {  -1, "Fountain",          0x00,0x00,0x00, col_light_white,    '*', },
};

const int nmapCityPieces = sizeof(mapCityPieces) / sizeof(mapCityPieces[0]);


typedef struct
{
    unsigned char c;
    char *ent;
} HTMLEntity;


static const HTMLEntity HTMLEntities[] =
{
    { '&', "amp" },
    { '<', "lt" },
    { '>', "gt" },
    { '"', "quot" },

    { 228, "#228" },
    { 246, "#246" },
    { 196, "#196" },
    { 214, "#214" },
};

static const int numHTMLEntities = sizeof(HTMLEntities) / sizeof(HTMLEntities[0]);


int tmpl_fprintve(int (*mputs)(const char *, FILE *), FILE *outFile, const char *fmt, va_list ap)
{
    int n, bufsize = strlen(fmt) * 2;
    char *buf, *tmp;

    if ((buf = th_malloc(bufsize)) == NULL)
        return -1;

    while (1)
    {
        va_list tap;
        va_copy(tap, ap);
        n = vsnprintf(buf, bufsize, fmt, tap);
        va_end(tap);
        if (n > -1 && n < bufsize)
        {
            // String fit the buffer, print it out and return
            int ret = mputs(buf, outFile);
            if (ret < 0)
                return ret;

            th_free(buf);
            return n;
        }

        // Didn't fit, try reallocating some more space
        if (n > -1)
            bufsize = n + 1;
        else
            bufsize *= 2;

        if ((tmp = th_realloc(buf, bufsize)) == NULL)
        {
            th_free(buf);
            return -2;
        }
        else
            buf = tmp;
    }

    return 0;
}


int fputse(const char *str, FILE *outFile)
{
    const char *s = str;
    if (str == NULL)
        return EOF;

    while (*s)
    {
        bool found = false;

        for (int i = 0; i < numHTMLEntities; i++)
        if (HTMLEntities[i].c == *s)
        {
            fprintf(outFile, "&%s;", HTMLEntities[i].ent);
            found = true;
            break;
        }

        if (!found)
            fputc(*s, outFile);
        s++;
    }

    return 0;
}


int fprintve(FILE *outFile, const char *fmt, va_list ap)
{
    return tmpl_fprintve(fputse, outFile, fmt, ap);
}


int fprintfe(FILE *outFile, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = fprintve(outFile, fmt, ap);
    va_end(ap);
    return ret;
}


int fputsesc1(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case '\\': ret = fputs("\\", f); break;
            case '"': ret = fputs("\\\"", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


int fprintvesc1(FILE *outFile, const char *fmt, va_list ap)
{
    return tmpl_fprintve(fputsesc1, outFile, fmt, ap);
}


int fprintfesc1(FILE *outFile, const char *fmt, ...)
{
    int ret;
    va_list ap;
    va_start(ap, fmt);
    ret = fprintvesc1(outFile, fmt, ap);
    va_end(ap);
    return ret;
}


int fputsesc3(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case ';': ret = fputs("\\;", f); break;
            case '\\': ret = fputs("\\", f); break;
            case '\'': ret = fputs("\\'", f); break;
            case '"': ret = fputs("\\\"", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


int fputsesc2(const char *str, FILE *f)
{
    const char *p = str;
    if (str == NULL)
        return -1;

    while (*p)
    {
        int ret;
        switch (*p)
        {
            case ';': ret = fputs("\\;", f); break;
            case '\\': ret = fputs("\\", f); break;
            default: ret = fputc(*p, f); break;
        }
        p++;
        if (ret < 0)
            return ret;
    }

    return 0;
}


char *muColorToCSSColor(char *buf, const size_t len, const int c)
{
    const MapColor *col = &mapColors[c];

    snprintf(buf, len, "#%02x%02x%02x",
        col->cr, col->cg, col->cb);

    return buf;
}


static int muGetPieceFromList(const MapPiece pieces[], const int npieces, int symbol, bool getOld)
{
    for (int i = 0; i < npieces; i++)
    {
        if ((getOld && pieces[i].oldSymbol == symbol) ||
            (!getOld && pieces[i].symbol == symbol))
            return i;
    }

    for (int i = 0; i < npieces; i++)
    {
        if (getOld && pieces[i].symbol == symbol)
            return i;
    }

    return -1;
}


int muGetMapPieceIndex(int symbol, bool getOld, bool getCity)
{
    int n;

    if (getCity && (n = muGetPieceFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0)
        return n;

    return muGetPieceFromList(mapPieces, nmapPieces, symbol, getOld);
}


static int muGetColorFromList(const MapPiece pieces[], const int npieces, int symbol, bool getOld)
{
    if (getOld)
    {
        for (int i = 0; i < npieces; i++)
        if (pieces[i].oldSymbol == symbol)
            return pieces[i].color;
    }

    for (int i = 0; i < npieces; i++)
    if (pieces[i].symbol == symbol)
         return pieces[i].color;

    return -1;
}


int muGetMapPieceColor(int symbol, bool getOld, bool getCity)
{
    int n;

    if (getCity && (n = muGetColorFromList(mapCityPieces, nmapCityPieces, symbol, getOld)) >= 0)
        return n;

    return ((n = muGetColorFromList(mapPieces, nmapPieces, symbol, getOld)) >= 0) ? n : 0;
}


void muPrintHTMLhead(FILE *outFile, const char *title, bool html5)
{
    static const char *strCharSet = "utf-8";
    assert(outFile != NULL);

    if (html5)
    {
        fprintf(outFile,
        "<!DOCTYPE html>\n"
        "<html lang=\"en\">\n"
        "<head>\n"
        " <meta charset=\"%s\">\n",
        strCharSet);
    }
    else
    {
        fprintf(outFile,
        "<?xml version=\"1.0\" encoding=\"%s\"?>\n"
        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
        "<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" xml:lang=\"en\">\n"
        "<head>\n"
        " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=%s\" />\n",
        strCharSet,
        strCharSet);
    }

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


void muPrintHTMLcolors(FILE *outFile, const char *tagName, const char *propName, const char *extra)
{
    assert(outFile != NULL);

    for (int n = 0; n < nmapColors; n++)
    {
        const MapColor *col = &mapColors[n];

        fprintf(outFile,
        "  %s.%c { %s: #%02x%02x%02x;%s%s }\n",
        tagName, 'a'+n, propName ? propName : "color",
        col->cr, col->cg, col->cb,
        col->bold ? " font-weight: bold;" : "",
        extra ? extra : "");
    }
}


int muCopyFileToStream(FILE *outFile, const char *filename)
{
    FILE *inFile;

    if ((inFile = fopen(filename, "rb")) != NULL)
    {
        int c;
        while ((c = fgetc(inFile)) != EOF)
        {
            if (fputc(c, outFile) == EOF)
            {
                fclose(inFile);
                return -2;
            }
        }
        fclose(inFile);
        return 0;
    }
    else
        return -1;
}


bool muStrChr(const unsigned char *symbols, const size_t nsymbols, const unsigned char ch)
{
    for (size_t n = 0; n < nsymbols; n++)
        if (symbols[n] == ch)
            return true;

    return false;
}


/* Map block handling
 */
MapBlock * mapBlockAlloc(const int width, const int height)
{
    MapBlock *res;

    // Check arguments
    if (width <= 0 || height <= 0)
        return NULL;

    // Allocate struct and data
    res = (MapBlock *) th_malloc0(sizeof(MapBlock));
    if (!res) return NULL;

    res->width = width;
    res->height = height;
    res->scansize = ((width / BLOCK_SCAN_ALIGN) + 1) * BLOCK_SCAN_ALIGN;
    res->size = res->height * res->scansize * sizeof(char);

    res->data = (unsigned char *) th_malloc0(res->size);
    if (!res->data)
    {
        th_free(res);
        return NULL;
    }

    return res;
}


void mapBlockFree(MapBlock *block)
{
    if (block)
    {
        th_free_r(&block->data);
        th_free(block);
    }
}


MapBlock * mapBlockCopy(const MapBlock *block)
{
    MapBlock *res;

    if (block == NULL)
        return NULL;

    res = (MapBlock *) th_malloc0(sizeof(MapBlock));
    if (!res) return NULL;

    res->width    = block->width;
    res->height   = block->height;
    res->scansize = block->scansize;
    res->size     = block->size;

    res->data = (unsigned char *) th_malloc(res->size);
    if (!res->data)
    {
        th_free(res);
        return NULL;
    }

    memcpy(res->data, block->data, res->size);

    return res;
}


/* Parse single arbitrary sized block from given mapfile
 */
MapBlock * mapBlockParseFile(const char *filename, bool isDiff)
{
    MapBlock *block;
    FILE *inFile;

    if ((inFile = fopen(filename, "rb")) == NULL)
    {
        THERR("Could not open mapfile '%s' for reading.\n", filename);
        return NULL;
    }

    block = mapBlockParseStream(filename, inFile, isDiff);

    fclose(inFile);

    return block;
}


MapBlock * mapBlockParseStream(const char *filename, FILE *inFile, bool isDiff)
{
    MapBlock *res;
    unsigned char *o;
    long pos = 0L;
    bool flag;
    int x, y, resW, resH, c;
    assert(filename != NULL);

    if (isDiff)
    {
        char buf[128];
        int ver;
        if (fgets(buf, sizeof(buf), inFile) == NULL)
        {
            THERR("Failed to read file header from '%s': %s\n",
                filename, th_error_str(th_get_error()));
            return NULL;
        }
        if (sscanf(buf, DIFF_MAGIC "%d\n", &ver) != 1)
        {
            THERR("Not a DIFF format file '%s'.\n", filename);
            return NULL;
        }
        if (ver != DIFF_VERSION)
        {
            THERR("Not a correct DIFF format version (%d != %d) '%s'.\n",
                ver, DIFF_VERSION, filename);
            return NULL;
        }
        pos = ftell(inFile);
    }

    // Probe map width
    resH = 1;
    resW = -1;
    x = 0;
    flag = false;
    while ((c = fgetc(inFile)) != EOF)
    {
        if ((!isDiff && c == '\n') || (isDiff && c == 0xff))
        {
            if (x > resW)
                resW = x;
            flag = true;
        }
        else
        {
            if (flag)
            {
                x = 0;
                resH++;
                flag = false;
            }
            x++;
        }
    }

    // Seek back
    if (fseek(inFile, pos, SEEK_SET) == -1)
    {
        THERR("Could not rewind file '%s'.\n",
            filename);
        return NULL;
    }

    // Allocate block
    if ((res = mapBlockAlloc(resW, resH)) == NULL)
    {
        THERR("Could not allocate mapblock (%d, %d) for '%s'.\n",
            resW, resH, filename);
        return NULL;
    }

    // Read data
    o = res->data;
    y = 0;
    x = 0;
    flag = false;
    while ((c = fgetc(inFile)) != EOF && (y < res->height))
    {
        if ((!isDiff && c == '\n') || (isDiff && c == 0xff))
        {
            if (x != res->width)
            {
                THERR("Broken block in '%s', line #%d width %d < %d!\n",
                    filename, y, x, res->width);
                mapBlockFree(res);
                return NULL;
            }
            flag = true;
        }
        else
        {
            if (flag)
            {
                x = 0;
                y++;
                o = res->data + (y * res->scansize);
                flag = false;
            }
            o[x++] = c;

            if (x > res->scansize)
            {
                THERR("Broken block in '%s', line #%d width %d > scansize %d!\n",
                    filename, y, x, res->scansize);
                mapBlockFree(res);
                return NULL;
            }
        }
    }

    // Close file
    if (y >= res->height)
    {
        THERR("Broken block in '%s', height %d >= %d\n", filename, y, res->height);
        mapBlockFree(res);
        return NULL;
    }

    return res;
}


/* Blit a block into another, assume that memory has been allocated
 * in sufficient way and other preparations are done.
 */
int mapBlockPutDo(MapBlock *map, const MapBlock *src, const int ox, const int oy)
{
    assert(map != NULL);
    assert(src != NULL);

    for (int y = 0; y < src->height; y++)
    for (int x = 0; x < src->width; x++)
    {
        const int dx = ox + x;
        const int dy = oy + y;

        if (dx >= 0 && dx < map->width && dy >= 0 && dy < map->height)
        {
            char c = src->data[(y * src->scansize) + x];

            if (c != 0)
                map->data[(dy * map->scansize) + dx] = c;
        }
        else
            return -1;
    }

    return 0;
}


int mapBlockPut(MapBlock **pmap, const MapBlock *src, int ox, int oy)
{
    MapBlock *tmp;
    int x0, y0, x1, y1, mx, my;

    assert(pmap != NULL);
    assert(*pmap != NULL);
    assert(src != NULL);

    // Determine new block size
    x0 = mx = y0 = my = 0;
    x1 = (*pmap)->width - 1;
    y1 = (*pmap)->height - 1;

    if (ox < 0) { x0 = ox; mx = -ox; ox = 0; }
    if (oy < 0) { y0 = oy; my = -oy; oy = 0; }

    if ((x0 + ox + src->width - 1) > x1)
        x1 = (x0 + ox + src->width - 1);

    if ((y0 + oy + src->height - 1) > y1)
        y1 = (y0 + oy + src->height - 1);

    // Allocate new block
    if ((tmp = mapBlockAlloc(x1 - x0 + 1, y1 - y0 + 1)) == NULL)
        return -1;

    // Copy data
    if (mapBlockPutDo(tmp, *pmap, mx, my) < 0)
    {
        mapBlockFree(tmp);
        return -2;
    }

    if (mapBlockPutDo(tmp, src, ox, oy) < 0)
    {
        mapBlockFree(tmp);
        return -3;
    }

    tmp->xc = -mx;
    tmp->yc = -my;

    // Out with the old, in with the new
    mapBlockFree(*pmap);
    *pmap = tmp;

    return 0;
}


/* Clean given block from position markers and whitespaces
 */
void mapBlockClean(MapBlock *map, const unsigned char *symbols, const size_t nsymbols)
{
    assert(map != NULL);
    assert(symbols != NULL);

    for (int y = 0; y < map->height; y++)
    {
        unsigned char *dp = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
        {
            if (muStrChr(symbols, nsymbols, *dp))
                *dp = 0;
            dp++;
        }
    }
}


/* Print block to given output stream
 */
void mapBlockPrint(FILE *fh, const MapBlock *map)
{
    assert(fh != NULL);
    assert(map != NULL);

    for (int y = 0; y < map->height; y++)
    {
        unsigned char *c = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
        {
            fputc(*c >= 32 && *c <= 126 ? *c : ' ', fh);
            c++;
        }
        fprintf(fh, "\n");
    }
}


void mapBlockPrintRaw(FILE *fh, const MapBlock *map)
{
    assert(fh != NULL);
    assert(map != NULL);

    for (int y = 0; y < map->height; y++)
    {
        unsigned char *dp = map->data + (y * map->scansize);
        for (int x = 0; x < map->width; x++)
            fputc(*dp++, fh);

        fputc(0xff, fh);
    }
}