view mkloc.c @ 941:e8d728edb54a

Set link targets in GMapsXML output to "_blank" to open them in separate windows/tabs.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 12 May 2010 12:40:03 +0000
parents ff806678ef3f
children e98b0f94327e
line wrap: on
line source

/*
 * mkloc - Manipulate and convert BatMUD location data files
 *
 * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen)
 * (C) Copyright 2006-2009 Tecnic Software productions (TNSP)
 */
#include "maputils.h"
#include <string.h>
#include <strings.h>
#include "th_args.h"
#include "th_string.h"
#include <time.h>

/* Version string
 */
#define LOC_MAGIC           "MapUtils LOC file"
#define LOC_VERSION_MAJOR   (3)
#define LOC_VERSION_MINOR   (0)


/* LOCD_* is an enum describing the preferred orientation of the
 * location marker _label_ in relation to the marker spot.
 */
enum {
    LOCD_NONE = 0,
    LOCD_LEFT,
    LOCD_LEFTDOWN,
    LOCD_DOWN
};


/* Timestamp used in loc-files, in sscanf() format.
 */
#define LOC_TIMEFMT         "%02d.%02d.%04d"


/* These flags are used to describe the marker/location  type and label
 * related information: [LOCF_M_*] Draw marker of specified type (by default,
 * no marker is drawn). LOCF_M_CITY and LOCF_M_PCITY are special, they also
 * work as aliases to LOCF_T_CITY/PCITY and thus have similar functionality.
 *
 * [LOCF_T_*] specify the type of the location/marker. Currently the effect
 * of this settings is addition of "GUILD", "SHRINE"-type strings to labels,
 * but this information can be used in other ways too.
 */
#define LOCF_NONE           (0x0000)
#define LOCF_M_SCENIC1      (0x0001)    /* '?' Scenic marker */
#define LOCF_M_SCENIC2      (0x0002)    /* '%' Shrine marker/etc */
#define LOCF_M_PCITY        (0x0004)    /* 'C' Player city */
#define LOCF_M_CITY         (0x0008)    /* 'c' City */
#define LOCF_M_MASK         (0x000F)

#define LOCF_T_SHRINE       (0x0010)    /* 'S' Raceshrine */
#define LOCF_T_GUILD        (0x0020)    /* 'G' Guild */
#define LOCF_T_SS           (0x0040)    /* 'P' Player guild/Secret Society */
#define LOCF_T_MONSTER      (0x0080)    /* 'M' Special monster */
#define LOCF_T_TRAINER      (0x0100)    /* 'T' Guild trainer */
#define LOCF_T_FORT         (0x0200)    /* 'F' Regions fort */
#define LOCF_T_MASK         (0x0FF0)
#define LOCF_MASK           (LOCF_M_MASK | LOCF_T_MASK)

#define LOCF_INVIS          (0x1000)    /* '-' Invisible marker / Don't show label */
#define LOCF_INVALID        (0x2000)    /* Possibly invalid location */
#define LOCF_NOMARKER       (0x4000)    /* Location has no marker in mapdata or explicitly defined */
#define LOCF_Q_MASK         (0xF000)


/* Misc constants
 */
#define LOC_MAX_NAMES       (64)        /* Probably more than enough? */
#define LOC_MARKERS         "?%C"
#define LOC_MAX_FILES       (64)

/* Structures
 */
typedef struct {
    int day, month, year;
} tm_t;

typedef struct {
    char *filename;
    char *continent;
    int x, y;

    FILE *f;
    size_t lineNum;
    int c;
} fileinfo_t;

typedef struct {
    int x, y, ox, oy;
    int dir, flags;
    tm_t added;
    BOOL addedValid;
    int nnames, ncoders;
    char *names[LOC_MAX_NAMES], *coders[LOC_MAX_NAMES];
    char *uri, *freeform;
    fileinfo_t *file;
} marker_t;

typedef struct {
    int    numLocations;
    marker_t **locations;
} locations_t;

enum {
    OUTFMT_MAP = 0,
    OUTFMT_LOCFILE,
    OUTFMT_SCRIPT,
    OUTFMT_MAPLOC,
    OUTFMT_GMAPS
};

enum {
    GMAPS_XML = 0,
    GMAPS_OVERLAY,
    GMAPS_LABELS,
    GMAPS_LAST
};

/* These must be in lower case */
const char *gmapsModes[GMAPS_LAST] = {
    "xml",
    "overlay",
    "labels"
};

/* Variables
 */
char    *srcFile = NULL,
        *destFile = NULL;

int     nlocFiles = -1;
fileinfo_t locFiles[LOC_MAX_FILES];

char    *optLocMarkers = LOC_MARKERS;
BOOL    optGetUpdateLoc = FALSE,
        optOutput = OUTFMT_MAP,
        optNoLabels = FALSE,
        optNoAdjust = FALSE,
        optLabelType = FALSE;
int     optGMapsMode = -1;
float   optScale = -1,
        optUnitSize = 1.0f,
        optFontScale = 1.0f;


/* Arguments
 */
optarg_t optList[] = {
    { 0, '?', "help",       "Show this help", OPT_NONE },
    { 2, 'v', "verbose",    "Be more verbose", OPT_NONE },
    { 3, 'q', "quiet",      "Be quiet", OPT_NONE },
    { 1, 'o', "output",     "Output file (default stdout)", OPT_ARGREQ },
    { 5, 'm', "map",        "Input map file", OPT_ARGREQ },
    { 6, 'l', "locinfo",    "Input location info file", OPT_ARGREQ },
    { 4, 'g', "getloc",     "Generate/update location info", OPT_NONE },
    { 7, 'x', "offset-x",   "Location X offset", OPT_ARGREQ },
    { 8, 'y', "offset-y",   "Location Y offset", OPT_ARGREQ },
    { 9, 's', "scale",      "Scale coordinates by", OPT_ARGREQ },
    { 12,'f', "font-scale", "(-S) Font scale factor", OPT_ARGREQ },
    { 13,'u', "unit-size",  "(-S) Unit size", OPT_ARGREQ },
    { 10,'S', "out-script", "Output script for ImageMagick", OPT_NONE },
    { 11,'L', "out-locinfo","Output location info file", OPT_NONE },
    { 17,'M', "out-maploc", "Output MapLoc HTML", OPT_NONE },
    { 20,'G', "out-gmaps",  "Output GMaps data (xml, overlay, labels)", OPT_ARGREQ },
    { 21,'c', "continent",  "Continent for GMaps XML output", OPT_ARGREQ },
    { 15,'n', "no-labels",  "No labels, only markers", OPT_NONE },
    { 16,'N', "no-adjust",  "No label adjustment", OPT_NONE },
    { 18,'t', "type-prefix","Prepend labels with type prefix", OPT_NONE },
    { 19,'X', "loc-markers","Location markers ('" LOC_MARKERS "')", OPT_ARGREQ },
};

