/* * Crossfire map browser generator. * * Author: Nicolas Weeger , (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 \#TAG#. 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: *
gcc mapper.c -I../include ../common/libcross.a -o mapper -lm -lgd
* * @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 #include /* For strcasecmp(). */ #include #include #include #include #include #include #include #include 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 \#VARIABLE# 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, ®ions[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, ®->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, from 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, ®ions[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, "Item list

Special items found in maps

\n"); fprintf(out, "\n"); for (item = 0; item < equipment_count; item++) { fprintf(out, "\n", special_equipment[item]->power, special_equipment[item]->calc_power, special_equipment[item]->diff); } fprintf(out, "\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, "Monster list

Monsters found in maps

\n"); fprintf(out, "
NameMap(s)Item powerCalc item powerDescription
%s
    ", special_equipment[item]->name); for (map = 0; map < special_equipment[item]->origin.count; map++) fprintf(out, "
  • %s
  • \n", special_equipment[item]->origin.maps[map]->path); fprintf(out, "
%d%d
%s
\n"); for (item = 0; item < races.count; item++) { fprintf(out, "\n"); } fprintf(out, "\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, "%s (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, "
    \n"); for (map = 0; map < info->maps[item].count; map++) { fprintf(file, "\t
  • "); write_slaying_map_name(file, info->maps[item].maps[map]); fprintf(file, "
  • \n"); } fprintf(file, "
\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, "\n\nSlaying information\n\n\n"); fprintf(file, "

This is a list of various slaying fields on keys, containers, doors, detectors.

"); for (lock = 0; lock < slaying_count; lock++) { info = slaying_info[lock]; fprintf(file, "

%s

\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.
\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.
\n"); } fprintf(file, "\n\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, "\n\nNPCs who have a special message\n\n\n"); fprintf(file, "

This is a list of NPCs having a special message.

"); fprintf(file, "
    \n"); for (map = 0; map < maps_list.count; map++) { if (maps_list.maps[map]->npcs.count == 0) continue; fprintf(file, "
  • %s
  • \n
      ", maps_list.maps[map]->path); for (npc = 0; npc < maps_list.maps[map]->npcs.count; npc++) { fprintf(file, "
    • %s (%d,%d):
      %s
    • \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, "
    \n\n"); } fprintf(file, "
\n"); fprintf(file, "\n\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= destination path. Default 'html'.\n"); printf(" -limit= 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= 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= 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", ®ion_template); read_template("templates/region_letter.template", ®ion_letter_template); read_template("templates/region_map.template", ®ion_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 */
NameCountMap(s)
%s%dFound on %d maps:
    ", 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, "
  • %s
  • \n", races.races[item]->origin.maps[map]->path); fprintf(out, "