server-1.12/utils/bwp.c

847 lines
27 KiB
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 -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;
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 <code>\#VARIABLE#</code> will be substituted for specified values.
*
* @param template
* template to process.
* @param vars
* variables to replace. Array must be NULL-terminated.
* @param values
* variables to replace by. Must be the same size as vars, no NULL element allowed.
* @return
* filled-in template, that must be free()d be caller. NULL if memory allocation error.
*
* @note
* returned string will be a memory block larger than required, for performance reasons.
*/
static char *do_template(const char *template, const char **vars, const char **values) {
int count = 0;
const char *sharp = template;
int maxlen = 0;
int var = 0;
char *result;
char *current_result;
const char *end;
while ((sharp = strchr(sharp, '#')) != NULL) {
sharp++;
count++;
}
if (!count)
return strdup(template);
if (count%2) {
printf("Malformed template, mismatched #!\n");
return strdup(template);
}
while (vars[var] != NULL) {
if (strlen(values[var]) > maxlen)
maxlen = strlen(values[var]);
var++;
}
result = calloc(1, strlen(template)+maxlen*(count/2)+1);
if (!result)
return NULL;
current_result = result;
sharp = template;
while ((sharp = strchr(sharp, '#')) != NULL) {
end = strchr(sharp+1, '#');
strncpy(current_result, template, sharp-template);
if (end == sharp+1) {
strcat(current_result, "#");
} else {
current_result = current_result+strlen(current_result);
var = 0;
while (vars[var] != 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<<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);
free(val[keycount]);
} else
attack_row = strdup("");
/* 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);
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 <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);
}
#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 */