const int optListN = (sizeof(optList) / sizeof(optarg_t));


void argShowHelp()
{
    th_args_help(stdout, optList, optListN, th_prog_name,
        "[options]");
}


BOOL argHandleOpt(const int optN, char *optArg, char *currArg)
{
    int i;
    fileinfo_t *f;

    switch (optN) {
    case 0:
        argShowHelp();
        exit(0);
        break;

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

    case 5:
        srcFile = optArg;
        THMSG(2, "Map file '%s'\n", srcFile);
        break;

    case 6:
        nlocFiles++;
        if (nlocFiles < LOC_MAX_FILES) {
            f = &locFiles[nlocFiles];
            f->filename = optArg;
            f->x = f->y = 0;
            f->lineNum = 1;
            f->continent = NULL;
            THMSG(2, "Added location data file #%d '%s'\n", nlocFiles, optArg);
        } else {
            THERR("Too many location data files specified, maximum is %d.\n", LOC_MAX_FILES);
            return FALSE;
        }
        break;
    
    case 4:
        THMSG(2, "Location updating/generation mode\n");
        optGetUpdateLoc = TRUE;
        break;

    case 7:
        f = &locFiles[nlocFiles];
        f->x = atoi(optArg);
        THMSG(2, "Location file #%d X offset = %d\n", nlocFiles, f->x);
        break;

    case 8:
        f = &locFiles[nlocFiles];
        f->y = atoi(optArg);
        THMSG(2, "Location file #%d Y offset = %d\n", nlocFiles, f->y);
        break;

    case 21:
        f = &locFiles[nlocFiles];
        th_free(f->continent);
        f->continent = th_strdup(optArg);
        THMSG(2, "Using continent name '%s' for location file #%d\n", optArg, nlocFiles);
        break;

    case 9:
        optScale = atof(optArg);
        THMSG(2, "Location scale factor = %1.3f\n", optScale);
        break;

    case 10:
        THMSG(2, "Script output mode\n");
        optOutput = OUTFMT_SCRIPT;
        break;

    case 11:
        THMSG(2, "Location file output mode\n");
        optOutput = OUTFMT_LOCFILE;
        break;

    case 17:
        THMSG(2, "MapLoc HTML output mode\n");
        optOutput = OUTFMT_MAPLOC;
        break;

    case 12:
        optFontScale = atof(optArg);
        THMSG(2, "Font scale factor = %1.3f\n", optFontScale);
        break;

    case 13:
        optUnitSize = atof(optArg);
        THMSG(2, "Unit size = %1.3f\n", optUnitSize);
        break;

    case 15:
        THMSG(2, "Not adding labels to locations\n");
        optNoLabels = TRUE;
        break;

    case 16:
        THMSG(2, "Not adjusting data under labels\n");
        optNoAdjust = TRUE;
        break;

    case 18:
        THMSG(2, "Adding label type prefixes\n");
        optLabelType = TRUE;
        break;

    case 19:
        optLocMarkers = optArg;
        THMSG(2, "Using location markers = '%s'\n", optLocMarkers);
        break;

    case 20:
        optOutput = OUTFMT_GMAPS;
        optGMapsMode = -1;
        for (i = 0; i < GMAPS_LAST; i++)
        if (tolower(optArg[0]) == gmapsModes[i][0]) {
            optGMapsMode = i;
            break;
        }
        if (optGMapsMode < 0) {
            THERR("Invalid GMaps mode '%s'.\n", optArg);
            return FALSE;
        }
        THMSG(2, "GMaps output mode '%s' selected\n", gmapsModes[optGMapsMode]);
        break;


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


void fprintEsc1(FILE *f, const char *s)
{
    const char *p = s;
    if (s == NULL) return;
    
    while (*p) {
        switch (*p) {
        case ';': fputs("\\;", f); break;
        case '\\': fputs("\\", f); break;
        case '\'': fputs("\\'", f); break;
        case '"': fputs("\\\"", f); break;
        default: fputc(*p, f);
        }
        p++;
    }
}


void fprintEsc2(FILE *f, const char *s)
{
    const char *p = s;
    if (s == NULL) return;
    
    while (*p) {
        switch (*p) {
            case ';': fputs("\\;", f); break;
            case '\\': fputs("\\", f); break;
            default: fputc(*p, f);
        }
        p++;
    }
}


BOOL addLocation(locations_t *l, int x, int y, int dir, int flags,
    char **names, char **coders, tm_t *added, BOOL addedValid,
    char *uri, char *freeform, fileinfo_t *file)
{
    marker_t *tmp;
    int i;
    
    /* Allocate location struct */
    if ((tmp = th_calloc(1, sizeof(marker_t))) == NULL)
        return FALSE;
    
    tmp->x = tmp->ox = x;
    tmp->y = tmp->oy = y;
    tmp->dir = dir;
    tmp->flags = flags;
    tmp->file = file;
    for (i = 0; i < LOC_MAX_NAMES; i++) {
        if (names != NULL && names[i] != NULL) {
            tmp->names[tmp->nnames] = th_strdup(names[i]);
            tmp->nnames++;
        }
        if (coders != NULL && coders[i] != NULL) {
            tmp->coders[tmp->ncoders] = th_strdup(coders[i]);
            tmp->ncoders++;
        }
    }
    if (added != NULL) {
        memcpy(&(tmp->added), added, sizeof(tmp->added));
        tmp->addedValid = addedValid;
    } else {
        time_t tmpT = time(NULL);
        struct tm *tmpTime = localtime(&tmpT);
        tmp->added.day = tmpTime->tm_mday;
        tmp->added.month = tmpTime->tm_mon + 1;
        tmp->added.year = tmpTime->tm_year + 1900;
        tmp->addedValid = TRUE;
    }
    tmp->uri = th_strdup(uri);
    tmp->freeform = th_strdup(freeform);

    /* Add new location */    
    l->locations = (marker_t **) th_realloc(l->locations,
        sizeof(marker_t*) * (l->numLocations+1));

    if (l->locations == NULL) return FALSE;

    l->locations[l->numLocations] = tmp;
    l->numLocations++;

    return TRUE;
}


int findLocationByCoords(locations_t *l, int x, int y, BOOL locTrue)
{
    int i;
    marker_t *tmp;

    for (i = 0; i < l->numLocations; i++) {
        tmp = l->locations[i];
        if (locTrue) {
            if (tmp->ox == x && tmp->oy == y) return i;
        } else {
            if (tmp->x == x && tmp->y == y) return i;
        }
    }
    
    return -1;
}


static BOOL checkFlag(int flags, int mask, int flag)
{
    if (mask)
        return (flags & mask) == flag;
    else
        return (flags & flag);
}

static BOOL checkMutex(fileinfo_t *f, int *flags, int mask, int flag)
{
    if (!checkFlag(*flags, mask, 0) &&
        !checkFlag(*flags, mask, flag)) {
        THERR("Invalid flags setting on line #%d in '%s'\n",
            f->lineNum, f->filename);
        return FALSE;
     } else {
        *flags |= flag;
        return TRUE;
    }
}


static BOOL parseFieldInt(fileinfo_t *f, int *val)
{
    int res = 0;
    if (!isdigit(f->c)) return FALSE;
    while (isdigit(f->c)) {
        res *= 10;
        res += f->c - '0';
        f->c = fgetc(f->f);
    }
    *val = res;
    return TRUE;
}


static char * parseFieldString(fileinfo_t *f, const char *endch)
{
    char res[4096];
    BOOL end = FALSE;
    size_t pos = 0;
    int i;
    
    if (strchr(endch, f->c))
        return NULL;
    
    while (!end && !strchr(endch, f->c) && pos < 4095) {
        switch (f->c) {
            case '\n':
            case '\r':
                THERR("Unexpected EOL inside text field on line #%d of '%s'.\n",
                    f->lineNum, f->filename);
                return NULL;
                break;
            case EOF:
                THERR("Unexpected EOF inside text field on line #%d of '%s'.\n",
                    f->lineNum, f->filename);
                return NULL;
                break;
            case '\\':
                /* Enable continuation via '\' at EOL */
                i = fgetc(f->f);
                if (i == EOF) {
                    THERR("Unexpected EOF inside text field on line #%d of '%s'.\n",
                        f->lineNum, f->filename);
                    return NULL;
                } else if (i == '\n' || i == '\r') {
                    f->c = fgetc(f->f);
                    if (i == '\r' && f->c == '\n')
                        f->c = fgetc(f->f);
                } else {
                    res[pos++] = i;
                    f->c = fgetc(f->f);
                }
                break;
            default:
                res[pos++] = f->c;
                f->c = fgetc(f->f);
                break;
        }
    }
    while (pos > 0 && isspace(res[pos - 1])) {
        pos--;
        res[pos] = 0;
    }
    res[pos] = 0;
    return (pos > 0) ? th_strdup(res) : NULL;
}


static BOOL parseFlags(fileinfo_t *f, int *flags)
{
    BOOL endFlags;
    
    *flags = LOCF_NONE;
    endFlags = FALSE;
    while (!endFlags) {
        switch (f->c) {
        /* Scenic marker flags */
        case '?':
            if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_SCENIC1))
                return FALSE;
            break;
        case '%':
            if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_SCENIC2))
                return FALSE;
            break;
        case 'C':
            if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_PCITY))
                return FALSE;
            break;
        case 'c':
            if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_CITY))
                return FALSE;
            break;

        /* Marker type flags */
        case 'S':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_SHRINE))
                return FALSE;
            break;
        case 'G':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_GUILD))
                return FALSE;
            break;
        case 'P':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_SS))
                return FALSE;
            break;
        case 'M':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_MONSTER))
                return FALSE;
            break;
        case 'T':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_TRAINER))
                return FALSE;
            break;
        case 'F':
            if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_FORT))
                return FALSE;
            break;

        /* Additional flags */
        case '-':
            *flags |= LOCF_INVIS;
            break;
        
        default:
            endFlags = TRUE;
            break;
        }
        
        if (!endFlags) {
            f->c = fgetc(f->f);
        }
    }
    
    return TRUE;
}


