view combine.c @ 1822:892d5277f1ff

Remove note about the search pattern parser being not very tolerant, it's somewhat better now.
author Matti Hamalainen <ccr@tnsp.org>
date Mon, 30 Oct 2017 12:55:21 +0200
parents cc59f80b0e78
children 79dd960610cb
line wrap: on
line source

/*
 * combine - Combine several maps into one bigger
 *
 * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen)
 * (C) Copyright 2006-2015 Tecnic Software productions (TNSP)
 */
#include "libmaputils.h"
#include "th_args.h"
#include "th_string.h"


#define    MAX_FILES    (256)


typedef struct
{
    char *fileName;
    int x, y;
    MapBlock *b;
} mapfile_t;


/* Variables
 */
int         nsrcFiles = 0;
mapfile_t   srcFiles[MAX_FILES];
char        *destFile = NULL;
int         optFillChar = -1;
BOOL        optInputIsDiff = FALSE;
int         optWorldMinW = 0,
            optWorldMinH = 0;


/* Arguments
 */
static const th_optarg optList[] =
{
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 1, 'o', "output",     "Specify output file", OPT_ARGREQ },
    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 3, 'q', "quiet",      "Be quiet", OPT_NONE },
    { 5, 'd', "diff",       "Map files are in 'diff' format", OPT_NONE },
    { 4, 'f', "fill",       "Fill character", OPT_ARGREQ },
    { 6, 'w', "width",      "Minimum width", OPT_ARGREQ },
    { 7, 'h', "height",     "Minimum height", OPT_ARGREQ },
};

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


void argShowHelp()
{
    th_print_banner(stdout, th_prog_name,
        "[options] <mapfile1:x-offset:y-offset> [<mapfile2:x:y> ...]");

    th_args_help(stdout, optList, optListN, 0);
}


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

    case 1:
        destFile = optArg;
        THMSG(1, "Output file '%s'\n", destFile);
        break;

    case 2:
        th_verbosityLevel++;
        break;

    case 3:
        th_verbosityLevel = -1;
        break;

    case 4:
        if (optArg[1] != 0)
        {
            THERR("Fill character is not a string, dumbass!\n");
            return FALSE;
        }

        optFillChar = optArg[0];
        THMSG(1, "Using fill character '%c'\n", optFillChar);
        break;

    case 5:
        THMSG(1, "Assuming all input files are diffs.\n");
        optInputIsDiff = TRUE;
        break;

    case 6:
    case 7:
        {
            char *s;
            int v = atoi(optArg);
            if (optN == 6)
            {
                s = "width";
                optWorldMinW = v;
            }
            else
            {
                s = "height";
                optWorldMinH = v;
            }

            if (v < 0 || v > 128 * 1024)
            {
                THERR("Invalid %s setting %d!\n", s, v);
                return FALSE;
            }

            THMSG(1, "Using world minimum %s %d\n", s, v);
        }
        break;

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

    return TRUE;
}


BOOL argHandleFile(char *currArg)
{
    if (nsrcFiles < MAX_FILES)
    {
        char *q, *c, *tmpArg = th_strdup(currArg);

        if (!tmpArg)
        {
            THERR("Could not allocate temp buffer!\n");
            return FALSE;
        }

        // Get filename component
        for (q = c = tmpArg; *c && (*c != ':'); c++);
        if (*c != ':')
        {
            th_free(tmpArg);
            THERR("Expected ':' after filename in '%s'.\n",
                currArg);
            return FALSE;
        }

        *(c++) = 0;
        srcFiles[nsrcFiles].fileName = th_strdup(q);

        // Get X offset
        if (!th_isdigit(*c) && *c != '-')
        {
            th_free(tmpArg);
            THERR("Expected decimal X offset value in '%s'.\n", currArg);
            return FALSE;
        }

        q = c; if (*c == '-') c++;
        while (*c && th_isdigit(*c)) c++;
        if (*c != ':')
        {
            th_free(tmpArg);
            THERR("Expected ':' after X offset value in '%s'.\n", currArg);
            return FALSE;
        }

        *(c++) = 0;
        srcFiles[nsrcFiles].x = atoi(q);

        // Get Y offset
        if (!th_isdigit(*c) && *c != '-')
        {
            th_free(tmpArg);
            THERR("Expected decimal Y offset value in '%s'.\n", currArg);
            return FALSE;
        }

        q = c; if (*c == '-') c++;
        while (*c && th_isdigit(*c)) c++;
        if (*c != 0)
        {
            th_free(tmpArg);
            THERR("Invalid Y offset value in '%s'.\n", currArg);
            return FALSE;
        }

        srcFiles[nsrcFiles].y = atoi(q);

        th_free(tmpArg);

        nsrcFiles++;
    }
    else
    {
        THERR("Too many input files specified (%d max)!\n", MAX_FILES);
        return FALSE;
    }

    return TRUE;
}


