server-1.12/utils/mapper.c

4012 lines
129 KiB
C

/*
* 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;
extern int nrofpixmaps; /* Found in common/image.c */
/** 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 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)
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))
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;
x = item->x;
y = item->y;
name = item->name;
namepl = item->name_pl;
item->x = item->arch->clone.x;
item->y = item->arch->clone.y;
item->name = item->arch->clone.name;
item->name_pl = item->arch->clone.name_pl;
get_ob_diff(bf, item, &item->arch->clone);
add->diff = stringbuffer_finish(bf);
item->x = x;
item->y = y;
item->name = name;
item->name_pl = namepl;
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, 0);
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) {
object *inv;
if (is_special_equipment(item))
add_one_item(item, map);
for (inv = item->inv; inv; inv = inv->below) {
check_equipment(inv, map);
}
}
/**
* 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 *item, *test;
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++) {
item = GET_MAP_OB(map, tx, ty);
while (item) {
test = item->head ? item->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);
if (item->elevation) {
elevation_min = MIN(elevation_min, item->elevation);
elevation_max = MAX(elevation_max, item->elevation);
elevation_info[x*50+tx][y*50+ty] = item->elevation;
}
item = item->above;
}
}
}
}
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, 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;
}
/**
* Link the specified NPC to the specified map.
* @param map
* map the NPC is in.
* @param npc
* NPC to link. Must have a name and message.
*/
static void add_npc_to_map(struct_map_info *map, const object *npc) {
if (map->npcs.count == map->npcs.allocated) {
map->npcs.allocated += 50;
map->npcs.npc = realloc(map->npcs.npc, map->npcs.allocated*sizeof(struct_npc_info *));
}
map->npcs.npc[map->npcs.count] = create_npc_info(npc);
map->npcs.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++;
}
/**
* Replaces a map in a map list. Will emit a warning if map can't be found.
*
* @param find
* map to replace.
* @param replace_by
* replacement map.
* @param list
* where to search.
*/
static void replace_map(struct_map_info *find, struct_map_info *replace_by, struct_map_list *list) {
int map;
for (map = 0; map < list->count; map++) {
if (list->maps[map] == find) {
list->maps[map] = replace_by;
return;
}
}
printf("replace_map: couldn't find map %s.\n", find->path);
}
/**
* 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);
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) {
object *inv;
for (inv = item->inv; inv; inv = inv->below) {
if (is_slaying(inv))
add_slaying(map, inv);
check_slaying_inventory(map, inv);
}
}
/**
* 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;
object *item;
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 (item = GET_MAP_OB(m, x, y); item; item = item->above) {
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, 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) {
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);
}
}
}
}
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, 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, 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, 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, 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, regions[region]->reg->name, color);
gdImageString(pic, font, x, y, 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, 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, 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, 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, "</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, "</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, 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, without);
return;
}
qsort(info->maps[item].maps, info->maps[item].count, sizeof(const char *), sort_mapname);
fprintf(file, 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");
}
/**
* 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 = add_string("unlinked");
dummy->longname = add_string("This dummy region contains all maps without a region set.");
dummy->longname = add_string("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();
return 0;
}
void do_auto_apply(mapstruct *m) {
object *tmp, *above = NULL;
int x, y;
if (m == NULL)
return;
for (x = 0; x < MAP_WIDTH(m); x++)
for (y = 0; y < MAP_HEIGHT(m); y++)
for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = above) {
above = tmp->above;
if (tmp->inv) {
object *invtmp, *invnext;
for (invtmp = tmp->inv; invtmp != NULL; invtmp = invnext) {
invnext = invtmp->below;
if (QUERY_FLAG(invtmp, FLAG_AUTO_APPLY))
auto_apply(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;
}
}
/* 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 == HORN
|| tmp->type == FIREWALL
|| tmp->type == POTION
|| tmp->type == ALTAR
|| tmp->type == SPELLBOOK)
tmp->randomitems = NULL;
}
if (QUERY_FLAG(tmp, FLAG_AUTO_APPLY))
auto_apply(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 = tmp->head != NULL ? tmp->head : tmp;
if (QUERY_FLAG(head, FLAG_IS_LINKED)) {
tmp->speed = 0;
update_ob_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, GT_APPLY, m->difficulty, 0);
tmp->randomitems = NULL;
}
}
for (x = 0; x < MAP_WIDTH(m); x++)
for (y = 0; y < MAP_HEIGHT(m); y++)
for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above)
if (tmp->above
&& (tmp->type == TRIGGER_BUTTON || tmp->type == TRIGGER_PEDESTAL))
check_trigger(tmp, tmp->above);
}
#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, const char *txt2) {
fprintf(logfile, "%s\n", txt);
}
void draw_ext_info_format(int flags, int pri, const object *pl, uint8 type, uint8 subtype, const char *new_format, const char *old_format, ...) {
va_list ap;
va_start(ap, old_format);
vfprintf(logfile, old_format, ap);
va_end(ap);
}
void ext_info_map(int color, const mapstruct *map, uint8 type, uint8 subtype, const char *str1, const char *str2) {
fprintf(logfile, "ext_info_map: %s\n", str2);
}
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, int tag) {
}
void esrv_update_item(int flags, object *pl, object *op) {
}
void esrv_update_spells(player *pl) {
}
void monster_check_apply(object *ob, object *obt) {
}
void trap_adjust(object *ob, int x) {
}
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;
}
int auto_apply(object *op) {
object *tmp = NULL, *tmp2;
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)) {
free_object(tmp);
tmp = NULL;
}
} while (!tmp);
tmp->x = op->x;
tmp->y = op->y;
SET_FLAG(tmp, FLAG_UNPAID);
insert_ob_in_map(tmp, op->map, NULL, 0);
CLEAR_FLAG(op, FLAG_AUTO_APPLY);
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, op->map ? GT_ENVIRONMENT : 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 (tmp = op->inv; tmp; tmp = tmp2) {
tmp2 = tmp->below;
remove_ob(tmp);
if (op->env)
insert_ob_in_ob(tmp, op->env);
else
free_object(tmp);
}
remove_ob(op);
free_object(op);
break;
}
return tmp ? 1 : 0;
}
void fix_auto_apply(mapstruct *m) {
}
#endif /* dummy DOXYGEN_SHOULD_SKIP_THIS */