enum {
    STATE_IDLE,
    STATE_FIELD,
    STATE_FIELD_SEP,
    STATE_COMMENT,
    STATE_NEXT,
    STATE_END,
    STATE_ERROR
};


static BOOL checkForEOL(fileinfo_t *f, int *state)
{
    if (f->c == '\n' || f->c == '\r') {
        *state = STATE_ERROR;
        THERR("Unexpected EOL on line #%d of '%s'.\n",
            f->lineNum, f->filename);
        return FALSE;
    } else
        return TRUE;
}


BOOL parseLocationStream(fileinfo_t *f, locations_t *l, int offX, int offY)
{
    int state, prev, next, field, subfield, sep;
    int tmpX, tmpY, tmpOrient, tmpFlags, i;
    char *fieldsep = NULL, *tmpStr, *tmpURI = NULL;
    char *tmpLocNames[LOC_MAX_NAMES],
         *tmpCoderNames[LOC_MAX_NAMES];
    BOOL res, tmpTimeSet = FALSE, versionSet = FALSE;
    tm_t tmpTime;
    
    /* Parse data */
    memset(tmpLocNames, 0, sizeof(tmpLocNames));
    memset(tmpCoderNames, 0, sizeof(tmpCoderNames));
    state = STATE_IDLE;
    next = prev = STATE_ERROR;
    field = subfield = sep = -1;
    res = FALSE;
    f->c = fgetc(f->f);
    do {
        switch (state) {
        case STATE_IDLE:
            if (f->c == EOF)
                state = STATE_END;
            else if (f->c == '\r') {
                f->lineNum++;
                f->c = fgetc(f->f);
                if (f->c == '\n')
                    f->c = fgetc(f->f);
            } else if (f->c == '\n') {
                f->lineNum++;
                f->c = fgetc(f->f);
            } else if (f->c == '#') {
                prev = state;
                state = STATE_COMMENT;
                next = STATE_IDLE;
            } else if (isdigit(f->c)) {
                /* Start of a record */
                state = STATE_FIELD;
                field = 1;
            } else if (isspace(f->c)) {
                f->c = fgetc(f->f);
            } else {
                /* Syntax error */
                state = STATE_ERROR;
                THERR("Syntax error in '%s' line #%d.\n",
                    f->filename, f->lineNum);
            }
            break;
        
        case STATE_COMMENT:
            switch (f->c) {
                case '\r':
                    f->c = fgetc(f->f);
                    if (f->c == '\n')
                        f->c = fgetc(f->f);
                    f->lineNum++;
                    prev = state;
                    state = next;
                    break;
                case '\n':
                    f->c = fgetc(f->f);
                    f->lineNum++;
                    prev = state;
                    state = next;
                    break;
                case EOF:
                    state = STATE_END;
                    break;
                default:
                    f->c = fgetc(f->f);
                    /* Because loc file identification should be the first
                     * comment line, we check it here.
                     */
                    if (!versionSet && isalpha(f->c)) {
                        char *tmp = parseFieldString(f, "(\n\r");
                        if (tmp != NULL && !strcmp(tmp, LOC_MAGIC)) {
                            /* ID found, check version */
                            char *verStr = parseFieldString(f, ")\n\r");
                            int verMajor, verMinor;
                            if (verStr != NULL && sscanf(verStr, "(version %d.%d", &verMajor, &verMinor) == 2) {
                                if (verMajor != LOC_VERSION_MAJOR) {
                                    /* Major version mismatch, bail out with informative message */
                                    THERR("LOC file format version %d.%d detected, internal version is %d.%d. "
                                    "Refusing to read due to potential incompatibilities. If you neverthless "
                                    "wish to proceed, change the loc file's version to match internal version.\n",
                                    verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR);
                                    state = STATE_ERROR;
                                } else if (verMinor != LOC_VERSION_MINOR) {
                                    /* Minor version mismatch, just inform about it */
                                    THERR("LOC file format version %d.%d detected, internal version is %d.%d, proceeding.\n",
                                        verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR);
                                }
                            } else {
                                THERR("Invalid or malformed LOC file, version not found (%s).\n", verStr);
                                state = STATE_ERROR;
                            }
                            th_free(verStr);
                        } else {
                            THERR("Invalid LOC file, the file ID is missing ('# %s (version %d.%d)' should be the first line.)\n",
                                LOC_MAGIC, LOC_VERSION_MAJOR, LOC_VERSION_MINOR);
                            state = STATE_ERROR;
                        }
                        th_free(tmp);
                        versionSet = TRUE;
                    }
                    break;
            }
            break;
        
        case STATE_NEXT:
            switch (f->c) {
                case EOF:
                    state = STATE_ERROR;
                    THERR("Unexpected end of file on line #%d of '%s'.\n",
                        f->lineNum, f->filename);
                    break;
                case 32: case 9:
                    f->c = fgetc(f->f);
                    break;
                case '\\':
                    /* Enable continuation via '\' at EOL */
                    i = fgetc(f->f);
                    if (i != '\n' && i != '\r') {
                        state = STATE_ERROR;
                        THERR("Expected EOL on line #%d of '%s'.\n",
                            f->lineNum, f->filename);
                    } else {
                        f->lineNum++;
                        f->c = fgetc(f->f);
                        if (i == '\r' && f->c == '\n')
                            f->c = fgetc(f->f);
                    }
                    break;
                default:
                    prev = state;
                    state = next;
                    break;
            }
            break;
        
        case STATE_FIELD_SEP:
            if (strchr(fieldsep, f->c) != NULL) {
                sep = f->c;
                f->c = fgetc(f->f);
                prev = state;
                next = STATE_FIELD;
                state = STATE_NEXT;
            } else {
                state = STATE_ERROR;
                THERR("Expected field separator '%s', got '%c' on line #%d of '%s'.\n",
                    fieldsep, f->c, f->lineNum, f->filename);
            }
            break;
        
        case STATE_FIELD:
            if (field > 0 && field < 8 && !checkForEOL(f, &state)) break;
            switch (field) {
                case 1: /* X-coordinate */
                    res = parseFieldInt(f, &tmpX);
                    fieldsep = ";";
                    if (res) {
                        field++;
                        prev = state;
                        next = STATE_FIELD_SEP;
                        state = STATE_NEXT;
                    }
                    break;
                
                case 2: /* Y-coordinate */
                    res = parseFieldInt(f, &tmpY);
                    if (res) {
                        field++;
                        prev = state;
                        next = STATE_FIELD_SEP;
                        state = STATE_NEXT;
                    }
                    break;
                
                case 3: /* Label orientation and flags */
                    res = parseFieldInt(f, &tmpOrient);
                    if (res)
                        res = parseFlags(f, &tmpFlags);
                    if (res) {
                        field++;
                        prev = state;
                        next = STATE_FIELD_SEP;
                        state = STATE_NEXT;
                    }
                    break;
                
                case 4: /* Location name(s) */
                    if (subfield < 0) {
                        subfield = 0;
                        fieldsep = "|;";
                        sep = '|';
                    }
                    if (sep == '|') {
                        if (subfield < LOC_MAX_NAMES) {
                            th_free(tmpLocNames[subfield]);
                            tmpLocNames[subfield++] = parseFieldString(f, fieldsep);
                            prev = state;
                            next = STATE_FIELD_SEP;
                            state = STATE_NEXT;
                            if (!strchr(fieldsep, f->c)) {
                                state = STATE_ERROR;
                                THERR("Expected field separator '%s' after LOCNAMES on line #%d of '%s'.\n",
                                    fieldsep, f->lineNum, f->filename);
                            }
                        } else {
                            state = STATE_ERROR;
                            THERR("Too many location names (max %d) on line #%d of '%s'.\n",
                                LOC_MAX_NAMES, f->lineNum, f->filename);
                        }
                    } else {
                        fieldsep = ";";
                        subfield = -1;
                        field++;
                        prev = state;
                        state = STATE_FIELD;
                    }
                    break;
                
                case 5: /* Coders */
                    if (subfield < 0) {
                        subfield = 0;
                        fieldsep = ",;";
                        sep = ',';
                    }
                    if (sep == ',') {
                        if (subfield < LOC_MAX_NAMES) {
                            th_free(tmpCoderNames[subfield]);
                            tmpCoderNames[subfield++] = parseFieldString(f, fieldsep);
                            prev = state;
                            next = STATE_FIELD_SEP;
                            state = STATE_NEXT;
                            if (!strchr(fieldsep, f->c)) {
                                state = STATE_ERROR;
                                THERR("Expected field separator '%s' after CODERNAMES on line #%d of '%s'.\n",
                                    fieldsep, f->lineNum, f->filename);
                            }
                        } else {
                            state = STATE_ERROR;
                            THERR("Too many coder names (max %d) on line #%d of '%s'.\n",
                                LOC_MAX_NAMES, f->lineNum, f->filename);
                        }
                    } else {
                        fieldsep = ";";
                        subfield = -1;
                        field++;
                        prev = state;
                        state = STATE_FIELD;
                    }
                    break;
                
                case 6: /* Date */
                    tmpTimeSet = FALSE;
                    tmpStr = parseFieldString(f, fieldsep);
                    if (tmpStr && tmpStr[0]) {
                        if (sscanf(tmpStr, LOC_TIMEFMT, &tmpTime.day, &tmpTime.month, &tmpTime.year) == 3)
                            tmpTimeSet = TRUE;
                        else {
                            THERR("Warning, invalid timestamp '%s' in '%s' (line #%d in '%s')\n",
                                tmpStr, tmpLocNames[0], f->lineNum, f->filename);
                            state = STATE_ERROR;
                        }
                    }
                    if (!strchr(fieldsep, f->c)) {
                        state = STATE_ERROR;
                        THERR("Expected field separator '%s' after DATE on line #%d of '%s'.\n",
                            fieldsep, f->lineNum, f->filename);
                    } else {
                        field++;
                        prev = state;
                        next = STATE_FIELD_SEP;
                        state = STATE_NEXT;
                    }
                    th_free(tmpStr);
                    break;
                
                case 7: /* URI */
                    th_free(tmpURI);
                    tmpURI = parseFieldString(f, fieldsep);
                    field++;
                    prev = state;
                    next = STATE_FIELD_SEP;
                    state = STATE_NEXT;
                    break;
                
                case 8: /* Freeform */
                    tmpStr = parseFieldString(f, "\r\n");

                    /* Check if location already exists */
                    tmpX += offX;
                    tmpY += offY;
            
                    i = findLocationByCoords(l, tmpX, tmpY, TRUE);
                    if (i >= 0) {
                        marker_t *tloc = l->locations[i];
                
                        THERR("Warning, location already in list!\n"
                        "(%d,%d) '%s' <-> (%d,%d) '%s'\n",
                            tloc->x, tloc->y, tloc->names[0],
                            tmpX, tmpY, tmpLocNames[0]);
                        state = STATE_ERROR;
                    } else {
                        /* Add new location to our list */
                        addLocation(l, tmpX, tmpY, tmpOrient, tmpFlags,
                            tmpLocNames, tmpCoderNames, &tmpTime,
                            tmpTimeSet, tmpURI, tmpStr, f);
                        prev = state;
                        state = STATE_IDLE;
                    }
                    for (i = 0; i < LOC_MAX_NAMES; i++) {
                        th_free(tmpLocNames[i]);
                        th_free(tmpCoderNames[i]);
                        tmpLocNames[i] = tmpCoderNames[i] = NULL;
                    }
                    th_free(tmpStr);
                    break;
                
                default:
                    THERR("FATAL ERROR! Invalid state=%d!\n", state);
                    state = STATE_ERROR;
            }
            if (!res) {
                state = STATE_ERROR;
                THERR("Error parsing field %d on line #%d of '%s'.\n",
                    field, f->lineNum, f->filename);
            }
            break;
        
        default:
            THERR("Invalid state in loc-file parser - state=%d, prev=%d, next=%d, lineNum=%d, file='%s'.\n",
                state, prev, next, f->lineNum, f->filename);
            state = STATE_ERROR;
            break;
        }
    } while (state != STATE_ERROR && state != STATE_END);
    
    for (i = 0; i < LOC_MAX_NAMES; i++) {
        th_free(tmpLocNames[i]);
        th_free(tmpCoderNames[i]);
    }
    th_free(tmpURI);
    
    return (state == STATE_END);
}


