/*
 * Crossfire map browser generator.
 *
 * Author: Nicolas Weeger <nicolas.weeger@laposte.net>, (C) 2006, 2007, 2008.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/**
 * @file mapper.c
 * This program generates map browsing web pages.
 *
 * Quick run: without arguments, will make sensible choices.
 *
 * For help, try the -help option.
 *
 * The following information is generated:
 * - a page per map
 * - a page per region
 * - a global map index
 * - the world map, including regions information
 * - the world map, with exits and blocking zones
 * - the world map, with elevation information.
 *
 * Since this program browses maps from the first map, only maps linked from there will be processed.
 *
 * Maps are generated as the server sees them, that is with weather effects, treasures instead of markers,
 * and things like that.
 *
 * For maps, 2 pictures are generated, one real size and one small-size.
 *
 * Three maps are generated for the world: raw world map, world map with region information, region information only.
 *
 * Maps are always sorted, by the map name (part after the last /).
 *
 * Files are generated based on templates, where tags are replaced by their runtime value.
 *
 * Templates are recursively included. Here is the list:
 * - map.template: main map template.
 *  - map_no_exit.template: template to use when no exit on the map.
 *  - map_with_exit.template: template to use when there are exits on the map.
 *   - map_exit.template: template to use for each exit on the map.
 *  - map_lore: template to use to insert the map's lore.
 *  - map_no_lore: template when no lore for the map.
 *  - map_no_quest.template: when the map is linked to no quest.
 *  - map_with_quests.template: the map is linked to at least one quest.
 *   - map_one_quest.template: one quest link.
 *  - map_no_monster: used when no monster on map.
 *  - map_monster_before: applied before the monster list.
 *  - map_monster_one: one monster on the map.
 *  - map_monster_between: added after each monster except the last.
 *  - map_monster_after: added after the last monster in the list.
 * - region.template: region page template.
 *  - region_letter: template for one letter
 *   - region_map.template: one map in a region
 * - world.template: world map template
 *  - world_row.template: one row of world maps.
 *   - world_map.template: one map in the template.
 * - index.template: global map index.
 *  - index_letter.template: one letter in the index
 *   - index_map.template: one map in the whole index
 * - index_region.template: region index template.
 *  - index_region_region.template: one region in the index.
 * - level.template: index of maps by level.
 *  - level_value.template: one level.
 *   - level_map.template: one map in the level.
 * - quests.template: quest index.
 *  - quests_quest.template: one quest.
 *   - quests_map.template: one map in a quest.
 *
 *
 * Tags must be in the form <code>\#TAG#</code>. To have a # in the code, please put ##. Valid tags are:
 *
 * - map:
 *  - NAME: map relative path
 *  - MAPPATH: full path to currently generated file.
 *  - MAPNAME: name of the map (part of the path after last /).
 *  - MAPPIC: name of full-size pic of the map.
 *  - MAPSMALLPIC: name of reduced-size pic of the map.
 *  - MAPEXIT: text generated by map_with_exit or map_no_exit.
 *  - INDEXPATH: path to index.html file.
 *  - REGIONPATH: path to region's file.
 *  - REGIONNAME: name of map's region.
 *  - REGIONINDEXPATH: path to region index file.
 *  - WORLDMAPPATH: path to world map file.
 *  - MAPLORE: map's lore.
 *  - MAPLEVEL: level as defined in the map.
 *  - MINMONSTER and MAXMONSTER: minimum and maximum level of monsters on the map.
 * - map_no_exit:
 *  - tags for map, except MAPEXIT.
 * - map_with_exit:
 *  - tags for map, except MAPEXIT.
 *  - EXIT: text generated by the map exits.
 * - map_exit:
 *  - map's tags.
 *  - EXITNAME: name of exit (part of the path after /).
 *  - EXITPATH: relative path of exit's page.
 * - map_lore:
 *  - tags for map, except MAPEXIT.
 *  - MAPLORE: map's lore.
 * - map_no_lore:
 *  - tags for map, except MAPEXIT.
 * - map_with_quests:
 *  - tags for map
 *  - QUESTS: result of map_one_quest processing.
 *   - map_one_quest:
 *    - NAME: map's name.
 *    - PATH: path to the quest page from the index.
 *    - TEXT: text associated to the map about the quest.
 * - map_no_quest: not processed.
 * - map_no_monster:
 *  - map's tags
 * - map_monster_before:
 *  - map's tags
 * - map_monster_one:
 *  - NAME: monster's name.
 * - map_monster_between:
 *  - map's tags
 * - map_monster_after:
 *  - map's tags
 * - region:
 *  - MAPCOUNT: count of maps in region.
 *  - LETTERS: text generated by region_letter processing.
 * - region_letter:
 *  - region's tags, except LETTERS.
 *  - MAPNAME: name of the map (part of the path after last /).
 *  - MAPPATH: relative path of the map from the map's root.
 *  - MAPHTML: relative path of HTML file for map.
 * - region_map:
 *  - tags of region_letter.
 *  - MAPNAME: name of the map (part of the path after last /).
 *  - MAPPATH: relative path of the map from the map's root.
 *  - MAPHTML: relative path of HTML file for map.
 * - world:
 *  - MAPS: text generated by world_row processing.
 *  - WORLDMAP: path to world map picture, with regions information.
 *  - WORLDRAW: path to raw world map, without regions information.
 *  - WORLDREGIONS: path to world map picture containing only regions information.
 * - world_row:
 *  - MAPS: text generated by world_map processing.
 * - world_map:
 *  - MAPNAME: name of the map (part of the path after last /).
 *  - MAPPATH: relative path of the map's generated page.
 *  - MAPLEFT, MAPTOP, MAPRIGHT, MAPBOTTOM: coordinates (in pixels) of current map in full world map.
 * - index:
 *  - MAPCOUNT: count of maps.
 *  - LETTERS: text generated by index_letter processing.
 * - index_letter:
 *  - tags of index, except LETTERS.
 *  - MAPS: text generated by index_letter processing.
 *  - LETTER: letter being processed, uppercase.
 *  - LETTERCOUNT: number of maps for current letter.
 * - index_map:
 *  - tags of index_letter.
 *  - MAPNAME: name of the map (part of the path after last /).
 *  - MAPPATH: relative path of the map from the map's root.
 *  - MAPHTML: relative path of HTML file for map.
 * - index_region:
 *  - REGIONCOUNT: total number of regions.
 *  - REGIONS: text generated by index_region_region processing.
 * - index_region_region:
 *  - REGIONFILE: relative path to region page.
 *  - REGIONNAME: region name.
 * - level:
 *  - COUNT: count of different levels.
 *  - LEVELS: result of the sub-level templates.
 * - level_value:
 *  - LEVEL: current level.
 *  - MAPS: result of the level maps templates.
 * - level_map:
 *  - MAPNAME: name of the map.
 *  - MAPPATH: relative path of the map from the index.
 * - quests:
 *  - QUESTS: processing of quest.
 * - quests_quest:
 *  - QUESTNAME: quest name.
 *  - QUESTTEXT: quest description.
 *  - QUESTMAPS: processing of quests_map.
 *  - QUESTID: unique quest identifier, anchor to index page.
 *  - MAINMAPPATH: path to the map defining the quest.
 *  - MAINMAPNAME: name of the map defining the quest.
 * - quests_map:
 *  - MAPPATH: path to the map.
 *  - MAPNAME: map name.
 *  - MAPTEXT: description associated.
 *
 * To build this program, from the utils directory:
 *  <pre>gcc mapper.c -I../include ../common/libcross.a -o mapper -lm -lgd</pre>
 *
 * @todo
 * - split this file in multiple ones for easier maintenance
 * - add missing documentation on variables / functions
 * - add command line argument for large / small picture size
 * - add maximum width/height for small picture
 * - add slaying information to maps themselves
 * - make the equipment page use templates
 * - shop catalog
 * - treasure list use
 */

#include <time.h>
#include <stdio.h>
/* For strcasecmp(). */
#include <strings.h>

#include <global.h>
#include <sproto.h>
#include <image.h>

#include <gd.h>
#include <gdfonts.h>
#include <gdfontl.h>
#include <gdfontg.h>

static gdImagePtr *gdfaces;

/** Information about a NPC with a custom message. */
typedef struct struct_npc_info {
    const char *name;       /**< NPC's name. */
    const char *message;    /**< NPC's message. */
    int x, y;               /**< Coordinates in the map. */
} struct_npc_info;

/** List of NPCs with a custom message. */
typedef struct struct_npc_list {
    struct_npc_info **npc;
    int count;
    int allocated;
} struct_npc_list;

/** Collection of races. */
typedef struct struct_race_list {
    struct struct_race **races; /**< Races on the list. */
    int count;                  /**< Number of races. */
    int allocated;              /**< Allocated space. */
} struct_race_list;

/** Utility structure to group map-quest link structure. */
typedef struct {
    struct struct_map_in_quest **list;
    int count;
    int allocated;
} struct_map_in_quest_list;

/** List of maps. */
typedef struct {
    struct struct_map_info **maps;
    int count;
    int allocated;
} struct_map_list;

/** Map information. */
typedef struct struct_map_info {
    char *path;
    char *name;
    char *filename;
    char *lore;
    region *cfregion;
    int level, pic_was_done, max_monster, min_monster;
    struct_map_list exits_from;
    struct_map_list exits_to;
    struct_map_in_quest_list quests;

    struct_map_list tiled_maps;

    struct_race_list monsters;

    struct_npc_list npcs;
    struct_npc_list readable;

    struct struct_map_info *tiled_group;
    int height, width;
    int tiled_x_from, tiled_y_from, processed;
    struct struct_map_info *tiles[4];
} struct_map_info;

/** Maps to process or found. */
static struct_map_list maps_list;

/** Pseudo-maps grouping other maps. */
static struct_map_list tiled_map_list;

/** One special item (weapon, shield, ...). */
typedef struct struct_equipment {
    char *name;             /**< Item's name. */
    int power;              /**< Item power as declared by the item itself. */
    int calc_power;         /**< Item power calculated via calc_item_power(). */
    char *diff;             /**< Result of get_ob_diff() with the item's clone. */
    struct_map_list origin; /**< Map(s) this item is found in. */
} struct_equipment;

static struct_equipment **special_equipment = NULL;    /**< Special equipment list. */

static int equipment_count = 0;                        /**< Number of items in special_equipment. */

static int equipment_allocated = 0;                    /**< Allocated items in special_equipment. */


/** One monster race in the maps. */
typedef struct struct_race {
    char *name;             /**< Monster's name. */
    int count;              /**< Number found on map. */
    struct_map_list origin; /**< Maps to find said monster. */
} struct_race;

static struct_race_list races;     /**< Monsters found in maps. */

/**
 * Blanks a struct_race_list.
 * @param list
 * list to blank.
 */
static void init_race_list(struct_race_list *list) {
    list->races = NULL;
    list->count = 0;
    list->allocated = 0;
}

/**
 * Appends a race to a race list.
 *
 * @param race
 * race to add.
 * @param list
 * list to add to.
 * @param check
 * if 0, don't check if race is already on the list ; else don't make duplicated entries.
 */
static void add_race_to_list(struct_race *race, struct_race_list *list, int check) {
    if (check) {
        int test;

        for (test = 0; test < list->count; test++) {
            if (list->races[test] == race)
                return;
        }
    }

    if (list->allocated == list->count) {
        list->allocated += 50;
        list->races = realloc(list->races, sizeof(struct_race *)*list->allocated);
    }
    list->races[list->count] = race;
    list->count++;
}

/** Path to store generated files. Relative or absolute, shouldn't end with a / */
static char root[500];

/** Number of created pictures for GD. */
static int pics_allocated;

/* Options */
static int generate_pics = 1;     /**< Whether to generate the picture or not. */
static int force_pics = 0;        /**< To force picture regeneration even if map didn't change. */
static int generate_index = 1;    /**< Whether to do the map index or not. */
static int size_small = 16;       /**< Tile size for small map */
static int map_limit = -1;        /**< Maximum number of maps to browse, -1 for all. */
static int show_maps = 0;         /**< If set, will generate much information on map loaded. */
static int world_map = 1;         /**< If set, will generate a world map. */
static int world_exit_info = 1;   /**< If set, will generate a world map of exits. */
static int tileset = 0;           /**< Tileset to use to generate pics. */

static char *world_template;               /**< World map template. */
static char *world_row_template;           /**< One row in the world map. */
static char *world_map_template;           /**< One map in a row. */

static char *map_template;                 /**< Map template. */
static char *map_no_exit_template;         /**< World map template: no exit. */
static char *map_with_exit_template;       /**< Map template: exit template. */
static char *map_exit_template;            /**< Map template: one exit. */
static char *map_no_exit_to_template;      /**< World map template: no exit leading to this map. */
static char *map_with_exit_to_template;    /**< Map template: exits leading to this map. */
static char *map_exit_to_template;         /**< Map template: one exit leading to this map. */
static char *map_lore_template;            /**< Map template: lore. */
static char *map_no_lore_template;         /**< Map template: no lore. */
static char *map_no_monster_template;      /**< Map template: no monster. */
static char *map_monster_before_template;  /**< Map template: before the monster list. */
static char *map_monster_between_template; /**< Map template: between each monster. */
static char *map_monster_one_template;     /**< Map template: one monster. */
static char *map_monster_after_template;   /**< Map template: after the monster list. */

static char *index_template;           /**< Index page template. */
static char *index_letter;             /**< Index page template: one letter, including the maps it contains. */
static char *index_map;                /**< Index page template: one map. */

static char *region_template;          /**< Region page template. */
static char *region_letter_template;   /**< One letter for the region. */
static char *region_map_template;      /**< Region page template: one map. */

static char *index_region_template;        /**< Region index template. */
static char *index_region_region_template; /**< One region in the region index template. */

static char *level_template;
static char *level_value_template;
static char *level_map_template;

static char *index_quest_template;
static char *quest_template;
static char *quest_map_template;

static char *map_no_quest_template;
static char *map_with_quests_template;
static char *map_one_quest_template;

/** Picture statistics. */
static int created_pics = 0; /**< Total created pics. */
static int cached_pics = 0;  /**< Non recreated pics. */

/** Map output formats. */
enum output_format_type {
    OF_PNG = 0,     /**< PNG, default value. */
    OF_JPG = 1      /**< JPG. */
};

/** Extensions depending on output format. */
static const char *output_extensions[] = {
    ".png",
    ".jpg"
};

/** Selected output format. */
static enum output_format_type output_format = OF_PNG;

/** Quality for jpg pictures. */
static int jpeg_quality = -1;

/** Whether to generate raw pics or instancied ones. */
static int rawmaps = 0;

/** Whether to warn of exits without a path */
static int warn_no_path = 0;

/** Region information. */
typedef struct struct_region_info {
    region *reg;                /**< Region. */
    struct_map_list maps_list;  /**< Maps in the region. */
    int sum_x, sum_y, sum;      /**< Sum of locations, to compute name position. */
    int is_world;               /**< If set, this region has at least one map part of the world, thus region name should be written. */
} struct_region_info;

static struct struct_region_info **regions = NULL; /**< Found regions. */
static int region_count = 0;                /**< Count of regions. */
static int region_allocated = 0;            /**< Allocated size of regions. */

static int list_unused_maps = 0;       /**< If set, program will list maps found in directory but not linked from the first maps. */
static char **found_maps = NULL;       /**< Maps found in directories. */
static int found_maps_count = 0;       /**< Number of items in found_maps. */
static int found_maps_allocated = 0;   /**< Allocated size of found_maps. */

/* Path/exit info */
static gdImagePtr infomap;         /**< World map with exits / roads / blocking / ... */
static int color_unlinked_exit;    /**< Color for exits without a path set. */
static int color_linked_exit;      /**< Exit leading to another map. */
static int color_road;             /**< Road or equivalent. */
static int color_blocking;         /**< Block all movement. */
static int color_slowing;          /**< Slows movement. */

static int **elevation_info;       /**< All elevation spots in the "world_" maps. */
static int elevation_min;          /**< Maximal elevation found. */
static int elevation_max;          /**< Lowest elevation found. */

/* Links between regions */
static int do_regions_link = 0;
static char **regions_link;
static int regions_link_count = 0;
static int regions_link_allocated = 0;

