User Tools

Site Tools


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:
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>

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
      *(const char* *)a

      , 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 :)


 * 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 (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
  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
#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);
    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);
        return 1;
    if (fread(*buffer, info.st_size, 1, file) != 1) {
        printf("Couldn't read template %s!\n", name);
        return 1;
    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) {
    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]);
    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))
            if (vars[var] == 0)
                printf("Wrong tag: %s\n", sharp);
                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){
 * 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]);
 * 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++ ){
            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? */
        /* Initialize templates */
    if (read_template("templates/wiki/monster_page_head", &monster_page_head))
    if (read_template("templates/wiki/monster_page_foot", &monster_page_foot))
    if (read_template("templates/wiki/monster_entry", &monster_entry))
    if (read_template("templates/wiki/monster_canuse_row",
    if (read_template("templates/wiki/monster_protected_row",
    if (read_template("templates/wiki/monster_vulnerable_row",
    if (read_template("templates/wiki/monster_special_row",
    if (read_template("templates/wiki/monster_attack_row",
    if (read_template("templates/wiki/monster_lore_row",
    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");
        /* 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) &&
            strcpy(archnames[archnum++], at->;
    qsort(archnames, archnum, MAX_SIZE, sortbyname);
    for(i=0; i<archnum; i++) {
            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 */
                    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");
                snprintf(wikifile, sizeof(wikifile),
                         "%s/%c", wikidir, letter);
                fp = fopen(wikifile, "w");            
                if(! fp){
                    fprintf(stderr, "Unable to write to wiki file!\n");
                char letterindex[256] = "";
                char letterindexnext[7];
                char 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;
                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;
            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;
            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->;
            val[keycount++] = buf[keycount];
            key[keycount]   = "NAME";
            val[keycount++] = archnames[i];
            key[keycount]   = "RACE";
                val[keycount++] = at->clone.race;
            } else {
                val[keycount++] = NA;
                key[keycount]   = "FACE";
                sprintf(buf[keycount], "{{}}",
                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));
        } else {
            LOG(llevError, "Archetype %s not found!!!\n", archnames[i]);
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 */
                    return 0;
                if(QUERY_FLAG(tmp, FLAG_CURSED) || QUERY_FLAG(tmp, FLAG_DAMNED))
                    tmp = NULL;
            } while(!tmp);
        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);
    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(tmp=get_map_ob(m,x,y);tmp!=NULL;tmp=above) {
                else if(tmp->type==TREASURE) {
                    if (HAS_RANDOM_ITEMS(tmp))
                        while ((tmp->stats.hp--)>0)
                            create_treasure(tmp->randomitems, tmp, 0,
                if(tmp && tmp->arch && tmp->type!=PLAYER && tmp->type!=TREASURE &&
                    if(tmp->type==CONTAINER) {
                        if (HAS_RANDOM_ITEMS(tmp))
                            while ((tmp->stats.hp--)>0)
                                create_treasure(tmp->randomitems, tmp, 0,
                    else if (HAS_RANDOM_ITEMS(tmp))
                        create_treasure(tmp->randomitems, tmp, GT_APPLY,
                if (tmp->above
                    && (tmp->type == TRIGGER_BUTTON || tmp->type == TRIGGER_PEDESTAL))
 * 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);
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;
user/mhoram/code/bwp.txt · Last modified: 2007/01/19 12:24 (external edit)