int printLocType(FILE *outFile, marker_t *loc, BOOL doPrint)
{
    char *s = NULL;
    
    switch (loc->flags & LOCF_M_MASK) {
    case LOCF_M_PCITY:  s = "PCITY"; break;
    case LOCF_M_CITY:   s = "CITY"; break;
    
    default:
        switch (loc->flags & LOCF_T_MASK) {
        case LOCF_T_SHRINE:     s = "SHRINE"; break;
        case LOCF_T_GUILD:      s = "GUILD"; break;
        case LOCF_T_SS:         s = "SS"; break;
        case LOCF_T_TRAINER:    s = "TRAINER"; break;
        case LOCF_T_FORT:       s = "FORT"; break;
        }
    }
    
    if (s != NULL) {
        if (doPrint)
            fprintf(outFile, "%s ", s);
        return strlen(s) + 1;
    } else
        return 0;
}


/* Adjust location label coordinates for the ASCII-CTRL map
 */
void adjustLocInfoCoords(mapblock_t *map, locations_t *l)
{
    int i;
    
    for (i = 0; i < l->numLocations; i++) {
        int y, x0, x1, len;
        marker_t *tmp = l->locations[i];
        
        len = strlen(tmp->names[0]);
        if (optLabelType) len += printLocType(NULL, tmp, FALSE);
        
        /* Compute text location */
        switch (tmp->dir) {
        case LOCD_LEFTDOWN:
            y = tmp->y + 1;
            x0 = tmp->x - len;
            break;
            
        case LOCD_LEFT:
            y = tmp->y - 1;
            x0 = tmp->x - len;
            break;

        case LOCD_DOWN:
            y = tmp->y + 1;
            x0 = tmp->x + 1;
            break;
            
        case LOCD_NONE:
        default:
            y = tmp->y - 1;
            x0 = tmp->x + 1;
            break;
        }
            
        x1 = x0 + len + 1;
            
        if (x1 > map->w)
            x0 -= (x1 - map->w - 1);
        if (x0 < 0) x0 = 0;
        
        if (y < 0) y += 2;
        if (y > map->h) y -= (y - map->h - 1);
        
        /* Update location info */
        tmp->x = x0;
        tmp->y = y;
    }
}