/** Connection/slaying information. */
#define S_DOOR      0
#define S_KEY       1
#define S_CONTAINER 2
#define S_DETECTOR  3
#define S_CONNECT   4
#define S_MAX       5

/** slaying information. */
typedef struct {
    char *slaying;          /**< Slaying value. */
    struct_map_list maps[S_MAX];
} struct_slaying_info;

static struct_slaying_info **slaying_info = NULL;  /**< Found slaying fields. */
static int slaying_count = 0;                      /**< Count of items in slaying_info. */
static int slaying_allocated = 0;                  /**< Allocated size of slaying_info. */

/**
 * Initialises a list structure.
 * @param list
 * list to blank.
 */
static void init_map_list(struct_map_list *list) {
    list->maps = NULL;
    list->count = 0;
    list->allocated = 0;
}

static void add_map(struct_map_info *info, struct_map_list *list);

static int is_special_equipment(object *item) {
    if (item->name == item->arch->clone.name && item->title == item->arch->clone.title)
        return 0;
    if (QUERY_FLAG(item, FLAG_NO_PICK))
        return 0;
    if (item->move_block == MOVE_ALL)
        return 0;

    if (IS_SHIELD(item) || IS_WEAPON(item) || IS_ARMOR(item) || IS_ARROW(item) || (item->type == ROD) || (item->type == WAND) || (item->type == RING) || (item->type == AMULET))
        return 1;

    return 0;
}

/**
 * Gets an empty struct_equipment.
 * @return
 * new item.
 */
static struct_equipment *get_equipment(void) {
    struct_equipment *add = calloc(1, sizeof(struct_equipment));

    init_map_list(&add->origin);
    return add;
}

/**
 * Frees a struct_equipment.
 *
 * @param equip
 * item to free.
 */
static void free_equipment(struct_equipment *equip) {
    free(equip->diff);
    free(equip->name);
    free(equip);
}

/**
 * Searches the item list for an identical item, except maps.
 *
 * @param item
 * item to search. The variable may be freed, so must not be used after calling this function.
 * @return
 * item guaranteed to be unique in the item list.
 */
static struct_equipment *ensure_unique(struct_equipment *item) {
    int check;
    struct_equipment *comp;

    for (check = 0; check < equipment_count; check++) {
        comp = special_equipment[check];

        if (strcmp(comp->name, item->name))
            continue;
        if (comp->power != item->power)
            continue;
        if (comp->calc_power != item->calc_power)
            continue;
        if (strcmp(comp->diff, item->diff))
            continue;

        free_equipment(item);
        return comp;
    }

    if (equipment_count == equipment_allocated) {
        equipment_allocated += 50;
        special_equipment = realloc(special_equipment, sizeof(struct_equipment *)*equipment_allocated);
    }
    special_equipment[equipment_count] = item;
    equipment_count++;

    return item;
}

/**
 * Adds an item to the list of special items.
 *
 * @param item
 * item to add.
 * @param map
 * map it is on.
 * @todo merge items with the same properties.
 */
static void add_one_item(object *item, struct_map_info *map) {
    struct_equipment *add = get_equipment();
    StringBuffer *bf = stringbuffer_new();
    int x, y;
    sstring name, namepl;
    uint32 nrof;
    object *base;

    x = item->x;
    y = item->y;
    name = item->name;
    namepl = item->name_pl;
    nrof = item->nrof;

    if (item->artifact != NULL) {
        const artifact *artifact;

        artifact = find_artifact(item, item->artifact);
        if (artifact == NULL) {
            LOG(llevError, "could not find artifact %s [%d] to save data\n", item->artifact, item->type);
            base = arch_to_object(item->arch);
        } else {
            base = arch_to_object(item->arch);
            give_artifact_abilities(base, artifact->item);
        }
    }
    else {
        base = arch_to_object(item->arch);
    }

    item->x = base->x;
    item->y = base->y;
    item->name = base->name;
    item->name_pl = base->name_pl;
    item->nrof = base->nrof;

    if (QUERY_FLAG(item, FLAG_IDENTIFIED) && !QUERY_FLAG(base, FLAG_IDENTIFIED)) {
        object_give_identified_properties(base);
        SET_FLAG(base, FLAG_IDENTIFIED);
    }
    if (QUERY_FLAG(item, FLAG_UNPAID))
        SET_FLAG(base, FLAG_UNPAID);
    get_ob_diff(bf, item, base);
    object_free2(base, FREE_OBJ_NO_DESTROY_CALLBACK | FREE_OBJ_FREE_INVENTORY);

    add->diff = stringbuffer_finish(bf);

    item->x = x;
    item->y = y;
    item->name = name;
    item->name_pl = namepl;
    item->nrof = nrof;

    if (add->diff == NULL || strcmp(add->diff, "") == 0) {
        free_equipment(add);
        return;
    }

    add->name = strdup(item->name);
    add->power = item->item_power;
    add->calc_power = calc_item_power(item);

    add = ensure_unique(add);
    add_map(map, &add->origin);
}

/**
 * Checks if item and its inventory are worthy to be listed.
 *
 * @param item
 * item to check.
 * @param map
 * map the item is on.
 */
static void check_equipment(object *item, struct_map_info *map) {
    if (is_special_equipment(item))
        add_one_item(item, map);

    FOR_INV_PREPARE(item, inv)
        check_equipment(inv, map);
    FOR_INV_FINISH();
}

/**
 * Sort 2 struct_equipment, first on item power then name.
 * @param a
 * @param b
 * items to compare.
 * @return
 * -1, 0 or 1.
 */
static int sort_equipment(const void *a, const void *b) {
    const struct_equipment *l = *(const struct_equipment **)a;
    const struct_equipment *r = *(const struct_equipment **)b;
    int c = l->power-r->power;

    if (c)
        return c;
    return strcasecmp(l->name, r->name);
}

/**
 * Returns the race for specified name.
 *
 * @param name
 * monster's name.
 * @return
 * race structure.
 */
static struct_race *get_race(const char *name) {
    int test;
    struct_race *item;

    for (test = 0; test < races.count; test++) {
        if (strcmp(races.races[test]->name, name) == 0) {
            races.races[test]->count++;
            return races.races[test];
        }
    }

    item = calloc(1, sizeof(struct_race));
    item->name = strdup(name);
    item->count = 1;
    init_map_list(&item->origin);

    add_race_to_list(item, &races, 0);

    return item;
}

/**
 * Adds a monster to the monster list.
 *
 * @param monster
 * monster to add. Can be any part.
 * @param map
 * map to add the monster to.
 */
static void add_monster(object *monster, struct_map_info *map) {
    struct_race *race;

    if (monster->head && monster != monster->head)
        return;

    map->min_monster = MIN(monster->level, map->min_monster);
    map->max_monster = MAX(monster->level, map->max_monster);

    race = get_race(monster->name);
    add_map(map, &race->origin);
    add_race_to_list(race, &map->monsters, 1);
}

/**
 * Sort 2 struct_race.
 * @param a
 * @param b
 * items to compare.
 * @return
 * -1, 0 or 1.
 */
static int sort_race(const void *a, const void *b) {
    const struct_race *l = *(const struct_race **)a;
    const struct_race *r = *(const struct_race **)b;
    return strcasecmp(l->name, r->name);
}

/**
 * Checks if ::object is considered a road or not.
 * @param item
 * ::object to check.
 * @return
 * 1 if object is a road, 0 else.
 */
static int is_road(object *item) {
    int test;
    /* Archetypes used as roads. */
    const char *roads[] = {
        "cobblestones",
        "flagstone",
        "ice_stone",
        "snow",
        NULL };
    const char *partial[] = {
        "dirtroad_",
        NULL };

    for (test = 0; partial[test] != NULL; test++) {
        if (strstr(item->arch->name, partial[test]) != NULL)
            return 1;
    }

    if (!QUERY_FLAG(item, FLAG_IS_FLOOR))
        return 0;

    for (test = 0; roads[test] != NULL; test++) {
        if (strcmp(item->arch->name, roads[test]) == 0)
            return 1;
    }

    return 0;
}

/**
 * Checks if item blocks movement or not.
 * @param item
 * ::object to test.
 * @return
 * 1 if item blocks all movement, 0 else.
 */
static int is_blocking(object *item) {
    return item->move_block == MOVE_ALL ? 1 : 0;
}

/**
 * Gets the color for an elevation.
 *
 * @param elevation
 * elevation to get color for.
 * @param elevationmap
 * picture that will get the color.
 * @return
 * color.
 */
static int get_elevation_color(int elevation, gdImagePtr elevationmap) {
    if (elevation > 0)
        return gdImageColorResolve(elevationmap, 200*elevation/elevation_max, 0, 0);
    else
        return gdImageColorResolve(elevationmap, 0, 0, 200*elevation/elevation_min);
}

/**
 * Proceses exit / road / blocking information for specified map into the global infomap map.
 *
 * If map isn't a world map, won't do anything.
 *
 * @param map
 * map to write info for.
 */
static void do_exit_map(mapstruct *map) {
    int tx, ty, x, y;
    object *test;
    sstring selevation;

    if (sscanf(map->path, "/world/world_%d_%d", &x, &y) != 2)
        return;

    x -= 100;
    y -= 100;

    for (tx = 0; tx < MAP_WIDTH(map); tx++) {
        for (ty = 0; ty < MAP_HEIGHT(map); ty++) {
            FOR_MAP_PREPARE(map, tx, ty, item) {
                test = HEAD(item);

                if (test->type == EXIT || test->type == TELEPORTER) {
                    if (!test->slaying)
                        gdImageSetPixel(infomap, x*50+tx, y*50+ty, color_unlinked_exit);
                    else
                        gdImageSetPixel(infomap, x*50+tx, y*50+ty, color_linked_exit);
                } else if (is_road(test))
                    gdImageSetPixel(infomap, x*50+tx, y*50+ty, color_road);
                else if (is_blocking(test)) {
                    gdImageSetPixel(infomap, x*50+tx, y*50+ty, color_blocking);
                    /* can't get on the spot, so no need to go on. */
                    break;
                } else if (test->move_slow != 0)
                    gdImageSetPixel(infomap, x*50+tx, y*50+ty, color_slowing);

                selevation = object_get_value(item, "elevation");
                if (selevation) {
                    sint32 elevation = atoi(selevation);
                    elevation_min = MIN(elevation_min, elevation);
                    elevation_max = MAX(elevation_max, elevation);
                    elevation_info[x*50+tx][y*50+ty] = elevation;
                }
            } FOR_MAP_FINISH();
        }
    }
}

void do_auto_apply(mapstruct *m);

/**
 * Sort values alphabetically
 * Used by qsort to sort values alphabetically.
 * @param a
 * First value
 * @param b
 * Second value
 * @return
 * -1 if a is less than b, 0 if a equals b, 1 else.
 */
static int sortbyname(const void *a, const void *b) {
    return (strcmp(*(const char **)a, *(const char **)b));
}

/**
 * Concatenates a string, and free concatenated string.
 *
 * @param source
 * string to append to. Can be NULL.
 * @param add
 * string that is appened. Will be free()d after. Must not be NULL.
 * @return
 * new string that should be free()d by caller.
 */
static char *cat_template(char *source, char *add) {
    if (!source)
        return add;
    source = realloc(source, strlen(source)+strlen(add)+1);
    strcat(source, add);
    free(add);
    return source;
}

/**
 * Reads a file in memory.
 *
 * @param name
 * file path to read.
 * @param buffer
 * where to store. Can be left uninitialized in case of errors.
 * @note
 * will exit() with code 1 if any error occurs or if the file doesn't exist.
 */
static void read_template(const char *name, char **buffer) {
    FILE *file;
    struct stat info;

    if (stat(name, &info)) {
        printf("Couldn't stat template %s!\n", name);
        exit(1);
    }

    (*buffer) = calloc(1, info.st_size+1);
    if (!(*buffer)) {
        printf("Template %s calloc failed!\n", name);
        exit(1);
    }

    if (info.st_size == 0) {
        (*buffer)[0] = '\0';
        return;
    }

    file = fopen(name, "rb");
    if (!file) {
        printf("Couldn't open template %s!\n", name);
        free(*buffer);
        exit(1);
    }
    if (fread(*buffer, info.st_size, 1, file) != 1) {
        printf("Couldn't read template %s!\n", name);
        free(*buffer);
        fclose(file);
        exit(1);
    }
    fclose(file);
}

/**
 * Processes a template.
 *
 * Variables in the form <code>\#VARIABLE#</code> will be substituted for specified values.
 *
 * @param template
 * template to process.
 * @param vars
 * variables to replace. Array must be NULL-terminated.
 * @param values
 * variables to replace by. Must be the same size as vars, no NULL element allowed.
 * @return
 * filled-in template, that must be free()d be caller. NULL if memory allocation error.
 *
 * @note
 * returned string will be a memory block larger than required, for performance reasons.
 */
static char *do_template(const char *template, const char **vars, const char **values) {
    int count = 0;
    const char *sharp = template;
    int maxlen = 0;
    int var = 0;
    char *result;
    char *current_result;
    const char *end;

    while ((sharp = strchr(sharp, '#')) != NULL) {
        sharp++;
        count++;
    }
    if (!count)
        return strdup(template);
    if (count%2) {
        printf("Malformed template, mismatched #!\n");
        return strdup(template);
    }

    while (vars[var] != NULL) {
        if (strlen(values[var]) > maxlen)
            maxlen = strlen(values[var]);
        var++;
    }
    result = calloc(1, strlen(template)+maxlen*(count/2)+1);
    if (!result)
        return NULL;
    current_result = result;

    sharp = template;
    while ((sharp = strchr(sharp, '#')) != NULL) {
        end = strchr(sharp+1, '#');
        strncpy(current_result, template, sharp-template);
        if (end == sharp+1) {
            strcat(current_result, "#");
        }
        else {
        current_result = current_result+strlen(current_result);
        var = 0;
        while (vars[var] != NULL && (strncmp(vars[var], sharp+1, end-sharp-1) || (strlen(vars[var]) != end-sharp-1)))
            /* tag must be the same length, else can take a wrong tag */
            var++;
        if (vars[var] == NULL)
            printf("Wrong tag: %s\n", sharp);
        else
            strcpy(current_result, values[var]);
        }
        current_result = current_result+strlen(current_result);
        sharp = end+1;
        template = sharp;
    }
    strcat(current_result, template);
    return result;
}

/**
 * Computes the shortest path from one file to another.
 *
 * @param from
 * origin.
 * @param to
 * destination.
 * @param result
 * string that will contain the calculated path. Must be large enough, no test done.
 * @warning
 * from and to must be absolute paths (starting with /).
 */
static void relative_path(const char *from, const char *to, char *result) {
    const char *fslash;
    const char *rslash;

    result[0] = '\0';

    fslash = strchr(from+1, '/');
    if (!fslash) {
        strcpy(result, to+1);
        return;
    }

    rslash = strchr(to+1, '/');
    while (fslash && rslash && (fslash-from == rslash-to) && strncmp(from, to, fslash-from+1) == 0) {
        from = fslash+1;
        to = rslash+1;
        fslash = strchr(fslash+1, '/');
        rslash = strchr(rslash+1, '/');
    }

    while (fslash) {
        strcat(result, "../");
        fslash = strchr(fslash+1, '/');
    }
    if (strlen(result) && result[strlen(result)-1] == '/' && *to == '/')
        result[strlen(result)-1] = '\0';
    strcat(result, to);
}

/**
 * Sorts the strings according to the last part of the filename (after the last /).
 *
 * @param left
 * first string.
 * @param right
 * second string.
 * @return
 * comparison on last element, and if equal then on whole string.
 */
static int sort_mapname(const void *left, const void *right) {
    const char *l = *(const char **)left;
    const char *r = *(const char **)right;
    const char *sl = strrchr(l, '/');
    const char *sr = strrchr(r, '/');
    int c;

    if (!sl)
        sl = l;
    if (!sr)
        sr = r;
    c = strcasecmp(sl, sr);
    if (c)
        return c;

    return strcasecmp(l, r);
}

/**
 * Compares struct_map_info according to the map name or the path if equal.
 *
 * @param left
 * first item.
 * @param right
 * second item.
 * @return
 * comparison on name, and if equal then on whole path.
 */
static int compare_map_info(const struct_map_info *left, const struct_map_info *right) {
    int c;

    if (left->tiled_group)
        left = left->tiled_group;
    if (right->tiled_group)
        right = right->tiled_group;

    c = strcasecmp(left->name, right->name);
    if (c)
        return c;

    return strcasecmp(left->path, right->path);
}

