view src/combine.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 eba783bef2ee
children 76b67c40fbf5
line wrap: on
line source

/*
 * Combine several maps into one bigger
 * 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_FILES    (256)


typedef struct
{
    char *filename;
    int xc, yc;
    MapBlock *blk;
} MapFile;


/* Variables
 */
int         nsrcFiles = 0;
MapFile     srcFiles[SET_MAX_FILES];
char        *dstFile = 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, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 2, 'o', "output",     "Specify output file", OPT_ARGREQ },
    { 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, 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:
        dstFile = optArg;
        THMSG(1, "Output file '%s'\n", dstFile);
        break;

    case 3:
        th_verbosity = -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)
{
    char *tmpArg = NULL;

    if (nsrcFiles < SET_MAX_FILES)
    {
        char *q, *c;
        MapFile *file = &srcFiles[nsrcFiles++];

        if ((tmpArg = th_strdup(currArg)) == NULL)
        {
            THERR("Could not allocate temp buffer!\n");
            goto err;
        }

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

        *(c++) = 0;
        file->filename = th_strdup(q);

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

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

        *(c++) = 0;
        file->xc = atoi(q);

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

        q = c;
        if (*c == '-') c++;

        while (*c && th_isdigit(*c)) c++;
        if (*c != 0)
        {
            THERR("Invalid Y offset value in '%s'.\n", currArg);
            goto err;
        }

        file->yc = atoi(q);
    }
    else
    {
        THERR("Too many input files specified (%d max)!\n", SET_MAX_FILES);
        goto err;
    }

    th_free(tmpArg);
    return true;

err:
    th_free(tmpArg);
    return false;
}


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

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


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

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

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

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


    /* Read map files
     */
    for (i = 0; i < nsrcFiles; i++)
    {
        MapFile *file = &srcFiles[i];
        if ((file->blk = mapBlockParseFile(file->filename, optInputIsDiff)) == NULL)
        {
            THERR("Error reading input file '%s'!\n",
                file->filename);
            goto exit;
        }

        mapBlockClean(file->blk, (unsigned char *) " ", 1);
        file->blk->xc = file->xc;
        file->blk->yc = file->yc;
    }

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

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


    // Get world dimensions
    worldX0 = worldY0 = worldX1 = worldY1 = 0;
    for (i = 0; i < nsrcFiles; i++)
        findWorldSize(srcFiles[i].blk, &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");
        goto exit;
    }


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


    /* Blit map blocks
     */
    for (i = 0; i < nsrcFiles; i++)
    {
        MapFile *file = &srcFiles[i];
        if (mapBlockPutDo(worldMap, file->blk, worldX0 + file->blk->xc, worldY0 + file->blk->yc) != 0)
        {
            THERR("Mapblock #%d ['%s' @ %d,%d] placement failed!\n",
                i, file->filename, file->blk->xc, file->blk->yc);
            goto exit;
        }
    }


    /* Output generated map
     */
    if (worldMap != NULL)
    {

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

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

        if (optInputIsDiff)
            mapBlockPrintRaw(tmpFile, worldMap);
        else
            mapBlockPrint(tmpFile, worldMap);
    }
    else
    {
        THERR("No map generated?\n");
    }

exit:
    mapBlockFree(worldMap);

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

    return 0;
}