/* Check for adjacent markers
 */
int checkForAdjacent(locations_t *worldLoc, int cx, int cy, int locMask)
{
    int x, y, n;
    
    for (y = -1; y <= 1; y++)
    for (x = -1; x <= 1; x++)
    if (!(y == 0 && x == 0) &&
        (n = findLocationByCoords(worldLoc, cx+x, cy+y, TRUE)) >= 0) {
        marker_t *tmpl = worldLoc->locations[n];
        if ((tmpl->flags & locMask) == locMask)
            return n;
    }
    
    return -1;
}


/* Scan given map and update location list with new locations,
 * if any are found.
 */
void updateLocations(mapblock_t *worldMap, locations_t *worldLoc)
{
    int x, y, n, numNewLoc = 0, numInvLoc = 0, numNoMarker = 0;
    unsigned char *d = worldMap->d;
    marker_t *tmpl;
        
    THMSG(2, "Updating location information ..\n");
        
    /* Find new, unknown locations */
    for (y = 0; y < worldMap->h; y++)
    for (x = 0; x < worldMap->w; x++) {
        if (strchr(optLocMarkers, *d)) {
            /* Check for new locations */
            if (findLocationByCoords(worldLoc, x, y, TRUE) < 0) {
                char *tmpNames[LOC_MAX_NAMES];
                char tmpDesc[512];
                int tmpFlags;

                if (*d == 'C') {
                    n = checkForAdjacent(worldLoc, x, y, LOCF_M_PCITY);
                    if (n >= 0) {
                        tmpl = worldLoc->locations[n];
                        tmpFlags = LOCF_M_PCITY | LOCF_INVIS;
                        snprintf(tmpDesc, sizeof(tmpDesc),
                        "%s", tmpl->names[0]);
                        
                    } else {
                        numNewLoc++;
                        tmpFlags = LOCF_M_PCITY;
                        snprintf(tmpDesc, sizeof(tmpDesc),
                        "%.3s-PCITY #%d",
                        srcFile, numNewLoc);
                    }
                } else {
                    numNewLoc++;
                    tmpFlags = LOCF_NONE;
                    snprintf(tmpDesc, sizeof(tmpDesc),
                        "%.3s-UNK #%d",
                        srcFile, numNewLoc);
                }
                memset(tmpNames, 0, sizeof(tmpNames));
                tmpNames[0] = tmpDesc;
                addLocation(worldLoc, x, y, LOCD_NONE, tmpFlags,
                    tmpNames, NULL, NULL, FALSE, NULL, NULL, NULL);
            }
        } else {
            /* Check for misplaced locations */
            if ((n = findLocationByCoords(worldLoc, x, y, TRUE)) >= 0) {
                tmpl = worldLoc->locations[n];
                
                if (tmpl->flags == LOCF_NONE) {
                    /* Mark as possibly invalid */
                    tmpl->flags |= LOCF_INVALID;
                    numInvLoc++;
                }
                
                if ((tmpl->flags & LOCF_M_MASK) == 0) {
                    /* No apparent marker */
                    tmpl->flags |= LOCF_NOMARKER;
                    numNoMarker++;
                }
            }
        }
        d++;
    }
    
    THMSG(1, "%d new locations, %d invalid, %d missing marker.\n",
        numNewLoc, numInvLoc, numNoMarker);
}