/**
 * Sorts the struct_map_info according to the map name or the path if equal.
 *
 * @param left
 * first item.
 * @param right
 * second item.
 * @return
 * comparison on name, and if equal then on whole path.
 */
static int sort_map_info(const void *left, const void *right) {
    const struct_map_info *l = *(const struct_map_info **)left;
    const struct_map_info *r = *(const struct_map_info **)right;
    return compare_map_info(l, r);
}

/**
 * Sorts the struct_map_info according to the map's level, and if equal the name or the path.
 *
 * @param left
 * first item.
 * @param right
 * second item.
 * @return
 * comparison on name, and if equal then on whole path.
 */
static int sort_map_info_by_level(const void *left, const void *right) {
    const struct_map_info *l = *(const struct_map_info **)left;
    const struct_map_info *r = *(const struct_map_info **)right;
    int c = l->level-r->level;
    if (c)
        return c;
    return compare_map_info(l, r);
}

/**
 * Sorts an array of struct_region_info by region name.
 *
 * @param left
 * first region.
 * @param right
 * second region.
 * @return
 * result of strcmp() for names.
 */
static int sort_region(const void *left, const void *right) {
    return strcmp((*((struct_region_info **)left))->reg->name, (*((struct_region_info **)right))->reg->name);
}

/************************************
 Start of quest-related definitions.
************************************/

/** Link between a quest and a map. */
typedef struct struct_map_in_quest {
    struct_map_info *map;   /**< Linked map. */
    char *description;      /**< Description associated with the map for the quest. */
    struct struct_quest *quest;    /**< Point back to the quest. */
} struct_map_in_quest;

/** One quest. */
typedef struct struct_quest {
    char *name;                     /**< Quest's name. */
    char *description;              /**< Description, from the main map's lore. */
    int number;                     /**< Unique quest identifier. */
    struct_map_info *mainmap;       /**< Map defining the quest. Can be NULL if quest has no definition or map not processed. */
    struct_map_in_quest_list maps;  /**< Maps part of this quest. */
} struct_quest;

static struct_quest **quests = NULL;   /**< All quests in the game. */

static int quests_count = 0;           /**< Count of quests. */

static int quests_allocated = 0;       /**< Allocated items in quests. */

static void init_struct_map_in_quest_list(struct_map_in_quest_list *list) {
    list->list = NULL;
    list->count = 0;
    list->allocated = 0;
}

static void add_to_struct_map_in_quest_list(struct_map_in_quest_list *list, struct_map_in_quest *item) {
    if (list->count == list->allocated) {
        list->allocated += 10;
        list->list = realloc(list->list, sizeof(struct_map_in_quest *)*list->allocated);
    }
    list->list[list->count++] = item;
}

/**
 * Gets the information for a quest, create the field if needed.
 *
 * @param name
 * quest's name.
 * @return
 * information, never NULL.
 */
static struct_quest *get_quest_info(const char *name) {
    int test;
    struct_quest *add;

    for (test = 0; test < quests_count; test++) {
        if (strcmp(quests[test]->name, name) == 0)
            return quests[test];
    }

    if (quests_count == quests_allocated) {
        quests_allocated += 10;
        quests = realloc(quests, sizeof(struct_quest *)*quests_allocated);
    }
    add = calloc(1, sizeof(struct_quest));
    add->name = strdup(name);
    add->number = quests_count;
    init_struct_map_in_quest_list(&add->maps);
    quests[quests_count] = add;
    quests_count++;
    return add;
}

/**
 * Links a map to a quest.
 *
 * @param map
 * map to link.
 * @param name
 * quest name.
 * @param description
 * associated link description. Must not be NULL.
 */
static void add_map_to_quest(struct_map_info *map, const char *name, const char *description) {
    struct_map_in_quest *add;
    struct_quest *quest = get_quest_info(name);

    add = calloc(1, sizeof(struct_map_in_quest));
    add->map = map;
    add->quest = quest;
    add->description = strdup(description);
    while (strlen(add->description) && add->description[strlen(add->description)-1] == '\n')
        add->description[strlen(add->description)-1] = '\0';
    add_to_struct_map_in_quest_list(&quest->maps, add);
    add_to_struct_map_in_quest_list(&map->quests, add);
}

/**
 * Sorts 2 struct_map_in_quest, on the map's name or path.
 * @param left
 * @param right
 * items to compare.
 * @return
 * -1, 0 or 1.
 */
static int sort_struct_map_in_quest(const void *left, const void *right) {
    int c;

    const struct_map_in_quest *l = *(const struct_map_in_quest **)left;
    const struct_map_in_quest *r = *(const struct_map_in_quest **)right;
    const struct_map_info *ml = l->map;
    const struct_map_info *mr = r->map;

    if (ml->tiled_group)
        ml = ml->tiled_group;
    if (mr->tiled_group)
        mr = mr->tiled_group;

    c = strcasecmp(ml->name, mr->name);
    if (c)
        return c;

    return strcasecmp(ml->path, mr->path);
}

/**
 * Sets the main map for a quest.
 *
 * @param name
 * quest name.
 * @param mainmap
 * main map to associate.
 * @param description
 * quest description. Must not be NULL.
 */
static void define_quest(const char *name, struct_map_info *mainmap, const char *description) {
    struct_quest *quest = get_quest_info(name);

    if (quest->description || quest->mainmap) {
        printf("warning, multiple quest definition for %s, found in %s and %s.\n", quest->name, quest->mainmap ? quest->mainmap->path : "(unknown map)", mainmap->path);
        return;
    }
    quest->description = strdup(description);
    while (strlen(quest->description) && quest->description[strlen(quest->description)-1] == '\n')
        quest->description[strlen(quest->description)-1] = '\0';
    quest->mainmap = mainmap;
}

/**
 * Extracts from the map's lore quest information if found. May modify map->lore.
 *
 * @param map
 * map to process.
 */
static void process_map_lore(struct_map_info *map) {
    char *start, *end, *next;
    char name[500];
    char description[500];

    start = strstr(map->lore, "@def");
    while (start) {
        description[0] = '\0';
        /* find name */
        end = strstr(start, "\n");
        if (end) {
            strncpy(name, start+5, end-start-5);
            name[end-start-5] = '\0';
            next = end+1;
            end = strstr(next, "@end");
            if (end) {
                strncpy(description, next, end-next);
                description[end-next] = '\0';
                /* need to erase the text. */
                memcpy(start, end+4, strlen(map->lore)-(end-start+3));
                end = start;
            }
            else {
                strcpy(description, next);
                *start = '\0';
                end = NULL;
            }
        } else {
            strcpy(name, start);
            *start = '\0';
            end = NULL;
        }

        define_quest(name, map, description);
        start = end ? strstr(end, "@def") : NULL;
    }

    start = strstr(map->lore, "@quest");
    while (start) {
        description[0] = '\0';
        /* find name */
        end = strstr(start, "\n");
        if (end) {
            strncpy(name, start+7, end-start-7);
            name[end-start-7] = '\0';
            next = end+1;
            end = strstr(next, "@end");
            if (end) {
                strncpy(description, next, end-next);
                description[end-next] = '\0';
                /* need to erase the text. */
                memcpy(start, end+4, strlen(map->lore)-(end-start+3));
                end = start;
            }
            else {
                strcpy(description, next);
                *start = '\0';
                end = NULL;
            }
        } else {
            strcpy(name, start);
            *start = '\0';
            end = NULL;
        }

        add_map_to_quest(map, name, description);
        start = end ? strstr(end, "@quest") : NULL;
    }
}

/**
 * Writes the global quests page.
 */
static void write_quests_page(void) {
    int quest, map;
    FILE *out;
    char path[500];
    char mappath[500];
    char mainmappath[500];
    char questid[500];
    const char *map_vars[] = { "MAPPATH", "MAPNAME", "MAPTEXT", NULL };
    const char *map_vals[] = { mappath, NULL, NULL, NULL };
    const char *quest_vars[] = { "QUESTNAME", "QUESTTEXT", "QUESTMAPS", "QUESTID", "MAINMAPPATH", "MAINMAPNAME", NULL };
    const char *quest_vals[] = { NULL, NULL, NULL, questid, mainmappath, NULL, NULL };
    const char *idx_vars[] = { "QUESTS", NULL };
    const char *idx_vals[] = { NULL, NULL };
    char *text_map = NULL;
    char *text_quest = NULL;
    char *text_idx = NULL;

    printf("Writing quest index...");

    for (quest = 0; quest < quests_count; quest++) {
        qsort(quests[quest]->maps.list, quests[quest]->maps.count, sizeof(struct_map_in_quest *), sort_struct_map_in_quest);
        for (map = 0; map < quests[quest]->maps.count; map++) {
            snprintf(mappath, sizeof(mappath), "%s.html", quests[quest]->maps.list[map]->map->path+1);
            map_vals[1] = quests[quest]->maps.list[map]->map->name;
            map_vals[2] = quests[quest]->maps.list[map]->description ? quests[quest]->maps.list[map]->description : "(no description)";
            text_map = cat_template(text_map, do_template(quest_map_template, map_vars, map_vals));
        }
        if (!text_map)
            text_map = strdup("");

        quest_vals[0] = quests[quest]->name;
        quest_vals[1] = quests[quest]->description ? quests[quest]->description : "(main map not processed)";
        quest_vals[2] = text_map;
        snprintf(questid, sizeof(questid), "quest_%d", quests[quest]->number);
        if (quests[quest]->mainmap) {
            snprintf(mainmappath, sizeof(mainmappath), "%s.html", quests[quest]->mainmap->path+1);
            quest_vals[5] = quests[quest]->mainmap->name;
        } else {
            snprintf(mainmappath, sizeof(mainmappath), "#");
            quest_vals[5] = "";
        }
        text_quest = cat_template(text_quest, do_template(quest_template, quest_vars, quest_vals));
        free(text_map);
        text_map = NULL;
    }

    if (!text_quest)
        text_quest = strdup("No quest.");

    idx_vals[0] = text_quest;
    text_idx = do_template(index_quest_template, idx_vars, idx_vals);
    free(text_quest);

    snprintf(path, sizeof(path), "%s/quests.html", root);
    out = fopen(path, "w+");
    fprintf(out, "%s", text_idx);
    fclose(out);
    free(text_idx);

    printf(" done.\n");
}

/************************************
 End of quest-related definitions.
************************************/

/*********
NPC-related stuff
********/

/**
 * Initialise a list of NPCs.
 * @param list
 * list to initialise.
 */
static void init_npc_list(struct_npc_list *list) {
    list->allocated = 0;
    list->count = 0;
    list->npc = NULL;
}

/**
 * Create the struct_npc_info from the specified NPC. It must have a name and message.
 * @param npc
 * NPC to gather info for.
 * @return
 * structure with info.
 */
static struct_npc_info *create_npc_info(const object *npc) {
    struct_npc_info *info = calloc(1, sizeof(struct_npc_info));

    info->name = strdup(npc->name);
    info->message = strdup(npc->msg);
    info->x = npc->x;
    info->y = npc->y;

    return info;
}

/**
 * Add the specified NPC to the list.
 * @param list
 * where to add the NPC.
 * @param npc
 * NPC to add. Must have a name and message.
 */
static void add_npc_to_map(struct_npc_list *list, const object *npc) {
    if (list->count == list->allocated) {
        list->allocated += 50;
        list->npc = realloc(list->npc, list->allocated*sizeof(struct_npc_info *));
    }

    list->npc[list->count] = create_npc_info(npc);
    list->count++;
}
/* end of NPC stuff */

/**
 * Adds a map to specified array, if it isn't already.
 *
 * @param info
 * map to add.
 * @param list
 * list to add to.
 *
 * @note
 * will allocate memory and update variables when required.
 */
static void add_map(struct_map_info *info, struct_map_list *list) {
    int map;

    for (map = 0; map < list->count; map++)
        if (list->maps[map] == info)
            return;

    if (list->count == list->allocated) {
        list->allocated += 50;
        list->maps = realloc(list->maps, list->allocated*sizeof(struct_map_info *));
    }
    list->maps[list->count] = info;
    list->count++;
}

/**
 * Returns an initialised struct_map_info.
 *
 * @return
 * new struct_map_info.
 */
static struct_map_info *create_map_info(void) {
    struct_map_info *add = calloc(1, sizeof(struct_map_info));

    add->min_monster = 2000;
    init_map_list(&add->exits_to);
    init_map_list(&add->exits_from);
    init_map_list(&add->tiled_maps);
    init_struct_map_in_quest_list(&add->quests);
    init_race_list(&add->monsters);
    init_npc_list(&add->npcs);
    init_npc_list(&add->readable);
    add->tiled_group = NULL;

    return add;
}

/**
 * Create a new tiled map and link it to the tiled map list.
 *
 * @return
 * new tiled map.
 */
static struct_map_info *create_tiled_map(void) {
    struct_map_info *add = create_map_info();

    add_map(add, &tiled_map_list);
    return add;
}

/**
 * Merge two tiled maps groups. This can happen if based on processing we do one map with tiled maps,
 * another with tiled maps, and later figure out the tiles are actually linked.
 *
 * @param map
 * the map that being processed has a tiling to a map in another group. Its group will be the final merging group.
 * @param tile
 * the tile index causing the merge
 * @param tiled_map
 * the map tiled to another group. Its group will disappear.
 */
static void merge_tiled_maps(struct_map_info *map, int tile, struct_map_info *tiled_map) {
    int g;
    struct_map_info *group = tiled_map->tiled_group;
    struct_map_info *change;

    while (group->tiled_maps.count > 0) {
        change = group->tiled_maps.maps[group->tiled_maps.count-1];
        change->tiled_group = map->tiled_group;
        add_map(change, &map->tiled_group->tiled_maps);
        group->tiled_maps.count--;
    }

    for (g = 0; g < tiled_map_list.count; g++) {
        if (tiled_map_list.maps[g] == group) {
            if (g < tiled_map_list.count-1)
                tiled_map_list.maps[g] = tiled_map_list.maps[tiled_map_list.count-1];
            tiled_map_list.count--;
            free(group);
            return;
        }
    }
    printf("tiled_map not in tiled_map_list!");
    abort();

}

/**
 * Gets or creates if required the info structure for a map.
 *
 * @param path
 * map to consider.
 * @return
 * associated structure.
 */
static struct_map_info *get_map_info(const char *path) {
    int map;
    struct_map_info *add;
    char *tmp;

    for (map = 0; map < maps_list.count; map++) {
        if (strcmp(maps_list.maps[map]->path, path) == 0)
            return maps_list.maps[map];
    }

    add = create_map_info();
    add->path = strdup(path);
    tmp = strrchr(path, '/');
    if (tmp)
        add->filename = strdup(tmp+1);
    else
        add->filename = strdup(path);

    add_map(add, &maps_list);
    return add;
}

/**
 * Marks specified path as processed.
 *
 * @param path
 * map to remove.
 */
static void list_map(const char *path) {
    int index;

    for (index = 0; index < found_maps_count; index++) {
        if (found_maps[index] && strcmp(path, found_maps[index]) == 0) {
            free(found_maps[index]);
            found_maps[index] = NULL;
            return;
        }
    }
    printf("Map processed but not found in directory reading? %s\n", path);
}

/**
 * Links a map to a region.
 *
 * Will not readd the map if already linked.
 *
 * @param map
 * map name.
 * @param reg
 * region to link the map to.
 */
static void add_map_to_region(struct_map_info *map, region *reg) {
    int test;
    int x, y;

    for (test = 0; test < region_count; test++) {
        if (regions[test]->reg == reg)
            break;
    }
    if (test == region_count) {
        if (test == region_allocated) {
            region_allocated++;
            regions = realloc(regions, sizeof(struct_region_info *)*region_allocated);
            regions[test] = calloc(1, sizeof(struct_region_info));
        }
        region_count++;
        regions[test]->reg = reg;
    }
    add_map(map, &regions[test]->maps_list);
    if (sscanf(map->path, "/world/world_%d_%d", &x, &y) == 2) {
        regions[test]->sum_x += (x-100);
        regions[test]->sum_y += (y-100);
        regions[test]->sum++;
        regions[test]->is_world = 1;
    }
}

/**
 * Saves a map to a file, based on jpg/png settings.
 *
 * @param file
 * opened file to which to save.
 * @param pic
 * picture to save.
 */
