/* * bwp - build wiki pages * * This program will sort out all monster archetypes and print wiki pages * for them, named 'a' through 'z'. It uses some *_template subroutines taken * from Ryo's mapper.c. It should compile if installed in server/trunk/utils. * Please direct all suggestions or corrections to aaron@baugher.biz (or * Mhoram on #crossfire). * * Compile command: gcc -g -O0 bwp.c -I../include ../common/libcross.a ../socket/libsocket.a -o bwp -lz -lcrypt -lm */ /* * CrossFire, A Multiplayer game for X-windows * * Copyright (C) 2002-2006 Mark Wedel & Crossfire Development Team * Copyright (C) 1992 Frank Tore Johansen * * 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. * * The authors can be reached via e-mail at crossfire-devel@real-time.com */ #define LO_NEWFILE 2 #define MAX_SIZE 64 #define NA "n/a" #include #include #include #include char *monster_page_head; /* Head of wiki page of monsters */ char *monster_page_foot; /* Foot of wiki page of monsters */ char *monster_entry; /* A single monster entry */ char *monster_canuse_row; /* Can_use table row */ char *monster_protected_row; /* Protected table row */ char *monster_vulnerable_row; /* Vulnerable table row */ char *monster_special_row; /* Special table row */ char *monster_attack_row; /* Attack types table row */ char *monster_lore_row; /* Lore table row */ typedef struct string_array { sint16 count; char **item; } String_Array; /** * This is a list of pointers that correspond to the FLAG_.. values. * This is a simple 1:1 mapping - if FLAG_FRIENDLY is 15, then * the 15'th element of this array should match that name. * If an entry is NULL, that is a flag not to loaded/saved. * * Copied from common/loader.c; perhaps should be defined elsewhere? * */ const char *const flag_names[NUM_FLAGS+1] = { "alive", "wiz", NULL, NULL, "was_wiz", "applied", "unpaid", "can_use_shield", "no_pick", "client_anim_sync", "client_anim_random", /* 10 */ "is_animated", NULL /* slow_move */, NULL /* flying */, "monster", "friendly", "generator", "is_thrown", "auto_apply", "treasure", "player sold", /* 20 */ "see_invisible", "can_roll", "overlay_floor", "is_turnable", NULL /* walk_off */, NULL /* fly_on */, NULL /*fly_off*/, "is_used_up", "identified", "reflecting", /* 30 */ "changing", "splitting", "hitback", "startequip", "blocksview", "undead", "scared", "unaggressive", "reflect_missile", "reflect_spell", /* 40 */ "no_magic", "no_fix_player", "is_lightable", "tear_down", "run_away", NULL /*pass_thru */, NULL /*can_pass_thru*/, "pick_up", "unique", "no_drop", /* 50 */ NULL /* wizcast*/, "can_cast_spell", "can_use_scroll", "can_use_range", "can_use_bow", "can_use_armour", "can_use_weapon", "can_use_ring", "has_ready_range", "has_ready_bow", /* 60 */ "xrays", NULL, "is_floor", "lifesave", "no_strength", "sleep", "stand_still", "random_movement", "only_attack", "confused", /* 70 */ "stealth", NULL, NULL, "cursed", "damned", "see_anywhere", "known_magical", "known_cursed", "can_use_skill", "been_applied", /* 80 */ "has_ready_scroll", "can_use_rod", NULL, "can_use_horn", "make_invisible", "inv_locked", "is_wooded", "is_hilly", "has_ready_skill", "has_ready_weapon", /* 90 */ "no_skill_ident", "is_blind", "can_see_in_dark", "is_cauldron", "is_dust", "no_steal", "one_hit", NULL, "berserk", "neutral", /* 100 */ "no_attack", "no_damage", NULL, NULL, "activate_on_push", "activate_on_release", "is_water", "use_content_on_gen", NULL, "is_buildable", /* 110 */ NULL, "blessed", "known_blessed" }; /** * 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. * @return * 1 if error, 0 else. */ static int read_template(const char *name, char **buffer) { FILE *file; size_t size; struct stat info; if (stat(name, &info)) { printf("Couldn't stat template %s!\n", name); return 1; } (*buffer) = calloc(1, info.st_size+1); if (!(*buffer)) { printf("Template %s calloc failed!\n", name); return 1; } if (info.st_size == 0) { (*buffer)[0] = '\0'; return 0; } file = fopen(name, "rb"); if (!file) { printf("Couldn't open template %s!\n", name); free(*buffer); return 1; } if (fread(*buffer, info.st_size, 1, file) != 1) { printf("Couldn't read template %s!\n", name); free(*buffer); fclose(file); return 1; } fclose(file); return 0; } /** * 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] != 0 && strncmp(vars[var], sharp+1, end-sharp-1)) var++; if (vars[var] == 0) 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; } /**** Mhoram's code starts here *****/ /** * Frees memory if the pointer was ever given a string. * * There's probably a cleaner way to do this, but this frees the memory * given to a pointer if the pointer points to a string longer than zero * length. It's to get rid of "in free(): warning: junk pointer, too high * to make sense" errors. * * @param p * Pointer to free memory from * */ static void free_if_used(char *p) { if (p && strlen(p) > 0) { free(p); } } /** * Sort values alphabetically * * Used by qsort to sort values alphabetically without regard to case * * @param a * First value * * @param b * Second value * */ static int sortbyname(const void *a, const void *b) { return (strcasecmp(*(const char **)a, *(const char **)b)); } /** * Sort archetypes alphabetically * * Used by qsort to sort archetypes alphabetically * without regard to case * * @param a * First value * * @param b * Second value * */ static int sort_archetypes(const void *a, const void *b) { archetype *aa; archetype *bb; aa = *(archetype **)a; bb = *(archetype **)b; return (strcasecmp(aa->clone.name, bb->clone.name)); } /** * Add a string to a String_Array struct * * Adds the new string to the struct's 'item' array, and updates the 'count'value. * * @param array * The array to be appended * * @param string * The new string to append * */ void push(String_Array *array, const char *string) { sint16 i = array->count; array->item[i] = strdup_local(string); array->count++; } /** * Frees the item's data of specified String_Array. * * Will free array->item and its fields. * * @param array * element we want to clean. */ void free_data(String_Array *array) { int item; for (item = 0; item < array->count; item++) free(array->item[item]); free(array->item); array->item = NULL; } /** * Joins strings with a comma and space. * * Takes an array of strings and joins them togther with a comma and a space * between each of them. * * @param array * Pointer to struct of type String_Array, containing strings to join * */ const char *join_with_comma(String_Array *array) { char *newtext; int i; newtext = calloc(1, 1); qsort(array->item, array->count, sizeof(char *), sortbyname); for (i = 0; i < array->count; i++) { if (i) { newtext = realloc(newtext, strlen(newtext)+strlen(", ")+1); newtext = strncat(newtext, ", ", 2); } newtext = realloc(newtext, strlen(newtext)+strlen(array->item[i])+1); newtext = strncat(newtext, array->item[i], strlen(array->item[i])); } return newtext; } int main(int argc, char *argv[]) { archetype *at; int archnum = 0; archetype *monster[4000]; int i; char letter; char last_letter; char *wiki_page = NULL; char *monster_entries = NULL; FILE *fp = NULL; FILE *image_list; char image_list_path[128]; char wikifile[128]; char *template; const char *wikidir = "/tmp"; /* Should change this to come from command line? */ init_globals(); init_library(); init_archetypes(); init_artifacts(); init_formulae(); init_readable(); init_gods(); /* Initialize templates */ if (read_template("templates/wiki/monster_page_head", &monster_page_head)) return; if (read_template("templates/wiki/monster_page_foot", &monster_page_foot)) return; if (read_template("templates/wiki/monster_entry", &monster_entry)) return; if (read_template("templates/wiki/monster_canuse_row", &monster_canuse_row)) return; if (read_template("templates/wiki/monster_protected_row", &monster_protected_row)) return; if (read_template("templates/wiki/monster_vulnerable_row", &monster_vulnerable_row)) return; if (read_template("templates/wiki/monster_special_row", &monster_special_row)) return; if (read_template("templates/wiki/monster_attack_row", &monster_attack_row)) return; if (read_template("templates/wiki/monster_lore_row", &monster_lore_row)) return; sprintf(image_list_path, "%s/image_list", wikidir); image_list = fopen(image_list_path, "w"); if (!image_list) { LOG(llevError, "Unable to open image list file!\n"); exit(1); } /* Pick out the monster archetypes and sort them into an array */ for (at = first_archetype; at != NULL; at = at->next) { if (QUERY_FLAG(&at->clone, FLAG_MONSTER) && QUERY_FLAG(&at->clone, FLAG_ALIVE)) { monster[archnum++] = at; } } printf("Sorting..."); /* Calling qsort on monster, which is archetype** */ qsort(&monster[0], archnum, sizeof(archetype *), sort_archetypes); printf("done. %i items found\n", archnum); last_letter = '\0'; for (i = 0; i < archnum; i++) { at = monster[i]; if (at) { const char *key[16] = { NULL, }; const char *val[16] = { NULL, }; char buf[16][MAX_BUF]; int keycount = 0; int res; letter = tolower(at->clone.name[0]); LOG(llevInfo, "Doing archetype %s\n", at->name); if (letter != last_letter) { /* New letter, new file */ if (fp) { keycount = 0; key[keycount] = NULL; template = do_template(monster_page_foot, key, val); res = fprintf(fp, "%s", template); free(template); template = NULL; if (res < 0) { LOG(llevError, "Unable to write to file!\n"); } fclose(fp); } snprintf(wikifile, sizeof(wikifile), "%s/%c", wikidir, letter); fp = fopen(wikifile, "w"); if (!fp) { fprintf(stderr, "Unable to write to wiki file!\n"); exit(1); } char letterindex[256] = ""; char letterindexnext[7]; char li; letterindexnext[0] = '\0'; for (li = 'a'; li <= 'z'; li++) { if (li == letter) { sprintf(letterindexnext, "%c ", toupper(li)); } else { sprintf(letterindexnext, "[[%c]] ", toupper(li)); } strncat(letterindex, letterindexnext, 256); } keycount = 0; key[keycount] = "LETTER"; sprintf(buf[keycount], "%c", toupper(letter)); val[keycount++] = buf[keycount]; key[keycount] = "LETTERINDEX"; val[keycount++] = letterindex; key[keycount] = NULL; template = do_template(monster_page_head, key, val); res = fprintf(fp, template); free(template); if (res < 0) { LOG(llevError, "Unable to write to file!"); } last_letter = letter; } /* add a monster entry */ char *canuse_row; char *protected_row; char *vulnerable_row; char *special_row; char *attack_row; char *lore_row; const int CANUSE_LENGTH = 16; String_Array canuse; String_Array resist; String_Array vulner; String_Array attack; String_Array special; /* Some flags that seemed useful; may need to add to this list. * *special_names[] is used because some of the names in * define.h are a bit awkward. Last one is negative to mark end. */ const sint8 special_flags[] = { 21, 93, 52, 38, 13, 32, 61, -1 }; const char *special_names[] = { "see invisible", "see in dark", "spellcaster", "unaggressive", "flying", "splitting", "x-ray vision" }; int j; canuse.item = calloc(1, sizeof(const char *)*(CANUSE_LENGTH+1)); resist.item = calloc(1, sizeof(const char *)*(NROFATTACKS+1)); vulner.item = calloc(1, sizeof(const char *)*(NROFATTACKS+1)); attack.item = calloc(1, sizeof(const char *)*(NROFATTACKS+1)); special.item = calloc(1, sizeof(const char *)*(NROFATTACKS+1)); /* Do lore row */ if (at->clone.lore) { key[keycount] = "LORE"; key[keycount+1] = NULL; val[keycount] = at->clone.lore; keycount++; lore_row = do_template(monster_lore_row, key, val); } else lore_row = strdup(""); /* Do canuse row */ canuse.count = 0; keycount = 0; for (j = 1; j <= NUM_FLAGS; j++) { if (QUERY_FLAG(&at->clone, j) && flag_names[j] && !strncmp(flag_names[j], "can_use_", 8)) { push(&canuse, flag_names[j]+8); } } if (canuse.count) { key[keycount] = "CANUSE"; key[keycount+1] = NULL; val[keycount] = join_with_comma(&canuse); canuse_row = do_template(monster_canuse_row, key, val); free(val[keycount]); } else canuse_row = strdup(""); /* Do protected/vulnerable rows */ resist.count = 0; vulner.count = 0; for (j = 0; j <= NROFATTACKS; j++) { if (at->clone.resist[j] && attacktype_desc[j]) { char rowtext[32]; if (at->clone.resist[j] < 0) { sprintf(rowtext, "%s %i", attacktype_desc[j], at->clone.resist[j]); push(&vulner, rowtext); } else { sprintf(rowtext, "%s +%i", attacktype_desc[j], at->clone.resist[j]); push(&resist, rowtext); } } } keycount = 0; if (resist.count) { key[keycount] = "PROTECTED"; key[keycount+1] = NULL; val[keycount] = join_with_comma(&resist); protected_row = do_template(monster_protected_row, key, val); free(val[keycount]); } else protected_row = strdup(""); keycount = 0; if (vulner.count) { key[keycount] = "VULNERABLE"; key[keycount+1] = NULL; val[keycount] = join_with_comma(&vulner); vulnerable_row = do_template(monster_vulnerable_row, key, val); free(val[keycount]); } else vulnerable_row = strdup(""); /* Do attacktype row */ attack.count = 0; keycount = 0; val[keycount] = NULL; for (j = 0; j <= NROFATTACKS; j++) { if (at->clone.attacktype&(1U<= 0; j++) { if (QUERY_FLAG(&at->clone, special_flags[j])) { push(&special, special_names[j]); } } if (special.count) { key[keycount] = "SPECIAL"; key[keycount+1] = NULL; val[keycount] = join_with_comma(&special); special_row = do_template(monster_special_row, key, val); free(val[keycount]); } else special_row = strdup(""); keycount = 0; key[keycount] = "CANUSEROW"; val[keycount++] = canuse_row; key[keycount] = "PROTECTEDROW"; val[keycount++] = protected_row; key[keycount] = "VULNERABLEROW"; val[keycount++] = vulnerable_row; key[keycount] = "SPECIALROW"; val[keycount++] = attack_row; key[keycount] = "ATTACKROW"; val[keycount++] = special_row; key[keycount] = "LOREROW"; val[keycount++] = lore_row; key[keycount] = "XP"; sprintf(buf[keycount], "%li", at->clone.stats.exp); val[keycount++] = buf[keycount]; key[keycount] = "HP"; sprintf(buf[keycount], "%i", at->clone.stats.hp); val[keycount++] = buf[keycount]; key[keycount] = "AC"; sprintf(buf[keycount], "%i", at->clone.stats.ac); val[keycount++] = buf[keycount]; key[keycount] = "NAME"; val[keycount++] = at->clone.name; key[keycount] = "RACE"; if (at->clone.race) { val[keycount++] = at->clone.race; } else { val[keycount++] = NA; } if (at->clone.face->name) { key[keycount] = "FACE"; sprintf(buf[keycount], "{{http://aaron.baugher.biz/images/cf/%s.png}}", at->clone.face->name); val[keycount++] = buf[keycount]; sprintf(buf[keycount], "%s.png\n", at->clone.face->name); fprintf(image_list, buf[keycount]); } /* Plan to add generator face too, when I decide how */ key[keycount] = "GENFACE"; val[keycount++] = ""; key[keycount] = NULL; template = do_template(monster_entry, key, val); fprintf(fp, template); free(template); template = NULL; free_data(&canuse); free_data(&resist); free_data(&vulner); free_data(&attack); free_data(&special); free(canuse_row); free(protected_row); free(vulnerable_row); free(attack_row); free(special_row); free(lore_row); } else { LOG(llevError, "Something is very wrong.\n"); } } fclose(image_list); } void set_map_timeout(void) { /* doesn't need to do anything */ } #include /* some plagarized code from apply.c--I needed just these two functions without all the rest of the junk, so.... */ int auto_apply(object *op) { object *tmp = NULL; int i; switch (op->type) { case SHOP_FLOOR: if (!HAS_RANDOM_ITEMS(op)) return 0; do { i = 10; /* let's give it 10 tries */ while ((tmp = generate_treasure(op->randomitems, op->stats.exp ? op->stats.exp : 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 (HAS_RANDOM_ITEMS(op)) while ((op->stats.hp--) > 0) create_treasure(op->randomitems, op, GT_ENVIRONMENT, op->stats.exp ? op->stats.exp : op->map == NULL ? 14 : op->map->difficulty, 0); remove_ob(op); free_object(op); break; } return tmp ? 1 : 0; } /* fix_auto_apply goes through the entire map (only the first time * when an original map is loaded) and performs special actions for * certain objects (most initialization of chests and creation of * treasures and stuff). Calls auto_apply if appropriate. */ void fix_auto_apply(mapstruct *m) { object *tmp, *above = NULL; int x, y; 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 (QUERY_FLAG(tmp, FLAG_AUTO_APPLY)) auto_apply(tmp); else if (tmp->type == TREASURE) { if (HAS_RANDOM_ITEMS(tmp)) while ((tmp->stats.hp--) > 0) create_treasure(tmp->randomitems, tmp, 0, m->difficulty, 0); } if (tmp && tmp->arch && tmp->type != PLAYER && tmp->type != TREASURE && tmp->randomitems) { if (tmp->type == CONTAINER) { if (HAS_RANDOM_ITEMS(tmp)) while ((tmp->stats.hp--) > 0) create_treasure(tmp->randomitems, tmp, 0, m->difficulty, 0); } else if (HAS_RANDOM_ITEMS(tmp)) create_treasure(tmp->randomitems, tmp, GT_APPLY, m->difficulty, 0); } } 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 /** * Those are dummy functions defined to resolve all symboles. * Added as part of glue cleaning. * Ryo 2005-07-15 **/ 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_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; } #endif /* dummy DOXYGEN_SHOULD_SKIP_THIS */