/* Quicksort comparision function for location names
 */
int maplocCompare(const void *p1, const void *p2)
{
    marker_t *vp1 = *(marker_t **) p1, *vp2 = *(marker_t **) p2;
    
    if (vp1->names[0] && vp2->names[0])
        return strcmp(vp1->names[0], vp2->names[0]);
    else
        return 0;
}


/* Sort locations by name
 */
void maplocSort(locations_t *l)
{
    qsort(l->locations, l->numLocations, sizeof(marker_t *), maplocCompare);
}

/* Output the map with labels and location markers, etc. in
 * special ASCII-CTRL format understood by colormap utility.
 */
void outputMapCTRL(FILE *outFile, mapblock_t *map, locations_t *l)
{
    unsigned char *d;
    int x, y, n;

    d = map->d;
    
    for (y = 0; y < map->h; y++) {
        for (x = 0; x < map->w; x++) {
            if ((n = findLocationByCoords(l, x, y, TRUE)) >= 0) {
                marker_t *tmp = l->locations[n];
                
                switch (tmp->flags & LOCF_M_MASK) {
                case LOCF_M_SCENIC1:
                    fputc('?', outFile);
                    break;
                    
                case LOCF_M_SCENIC2:
                    fputc('%', outFile);
                    break;
                    
                case LOCF_M_PCITY:
                    fputc('C', outFile);
                    break;
                
                case LOCF_M_CITY:
                    fputc('c', outFile);
                    break;
                    
                default:
                    if (tmp->flags & LOCF_INVALID)
                        fputc('$', outFile);
                    else
                        fputc(d[(map->w * y) + x], outFile);
                    break;
                }
                
            } else
            if (!optNoLabels && (n = findLocationByCoords(l, x, y, FALSE)) >= 0) {
                marker_t *tmp = l->locations[n];
                
                if ((tmp->flags & LOCF_INVIS) == 0) {
                    int col = col_light_white;
                    
                    fputc(0xff, outFile);
                    fprintf(outFile, "loc%d_%d", tmp->ox, tmp->oy);
                    fputc(0xfc, outFile);
                    
                    switch (tmp->flags & LOCF_M_MASK) {
                    case LOCF_M_PCITY: col = col_light_green; break;
                    case LOCF_M_CITY: col = col_light_red; break;
                    
                    default:
                        switch (tmp->flags & LOCF_T_MASK) {
                        case LOCF_T_SHRINE: col = col_light_yellow; break;
                        case LOCF_T_GUILD: col = col_light_magenta; break;
                        case LOCF_T_MONSTER: col = col_light_cyan; break;
                        case LOCF_T_TRAINER: col = col_light_magenta; break;
                        case LOCF_T_FORT: col = col_light_cyan; break;
                        default: col = col_light_white; break;
                        }
                        break;
                    }
                    
                    fputc(col, outFile);
                    
                    if (optLabelType) {
                        if (optNoAdjust)
                            printLocType(outFile, tmp, TRUE);
                        else
                            x += printLocType(outFile, tmp, TRUE);
                    }
                    
                    fputs(tmp->names[0], outFile);
                    fputc(0xfe, outFile);
                    
                    if (!optNoAdjust)
                        x += strlen(tmp->names[0]) - 1;
                    else
                        fputc(d[(map->w * y) + x], outFile);
                } else {
                    fputc(d[(map->w * y) + x], outFile);
                }
            } else {
                fputc(d[(map->w * y) + x], outFile);
            }
        }
        
        fprintf(outFile, "\n");
    }
}


/* Print out location list as HTML option list.
 */
void outputMapLocHTML(FILE *outFile, locations_t *l)
{
    int i;
    assert(l != NULL);
    
    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];

        if ((tmp->flags & LOCF_INVIS) == 0) {
            fprintf(outFile, "<option value=\"loc%d_%d\">", tmp->ox, tmp->oy);
            printLocType(outFile, tmp, TRUE);
            fprinte(outFile, tmp->names[0]);
            fprintf(outFile, "</option>\n");
        }
    }
}


/* Output generated locations into given file stream
 */
void outputLocationFile(FILE *outFile, locations_t *l)
{
    int i, n;
    
    /* Output header */
    fprintf(outFile,
    "# " LOC_MAGIC " (version %d.%d)\n",
    LOC_VERSION_MAJOR, LOC_VERSION_MINOR
    );
    
    fprintf(outFile,
    "# Refer to README.loc for more information.\n"
    "#\n"
    );
    
    /* Output each location entry */
    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];
        
        /* Add comment in few cases */
        if (tmp->flags & LOCF_Q_MASK) {
            char *s = NULL;
            if (tmp->flags & LOCF_NOMARKER)
                s = "Location missing marker";
            else if (tmp->flags & LOCF_INVALID)
                s = "Possibly invalid location";
            
            if (s) {
                fprintf(outFile, "\n# %s #%d: %s\n",
                    s, i, tmp->names[0]);
            }
        }
        
        fprintf(outFile, "%d\t; %d\t; %d",
            tmp->ox, tmp->oy, tmp->dir);
        
        switch (tmp->flags & LOCF_M_MASK) {
        case LOCF_M_SCENIC1:    fputc('?', outFile); break;
        case LOCF_M_SCENIC2:    fputc('%', outFile); break;
        case LOCF_M_PCITY:      fputc('C', outFile); break;
        case LOCF_M_CITY:       fputc('c', outFile); break;
        }

        switch (tmp->flags & LOCF_T_MASK) {
        case LOCF_T_SHRINE:     fputc('S', outFile); break;
        case LOCF_T_GUILD:      fputc('G', outFile); break;
        case LOCF_T_SS:         fputc('P', outFile); break;
        case LOCF_T_MONSTER:    fputc('M', outFile); break;
        case LOCF_T_TRAINER:    fputc('T', outFile); break;
        case LOCF_T_FORT:       fputc('F', outFile); break;
        }
        
        if (tmp->flags & LOCF_INVIS)
            fputc('-', outFile);
        
        fprintf(outFile, "\t;");
        fprintEsc2(outFile, tmp->names[0]);
        for (n = 1; n < tmp->nnames; n++) {
            fprintf(outFile, "|");
            fprintEsc2(outFile, tmp->names[n]);
        }
        fprintf(outFile, ";");
        
        if (tmp->ncoders > 0) {
            fprintEsc2(outFile, tmp->coders[0]);
            for (n = 1; n < tmp->ncoders; n++) {
                fprintf(outFile, ",");
                fprintEsc2(outFile, tmp->coders[n]);
            }
        }
        
        fprintf(outFile, ";");
        
        if (tmp->addedValid) {
            fprintf(outFile, LOC_TIMEFMT,
            tmp->added.day, tmp->added.month, tmp->added.year);
        }
        
        fprintf(outFile, ";");
        
        if (tmp->uri)
            fprintEsc2(outFile, tmp->uri);
        
        fprintf(outFile, ";");
        
        if (tmp->freeform)
            fprintEsc2(outFile, tmp->freeform);
        
        fprintf(outFile, "\n");
    }
}