static void save_picture(FILE *file, gdImagePtr pic) {
    if (output_format == OF_PNG)
        gdImagePng(pic, file);
    else
        gdImageJpeg(pic, file, jpeg_quality);
}

/**
 * Creates a link between two maps if they are on different regions.
 * @param source
 * map from.
 * @param dest
 * map to.
 * @param linkname
 * name of the link as it should appear. Unused.
 */
static void add_region_link(mapstruct *source, mapstruct *dest, const char *linkname) {
    int search = 0;
    char entry[500];
    region *s, *d;

    s = get_region_by_map(source);
    d = get_region_by_map(dest);
    if (s == d)
        return;

    if (linkname && 0)
        snprintf(entry, sizeof(entry), "%s -> %s [ label = \"%s\" ]\n", s->name, d->name, linkname);
    else
        snprintf(entry, sizeof(entry), "%s -> %s\n", s->name, d->name);

    for (search = 0; search < regions_link_count; search++) {
        if (strcmp(regions_link[search], entry) == 0)
            return;
    }

    if (regions_link_count == regions_link_allocated) {
        regions_link_allocated += 10;
        regions_link = realloc(regions_link, sizeof(const char *)*regions_link_allocated);
    }
    regions_link[regions_link_count] = strdup(entry);
    regions_link_count++;
}

/**
 * Is the slaying field relevant for this item?
 *
 * @param item
 * item to check.
 * @return
 * 1 if relevant, 0 else.
 */
static int is_slaying(object *item) {
    return (item->type == LOCKED_DOOR || item->type == SPECIAL_KEY || item->type == CONTAINER || item->type == CHECK_INV);
}


/**
 * Returns a struct_slaying_info for specified slaying. Creates a new one if required.
 *
 * @param slaying
 * value to get the structure of.
 * @return
 * structure for slaying. Never NULL.
 */
static struct_slaying_info *get_slaying_struct(const char *slaying) {
    struct_slaying_info *add;
    int l;

    for (l = 0; l < slaying_count; l++) {
        if (!strcmp(slaying_info[l]->slaying, slaying))
            return slaying_info[l];
    }
    if (slaying_count == slaying_allocated) {
        slaying_allocated += 10;
        slaying_info = (struct_slaying_info **)realloc(slaying_info, sizeof(struct_slaying_info *)*slaying_allocated);
    }

    add = (struct_slaying_info *)calloc(1, sizeof(struct_slaying_info));
    add->slaying = strdup(slaying);
    for (l = 0; l < S_MAX; l++)
        init_map_list(&add->maps[l]);

    slaying_info[slaying_count] = add;
    slaying_count++;

    return add;
}

/**
 * Adds the specified map to the slaying information if not already present.
 *
 * @param info
 * structure to add to.
 * @param item
 * one of the S_xxx values specifying what type of slaying this is.
 * @param map
 * map to add.
 */
static void add_map_to_slaying(struct_slaying_info *info, int item, struct_map_info *map) {
    add_map(map, &info->maps[item]);
}

/**
 * Adds the item's information to the map.
 *
 * @param map
 * map containing the item.
 * @param item
 * item which slaying field we're considering.
 */
static void add_slaying(struct_map_info *map, object *item) {
    struct_slaying_info *info;

    if (!item->slaying)
        /* can be undefined */
        return;

    info = get_slaying_struct(item->slaying);
    if (item->type == LOCKED_DOOR)
        add_map_to_slaying(info, S_DOOR, map);
    else if (item->type == SPECIAL_KEY)
        add_map_to_slaying(info, S_KEY, map);
    else if (item->type == CONTAINER)
        add_map_to_slaying(info, S_CONTAINER, map);
    else
        add_map_to_slaying(info, S_CONNECT, map);
}

/**
 * Recursively checks if the object should be considered for slaying information.
 *
 * @param map
 * map containing the items.
 * @param item
 * item to consider. Must not be NULL.
 */
static void check_slaying_inventory(struct_map_info *map, object *item) {
    FOR_INV_PREPARE(item, inv) {
        if (is_slaying(inv))
            add_slaying(map, inv);
        check_slaying_inventory(map, inv);
    } FOR_INV_FINISH();
}

/**
 * Processes a map.
 *
 * Generates the map pictures (big and small), and exit information.
 *
 * @param info
 * map to process.
 */
static void process_map(struct_map_info *info) {
    mapstruct *m;
    int x, y, isworld;
    FILE *out;
    gdImagePtr pic;
    gdImagePtr small;
    struct stat stats;
    struct stat statspic;
    char exit_path[500];
    char tmppath[MAX_BUF];
    char picpath[MAX_BUF], smallpicpath[MAX_BUF];
    int needpic = 0;
    struct_map_info *link;

    if (list_unused_maps)
        list_map(info->path);

    if (show_maps)
        printf(" processing map %s\n", info->path);

    m = ready_map_name(info->path, 0);
    if (!m) {
        printf("couldn't load map %s\n", info->path);
        return;
    }

    do_exit_map(m);

    if (!rawmaps)
        do_auto_apply(m);

    info->level = m->difficulty;
    if (m->maplore) {
        info->lore = strdup(m->maplore);
        process_map_lore(info);
    }

    isworld = (sscanf(info->path, "/world/world_%d_%d", &x, &y) == 2);

    if (m->name)
        info->name = strdup(m->name);
    else
        info->name = strdup(info->filename);

    info->cfregion = get_region_by_map(m);
    add_map_to_region(info, info->cfregion);

    snprintf(picpath, sizeof(picpath), "%s%s%s", root, info->path, output_extensions[output_format]);
    snprintf(smallpicpath, sizeof(smallpicpath), "%s%s.small%s", root, info->path, output_extensions[output_format]);

    if (force_pics)
        needpic = 1;
    else if (generate_pics) {
        create_pathname(info->path, tmppath, MAX_BUF);
        stat(tmppath, &stats);
        if (stat(picpath, &statspic) || (statspic.st_mtime < stats.st_mtime))
            needpic = 1;
        else if (stat(smallpicpath, &statspic) || (statspic.st_mtime < stats.st_mtime))
            needpic = 1;
    }
    else
        needpic = 0;

    if (needpic) {
        pic = gdImageCreateTrueColor(MAP_WIDTH(m)*32, MAP_HEIGHT(m)*32);
        created_pics++;
    }
    else
        cached_pics++;

    for (x = 0; x < 4; x++)
        if (m->tile_path[x] != NULL) {
            path_combine_and_normalize(m->path, m->tile_path[x], exit_path, sizeof(exit_path));
            create_pathname(exit_path, tmppath, MAX_BUF);
            if (stat(tmppath, &stats)) {
                printf("  map %s doesn't exist in map %s, for tile %d.\n", exit_path, info->path, x);
            }

            if (isworld) {
                link = get_map_info(exit_path);
                add_map(link, &info->exits_from);
                add_map(info, &link->exits_to);

                if (do_regions_link) {
                    mapstruct *link = ready_map_name(exit_path, 0);

                    if (link && link != m) {
                        /* no need to link a map with itself. Also, if the exit points to the same map, we don't
                        * want to reset it. */
                        add_region_link(m, link, NULL);
                        link->reset_time = 1;
                        link->in_memory = MAP_IN_MEMORY;
                        delete_map(link);
                    }
                }
            } else {
                link = get_map_info(exit_path);
                info->tiles[x] = link;
                if (link->tiled_group) {
                    if (info->tiled_group && link->tiled_group != info->tiled_group) {
                        merge_tiled_maps(info, x, link);
                        continue;
                    }
                    if (link->tiled_group == info->tiled_group) {
                        continue;
                    }
                    if (!info->tiled_group) {
                        add_map(info, &link->tiled_group->tiled_maps);
                        continue;
                    }
                }

                if (!info->tiled_group) {
                    info->tiled_group = create_tiled_map();
                    add_map(info, &info->tiled_group->tiled_maps);
                }
                link->tiled_group = info->tiled_group;
                add_map(link, &info->tiled_group->tiled_maps);
            }
        }

    info->width = MAP_WIDTH(m);
    info->height = MAP_HEIGHT(m);

    for (x = MAP_WIDTH(m)-1; x >= 0; x--)
        for (y = MAP_HEIGHT(m)-1; y >= 0; y--) {
            FOR_MAP_PREPARE(m, x, y, item) {
                if (item->type == EXIT || item->type == TELEPORTER || item->type == PLAYER_CHANGER) {
                    char ep[500];
                    const char *start;

                    if (!item->slaying) {
                        ep[0] = '\0';
                        if (warn_no_path)
                            printf(" exit without any path at %d, %d on %s\n", item->x, item->y, info->path);
                    } else {
                        memset(ep, 0, 500);
                        if (strcmp(item->slaying, "/!"))
                            strcpy(ep, EXIT_PATH(item));
                        else {
                            if (!item->msg) {
                                printf("  random map without message in %s at %d, %d\n", info->path, item->x, item->y);
                            } else {
                                /* Some maps have a 'exit_on_final_map' flag, ignore it. */
                                start = strstr(item->msg, "\nfinal_map ");
                                if (!start && strncmp(item->msg, "final_map", strlen("final_map")) == 0)
                                    /* Message start is final_map, nice */
                                    start = item->msg;
                                if (start) {
                                    char *end = strchr(start+1, '\n');

                                    start += strlen("final_map")+2;
                                    strncpy(ep, start, end-start);
                                }
                            }
                        }

                        if (strlen(ep)) {
                            path_combine_and_normalize(m->path, ep, exit_path, 500);
                            create_pathname(exit_path, tmppath, MAX_BUF);
                            if (stat(tmppath, &stats)) {
                                printf("  map %s doesn't exist in map %s, at %d, %d.\n", ep, info->path, item->x, item->y);
                            } else {
                                link = get_map_info(exit_path);
                                add_map(link, &info->exits_from);
                                add_map(info, &link->exits_to);

                                if (do_regions_link) {
                                    mapstruct *link = ready_map_name(exit_path, 0);

                                    if (link && link != m) {
                                        /* no need to link a map with itself. Also, if the exit points to the same map, we don't
                                         * want to reset it. */
                                        add_region_link(m, link, item->arch->clone.name);
                                        link->reset_time = 1;
                                        link->in_memory = MAP_IN_MEMORY;
                                        delete_map(link);
                                    }
                                }
                            }
                        }
                    }
                } else if (is_slaying(item))
                    add_slaying(info, item);

                check_equipment(item, info);

                check_slaying_inventory(info, item);

                if (QUERY_FLAG(item, FLAG_MONSTER)) {
                    /* need to get the "real" archetype, as the item's archetype can certainly be a temporary one. */
                    archetype *arch = find_archetype(item->arch->name);

                    add_monster(item, info);
                    if ((QUERY_FLAG(item, FLAG_UNAGGRESSIVE) || QUERY_FLAG(item, FLAG_FRIENDLY)) && (item->msg != arch->clone.msg) && (item->msg != NULL))
                        add_npc_to_map(&info->npcs, item);
                } else if ((item->type == SIGN || item->type == BOOK) && (item->msg != item->arch->clone.msg) && (item->msg != NULL)) {
                    add_npc_to_map(&info->readable, item);
                }

                if (item->invisible)
                    continue;

                if (needpic) {
                    int sx, sy, hx, hy;

                    if (gdfaces[item->face->number] == NULL) {
                        int set = get_face_fallback(tileset, item->face->number);

                        gdfaces[item->face->number] = gdImageCreateFromPngPtr(facesets[set].faces[item->face->number].datalen, facesets[set].faces[item->face->number].data);
                        pics_allocated++;
                    }
                    if (item->head || item->more) {
                        object_get_multi_size(item, &sx, &sy, &hx, &hy);
                    } else {
                        hx = 0;
                        hy = 0;
                    }
                    if (gdfaces[item->face->number] != NULL && ((!item->head && !item->more) || (item->arch->clone.x+hx == 0 && item->arch->clone.y+hy == 0))) {
                        gdImageCopy(pic, gdfaces[item->face->number], x*32, y*32, 0, 0, gdfaces[item->face->number]->sx, gdfaces[item->face->number]->sy);
                    }
                }
            } FOR_MAP_FINISH();
        }

    if (needpic) {
        make_path_to_file(picpath);
        out = fopen(picpath, "wb+");
        save_picture(out, pic);
        fclose(out);

        small = gdImageCreateTrueColor(MAP_WIDTH(m)*size_small, MAP_HEIGHT(m)*size_small);
        gdImageCopyResampled(small, pic, 0, 0, 0, 0, MAP_WIDTH(m)*size_small, MAP_HEIGHT(m)*size_small, MAP_WIDTH(m)*32, MAP_HEIGHT(m)*32);

        out = fopen(smallpicpath, "wb+");
        save_picture(out, small);
        fclose(out);
        gdImageDestroy(small);

        gdImageDestroy(pic);

        info->pic_was_done = 1;
    }

    m->reset_time = 1;
    m->in_memory = MAP_IN_MEMORY;
    delete_map(m);
}

/**
 * Creates the page for a map index.
 *
 * @param dest
 * path relative to root where the index will be located, without leading /. Used to compute the map's path relative to the index.
 * @param maps_list
 * maps in the index.
 * @param template_page
 * global page template.
 * @param template_letter
 * template for one letter of the index.
 * @param template_map
 * template for one map.
 * @param vars
 * template variables to give access to.
 * @param values
 * associated values.
 * @return
 * processed template. Should be free() by the caller.
 */
static char *do_map_index(const char *dest, struct_map_list *maps_list,
                          const char *template_page, const char *template_letter,
                          const char *template_map, const char **vars,
                          const char **values) {
#define VARSADD 6
    int map;
    char *string;
    char mappath[500];
    char maphtml[500];
    char count[50];
    char lettercount[50];
    char *tmp;
    const char **idx_vars;
    const char **idx_values;
    char str_letter[2];
    char last_letter;
    char index_path[500];
    char *mapstext = NULL;
    int byletter;
    int basevalues, realcount = 0;
    struct_map_info *last_group = NULL;

    if (!generate_index)
        return strdup("");

    if (vars)
        for (basevalues = 0; vars[basevalues] != NULL; basevalues++)
            ;
    else
        basevalues = 0;

    idx_vars = malloc(sizeof(char *)*(basevalues+VARSADD));
    idx_vars[0] = "MAPCOUNT";
    memcpy(&idx_vars[1], vars, sizeof(char *)*basevalues);
    idx_vars[basevalues+VARSADD-1] = NULL;

    idx_values = malloc(sizeof(char *)*(basevalues+VARSADD-1));
    memcpy(&idx_values[1], values, sizeof(char *)*basevalues);

    string = NULL;

    idx_values[0] = count;
    /* wrong value, but in case the template needs to display something... */
    snprintf(count, sizeof(count), "%d", maps_list->count);

    idx_vars[basevalues+1] = "MAPNAME";
    idx_vars[basevalues+2] = "MAPPATH";
    idx_vars[basevalues+3] = "MAPHTML";
    idx_vars[basevalues+4] = NULL;

    qsort(maps_list->maps, maps_list->count, sizeof(const char *), sort_map_info);

    last_letter = '\0';
    str_letter[0] = '\0';
    str_letter[1] = '\0';

    strcpy(index_path, "/");
    strcat(index_path, dest);

    string = NULL;
    for (map = 0; map < maps_list->count; map++) {
        if (tolower(maps_list->maps[map]->name[0]) != last_letter) {
            if (mapstext != NULL) {
                idx_vars[basevalues+1] = "MAPS";
                idx_vars[basevalues+2] = "LETTER";
                idx_vars[basevalues+3] = "LETTERCOUNT";
                idx_vars[basevalues+4] = NULL;
                idx_values[basevalues+1] = mapstext;
                idx_values[basevalues+2] = str_letter;
                snprintf(lettercount, sizeof(lettercount), "%d", byletter);
                idx_values[basevalues+3] = lettercount;
                string = cat_template(string, do_template(template_letter, idx_vars, idx_values));
                free(mapstext);
                mapstext = NULL;
                idx_values[basevalues+2] = NULL;
            }
            last_letter = tolower(maps_list->maps[map]->name[0]);
            str_letter[0] = last_letter;
            byletter = 0;
            last_group = NULL;
        }

        if (last_group && last_group == maps_list->maps[map]->tiled_group)
            continue;
        else
            last_group = maps_list->maps[map]->tiled_group;

        realcount++;
        idx_vars[basevalues+1] = "MAPNAME";
        idx_vars[basevalues+2] = "MAPPATH";
        idx_vars[basevalues+3] = "MAPHTML";
        idx_values[basevalues+1] = last_group ? last_group->name : (maps_list->maps[map]->name ? maps_list->maps[map]->name : maps_list->maps[map]->path);
        relative_path(index_path, last_group ? last_group->path : maps_list->maps[map]->path, mappath);
        strcpy(maphtml, mappath);
        strcat(maphtml, ".html");
        idx_values[basevalues+2] = mappath;
        idx_values[basevalues+3] = maphtml;
        mapstext = cat_template(mapstext, do_template(template_map, idx_vars, idx_values));
        byletter++;
    }
    if (last_letter != '\0') {
        idx_vars[basevalues+1] = "MAPS";
        idx_vars[basevalues+2] = "LETTER";
        idx_vars[basevalues+3] = "LETTERCOUNT";
        idx_vars[basevalues+4] = NULL;
        idx_values[basevalues+1] = mapstext;
        idx_values[basevalues+2] = str_letter;
        snprintf(lettercount, sizeof(lettercount), "%d", byletter);
        idx_values[basevalues+3] = lettercount;
        string = cat_template(string, do_template(template_letter, idx_vars, idx_values));
        free(mapstext);
        mapstext = NULL;
        idx_values[basevalues+2] = NULL;
    }

    snprintf(count, sizeof(count), "%d", realcount);
    idx_values[basevalues+1] = string;
    idx_vars[basevalues+1] = "LETTERS";
    idx_vars[basevalues+2] = NULL;
    tmp = do_template(template_page, idx_vars, idx_values);
    free(string);
    free(idx_vars);
    free(idx_values);
    return tmp;
}

