User Tools

Site Tools


wiki:data:pages:user:mhoram:code:bwp

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
wiki:data:pages:user:mhoram:code:bwp [2024/05/20 16:47]
leaf Remove wiki-data version of bwp page
— (current)
Line 1: Line 1:
-====== Build Wiki Pages ====== 
- 
-This is a program I'm working on to automate the building of pages for the wiki from the monster archetypes (and eventually other categories, perhaps). ​ I'm still working on it, but I thought I'd put it here in case anyone wanted to help me fix a problem I'm having, or critique my code.  (I'm still very much an amateur at writing C.) 
- 
-The main problem I'm having right now is that a couple variables are getting clobbered; I assume by bad memory allocation. ​ It happens at line 502: the variables ''​*key[]''​ and ''​*val[]''​ are fine before that call to ''​join_with_comma(),''​ but after it they are messed up.  (Search for ''​BEFORE''​ and ''​AFTER''​.) ​ Since they aren't involved in that function at all, I assume that something in that function is causing an overflow. ​ There'​s probably a way with gdb or some other tool to see what variable is using a particular memory address, but I don't know what it is.  
- 
-Also, if anyone knows of a good C primer/​tutorial online, please let me know. 
- 
-  * In the sortbyname, a and b will be const char* *, not char*, thus you need to dereference them => ''​strcpy(aa,​(*(const char* *)a));''​ or something like that :) 
-    * I've stared at this for a while, and found some examples that do it, and I think I'm starting to understand it.  I still don't quite see how the arguments can be char* *, when the function defines them as const void *, but I'll get there. 
-      * I changed it to the example you gave, and now it segfaults on that ''​strcpy''​. ;-)  I think I'm on the right track now, though. ​ It makes sense that the problem was in that comparison routine, since I checked the addresses of the variables within ''​join_with_comma'',​ and none of them were even close to the ones that were getting mangled. 
-    * Ok, I've updated the code below to reflect those changes, but I'm still having trouble in ''​sortbyname''​. ​ All the examples I can find online do it like you suggested, or something similar, but when I do that, I get strange results. ​ Here's what gdb says about the first argument: 
- 
-<​code>​ 
-Breakpoint 1, sortbyname (a=0xbfbbffd0,​ b=0xbfbc09d0) at bwp.c:259 
-259         ​return strcasecmp( *(char **)a, *(char **)b ); 
-(gdb) p a 
-$1 = (const void *) 0xbfbbffd0 
-(gdb) p (char **)a 
-$2 = (char **) 0xbfbbffd0 
-(gdb) p (char *)a 
-$3 = 0xbfbbffd0 "​Gnarg"​ 
-(gdb) p *(char *)a 
-$4 = 71 '​G'​ 
-(gdb) p *(char **)a 
-$5 = 0x72616e47 <Error reading address 0x72616e47: Bad address> 
-</​code>​ 
- 
-The way I read that, it looks like what I really want to pass to ''​strcasecmp''​ is a pointer to ''​(char *)a'',​ but that doesn'​t work either. ​ (You can see that attempt commented out.)  I think for today I'm just going to comment out the ''​qsort''​ call so I can finish the rest of the program; in the meantime, any further hints on what I'm doing wrong would be much appreciated. 
- 
-  * You need to do ''​qsort(array->​item,​ array->​count,​ sizeof(char*),​ sortbyname);'',​ not ''​qsort(array->​item,​ array->​count,​ array->​longest,​ sortbyname);''​. The item size is a char*, not the longest element. ''​array->​item''​ is an array of ''​char*'',​ that is of pointers to memory zones containing ''​char'',​ not an array of items of size ''​array->​longest''​ :) This can certainly explain some weird things. 
-    * I see what you're saying, but I was calling it that way in my ''​qsort''​ of ''​archnames'',​ and that was working ok.  ''​archames''​ is an array of arrays, though, while ''​array->​item''​ is a pointer to an array of pointers, so I ssupose that's the difference. ​ In any case, I think I have everything else working, so I've updated the code below. ​ ''​sortbyname''​ now no longer causes segfaults, but it still isn't sorting correctly either. ​ To be exact, it sorts ''​archnames''​ correctly, but not ''​array->​item''​. ​ When I try <​code>​*(const char* *)a</​code>,​ it segfaults, and when I inspect the variables with gdb, it says that's accessing a bad address. ​ At this point, I'm really stumped on this part, but I'm still looking for other examples. 
- 
-      * can you try with ''​qsort(&​array->​item,​ ...)''​ and the ''​const char * ''?​ Not sure that's the issue, but well... Also a few hints: 
-          * It seems like I've tried every possible combination at this point -- passing ''​array->​item''​ to ''​qsort''​ by itself and with & and * ; casting the arguments inside ''​sortbyname''​ in various ways.  The combination I'm doing now doesn'​t complain about anything and doesn'​t mangle any memory, but it doesn'​t sort it either. 
-            * well, when i get time i'll look at that :) 
- 
-        * do ''​canuse.count = 0;''​ before using ''​push''​. Count isn't initialized,​ so weird effects can happen 
-          * I do, a few lines up. 
-            * didn't see that, my mistake 
-        * ''​free(NULL)''​ is perfectly correct in ANSI, i don't know what your debugger/​compiler is complainign about :) 
-          * Not the compiler, but running the program spit out a bunch of ''"​in free(): warning: junk pointer, too high to make sense"''​ warnings. ​ I assumed that assigning ""​ to a ''​char*''​ would give it a valid pointer; obviously I was wrong. ​ But I didn't want to initialize those ''​_row''​ pointers to ''​NULL''​ either, because I'd be passing them as values to ''​do_template''​ later. ​ Initializing them with ''​strdup_local''​ did the trick. 
-            * if free() says such a warning, it may mean other issues somewhere :) note that once you called ''​do_template''​ you ca, safely ''​free()''​ the values you used - since the function does a strcpy. In the current version, ''​canuse_row''​ and friends should be set to ''​strdup_local(""​)'',​ since you ''​free''​ them (i think you did that, but i'm not sure based on your sentence, so... ;p) 
-              * Yes, that's what I meant. ​ I now initialize them with ''​strdup_lcoal(""​)'',​ and am able to scrap my ''​free_if_used''​ routine. 
- 
-        * instead of ''​calloc(1,​ strlen(string)+1) / strncpy'',​ use ''​strdup(string)''​ in ''​push''​ (that'​s what this is for ^_-) - Crossfire may have a ''​strdup_local''​ somewhere, better use that if it exists 
-          * It does; using that now. 
-        * doing ''​char *newtext = "";''​ is not required, and may be dangerous since you do ''​realloc''​ after (but of course you do ''​calloc'',​ so the initialization won't do anything anyway ;p) 
-          * Good point. ​ I think that was an attempt to fix a problem that went away. 
-        * for duplicated names, check if the monster isn't yet part of the array before inserting it 
-          * Maybe the ideal would be to spit out a warning on duplicate names, but skip the duplicates in the actual output. ​ Those duplicates are actually separate archetypes; often from separate arch files; check ''​monster/​humanoid/​Dragon/​green_dragon?''​ for example. ​ Two identical archetypes except for their faces, so perhaps one should be deleted, or turned into "fiery green dragon"​ or something (so as not to waste the images). 
-            * those specific pisc are for player images - i'd check to make sure there isn't some magic at work before removing them :) 
-              * Absoultely. ​ Looking at another duplicate made me aware of a bug in my code, by the way.  There are three archetypes with the ''​name''​ "​castle guard":​ light_guard,​ medium_guard,​ and guard. ​ Because of the way my code makes an array of the names and then uses ''​find_object_by_archetype_name''​ to select each one for printing, it ends up selecting whichever one comes first in the list three times. ​ Oops.  Following your suggestion about using an array of archt* types to hold them should clear up that confusion. 
- 
- 
-        * it may be better to use ''​archt* archetypes[4000]''​ instead of ''​char archnames[4000][MAX_SIZE];''​. It would let you sort by name, then plural name, then archetype name in case of duplicated monsters, and also enable you to check for head/more 
-          * Nice idea; I should have thought of that.  Of course, I might end up having trouble sorting that too. ;-)  Should be examples of that in the code to copy, though. 
-        * similarly, you may want to avoid inserting archetypes which are ''​more''​ of another one - thus check ''​more != NULL''​ somewhere 
-          * I don't think that's a problem. ​ The ''​first_archetype''​ list built by ''​init_archetypes()''​ seems to only contain whole archetypes, not parts. ​ Unless I'm misunderstanding your meaning; for example? 
-            * hum, you may be right, archetypes probably only contain the HEAD part. 
- 
-        * all in all, seems it'll work fine soon :) 
-          * We'll see.  I won't make the mistake of claiming to be down to one bug again. ;-)  I'll finish making the change to use ''​archt*''​ and some other improvements before I bother updating the code here. 
- 
-        * if you wanted to be really really flexible, you could use templates for flags themselves - instead of concatenating items with a comma, let a template (or a few, in this case) decide how to adjust stuff. This may be overkill, though, of course ^_- 
-          * I thought about that, but I wasn't sure how to do a template where an item could be repeated a variable number of times. ​ I also intend to insert wiki-style line breaks every N characters in long lines of text, like my perl version does, but that was one line of code there. ​ It'll take a bit more here. ;-) 
-            * well, what i did for the mapper was just: for each eg flag generate the template for this flag, and concatenate with previously generated text (thus my ''​cat_template''​ which i use specially for that ^_-). Then you use the resulting value in another template, and so on. 
-              * I understand that part, but I'm not sure how to design a template that allows for a variable number of attacktypes,​ for example, without having the program concat them and plug them in for a single flag.  Maybe I'll come up with something eventually. 
- 
-  * Also, to compare, try ''​strcasecmp''​ which does case-insensitive comparison. 
-    * That would be much easier, wouldn'​t it? :-) 
- 
-  * ''​append_with_comma''​ (not used, but just in case) has a side effect: old will be destroyed if not NULL - the ''​realloc''​ can make the original pointer (which still exists probably) point to a freed memory. 
-    * That function got replaced by join_with_comma and will be deleted anyway, but I should understand this.  Isn't the whole point of realloc that it leaves the data pointed to unchanged? 
-      * ''​realloc''​ will leave the **contents** of the zone unchanged. But if you ask for a bigger memory zone, it will allocate a new block somewhere else, and copy the initial contents. Thus the original pointer can become invalid. 
- 
-  * and some day don't forget to ''​free()''​ allocated memory (for the char* and such) ^_- 
-    * Yes, I figured I'd need to do that, but I'm still learning just what I have to free.  Anything I allocated manually, I assume, but I get a little hazy on it when something was allocated in a subroutine. 
- 
-Thanks for all your help! 
-  * no problem, thanks for the program :) 
- 
- 
- 
-===== Code ===== 
-<code c> 
-/* 
- * 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 -pg -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 <​time.h>​ 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
-#include <​global.h>​ 
- 
-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; 
-    sint16 longest; 
-    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_move",​ "​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){ 
- 
-    const char* aa  = (const char *)a; 
-    const char* bb  = (const char *)b; 
- 
-    return( strcasecmp(aa,​ bb)); 
-} 
- 
-/** 
- * Add a string to a String_Array struct 
- * 
- * Adds the new string to the struct'​s '​item'​ array, and updates the '​count'​ and 
- * '​longest'​ values. ​ (THIS ROUTINE IS NOT RIGHT YET.) 
- * 
- * @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] = calloc(1, strlen(string)+1);​ 
-    strncpy(array->​item[i],​ string, strlen(string));​ 
-    if( strlen(array->​item[i]) > array->​longest ){ 
-        array->​longest = strlen(array->​item[i]);​ 
-    } 
-    array->​count++;​ 
-} 
- 
-/** 
- * 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 String_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; 
-    int archmem=0; 
-    char archnames[4000][MAX_SIZE];​ 
-    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];​ 
-    ​ 
-    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)){ 
-            strcpy(archnames[archnum++],​ at->​clone.name);​ 
-        } 
-    } 
-    printf("​Sorting..."​);​ 
-    qsort(archnames,​ archnum, MAX_SIZE, sortbyname);​ 
-    printf("​done.\n"​);​ 
-    ​ 
-    for(i=0; i<​archnum;​ i++) { 
-        at=find_archetype_by_object_name(archnames[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(archnames[i][0]);​ 
- 
-            LOG(llevInfo,​ "Doing archetype %s\n", archnames[i]);​ 
-            ​ 
-            if(letter != last_letter) {  /* New letter, new file */ 
-                if(fp){ 
-                    keycount = 0; 
-                    key[keycount] = NULL; 
-                    res = fprintf(fp, "​%s",​ do_template(monster_page_foot,​ 
-                                                        key, val)); 
-                    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; 
-                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; 
-                res = fprintf(fp, "​%s",​ 
-                              do_template(monster_page_head,​ key, val)); 
-                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); 
-            } 
- 
-                /* 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); 
-            } 
-                ​ 
-                /* 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); 
-            } 
- 
-            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); 
-            } 
-                ​ 
-                /* Do attacktype row */ 
-            attack.count = 0; 
-            keycount = 0; 
-            val[keycount]=NULL;​ 
-            for( j=0; j<​=NROFATTACKS;​ j++ ) { 
-                if( at->​clone.attacktype & (1U << j)) { 
-                    push(&​attack,​ attacktype_desc[j]);​ 
-                } 
-            } 
-            if( attack.count ){ 
-                key[keycount] = "​ATTACKS";​ 
-                key[keycount+1] = NULL; 
-                val[keycount] = join_with_comma(&​attack);​ 
-                attack_row = do_template(monster_attack_row,​ key, val); 
-            } 
- 
-                /* Do special row */ 
-            special.count = 0; 
-            keycount = 0; 
-            val[keycount]=NULL;​ 
-            for( j=0; special_flags[j] >= 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); 
-            } 
-            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++] = archnames[i];​ 
-            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; 
- 
-            fprintf(fp, "​%s",​ do_template(monster_entry,​ key, val)); 
- 
-            free(canuse.item);​ 
-            free(resist.item);​ 
-            free(vulner.item);​ 
-            free(attack.item);​ 
-            free(special.item);​ 
-            free_if_used(canuse_row);​ 
-            free_if_used(protected_row);​ 
-            free_if_used(vulnerable_row);​ 
-            free_if_used(attack_row);​ 
-            free_if_used(special_row);​ 
-            free_if_used(lore_row);​ 
-        } else { 
-            LOG(llevError,​ "​Archetype %s not found!!!\n",​ archnames[i]);​ 
-        } 
-    } 
-    fclose(image_list);​ 
-} 
- 
- 
-void set_map_timeout(void) {}   /* doesn'​t need to do anything */ 
- 
-#include <​global.h>​ 
- 
- 
-/* 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);​ 
-} 
- 
-/** 
- * 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_teleporter( object* ob){ 
-} 
-  
-void move_firewall( object* ob){ 
-} 
-  
-void move_duplicator( object* ob){ 
-} 
-  
-void move_marker( object* ob){ 
-} 
-  
-void move_creator( 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 weather_effect( const char* c ){ 
-} 
-  
-void set_darkness_map( mapstruct* m){ 
-} 
-  
-void move_apply( object* ob, object* obt, object* obx ){ 
-} 
-  
-object* find_skill_by_number( object* ob, int x ){ 
-    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; 
-} 
-</​code>​