void findWorldSize(MapBlock *tmp,
    int *worldX0, int *worldY0,
    int *worldX1, int *worldY1)
{
    if (tmp->x < *worldX0)
        *worldX0 = tmp->x;
    if (tmp->x + tmp->width > *worldX1)
        *worldX1 = tmp->x + tmp->width;

    if (tmp->y < *worldY0)
        *worldY0 = tmp->y;
    if (tmp->y + tmp->height > *worldY1)
        *worldY1 = tmp->y + tmp->height;
}


int main(int argc, char *argv[])
{
    int i, worldX0, worldY0, worldX1, worldY1, worldW, worldH;
    MapBlock *worldMap = NULL;

    th_init("combine", "Combine several maps into one", "0.1", NULL, NULL);
    th_verbosityLevel = 0;

    // Parse arguments
    if (!th_args_process(argc, argv, optList, optListN,
        argHandleOpt, argHandleFile, OPTH_BAILOUT))
        exit(1);

    if (nsrcFiles < 1)
    {
        THERR("Nothing to do. (try --help)\n");
        exit(0);
    }


    /* Read map files
     */
    for (i = 0; i < nsrcFiles; i++)
    {
        if ((srcFiles[i].b = mapBlockParseFile(srcFiles[i].fileName, optInputIsDiff)) == NULL)
        {
            THERR("Error reading input file '%s'!\n",
                srcFiles[i]);
            exit(1);
        }

        mapBlockClean(srcFiles[i].b, " ");
        srcFiles[i].b->x = srcFiles[i].x;
        srcFiles[i].b->y = srcFiles[i].y;
    }

    THMSG(2, "Total of %d maps read.\n", nsrcFiles);

    if (nsrcFiles <= 0)
    {
        THERR("No maps, nothing to do.\n");
        exit(11);
    }


    // Get world dimensions
    worldX0 = worldY0 = worldX1 = worldY1 = 0;
    for (i = 0; i < nsrcFiles; i++)
        findWorldSize(srcFiles[i].b, &worldX0, &worldY0, &worldX1, &worldY1);

    worldW = worldX1 - worldX0 + 1;
    worldH = worldY1 - worldY0 + 1;

    THMSG(2, "Initial dimensions <%d, %d> - <%d, %d> (%d x %d)\n",
        worldX0, worldY0, worldX1, worldY1, worldW, worldH);

    // Adjust to origo <0, 0>
    worldX0 = -worldX0;
    worldY0 = -worldY0;
    worldX1 = worldX0 + worldW - 1;
    worldY1 = worldY0 + worldH - 1;

    THMSG(2, "Adjusted dimensions <%d, %d> - <%d, %d> (%d x %d)\n",
        worldX0, worldY0, worldX1, worldY1, worldW, worldH);

    // Adjust for requested minimum dimensions
    if (worldW < optWorldMinW)
    {
        int tmp = (optWorldMinW - worldW) / 2;
        worldX0 += tmp;
        worldX1 += tmp;
        worldW = optWorldMinW;
    }
    if (worldH < optWorldMinH)
    {
        int tmp = (optWorldMinH - worldH) / 2;
        worldY0 += tmp;
        worldY1 += tmp;
        worldH = optWorldMinH;
    }

    // Allocate world map
    THMSG(1, "Initializing world map of <%d, %d> - <%d, %d> (%d x %d)\n",
        worldX0, worldY0, worldX1, worldY1,
        worldW, worldH);

    if ((worldMap = mapBlockAlloc(worldW, worldH)) == NULL)
    {
        THERR("Error allocating world map!\n");
        exit(4);
    }


    /* Clear with some character, if requested
     */
    if (optFillChar > 0)
    {
        memset(worldMap->data, optFillChar, worldMap->size);
    }


    /* Blit map blocks
     */
    for (i = 0; i < nsrcFiles; i++)
    if (mapBlockPutDo(worldMap, srcFiles[i].b, worldX0 + srcFiles[i].b->x, worldY0 + srcFiles[i].b->y) != 0)
    {
        THERR("Mapblock #%d ['%s' @ %d,%d] placement failed!\n",
        i, srcFiles[i].fileName, srcFiles[i].b->x, srcFiles[i].b->y);
        exit(8);
    }


    /* Output generated map
     */
    if (worldMap)
    {
        FILE *tmpFile;

        THMSG(1, "Outputting generated map of (%d x %d) ...\n",
            worldMap->width, worldMap->height);

        if (destFile == NULL)
            tmpFile = stdout;
        else
        if ((tmpFile = fopen(destFile, "wb")) == NULL)
        {
            THERR("Error opening output file '%s'!\n", destFile);
            exit(1);
        }

        if (optInputIsDiff)
            mapBlockPrintRaw(tmpFile, worldMap);
        else
            mapBlockPrint(tmpFile, worldMap);

        fclose(tmpFile);
    }
    else
    {
        THERR("No map generated?\n");
    }

    exit(0);
    return 0;
}