/**
 * Generates the web page for a region.
 *
 * @param reg
 * region/maps for which to generate.
 *
 * @note
 * will sort the maps.
 */
static void write_region_page(struct_region_info *reg) {
    char *string;
    FILE *index;
    char html[500];
    const char *vars[] = { "REGIONNAME", "REGIONHTML", "REGIONLONGNAME", "REGIONDESC", NULL };
    const char *values[] = { reg->reg->name, html, NULL, NULL };

    printf("Generating map index for region %s...", reg->reg->name);

    values[2] = get_region_longname(reg->reg);
    values[3] = get_region_msg(reg->reg);

    strcpy(html, reg->reg->name);
    strcat(html, ".html");

    string = do_map_index(html, &reg->maps_list, region_template, region_letter_template, region_map_template, vars, values);

    strcpy(html, root);
    strcat(html, "/");
    strcat(html, reg->reg->name);
    strcat(html, ".html");
    index = fopen(html, "w+");
    fprintf(index, "%s", string);
    fclose(index);
    free(string);

    printf(" done.\n");

}

/**
 * Generates all map indexes for a region.
 */
static void write_all_regions(void) {
    int reg;

    qsort(regions, region_count, sizeof(struct_region_info *), sort_region);

    for (reg = 0; reg < region_count; reg++)
        write_region_page(regions[reg]);
}

/**
 * Generates global map index, file maps.html.
 */
static void write_maps_index(void) {
    char index_path[500];
    char *tmp;
    FILE *index;

    printf("Generating global map index in maps.html...");

    tmp = do_map_index("maps.html", &maps_list, index_template, index_letter, index_map, NULL, NULL);

    strcpy(index_path, root);
    strcat(index_path, "/maps.html");
    index = fopen(index_path, "w+");
    fprintf(index, "%s", tmp);
    fclose(index);
    free(tmp);

    printf(" done.\n");
}

/**
 * Generates region index.
 */
static void write_region_index(void) {
    char *txt;
    char *final;
    char count[10];
    struct_region_info *region;
    int reg;
    char file[500];
    const char *vars[] = { "REGIONCOUNT", "REGIONFILE", "REGIONNAME", NULL };
    const char *values[] = { count, file, NULL };
    FILE *out;

    printf("Generating regions index in regions.html...");

    snprintf(count, sizeof(count), "%d", region_count);
    txt = NULL;

    for (reg = 0; reg < region_count; reg++) {
        region = regions[reg];
        snprintf(file, sizeof(file), "%s.html", region->reg->name);
        values[2] = get_region_longname(region->reg);
        txt = cat_template(txt, do_template(index_region_region_template, vars, values));
    }
    vars[1] = "REGIONS";
    values[1] = txt;
    vars[2] = NULL;
    final = do_template(index_region_template, vars, values);
    free(txt);

    strcpy(file, root);
    strcat(file, "/regions.html");
    out = fopen(file, "w+");
    fprintf(out, "%s", final);
    fclose(out);
    free(final);

    printf(" done.\n");
}

/**
 * Generates a big world map.
 */
static void write_world_map(void) {
#define SIZE 50
    int x, y;
    FILE *out;
    int wx, wy;
    char file[500];
    char *map = NULL;
    char *total;
    char *row = NULL;
    char mapleft[10], maptop[10], mapright[10], mapbottom[10];
    const char *vars[] = { NULL, NULL, "MAPLEFT", "MAPTOP", "MAPRIGHT", "MAPBOTTOM", NULL };
    const char *values[] = { NULL, NULL, mapleft, maptop, mapright, mapbottom, NULL };
    char name[100];
    char mappath[500], mapraw[500], mapregion[500];
    gdImagePtr pic;
    gdImagePtr small;
    gdFontPtr font;
    int region, color;

    if (!world_map)
        return;

    printf("Generating world map in world.html...");
    fflush(stdout);

    pic = gdImageCreateTrueColor(SIZE*30, SIZE*30);

    strcpy(file, root);
    strcat(file, "/world.html");

    wx = 100;
    wy = 100;

    for (y = 0; y < 30; y++) {
        for (x = 0; x < 30; x++) {
            values[0] = name;
            vars[0] = "MAPNAME";
            vars[1] = "MAPPATH";
            values[1] = mappath,
            snprintf(name, sizeof(name), "world_%d_%d", wx, wy);
            snprintf(mappath, sizeof(mappath), "world/%s.html", name);
            snprintf(mapleft, sizeof(mapleft), "%d", SIZE*x);
            snprintf(maptop, sizeof(maptop), "%d", SIZE*y);
            snprintf(mapright, sizeof(mapright), "%d", SIZE*(x+1)-1);
            snprintf(mapbottom, sizeof(mapbottom), "%d", SIZE*(y+1)-1);

            map = cat_template(map, do_template(world_map_template, vars, values));

            snprintf(mappath, sizeof(mappath), "%s/world/%s%s", root, name, output_extensions[output_format]);

            out = fopen(mappath, "rb");
            if (!out) {
                printf("\n  warning: large pic not found for world_%d_%d", wx, wy);
                wx++;
                continue;
            }
            if (output_format == OF_PNG)
                small = gdImageCreateFromPng(out);
            else
                small = gdImageCreateFromJpeg(out);
            fclose(out);
            if (!small) {
                printf("\n  warning: pic not found for world_%d_%d", wx, wy);
                wx++;
                continue;
            }
            gdImageCopyResized(pic, small, SIZE*x, SIZE*y, 0, 0, SIZE, SIZE, small->sx, small->sy);
            gdImageDestroy(small);

            wx++;
        }
        wy++;
        wx = 100;
        values[0] = map;
        vars[0] = "MAPS";
        vars[1] = NULL;
        row = cat_template(row, do_template(world_row_template, vars, values));
        free(map);
        map = NULL;
    }
    snprintf(mappath, sizeof(mappath), "world%s", output_extensions[output_format]);
    snprintf(mapraw, sizeof(mapraw), "world_raw%s", output_extensions[output_format]);
    snprintf(mapregion, sizeof(mapregion), "world_regions%s", output_extensions[output_format]);

    values[0] = row;
    vars[0] = "MAPS";
    values[1] = mappath;
    vars[1] = "WORLDMAP";
    values[2] = mapraw;
    vars[2] = "WORLDRAW";
    values[3] = mapregion;
    vars[3] = "WORLDREGIONS";
    vars[4] = NULL;
    total = do_template(world_template, vars, values);
    free(row);
    out = fopen(file, "w+");
    fprintf(out, "%s", total);
    free(total);
    fclose(out);

    snprintf(mappath, sizeof(mappath), "%s/world_raw%s", root, output_extensions[output_format]);
    out = fopen(mappath, "wb+");
    save_picture(out, pic);
    fclose(out);

    /* Write region names. */
    small = gdImageCreateTrueColor(SIZE*30, SIZE*30);
    font = gdFontGetGiant();
    color = gdImageColorAllocateAlpha(pic, 255, 0, 0, 20);
    for (region = 0; region < region_allocated; region++) {
        if (!regions[region]->is_world || regions[region]->sum == 0)
            continue;

        x = regions[region]->sum_x*SIZE/regions[region]->sum+SIZE/2-strlen(regions[region]->reg->name)*font->w/2;
        y = regions[region]->sum_y*SIZE/regions[region]->sum+SIZE/2-font->h/2;
        gdImageString(small, font, x, y, (unsigned char *)regions[region]->reg->name, color);
        gdImageString(pic, font, x, y, (unsigned char *)regions[region]->reg->name, color);

        /* For exit/road map, size isn't the same. */
        x = regions[region]->sum_x*50/regions[region]->sum+50/2-strlen(regions[region]->reg->name)*font->w/2;
        y = regions[region]->sum_y*50/regions[region]->sum+50/2-font->h/2;
        gdImageString(infomap, font, x, y, (unsigned char *)regions[region]->reg->name, color);
    }

    snprintf(mappath, sizeof(mappath), "%s/world_regions%s", root, output_extensions[output_format]);
    out = fopen(mappath, "wb+");
    save_picture(out, small);
    fclose(out);
    gdImageDestroy(small);

    snprintf(mappath, sizeof(mappath), "%s/world%s", root, output_extensions[output_format]);
    out = fopen(mappath, "wb+");
    save_picture(out, pic);
    fclose(out);
    gdImageDestroy(pic);

    printf(" done.\n");
#undef SIZE
}

/**
 * Write a map page.
 *
 * @param map
 * map to write page of.
 */
static void write_map_page(struct_map_info *map) {
    char *exits_text;
    char *exits_to;
    char *maplore;
    char *tmp;
    char *quests, *quest;
    char *monsters;

    char htmlpath[500];         /* Map file path. */
    char mappic[500];           /* Name of map's full size picture. */
    char mapsmallpic[500];      /* Name of map's small size picture. */
    char indexpath[500];        /* Relative path of full index. */
    char regionpath[500];       /* Path to region's filename. */
    char regionname[500];       /* Name of map's region. */
    char regionindexpath[500];  /* Path to region index file. */
    char worldmappath[500];     /* Path to world map. */
    char exit_path[500];
    char maplevel[5], minmonster[5], maxmonster[5];
    FILE *out;
    char questpath[500], questtemp[500];
    const char *quest_vars[] = { "NAME", "PATH", "TEXT", NULL };
    const char *quest_vals[] = { NULL, questpath, NULL, NULL };
    const char *q_vars[] = { "QUESTS", NULL };
    const char *q_vals[] = { NULL, NULL };
    const char *m_vars[] = { "NAME", NULL };
    const char *m_vals[] = { NULL, NULL };
    const char *vars[] = { "NAME", "MAPPATH", "MAPNAME", "MAPPIC", "MAPSMALLPIC", "MAPEXITFROM", "INDEXPATH", "REGIONPATH", "REGIONNAME", "REGIONINDEXPATH", "WORLDMAPPATH", "MAPLORE", "MAPEXITTO", "MAPLEVEL", "QUESTS", "MONSTERS", "MINMONSTER", "MAXMONSTER", NULL, NULL, NULL };
    const char *values[] = { map->path, htmlpath, map->name, mappic, mapsmallpic, "", indexpath, regionpath, regionname, regionindexpath, worldmappath, "", "", maplevel, NULL, "", minmonster, maxmonster, NULL, NULL, NULL };
    int vars_count = 0;

    while (vars[vars_count])
        vars_count++;

    snprintf(minmonster, sizeof(minmonster), "%d", map->min_monster);
    snprintf(maxmonster, sizeof(maxmonster), "%d", map->max_monster);

    relative_path(map->path, "/maps.html", indexpath);
    relative_path(map->path, "/world.html", worldmappath);
    relative_path(map->path, "/regions.html", regionindexpath);

    if (map->cfregion) {
        strcpy(regionname, get_region_longname(map->cfregion));
        strcpy(exit_path, "/");
        strcat(exit_path, map->cfregion->name);
        strcat(exit_path, ".html");
        relative_path(map->path, exit_path, regionpath);
    } else {
        regionpath[0]='\0';
        snprintf(regionname, sizeof(regionname), "(map was not processed)");
    }

    snprintf(mappic, sizeof(mappic), "%s%s", map->filename, output_extensions[output_format]);
    snprintf(mapsmallpic, sizeof(mapsmallpic), "%s.small%s", map->filename, output_extensions[output_format]);

    snprintf(htmlpath, sizeof(htmlpath), "%s%s.html", root, map->path);
    make_path_to_file(htmlpath);

    values[14] = "";

    snprintf(maplevel, sizeof(maplevel), "%d", map->level);
    if (map->lore && map->lore[0] != '\0') {
        values[11] = map->lore;
        maplore = do_template(map_lore_template, vars, values);
    } else {
        maplore = do_template(map_no_lore_template, vars, values);
    }
    values[11] = maplore;

    if (map->exits_from.count) {
        char *one_exit = NULL;
        int exit;
        char relative[500];

        vars[vars_count] = "EXITNAME";
        vars[vars_count+1] = "EXITFILE";

        qsort(map->exits_from.maps, map->exits_from.count, sizeof(const char *), sort_map_info);
        for (exit = 0; exit < map->exits_from.count; exit++) {
            relative_path(map->path, map->exits_from.maps[exit]->path, relative);
            values[vars_count] = map->exits_from.maps[exit]->name;
            strcat(relative, ".html");
            values[vars_count+1] = relative;
            one_exit = cat_template(one_exit, do_template(map_exit_template, vars, values));
        }
        vars[vars_count] = "EXIT";
        vars[vars_count+1] = NULL;
        values[vars_count] = one_exit;
        exits_text = do_template(map_with_exit_template, vars, values);
        free(one_exit);
    }
    else
        exits_text = do_template(map_no_exit_template, vars, values);

    values[5] = exits_text;

    if (map->exits_to.count) {
        char *one_exit = NULL;
        int exit;
        char relative[500];

        vars[vars_count] = "EXITNAME";
        vars[vars_count+1] = "EXITFILE";

        qsort(map->exits_to.maps, map->exits_to.count, sizeof(struct_map_info *), sort_map_info);
        for (exit = 0; exit < map->exits_to.count; exit++) {
            relative_path(map->path, map->exits_to.maps[exit]->path, relative);
            values[vars_count] = map->exits_to.maps[exit]->name;
            strcat(relative, ".html");
            values[vars_count+1] = relative;
            one_exit = cat_template(one_exit, do_template(map_exit_to_template, vars, values));
        }
        vars[vars_count] = "EXIT";
        vars[vars_count+1] = NULL;
        values[vars_count] = one_exit;
        exits_to = do_template(map_with_exit_to_template, vars, values);
        free(one_exit);
    } else
        exits_to = do_template(map_no_exit_to_template, vars, values);

    values[12] = exits_to;

    if (map->quests.count) {
        int q;

        quest = NULL;
        for (q = 0; q < map->quests.count; q++) {
            quest_vals[0] = map->quests.list[q]->quest->name;
            relative_path(map->path, "/quests.html", questtemp);
            snprintf(questpath, sizeof(questpath), "%s#quest_%d", questtemp, map->quests.list[q]->quest->number);
            quest_vals[2] = map->quests.list[q]->description;
            quest = cat_template(quest, do_template(map_one_quest_template, quest_vars, quest_vals));
        }

        q_vals[0] = quest;
        quests = do_template(map_with_quests_template, q_vars, q_vals);
        free(quest);
        quest = NULL;
    } else
        quests = strdup(map_no_quest_template);
    values[14] = quests;

    if (map->monsters.count) {
        int m;

        qsort(map->monsters.races, map->monsters.count, sizeof(struct_race *), sort_race);

        monsters = do_template(map_monster_before_template, vars, values);
        for (m = 0; m < map->monsters.count; m++) {
            m_vals[0] = map->monsters.races[m]->name;
            monsters = cat_template(monsters, do_template(map_monster_one_template, m_vars, m_vals));
            if (m != map->monsters.count-1)
                monsters = cat_template(monsters, do_template(map_monster_between_template, vars, values));
        }
        monsters = cat_template(monsters, do_template(map_monster_after_template, vars, values));
    } else
        monsters = do_template(map_no_monster_template, vars, values);
    values[15] = monsters;

    vars[vars_count] = NULL;
    out = fopen(htmlpath, "w+");
    tmp = do_template(map_template, vars, values);
    fprintf(out, "%s", tmp);
    fclose(out);
    free(tmp);
    free(exits_text);
    free(exits_to);
    free(maplore);
    free(quests);
    free(monsters);
}