/* Generate a shell-script for running ImageMagick to add
 * location and label information to an map image.
 */
void outputMagickScript(FILE *outFile, locations_t *l)
{
    int i, numCity, numLoc, numTotal;

    /* Output script start */
    fprintf(outFile,
        "#!/bin/sh\n"
        "convert \"$1\" @OPTS_START@ \\\n");
    
    numCity = numLoc = numTotal = 0;
    
    /* Output instructions for each visible location */
    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];
        int x, y, leftMove;
        char *cs;
        
        /* Is location visible? */
        if ((tmp->flags & LOCF_INVIS) == 0) {
        
        leftMove = ((float) strlen(tmp->names[0])) * optFontScale;
        
        switch (tmp->dir) {
        case LOCD_LEFTDOWN:
            y = tmp->y + optUnitSize*3.0f;
            x = tmp->x - leftMove;
            break;
            
        case LOCD_LEFT:
            y = tmp->y - optUnitSize;
            x = tmp->x - leftMove;
            break;

        case LOCD_DOWN:
            y = tmp->y + optUnitSize*3.0f;
            x = tmp->x + optUnitSize;
            break;
            
        case LOCD_NONE:
        default:
            y = tmp->y - optUnitSize;
            x = tmp->x + optUnitSize;
            break;
        }
        
        /* Determine colour */
        switch (tmp->flags & LOCF_M_MASK) {
        case LOCF_M_CITY:
            cs = "red";
            numLoc++;
            break;
        case LOCF_M_PCITY:
            cs = "'#00ff00'";
            numCity++;
            break;
        default:
            switch (tmp->flags & LOCF_T_MASK) {
            case LOCF_T_SHRINE: cs = "yellow"; break;
            case LOCF_T_GUILD: cs = "magenta"; break;
            case LOCF_T_MONSTER: cs = "cyan"; break;
            case LOCF_T_TRAINER: cs = "magenta"; break;
            case LOCF_T_FORT: cs = "'#00ffff'"; break;
            default: cs = "white"; break;
            }
            numLoc++;
            break;
        }
        
        numTotal++;
        
        /* Location marker */
        fprintf(outFile,
            "\t-fill black -draw \"circle %d,%d %d,%d\" ",
            tmp->x, tmp->y,
            (int) (tmp->x + optUnitSize), (int) (tmp->y + optUnitSize));
        
        fprintf(outFile,
            "-fill %s -draw \"circle %d,%d %d,%d\" ",
            cs, tmp->x, tmp->y,
            (int) (tmp->x + optUnitSize * 0.90f),
            (int) (tmp->y + optUnitSize * 0.90f));
        

        /* Location description text */
        if (!optNoLabels) {
            fprintf(outFile,
                "-fill %s -box '#00000080' -draw \"text %d,%d '",
                cs, x, y);
            fprintEsc1(outFile, tmp->names[0]);
            fprintf(outFile, "'\" ");
        }

        fprintf(outFile, " \\\n");
        }
    }

    fprintf(outFile,
        "@OPTS_END@"
        "'\" \\\n");

    fprintf(outFile, "\t\"$2\"\n");
}


/* Output locations in GMaps XML format. Character set conversion
 * is not performed, caller must take care of it via iconv or similar.
 */
char *getLocType(marker_t *loc)
{
    char *type = NULL;
    switch (loc->flags & LOCF_M_MASK) {
        case LOCF_M_CITY:   type = "city"; break;
        case LOCF_M_PCITY:  type = "pcity"; break;
        default:
            switch (loc->flags & LOCF_T_MASK) {
            case LOCF_T_SHRINE:  type = "shrine"; break;
            case LOCF_T_GUILD:   type = "guild"; break;
            case LOCF_T_SS:      type = "ss"; break;
            case LOCF_T_MONSTER: type = "monster"; break;
            case LOCF_T_TRAINER: type = "trainer"; break;
            case LOCF_T_FORT:    type = "fort"; break;
            default:             type = "default"; break;
        }
        break;
    }
    return type;
}

void outputGMapsOverlay(FILE *outFile, locations_t *l)
{
    int i;

    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];
        int type;

        /* Skip disabled / invisible locations */
        //if (tmp->flags & LOCF_INVIS) continue;

        switch (tmp->flags & LOCF_M_MASK) {
        case LOCF_M_SCENIC1:    type = '?'; break;
        case LOCF_M_SCENIC2:    type = '%'; break;
        case LOCF_M_CITY:       type = 'c'; break;
        case LOCF_M_PCITY:      type = 'C'; break;
        default: type = -1;
        }

        if (type > 0)
            fprintf(outFile, "%d,%d,%c\n", tmp->x, tmp->y, type);
    }
}

void outputGMapsLabels(FILE *outFile, locations_t *l)
{
    int i;

    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];

        /* Skip disabled / invisible locations */
        if (tmp->flags & LOCF_INVIS) continue;

        fprintf(outFile, "%d,%d,", tmp->x, tmp->y);
        printLocType(outFile, tmp, TRUE);
        fprintf(outFile, "%s\n", tmp->names[0]);
    }
}