/** Ensures all maps have a name (if there was a limit to map processing, some maps will have a NULL name which causes issues). */
static void fix_map_names(void) {
    int map;

    for (map = 0; map < maps_list.count; map++) {
        if (maps_list.maps[map]->name)
            continue;
        if (!maps_list.maps[map]->filename) {
            printf("map without path!\n");
            abort();
        }
        maps_list.maps[map]->name = strdup(maps_list.maps[map]->filename);
    }
}

/**
 * Ensures all tiled maps have a name, a region, a filename and a path.
 * Will try to find a suitable name and region from the maps in the group.
 * @todo
 * use a better filename, try to get the start of the map filenames.
 */
static void fix_tiled_map(void) {
    int map, tile;
    char name[500];
    char *slash, *test;
    region *cfregion;

    for (map = 0; map < tiled_map_list.count; map++) {
        if (tiled_map_list.maps[map]->tiled_maps.count == 0) {
            printf("empty tiled map group!");
            abort();
        }

        snprintf(name, sizeof(name), "tiled_map_group_%d", map);
        tiled_map_list.maps[map]->filename = strdup(name);

        cfregion = NULL;
        test = NULL;

        for (tile = 0; tile < tiled_map_list.maps[map]->tiled_maps.count; tile++) {
            if (tiled_map_list.maps[map]->tiled_maps.maps[tile]->cfregion == NULL)
                /* map not processed, ignore it. */
                continue;

            if (!cfregion)
                cfregion = tiled_map_list.maps[map]->tiled_maps.maps[tile]->cfregion;
            else if (cfregion != tiled_map_list.maps[map]->tiled_maps.maps[tile]->cfregion) {
                printf("*** warning: tiled maps %s and %s not in same region (%s and %s).\n",
                    tiled_map_list.maps[map]->tiled_maps.maps[0]->path, tiled_map_list.maps[map]->tiled_maps.maps[tile]->path,
                    tiled_map_list.maps[map]->tiled_maps.maps[0]->cfregion->name, tiled_map_list.maps[map]->tiled_maps.maps[tile]->cfregion->name);
                cfregion = NULL;
            }

            if (strcmp(tiled_map_list.maps[map]->tiled_maps.maps[tile]->name, tiled_map_list.maps[map]->tiled_maps.maps[tile]->filename)) {
                /* map has a custom name, use it */
                if (!test)
                    test = tiled_map_list.maps[map]->tiled_maps.maps[tile]->name;
            }
        }

        if (!test) {
            /* this can happen of course if only partial maps were processed, but well... */
            printf("*** warning: tiled map without any name. First map path %s\n", tiled_map_list.maps[map]->tiled_maps.maps[0]->path);
            test = name;
        }

        tiled_map_list.maps[map]->name = strdup(test);
        tiled_map_list.maps[map]->cfregion = cfregion;

        strncpy(name, tiled_map_list.maps[map]->tiled_maps.maps[0]->path, sizeof(name));
        slash = strrchr(name, '/');
        if (!slash)
            snprintf(name, sizeof(name), "/");
        else
            *(slash+1) = '\0';
        strncat(name, tiled_map_list.maps[map]->filename, sizeof(name));
        tiled_map_list.maps[map]->path = strdup(name);
    }
}

/**
 * Changes for the list all maps to the tiled map they are part of, if applicable.
 *
 * @param current
 * map currently being processed.
 * @param from
 * list that contains the exits to/from map to be fixed.
 * @param is_from
 * if non zero, <code>from</code> is exit_from field, else it is an exit_to.
 */
static void fix_exits_for_map(struct_map_info *current, struct_map_list *from, int is_from) {
    int map, max;
    struct_map_info *group;

    max = from->count-1;
    for (map = max; map >= 0; map--) {
        if (from->maps[map]->tiled_group) {
            group = from->maps[map]->tiled_group;
            if (map != max)
                from->maps[map] = from->maps[max];
            from->count--;
            max--;
            add_map(group, from);
            add_map(current->tiled_group ? current->tiled_group : current, is_from ? &group->exits_to : &group->exits_from);
        }
    }
}

/** Changes all exits to maps in a tiled map to point directly to the tiled map. Same for region lists. */
static void fix_exits_to_tiled_maps(void) {
    int map, region, max;
    struct_map_info *group;

    for (map = 0; map < maps_list.count; map++) {
        fix_exits_for_map(maps_list.maps[map], &maps_list.maps[map]->exits_from, 1);
        fix_exits_for_map(maps_list.maps[map], &maps_list.maps[map]->exits_to, 0);
    }

    for (region = 0; region < region_count; region++) {
        max = regions[region]->maps_list.count-1;
        for (map = max; map >= 0; map--) {
            if (regions[region]->maps_list.maps[map]->tiled_group) {
                group = regions[region]->maps_list.maps[map]->tiled_group;
                if (map != max)
                    regions[region]->maps_list.maps[map] = regions[region]->maps_list.maps[max];
                regions[region]->maps_list.count--;
                max--;
                add_map(group, &regions[region]->maps_list);
            }
        }
    }
}

/**
 * Makes all monsters point to tiled maps instead of map when appliable, and merge
 * map monster to tiled map.
 */
static void fix_tiled_map_monsters(void) {
    int map, race, max;
    struct_map_info *group;

    for (race = 0; race < races.count; race++) {
        max = races.races[race]->origin.count-1;
        for (map = max; map >= 0; map--) {
            if (races.races[race]->origin.maps[map]->tiled_group) {
                group = races.races[race]->origin.maps[map]->tiled_group;
                if (map != max)
                    races.races[race]->origin.maps[map] = races.races[race]->origin.maps[max];
                races.races[race]->origin.count--;
                max--;
                add_map(group, &races.races[race]->origin);
            }
        }
    }

    for (map = 0; map < maps_list.count; map++) {
        if (maps_list.maps[map]->tiled_group) {
            for (race = 0; race < maps_list.maps[map]->monsters.count; race++) {
                add_race_to_list(maps_list.maps[map]->monsters.races[race], &maps_list.maps[map]->tiled_group->monsters, 1);
            }
        }
    }
}

/** Ensures all maps have a name, and writes all map pages. */
static void write_all_maps(void) {
    int map;

    printf("Writing map pages...");

    for (map = 0; map < maps_list.count; map++)
        if (!maps_list.maps[map]->tiled_group)
            write_map_page(maps_list.maps[map]);

    printf(" done.\n");
}

static int tiled_map_need_pic(struct_map_info *map) {
    int test;
    char picpath[500];
    struct stat stats;

    snprintf(picpath, sizeof(picpath), "%s%s%s", root, map->path, output_extensions[output_format]);
    if (stat(picpath, &stats))
        return 1;

    snprintf(picpath, sizeof(picpath), "%s%s.small%s", root, map->path, output_extensions[output_format]);
    if (stat(picpath, &stats))
        return 1;

    for (test = 0; test < map->tiled_maps.count; test++) {
        if (map->tiled_maps.maps[test]->pic_was_done)
            return 1;
    }

    return 0;
}

/**
 * Generates the large and small pictures for a tiled map.
 * This uses the large/small pictures made during process_map(), so having a map limit could lead
 * to maps not found and invalid results.
 *
 * @param map
 * tiled map to make the picture of.
 * @todo
 * add a field to struct_map_info to remember if pic was updated or not, and update the tiled map
 * only if one map has changed / the pic doesn't exist.
 */
static void do_tiled_map_picture(struct_map_info *map) {
    int xmin = 0, xmax = 0, ymin = 0, ymax = 0, tiled, count, last;
    char picpath[500];
    gdImagePtr small, large, load;
    FILE *out;
    struct_map_info *current;

    if (!generate_pics)
        return;

    printf(" Generating composite map for %s...", map->name);
    fflush(stdout);

    if (!tiled_map_need_pic(map)) {
        printf(" already uptodate.\n");
        return;
    }

    count = map->tiled_maps.count;
    if (count == 0) {
        printf("Tiled map without tiled maps?\n");
        abort();
    }
    map->tiled_maps.maps[0]->processed = 1;
    map->tiled_maps.maps[0]->tiled_x_from = 0;
    map->tiled_maps.maps[0]->tiled_y_from = 0;

    while (count > 0) {
        last = count;

        for (tiled = 0; tiled < map->tiled_maps.count; tiled++) {
            current = map->tiled_maps.maps[tiled];
            if (current->processed != 1)
                continue;

            count--;

            if ((current->tiles[0]) && (current->tiles[0]->processed == 0)) {
                current->tiles[0]->processed = 1;
                current->tiles[0]->tiled_x_from = current->tiled_x_from;
                current->tiles[0]->tiled_y_from = current->tiled_y_from-current->tiles[0]->height;
            }
            if ((current->tiles[1]) && (current->tiles[1]->processed == 0)) {
                current->tiles[1]->processed = 1;
                current->tiles[1]->tiled_x_from = current->tiled_x_from+current->width;
                current->tiles[1]->tiled_y_from = current->tiled_y_from;
            }
            if ((current->tiles[2]) && (current->tiles[2]->processed == 0)) {
                current->tiles[2]->processed = 1;
                current->tiles[2]->tiled_x_from = current->tiled_x_from;
                current->tiles[2]->tiled_y_from = current->tiled_y_from+current->height;
            }
            if ((current->tiles[3]) && (current->tiles[3]->processed == 0)) {
                current->tiles[3]->processed = 1;
                current->tiles[3]->tiled_x_from = current->tiled_x_from-current->tiles[3]->width;
                current->tiles[3]->tiled_y_from = current->tiled_y_from;
            }
        }

        if (last == count) {
            printf("do_tiled_map_picture: didn't process any map in %s (%d left)??\n", map->path, last);
            abort();
        }
    }

    for (tiled = 0; tiled < map->tiled_maps.count; tiled++) {
        if (map->tiled_maps.maps[tiled]->tiled_x_from < xmin)
            xmin = map->tiled_maps.maps[tiled]->tiled_x_from;
        if (map->tiled_maps.maps[tiled]->tiled_y_from < ymin)
            ymin = map->tiled_maps.maps[tiled]->tiled_y_from;
        if (map->tiled_maps.maps[tiled]->tiled_x_from+map->tiled_maps.maps[tiled]->width > xmax)
            xmax = map->tiled_maps.maps[tiled]->tiled_x_from+map->tiled_maps.maps[tiled]->width;
        if (map->tiled_maps.maps[tiled]->tiled_y_from+map->tiled_maps.maps[tiled]->height > ymax)
            ymax = map->tiled_maps.maps[tiled]->tiled_y_from+map->tiled_maps.maps[tiled]->height;
    }

    large = gdImageCreateTrueColor(32*(xmax-xmin), 32*(ymax-ymin));
    small = gdImageCreateTrueColor(size_small*(xmax-xmin), size_small*(ymax-ymin));

    for (tiled = 0; tiled < map->tiled_maps.count; tiled++) {
        snprintf(picpath, sizeof(picpath), "%s%s%s", root, map->tiled_maps.maps[tiled]->path, output_extensions[output_format]);

        out = fopen(picpath, "rb");
        if (!out) {
            printf("\n  do_tiled_map_picture: warning: pic file not found for %s (errno=%d)\n", map->tiled_maps.maps[tiled]->path, errno);
            continue;
        }
        if (output_format == OF_PNG)
            load = gdImageCreateFromPng(out);
        else
            load = gdImageCreateFromJpeg(out);
        fclose(out);
        if (!load) {
            printf("\n  do_tiled_map_picture: warning: pic not found for %s\n", map->tiled_maps.maps[tiled]->path);
            continue;
        }
        gdImageCopy(large, load, 32*(map->tiled_maps.maps[tiled]->tiled_x_from-xmin), 32*(map->tiled_maps.maps[tiled]->tiled_y_from-ymin), 0, 0, load->sx, load->sy);
        gdImageDestroy(load);

        snprintf(picpath, sizeof(picpath), "%s%s.small%s", root, map->tiled_maps.maps[tiled]->path, output_extensions[output_format]);
        out = fopen(picpath, "rb");
        if (output_format == OF_PNG)
            load = gdImageCreateFromPng(out);
        else
            load = gdImageCreateFromJpeg(out);
        fclose(out);
        if (!load) {
            printf("\n  do_tiled_map_picture: warning: small pic not found for %s\n", map->tiled_maps.maps[tiled]->path);
            continue;
        }
        gdImageCopy(small, load, size_small*(map->tiled_maps.maps[tiled]->tiled_x_from-xmin), size_small*(map->tiled_maps.maps[tiled]->tiled_y_from-ymin), 0, 0, load->sx, load->sy);
        gdImageDestroy(load);
    }

    snprintf(picpath, sizeof(picpath), "%s%s%s", root, map->path, output_extensions[output_format]);
    out = fopen(picpath, "wb+");
    save_picture(out, large);
    fclose(out);

    snprintf(picpath, sizeof(picpath), "%s%s.small%s", root, map->path, output_extensions[output_format]);
    out = fopen(picpath, "wb+");
    save_picture(out, small);
    fclose(out);

    gdImageDestroy(small);
    gdImageDestroy(large);

    printf(" done.\n");
}

/** Writes the page for a tiled map group. */
static void write_tiled_map_page(struct_map_info *map) {

    do_tiled_map_picture(map);

    /** @todo: do a real page, with the various levels, maps and such. */

    write_map_page(map);
}

/** Outputs all tiled map pages. */
static void write_tiled_maps(void) {
    int map;

    printf("Writing tiled map information...\n");

    for (map = 0; map < tiled_map_list.count; map++)
        write_tiled_map_page(tiled_map_list.maps[map]);

        printf(" done.\n");
}

/** Outputs the list of maps sorted by level. */
static void write_maps_by_level(void) {
    int map;
    FILE *out;
    char name[500];
    char mappath[500];
    char *letters = NULL;
    char *maps = NULL;
    char *level = NULL;
    int lastlevel = -1;
    char strlevel[10];
    char strcount[10];
    const char *val_vars[] = { "LEVEL", "MAPS", NULL };
    const char *val_values[] = { strlevel, NULL, NULL };
    const char *map_vars[] = { "MAPNAME", "MAPPATH", NULL };
    const char *map_values[] = { NULL, mappath, NULL };
    const char *idx_vars[] = { "COUNT", "LEVELS", NULL };
    const char *idx_values[] = { strcount, NULL, NULL };
    int levelcount = 0;
    struct_map_info *last_tiled = NULL;
    struct_map_info *process;

    printf("Writing map index by level...");

    snprintf(name, sizeof(name), "%s/index_by_level.html", root);

    qsort(maps_list.maps, maps_list.count, sizeof(struct_map_info *), sort_map_info_by_level);

    for (map = 0; map < maps_list.count; map++) {
        process = maps_list.maps[map];
        if (maps_list.maps[map]->level != lastlevel) {
            if (maps) {
                snprintf(strlevel, sizeof(strlevel), "%d", lastlevel);
                val_values[1] = maps;
                letters = cat_template(letters, do_template(level_value_template, val_vars, val_values));
                free(maps);
                maps = NULL;
            }
            lastlevel = process->level;
            levelcount++;
            last_tiled = NULL;
        } else
            if (last_tiled && last_tiled == process->tiled_group)
                /* Group maps of same tiled group and level, but make them appear in different levels if applicable. */
                continue;

        if (process->tiled_group) {
            process = process->tiled_group;
            last_tiled = process;
        } else
            last_tiled = process->tiled_group;

        map_values[0] = process->name;
        snprintf(mappath, sizeof(mappath), "%s.html", process->path+1); /* don't want the leading / */
        maps = cat_template(maps, do_template(level_map_template, map_vars, map_values));
    }

    snprintf(strlevel, sizeof(strlevel), "%d", lastlevel);
    val_values[1] = maps;
    letters = cat_template(letters, do_template(level_value_template, val_vars, val_values));
    free(maps);
    maps = NULL;

    snprintf(strcount, sizeof(strcount), "%d", levelcount);
    idx_values[1] = letters;
    level = do_template(level_template, idx_vars, idx_values);
    free(letters);

    out = fopen(name, "w+");
    fprintf(out, "%s", level);
    fclose(out);
    free(level);

    printf(" done.\n");
}