void outputGMapsXML(FILE *outFile, locations_t *l)
{
    int i, n;
    
    fprintf(outFile,
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    "<markers>\n");
    
    /* Output each location entry */
    for (i = 0; i < l->numLocations; i++) {
        marker_t *tmp = l->locations[i];
        char *str;

        /* Skip disabled / invisible locations */
        if (tmp->flags & LOCF_INVIS) continue;

        /* Print out coordinates etc. */
        fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\" name=\"",
            tmp->ox, tmp->oy, tmp->dir);

        /* Location name */
        str = (tmp->names[0] != NULL) ? tmp->names[0] : "UNKNOWN";
        printLocType(outFile, tmp, TRUE);
        fprinte(outFile, str);
        fprintf(outFile, "\" html=\"");
        if (tmp->uri != NULL)
            fprintfe(outFile, "<b><a target=\"_blank\" href=\"%s\">%s</a></b><br>", tmp->uri, str);
        else
            fprintfe(outFile, "<b>%s</b><br>", str);

        /* Alternative names, if any */
        if (tmp->nnames > 1) {
            fprinte(outFile, "Also known as <i>");
            for (n = 1; n < tmp->nnames; n++) {
                fprinte(outFile, tmp->names[n]);
                if (n < tmp->nnames - 1)
                    fprinte(outFile, " | ");
            }
            fprinte(outFile, "</i>.<br>");
        }

        /* Added to game timestamp */
        if (tmp->addedValid) {
            fprintfe(outFile, "Added " LOC_TIMEFMT ".<br>",
                tmp->added.day, tmp->added.month, tmp->added.year);
        }

        /* Coder names or societies */
        if (tmp->ncoders > 0) {
            if (tmp->flags & LOCF_M_PCITY) {
                fprinte(outFile, "Societies: ");
                for (n = 0; n < tmp->ncoders; n++) {
                    fprinte(outFile, tmp->coders[n]);
                    if (n < tmp->ncoders - 1)
                        fprinte(outFile, ", ");
                }
            } else {
                fprinte(outFile, "Coders: ");
                for (n = 0; n < tmp->ncoders; n++) {
                    fprintfe(outFile, "<a target=\"_blank\" href=\"http://www.bat.org/char/%s\">%s</a>",
                        tmp->coders[n], tmp->coders[n]);
                    if (n < tmp->ncoders - 1)
                        fprinte(outFile, ", ");
                }
            }
            fprinte(outFile, ".<br>");
        }

        /* Print out freeform information field */
        if (tmp->freeform)
            fprintfe(outFile, "<br>%s<br>", tmp->freeform);

        /* Flags */
        fprintf(outFile, "\" flags=\"%d\"",
            tmp->flags);

        /* Continent name */
        if (tmp->file != NULL && tmp->file->continent != NULL) {
            fprintf(outFile, " continent=\"");
            fprinte(outFile, tmp->file->continent);
            fprintf(outFile, "\"");
        }

        /* Type of the marker */
        fprintf(outFile, " type=\"%s\"/>\n", getLocType(tmp));
    }
    
    fprintf(outFile, "</markers>\n");
}


int main(int argc, char *argv[])
{
    FILE *outFile;
    mapblock_t *worldMap = NULL;
    locations_t worldLoc;
    int i;
    
    th_memset(&worldLoc, 0, sizeof(worldLoc));

    /* Initialize */
    th_init("mkloc", "Manipulate and convert location files and ASCII map data", "1.3", NULL, NULL);
    th_verbosityLevel = 0;
    
    /* Parse arguments */
    if (!th_args_process(argc, argv, optList, optListN,
        argHandleOpt, NULL, FALSE))
        exit(1);
    
    /* Check the mode and arguments */
    if (srcFile == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) {
        THERR("Map file required for location update mode!\n");
        exit(0);
    }    
        
    if (optOutput == OUTFMT_LOCFILE && nlocFiles < 0 && !optGetUpdateLoc) {
        THERR("Location file or location update mode required for location file output!\n");
        exit(0);
    }
    
    if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && nlocFiles < 0) {
        THERR("Location file required for script or MapLoc HTML output!\n");
        exit(0);
    }
    
    if (srcFile == NULL && optOutput == OUTFMT_MAP) {
        THERR("Mapfile required for map generation.\n");
        exit(0);
    }
    
    /* Read initial map */
    if (srcFile) {
        THMSG(2, "Reading map '%s'\n", srcFile);
        worldMap = parseFile(srcFile, FALSE);
        if (!worldMap) {
            THERR("World map could not be loaded!\n");
            exit(1);
        }
        
        THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->w, worldMap->h);
    }

    /* Read location info */
    for (i = 0; i <= nlocFiles; i++) {
        fileinfo_t *f = &locFiles[i];

        if (optOutput == OUTFMT_GMAPS && f->continent == NULL) {
            THERR("Required continent name not set for #%d '%s'.\n", i, f->filename);
            exit(19);
        }

        THMSG(2, "Reading location info '%s'\n", f->filename);
        if ((f->f = fopen(f->filename, "rb")) == NULL) {
            THERR("Could not open location file '%s' for reading.\n",
                f->filename);
            exit(2);
        }

        if (!parseLocationStream(f, &worldLoc, f->x, f->y))
            exit(1);
        
        fclose(f->f);
    }
    
    /* Update locations */
    if (optGetUpdateLoc)
        updateLocations(worldMap, &worldLoc);
    
    /* Scale locations */
    if (optScale > 0) {
        int i;
        
        THMSG(1, "Scaling locations ..\n");
        
        for (i = 0; i < worldLoc.numLocations; i++) {
            marker_t *tmp = worldLoc.locations[i];
            
            tmp->x = ((float) tmp->x) * optScale;
            tmp->y = ((float) tmp->y) * optScale;
        }
    }
    
    /* Open output file */
    if (destFile == NULL)
        outFile = stdout;
    else if ((outFile = fopen(destFile, "wb")) == NULL) {
        THERR("Error opening output file '%s'!\n",
            destFile);
        exit(1);
    }

    /* Output results */
    switch (optOutput) {
    case OUTFMT_SCRIPT:
        THMSG(1, "Generating ImageMagick script ...\n");
        outputMagickScript(outFile, &worldLoc);
        break;

    case OUTFMT_LOCFILE:
        THMSG(1, "Outputting generated location list ...\n");
        outputLocationFile(outFile, &worldLoc);
        THMSG(2, "%d locations\n", worldLoc.numLocations);
        break;
    
    case OUTFMT_MAPLOC:
        maplocSort(&worldLoc);
        THMSG(1, "Outputting MapLoc HTML ...\n");
        outputMapLocHTML(outFile, &worldLoc);
        THMSG(2, "%d locations\n", worldLoc.numLocations);
        break;
        
    case OUTFMT_GMAPS:
        maplocSort(&worldLoc);
        THMSG(1, "Outputting GMaps data (%s) ...\n", gmapsModes[optGMapsMode]);
        switch (optGMapsMode) {
            case GMAPS_XML: outputGMapsXML(outFile, &worldLoc); break;
            case GMAPS_OVERLAY: outputGMapsOverlay(outFile, &worldLoc); break;
            case GMAPS_LABELS: outputGMapsLabels(outFile, &worldLoc); break;
        }
        THMSG(2, "%d locations\n", worldLoc.numLocations);
        break;

    case OUTFMT_MAP:
        THMSG(1, "Outputting generated map of (%d x %d) ...\n",
            worldMap->w, worldMap->h);

        THMSG(2, "Adjusting location labels ..\n");
        adjustLocInfoCoords(worldMap, &worldLoc);
        
        outputMapCTRL(outFile, worldMap, &worldLoc);
        break;
    }
    
    fclose(outFile);

    exit(0);
    return 0;
}