/**
 * Writes the item page.
 */
static void write_equipment_index(void) {
    int item, map;
    FILE *out;
    char name[500];

    printf("Generating special equipment list..");
    fflush(stdout);

    qsort(special_equipment, equipment_count, sizeof(struct_equipment *), sort_equipment);

    snprintf(name, sizeof(name), "%s/items.html", root);
    out = fopen(name, "w+");

    fprintf(out, "<html><head><title>Item list</title></head><body><h1>Special items found in maps</h1>\n");
    fprintf(out, "<table border=\"1\"><tr><th>Name</th><th>Map(s)</th><th>Item power</th><th>Calc item power</th><th>Description</th></tr>\n");

    for (item = 0; item < equipment_count; item++) {
        fprintf(out, "<tr><td>%s</td><td><ul>", special_equipment[item]->name);

        for (map = 0; map < special_equipment[item]->origin.count; map++)
            fprintf(out, "<li>%s</li>\n", special_equipment[item]->origin.maps[map]->path);

        fprintf(out, "</ul></td><td>%d</td><td>%d</td><td><pre>%s</pre></td></tr>\n", special_equipment[item]->power, special_equipment[item]->calc_power, special_equipment[item]->diff);
    }
    fprintf(out, "</table></body></html>\n");
    fclose(out);

    printf(" done.\n");
}

/**
 * Writes the monster information page.
 */
static void write_race_index(void) {
    int item, map;
    FILE *out;
    char name[500];

    printf("Generating monster list...");
    fflush(stdout);

    qsort(races.races, races.count, sizeof(struct_race *), sort_race);

    snprintf(name, sizeof(name), "%s/monsters.html", root);
    out = fopen(name, "w+");

    fprintf(out, "<html><head><title>Monster list</title></head><body><h1>Monsters found in maps</h1>\n");
    fprintf(out, "<table border=\"1\"><tr><th>Name</th><th>Count</th><th>Map(s)</th></tr>\n");

    for (item = 0; item < races.count; item++) {
        fprintf(out, "<tr><td>%s</td><td>%d</td><td>Found on %d maps:<ul>", races.races[item]->name, races.races[item]->count, races.races[item]->origin.count);

        qsort(races.races[item]->origin.maps, races.races[item]->origin.count, sizeof(struct_map_info *), sort_map_info);

        for (map = 0; map < races.races[item]->origin.count; map++)
            fprintf(out, "<li>%s</li>\n", races.races[item]->origin.maps[map]->path);

        fprintf(out, "</ul></td></tr>\n");
    }
    fprintf(out, "</table></body></html>\n");
    fclose(out);

    printf(" done.\n");
}

/** Directories to ignore for map search. */
static const char *ignore_path[] = {
    "/Info",
    "/editor",
    "/python",
    "/styles",
    "/templates",
    "/test",
    "/unlinked",
    NULL };

/** File names to ignore for map search. */
static const char *ignore_name[] = {
    ".",
    "..",
    ".svn",
    "README",
    NULL };

/**
 * Recursively find all all maps in a directory.
 *
 * @param from
 * path to search from, without trailing /.
 */
static void find_maps(const char *from) {
    struct dirent *file;
    struct stat statbuf;
    int status, ignore;
    char path[1024], full[1024];
    DIR *dir;

    for (ignore = 0; ignore_path[ignore] != NULL; ignore++) {
        if (strcmp(from, ignore_path[ignore]) == 0)
            return;
    }

    snprintf(path, sizeof(path), "%s/%s%s", settings.datadir, settings.mapdir, from);
    dir = opendir(path);

    if (dir) {
        for (file = readdir(dir); file; file = readdir(dir)) {

            for (ignore = 0; ignore_name[ignore] != NULL; ignore++) {
                if (strcmp(file->d_name, ignore_name[ignore]) == 0)
                    break;
            }
            if (ignore_name[ignore] != NULL)
                continue;

            snprintf(full, sizeof(full), "%s/%s", path, file->d_name);

            status = stat(full, &statbuf);
            if ((status != -1) && (S_ISDIR(statbuf.st_mode))) {
                snprintf(full, sizeof(full), "%s/%s", from, file->d_name);
                find_maps(full);
                continue;
            }
            if (found_maps_count == found_maps_allocated) {
                found_maps_allocated += 50;
                found_maps = realloc(found_maps, found_maps_allocated*sizeof(char *));
            }
            snprintf(full, sizeof(full), "%s/%s", from, file->d_name);
            found_maps[found_maps_count++] = strdup(full);
        }
        closedir(dir);
    }
}

/** Writes the list of unused maps, maps found in the directories but not linked from the other maps. */
static void dump_unused_maps(void) {
    FILE *dump;
    char path[1024];
    int index, found = 0;

    snprintf(path, sizeof(path), "%s/%s", root, "maps.unused");
    dump = fopen(path, "w+");
    if (dump == NULL) {
        printf("Unable to open file maps.unused!\n");
        return;
    }
    for (index = 0; index < found_maps_count; index++) {
        if (found_maps[index] != NULL) {
            fprintf(dump, "%s\n", found_maps[index]);
            free(found_maps[index]);
            found++;
        }
    }
    fclose(dump);
    printf("%d unused maps.\n", found);
}

/** Writes the exit information world map. */
static void write_world_info(void) {
    FILE *file;
    char path[MAX_BUF];
    int x, y;
    gdImagePtr elevationmap;

    if (!world_exit_info)
        return;

    printf("Saving exit/blocking/road information...");
    snprintf(path, sizeof(path), "%s/%s%s", root, "world_info", output_extensions[output_format]);
    file = fopen(path, "wb+");
    save_picture(file, infomap);
    fclose(file);
    printf("done.\n");
    gdImageDestroy(infomap);
    infomap = NULL;

   if (elevation_min == 0 || elevation_max == 0) {
       puts("Error: Could not save elevation world map due to not finding any minimum or maximum elevation.");
       return;
   }

    elevationmap = gdImageCreateTrueColor(30*50, 30*50);;

    for (x = 0; x < 30*50; x++) {
        for (y = 0; y < 30*50; y++) {
            gdImageSetPixel(elevationmap, x, y, get_elevation_color(elevation_info[x][y], elevationmap));
        }
    }

    printf("Saving elevation world map...");
    snprintf(path, sizeof(path), "%s/%s%s", root, "world_elevation", output_extensions[output_format]);
    file = fopen(path, "wb+");
    save_picture(file, elevationmap);
    fclose(file);
    printf("done.\n");
    gdImageDestroy(elevationmap);
    elevationmap = NULL;
}

/** Write the .dot file representing links between regions. */
static void write_regions_link(void) {
    FILE *file;
    char path[MAX_BUF];
    int link;

    if (!do_regions_link)
        return;

    printf("Writing regions link file...");
    snprintf(path, sizeof(path), "%s/%s", root, "region_links.dot");
    file = fopen(path, "wb+");
    fprintf(file, "digraph {\n");
    for (link = 0; link < regions_link_count; link++)
        fprintf(file, "%s", regions_link[link]);
    fprintf(file, "}\n");
    fclose(file);
    printf("done.\n");
}

/**
 * Helper function to write a map to a file with its link and full path.
 *
 * @param file
 * where to write.
 * @param map
 * map info to write.
 */
static void write_slaying_map_name(FILE *file, struct_map_info *map) {
    fprintf(file, "<a href=\"%s.html\">%s</a> (full map path: %s)", map->tiled_group ? map->tiled_group->path+1 : map->path+1, map->name, map->path);
}

/**
 * Writes all maps of the specified slaying information.
 *
 * @param file
 * file to write to.
 * @param info
 * slaying information to write.
 * @param item
 * which of the S_xxx to write.
 * @param with
 * text to write when there are maps to write. Mustn't be NULL.
 * @param without
 * text to write when there are no maps. Can be NULL.
 */
static void write_one_slaying_info(FILE *file, struct_slaying_info *info, int item, const char *with, const char *without) {
    int map;

    if (info->maps[item].count == 0) {
        if (without)
            fprintf(file, "%s", without);
        return;
    }

    qsort(info->maps[item].maps, info->maps[item].count, sizeof(const char *), sort_mapname);

    fprintf(file, "%s", with);
    fprintf(file, "<ul>\n");
    for (map = 0; map < info->maps[item].count; map++) {
        fprintf(file, "\t<li>");
        write_slaying_map_name(file, info->maps[item].maps[map]);
        fprintf(file, "</li>\n");
    }
    fprintf(file, "</ul>\n");
}

/**
 * Helper function to sort an array of struct_slaying_info.
 *
 * @param left
 * first item.
 * @param right
 * second item.
 * @return
 * sort order.
 */
static int sort_slaying(const void *left, const void *right) {
    struct_slaying_info *l = *(struct_slaying_info **)left;
    struct_slaying_info *r = *(struct_slaying_info **)right;

    return strcasecmp(l->slaying, r->slaying);
}

/**
 * Writes all slaying info to file.
 */
static void write_slaying_info(void) {
    FILE *file;
    char path[MAX_BUF];
    int lock;
    struct_slaying_info *info;

    printf("Writing slaying info file...");

    qsort(slaying_info, slaying_count, sizeof(struct_slaying_info *), sort_slaying);

    snprintf(path, sizeof(path), "%s/%s", root, "slaying_info.html");
    file = fopen(path, "wb+");

    fprintf(file, "<html>\n<head>\n<title>Slaying information</title>\n</head>\n<body>\n");
    fprintf(file, "<p>This is a list of various slaying fields on keys, containers, doors, detectors.</p>");

    for (lock = 0; lock < slaying_count; lock++) {
        info = slaying_info[lock];
        fprintf(file, "<h1>%s</h1>\n", info->slaying);

        if (info->maps[S_DOOR].count == 0 && info->maps[S_CONTAINER].count == 0 && info->maps[S_CONNECT].count == 0) {
            fprintf(file, "No door, container or detector matching this slaying.<br />\n");
        } else {
            write_one_slaying_info(file, info, S_DOOR, "Connected doors:\n", NULL);
            write_one_slaying_info(file, info, S_CONTAINER, "Matching containers:\n", NULL);
            write_one_slaying_info(file, info, S_CONNECT, "Detectors and such:\n", NULL);
        }
        write_one_slaying_info(file, info, S_KEY, "Matching keys:\n", "No key with this slaying.<br />\n");
    }

    fprintf(file, "</body>\n</html>\n");

    fclose(file);
    printf("done.\n");
}

/**
 * Write the list of all found NPCs in maps.
 */
static void write_npc_list(void) {
    FILE *file;
    char path[MAX_BUF];
    int map, npc;

    printf("Writing NPC info file...");

    qsort(slaying_info, slaying_count, sizeof(struct_slaying_info *), sort_slaying);

    snprintf(path, sizeof(path), "%s/%s", root, "npc_info.html");
    file = fopen(path, "wb+");

    fprintf(file, "<html>\n<head>\n<title>NPCs who have a special message</title>\n</head>\n<body>\n");
    fprintf(file, "<p>This is a list of NPCs having a special message.</p>");
    fprintf(file, "<ul>\n");

    for (map = 0; map < maps_list.count; map++) {
        if (maps_list.maps[map]->npcs.count == 0)
            continue;
        fprintf(file, "<li>%s</li>\n<ul>", maps_list.maps[map]->path);
        for (npc = 0; npc < maps_list.maps[map]->npcs.count; npc++) {
            fprintf(file, "<li>%s (%d,%d): <br /><pre>%s</pre></li>\n", maps_list.maps[map]->npcs.npc[npc]->name, maps_list.maps[map]->npcs.npc[npc]->x, maps_list.maps[map]->npcs.npc[npc]->y, maps_list.maps[map]->npcs.npc[npc]->message);
        }
        fprintf(file, "</ul>\n</li>\n");
    }

    fprintf(file, "</ul>\n");
    fprintf(file, "</body>\n</html>\n");

    fclose(file);
    printf("done.\n");
}

/**
 * Write the list of all found SIGN and BOOK in maps.
 */
static void write_readable_list(void) {
    FILE *file;
    char path[MAX_BUF];
    int map, readable;

    printf("Writing readable info file...");

    snprintf(path, sizeof(path), "%s/%s", root, "readable_info.html");
    file = fopen(path, "wb+");

    fprintf(file, "<html>\n<head>\n<title>SIGN and BOOK with a special message</title>\n</head>\n<body>\n");
    fprintf(file, "<p>This is a list of SIGN and BOOK with a special message.</p>");
    fprintf(file, "<ul>\n");

    for (map = 0; map < maps_list.count; map++) {
        if (maps_list.maps[map]->readable.count == 0)
            continue;
        fprintf(file, "<li>%s</li>\n<ul>", maps_list.maps[map]->path);
        for (readable = 0; readable < maps_list.maps[map]->readable.count; readable++) {
            fprintf(file, "<li>%s (%d,%d): <br /><pre>%s</pre></li>\n", maps_list.maps[map]->readable.npc[readable]->name, maps_list.maps[map]->readable.npc[readable]->x, maps_list.maps[map]->readable.npc[readable]->y, maps_list.maps[map]->readable.npc[readable]->message);
        }
        fprintf(file, "</ul>\n</li>\n");
    }

    fprintf(file, "</ul>\n");
    fprintf(file, "</body>\n</html>\n");

    fclose(file);
    printf("done.\n");
}

/**
 * Prints usage information, and exit.
 *
 * @param program
 * program path.
 */
static void do_help(const char *program) {
    printf("Crossfire Mapper will generate pictures of maps, and create indexes for all maps and regions.\n\n");
    printf("Syntax: %s\n\n", program);
    printf("Optional arguments:\n");
    printf("  -nopics             don't generate pictures.\n");
    printf("  -noindex            don't generate global map index.\n");
    printf("  -root=<path>        destination path. Default 'html'.\n");
    printf("  -limit=<number>     stop processing after this number of maps, -1 to do all maps (default).\n");
    printf("  -showmaps           outputs the name of maps as they are processed.\n");
    printf("  -jpg[=quality]      generate jpg pictures, instead of default png. Quality should be 0-95, -1 for automatic.\n");
    printf("  -forcepics          force to regenerate pics, even if pics's date is after map's.\n");
    printf("  -addmap=<map>       adds a map to process. Path is relative to map's directory root.\n");
    printf("  -rawmaps            generates maps pics without items on random (shop, treasure) tiles.\n");
    printf("  -warnnopath         inform when an exit has no path set.\n");
    printf("  -listunusedmaps     finds all unused maps in the maps directory.\n");
    printf("  -noworldmap         don't write the world map in world.png.\n");
    printf("  -noregionslink      don't generate regions relation file.\n");
    printf("  -regionslink        generate regions relation file.\n");
    printf("  -noexitmap          don't generate map of exits.\n");
    printf("  -exitmap            generate map of exits.\n");
    printf("  -tileset=<number>   use specified tileset to generate the pictures. Default 0 (standard).\n");
    printf("\n\n");
    exit(0);
}

/**
 * Handles command-line parameters.
 *
 * @param argc
 * number of parameters, including program name.
 * @param argv
 * arguments, including program name.
 */
static void do_parameters(int argc, char **argv) {
    int arg = 1;
    char path[500];

    root[0] = '\0';

    while (arg < argc) {
        if (strcmp(argv[arg], "-nopics") == 0)
            generate_pics = 0;
        else if (strcmp(argv[arg], "-noindex") == 0)
            generate_index = 0;
        else if (strncmp(argv[arg], "-root=", 6) == 0)
            strncpy(root, argv[arg]+6, 500);
        else if (strncmp(argv[arg], "-limit=", 7) == 0)
            map_limit = atoi(argv[arg]+7);
        else if (strcmp(argv[arg], "-showmaps") == 0)
            show_maps = 1;
        else if (strcmp(argv[arg], "-jpg") == 0) {
            output_format = OF_JPG;
            if (argv[arg][4] == '=') {
                jpeg_quality = atoi(argv[arg]+5);
                if (jpeg_quality < 0)
                    jpeg_quality = -1;
            }
        }
        else if (strcmp(argv[arg], "-forcepics") == 0)
            force_pics = 1;
        else if (strncmp(argv[arg], "-addmap=", 8) == 0) {
            if (*(argv[arg]+8) == '/')
                strncpy(path, argv[arg]+8, 500);
            else
                snprintf(path, 500, "/%s", argv[arg]+8);
            add_map(get_map_info(path), &maps_list);
        }
        else if (strcmp(argv[arg], "-rawmaps") == 0)
            rawmaps = 1;
        else if (strcmp(argv[arg], "-warnnopath") == 0)
            warn_no_path = 1;
        else if (strcmp(argv[arg], "-listunusedmaps") == 0)
            list_unused_maps = 1;
        else if (strcmp(argv[arg], "-noworldmap") == 0)
            world_map = 0;
        else if (strcmp(argv[arg], "-noregionslink") == 0)
            do_regions_link = 0;
        else if (strcmp(argv[arg], "-regionslink") == 0)
            do_regions_link = 1;
        else if (strcmp(argv[arg], "-noexitmap") == 0)
            world_exit_info = 0;
        else if (strcmp(argv[arg], "-exitmap") == 0)
            world_exit_info = 1;
        else if (strncmp(argv[arg], "-tileset=", 9) == 0) {
            tileset = atoi(argv[arg]+9);
            /* check of validity is done in main() as we need to actually have the sets loaded. */
        } else
            do_help(argv[0]);
        arg++;
    }
    if (!strlen(root))
        strcpy(root, "html");
    if (root[strlen(root)-1] == '/')
        root[strlen(root)-1] = '\0';
    if (map_limit < -1)
        map_limit = -1;
}

/**
 * Ensures destination directory exists.
 */
static void create_destination(void) {
    char dummy[502];

    strcpy(dummy, root);
    strcat(dummy, "/a");
    make_path_to_file(dummy);
}

/**
 * Helper to write yes/no.
 *
 * @param value
 * value to print.
 * @return
 * "no" if value == 0, "yes" else.
 */
static const char *yesno(int value) {
    return (value ? "yes" : "no");
}

int main(int argc, char **argv) {
    int current_map = 0, i;
    char max[50];
    region *dummy;

    init_map_list(&maps_list);
    init_map_list(&tiled_map_list);
    init_race_list(&races);
    pics_allocated = 0;

    do_parameters(argc, argv);

    printf("Initializing Crossfire data...\n");

    settings.debug = 0;

    init_globals();
    init_library();
    init_archetypes();
    init_artifacts();
    init_formulae();
    init_readable();
    init_regions();

    init_gods();
    read_client_images();

    /* Add a dummy region so unlinked maps can be identified. */
    dummy = get_region_struct();
    dummy->fallback = 1;
    dummy->name = strdup_local("unlinked");
    dummy->longname = strdup_local("This dummy region contains all maps without a region set.");
    dummy->longname = strdup_local("This dummy region contains all maps without a region set.");
    dummy->next = first_region;
    first_region = dummy;

    printf("\n\n done.\n\n");

    if (!is_valid_faceset(tileset)) {
        printf("Erreor: invalid tileset %d!\n", tileset);
        exit(1);
    }

    create_destination();
    gdfaces = calloc(1, sizeof(gdImagePtr)*nrofpixmaps);

    read_template("templates/map.template", &map_template);
    read_template("templates/map_no_exit.template", &map_no_exit_template);
    read_template("templates/map_with_exit.template", &map_with_exit_template);
    read_template("templates/map_exit.template", &map_exit_template);
    read_template("templates/map_no_exit_to.template", &map_no_exit_to_template);
    read_template("templates/map_with_exit_to.template", &map_with_exit_to_template);
    read_template("templates/map_exit_to.template", &map_exit_to_template);
    read_template("templates/map_lore.template", &map_lore_template);
    read_template("templates/map_no_lore.template", &map_no_lore_template);
    read_template("templates/map_no_monster.template", &map_no_monster_template);
    read_template("templates/map_monster_before.template", &map_monster_before_template);
    read_template("templates/map_monster_between.template", &map_monster_between_template);
    read_template("templates/map_monster_one.template", &map_monster_one_template);
    read_template("templates/map_monster_after.template", &map_monster_after_template);

    read_template("templates/index.template", &index_template);
    read_template("templates/index_letter.template", &index_letter);
    read_template("templates/index_map.template", &index_map);

    read_template("templates/region.template", &region_template);
    read_template("templates/region_letter.template", &region_letter_template);
    read_template("templates/region_map.template", &region_map_template);

    read_template("templates/index_region.template", &index_region_template);
    read_template("templates/index_region_region.template", &index_region_region_template);

    read_template("templates/world.template", &world_template);
    read_template("templates/world_row.template", &world_row_template);
    read_template("templates/world_map.template", &world_map_template);

    read_template("templates/level.template", &level_template);
    read_template("templates/level_value.template", &level_value_template);
    read_template("templates/level_map.template", &level_map_template);

    read_template("templates/quests.template", &index_quest_template);
    read_template("templates/quests_quest.template", &quest_template);
    read_template("templates/quests_map.template", &quest_map_template);

    read_template("templates/map_with_quests.template", &map_with_quests_template);
    read_template("templates/map_one_quest.template", &map_one_quest_template);
    read_template("templates/map_no_quest.template", &map_no_quest_template);

    if (map_limit != -1)
        snprintf(max, sizeof(max), "%d", map_limit);
    else
        strcpy(max, "(none)");
    printf("Crossfire map browser generator\n");
    printf("-------------------------------\n\n");
    printf("Parameters:\n");
    printf("  path to write files:                 %s\n", root);
    printf("  maximum number of maps to process:   %s\n", max);
    printf("  will generate map picture:           %s\n", yesno(generate_pics));
    printf("  will always generate map picture:    %s\n", yesno(force_pics));
    printf("  picture output format:               %s\n", output_extensions[output_format]);
    if (output_format == OF_JPG)
        printf("  JPEG quality:                        %d\n", jpeg_quality);
    printf("  will generate map index:             %s\n", yesno(generate_index));
    printf("  show map being processed:            %s\n", yesno(show_maps));
    printf("  generate raw maps:                   %s\n", yesno(rawmaps));
    printf("  warn of exit without path:           %s\n", yesno(warn_no_path));
    printf("  list unused maps:                    %s\n", yesno(list_unused_maps));
    printf("  generate world map:                  %s\n", yesno(world_map));
    printf("  generate exit map:                   %s\n", yesno(world_exit_info));
    printf("  generate regions link file:          %s\n", yesno(do_regions_link));
    printf("  tileset:                             %s\n", facesets[tileset].fullname);
    printf("\n");

    if (list_unused_maps) {
        printf("listing all maps...");
        find_maps("");
        printf("done, %d maps found.\n", found_maps_count);
        qsort(found_maps, found_maps_count, sizeof(char *), sortbyname);
    }

    /* exit/blocking information. */
    infomap = gdImageCreateTrueColor(30*50, 30*50);
    color_unlinked_exit = gdImageColorResolve(infomap, 255, 0, 0);
    color_linked_exit = gdImageColorResolve(infomap, 255, 255, 255);
    color_road = gdImageColorResolve(infomap, 0, 255, 0);
    color_blocking = gdImageColorResolve(infomap, 0, 0, 255);
    color_slowing = gdImageColorResolve(infomap, 0, 0, 127);
    elevation_info = calloc(50*30, sizeof(int *));
    for (i = 0; i < 50*30; i++)
        elevation_info[i] = calloc(50*30, sizeof(int));
    elevation_min = 0;
    elevation_max = 0;

    printf("browsing maps...\n");

    get_map_info(first_map_path);

    while (current_map < maps_list.count) {
        process_map(maps_list.maps[current_map++]);
        if (current_map%100 == 0) {
            printf(" %d maps processed, %d map pictures created, %d map pictures were uptodate. %d faces used.\n", current_map, created_pics, cached_pics, pics_allocated);
        }
        if ((map_limit != -1) && (current_map == map_limit)) {
            printf(" --- map limit reached, stopping ---\n");
            break;
        }
    }

    printf(" finished map parsing, %d maps processed, %d map pictures created, %d map pictures were uptodate. Total %d faces used.\n", current_map, created_pics, cached_pics, pics_allocated);

    if (list_unused_maps)
        dump_unused_maps();

    fix_exits_to_tiled_maps();
    fix_map_names();
    fix_tiled_map();
    fix_tiled_map_monsters();

    write_all_maps();
    write_maps_index();
    write_maps_by_level();
    write_tiled_maps();

    write_all_regions();
    write_region_index();

    write_world_map();
    write_world_info();

    write_regions_link();
    write_slaying_info();

    write_quests_page();

    write_equipment_index();
    write_race_index();
    write_npc_list();
    write_readable_list();

    return 0;
}

void do_auto_apply(mapstruct *m) {
    int x, y;

    if (m == NULL)
        return;

    for (x = 0; x < MAP_WIDTH(m); x++)
        for (y = 0; y < MAP_HEIGHT(m); y++)
            FOR_MAP_PREPARE(m, x, y, tmp) {
                if (tmp->inv) {
                    FOR_INV_PREPARE(tmp, invtmp) {
                        if (QUERY_FLAG(invtmp, FLAG_AUTO_APPLY))
                            apply_auto(invtmp);
                        else if (invtmp->type == TREASURE && HAS_RANDOM_ITEMS(invtmp)) {
                            while ((invtmp->stats.hp--) > 0)
                                create_treasure(invtmp->randomitems, invtmp, 0, m->difficulty, 0);
                            invtmp->randomitems = NULL;
                        } else if (invtmp
                        && invtmp->arch
                        && invtmp->type != TREASURE
                        && invtmp->type != SPELL
                        && invtmp->type != CLASS
                        && HAS_RANDOM_ITEMS(invtmp)) {
                            create_treasure(invtmp->randomitems, invtmp, 0, m->difficulty, 0);
                            /* Need to clear this so that we never try to create
                             * treasure again for this object
                             */
                            invtmp->randomitems = NULL;
                        }
                    } FOR_INV_FINISH();
                    /* This is really temporary - the code at the bottom will
                     * also set randomitems to null.  The problem is there are bunches
                     * of maps/players already out there with items that have spells
                     * which haven't had the randomitems set to null yet.
                     * MSW 2004-05-13
                     *
                     * And if it's a spellbook, it's better to set randomitems to NULL too,
                     * else you get two spells in the book ^_-
                     * Ryo 2004-08-16
                     */
                    if (tmp->type == WAND
                    || tmp->type == ROD
                    || tmp->type == SCROLL
                    || tmp->type == FIREWALL
                    || tmp->type == POTION
                    || tmp->type == ALTAR
                    || tmp->type == SPELLBOOK)
                        tmp->randomitems = NULL;
                }

                if (QUERY_FLAG(tmp, FLAG_AUTO_APPLY))
                    apply_auto(tmp);
                else if ((tmp->type == TREASURE || (tmp->type == CONTAINER)) && HAS_RANDOM_ITEMS(tmp)) {
                    while ((tmp->stats.hp--) > 0)
                        create_treasure(tmp->randomitems, tmp, 0, m->difficulty, 0);
                    tmp->randomitems = NULL;
                } else if (tmp->type == TIMED_GATE) {
                    object *head = HEAD(tmp);

                    if (QUERY_FLAG(head, FLAG_IS_LINKED)) {
                        tmp->speed = 0;
                        object_update_speed(tmp);
                    }
                    /* This function can be called everytime a map is loaded, even when
                     * swapping back in.  As such, we don't want to create the treasure
                     * over and ove again, so after we generate the treasure, blank out
                     * randomitems so if it is swapped in again, it won't make anything.
                     * This is a problem for the above objects, because they have counters
                     * which say how many times to make the treasure.
                     */
                } else if (tmp
                && tmp->arch
                && tmp->type != PLAYER
                && tmp->type != TREASURE
                && tmp->type != SPELL
                && tmp->type != PLAYER_CHANGER
                && tmp->type != CLASS
                && HAS_RANDOM_ITEMS(tmp)) {
                    create_treasure(tmp->randomitems, tmp, 0, m->difficulty, 0);
                    tmp->randomitems = NULL;
                }
            } FOR_MAP_FINISH();

    for (x = 0; x < MAP_WIDTH(m); x++)
        for (y = 0; y < MAP_HEIGHT(m); y++)
            FOR_MAP_PREPARE(m, x, y, tmp) {
                if (tmp->above
                && (tmp->type == TRIGGER_BUTTON || tmp->type == TRIGGER_PEDESTAL))
                    check_trigger(tmp, tmp->above);
            } FOR_MAP_FINISH();
}

#ifndef DOXYGEN_SHOULD_SKIP_THIS

/**
 * Dummy functions to link the library.
 */

void draw_ext_info(int flags, int pri, const object *pl, uint8 type, uint8 subtype, const char *txt) {
    fprintf(logfile, "%s\n", txt);
}

void draw_ext_info_format(int flags, int pri, const object *pl, uint8 type, uint8 subtype, const char *format, ...) {
    va_list ap;

    va_start(ap, format);
    vfprintf(logfile, format, ap);
    va_end(ap);
}

void ext_info_map(int color, const mapstruct *map, uint8 type, uint8 subtype, const char *str1) {
    fprintf(logfile, "ext_info_map: %s\n", str1);
}

void move_firewall(object *ob) {
}

void emergency_save(int x) {
}

void clean_tmp_files(void) {
}

void esrv_send_item(object *ob, object *obx) {
}

void dragon_ability_gain(object *ob, int x, int y) {
}

void set_darkness_map(mapstruct *m) {
}

object *find_skill_by_number(object *who, int skillno) {
    return NULL;
}

void esrv_del_item(player *pl, object *ob) {
}

void esrv_update_item(int flags, object *pl, object *op) {
}

void esrv_update_spells(player *pl) {
}

void rod_adjust(object *rod) {
}

int execute_event(object *op, int eventcode, object *activator, object *third, const char *message, int fix) {
    return 0;
}

int execute_global_event(int eventcode, ...) {
    return 0;
}
/*
 * This a modified version of apply_auto: BOOK are not generated, so they don't pollute
 * the readable list.
 */
int apply_auto(object *op) {
    object *tmp = NULL;
    int i;

    switch (op->type) {
    case SHOP_FLOOR:
        if (!HAS_RANDOM_ITEMS(op))
            return 0;
        do {
            i = 10; /* let's give it 10 tries */
            while ((tmp = generate_treasure(op->randomitems, op->stats.exp ? (int)op->stats.exp : MAX(op->map->difficulty, 5))) == NULL && --i)
                ;
            if (tmp == NULL)
                return 0;
            if (QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED) || tmp->type == BOOK) {
                object_free_drop_inventory(tmp);
                tmp = NULL;
            }
        } while (!tmp);
        SET_FLAG(tmp, FLAG_UNPAID);
        object_insert_in_map_at(tmp, op->map, NULL, 0, op->x, op->y);
        CLEAR_FLAG(op, FLAG_AUTO_APPLY);
        tmp = identify(tmp);
        break;

    case TREASURE:
        if (QUERY_FLAG(op, FLAG_IS_A_TEMPLATE))
            return 0;

        while ((op->stats.hp--) > 0)
            create_treasure(op->randomitems, op, 0, op->stats.exp ? (int)op->stats.exp : op->map == NULL ? 14 : op->map->difficulty, 0);

        /* If we generated an object and put it in this object inventory,
         * move it to the parent object as the current object is about
         * to disappear.  An example of this item is the random_ *stuff
         * that is put inside other objects.
         */
        FOR_INV_PREPARE(op, tmp) {
            object_remove(tmp);
            if (op->env && tmp->type != BOOK)
                object_insert_in_ob(tmp, op->env);
            else
                object_free_drop_inventory(tmp);
            }
        FOR_INV_FINISH();
        object_remove(op);
        object_free_drop_inventory(op);
        break;
    }
    return tmp ? 1 : 0;
}

void apply_auto_fix(mapstruct *m) {
}

#endif /* dummy DOXYGEN_SHOULD_SKIP_THIS */
