/* ************************************************************************
* File: db.c Part of CircleMUD *
* Usage: Loading/saving chars, booting/resetting world, internal funcs *
* *
* All rights reserved. See license.doc for complete information. *
* *
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. *
************************************************************************ */
/*
* This file contains the functions to load the world files into memory
* into the stock CircleMUD arrays.
*
* You should update this code to match the code in your MUD if it's been
* changed from stock in any way.
*/
#define __DB_C__
#include <string.h>
#include "sysdep.h"
#include "structs.h"
#include "main.h"
#include "db.h"
#include "utils.h"
#include "interpreter.h"
#include "spells.h"
#include "shop.h"
/*
* Globals for the world data
*/
struct room_data *world = NULL; /* array of rooms */
room_rnum top_of_world = 0; /* ref to top element of world */
struct char_data *mob_proto; /* prototypes for mobs */
struct index_data *mob_index; /* index table for mobile file */
mob_rnum top_of_mobt = 0; /* top of mobile index table */
struct obj_data *obj_proto; /* prototypes for objs */
struct index_data *obj_index; /* index table for object file */
obj_rnum top_of_objt = 0; /* top of object index table */
struct zone_data *zone_table; /* zone table */
zone_rnum top_of_zone_table = 0;/* top element of zone tab */
struct player_special_data dummy_mob; /* dummy spec area for mobs */
int scheck = 0; /* No syntax checking mode here, but keeping this here for code sanity */
const char *unused_spellname = "!UNUSED!"; /* So we can get &unused_spellname */
struct spell_info_type spell_info[TOP_SPELL_DEFINE + 1];
/*
* Functions
*/
void index_boot(int mode);
int count_alias_records(FILE *fl);
int count_hash_records(FILE *fl);
void get_one_line(FILE *fl, char *buf);
void discrete_load(FILE *fl, int mode, char *filename);
void load_zones(FILE *fl, char *zonename);
void parse_room(FILE *fl, int virtual_nr);
void parse_mobile(FILE *mob_f, int nr);
char *parse_object(FILE *obj_f, int nr);
char *fread_string(FILE *fl, const char *error);
int asciiflag_conv(char *flag);
int check_bitvector_names(int bits, size_t namecount, const char *whatami, const char *whatbits);
void setup_dir(FILE *fl, int room, int dir);
void clear_char(struct char_data *ch);
void clear_object(struct obj_data *obj);
int check_object_level(struct obj_data *obj, int val);
int check_object_spell_number(struct obj_data *obj, int val);
int check_object(struct obj_data *obj);
void parse_simple_mob(FILE *mob_f, int i, int nr);
void parse_enhanced_mob(FILE *mob_f, int i, int nr);
const char *skill_name(int num);
void renum_world(void);
void renum_zone_table(void);
room_rnum real_zone(room_vnum vnum);
room_rnum real_room(room_vnum vnum);
mob_rnum real_mobile(mob_vnum vnum);
obj_rnum real_object(obj_vnum vnum);
void log_zone_error(zone_rnum zone, int cmd_no, const char *message);
/*
* Bits from constants.c
*/
const char *room_bits[] = {
"DARK",
"DEATH",
"NO_MOB",
"INDOORS",
"PEACEFUL",
"SOUNDPROOF",
"NO_TRACK",
"NO_MAGIC",
"TUNNEL",
"PRIVATE",
"GODROOM",
"HOUSE",
"HCRSH",
"ATRIUM",
"OLC",
"*", /* BFS MARK */
"\n"
};
/* MOB_x */
const char *action_bits[] = {
"SPEC",
"SENTINEL",
"SCAVENGER",
"ISNPC",
"AWARE",
"AGGR",
"STAY-ZONE",
"WIMPY",
"AGGR_EVIL",
"AGGR_GOOD",
"AGGR_NEUTRAL",
"MEMORY",
"HELPER",
"NO_CHARM",
"NO_SUMMN",
"NO_SLEEP",
"NO_BASH",
"NO_BLIND",
"DEAD", /* You should never see this. */
"\n"
};
/* AFF_x */
const char *affected_bits[] =
{
"BLIND",
"INVIS",
"DET-ALIGN",
"DET-INVIS",
"DET-MAGIC",
"SENSE-LIFE",
"WATWALK",
"SANCT",
"GROUP",
"CURSE",
"INFRA",
"POISON",
"PROT-EVIL",
"PROT-GOOD",
"SLEEP",
"NO_TRACK",
"UNUSED",
"UNUSED",
"SNEAK",
"HIDE",
"UNUSED",
"CHARM",
"\n"
};
/* ITEM_WEAR_ (wear bitvector) */
const char *wear_bits[] = {
"TAKE",
"FINGER",
"NECK",
"BODY",
"HEAD",
"LEGS",
"FEET",
"HANDS",
"ARMS",
"SHIELD",
"ABOUT",
"WAIST",
"WRIST",
"WIELD",
"HOLD",
"\n"
};
/* ITEM_x (extra bits) */
const char *extra_bits[] = {
"GLOW",
"HUM",
"NO_RENT",
"NO_DONATE",
"NO_INVIS",
"INVISIBLE",
"MAGIC",
"NO_DROP",
"BLESS",
"ANTI_GOOD",
"ANTI_EVIL",
"ANTI_NEUTRAL",
"ANTI_MAGE",
"ANTI_CLERIC",
"ANTI_THIEF",
"ANTI_WARRIOR",
"NO_SELL",
"\n"
};
/* one-word alias for each drink */
const char *drinknames[] =
{
"water",
"beer",
"wine",
"ale",
"ale",
"whisky",
"lemonade",
"firebreather",
"local",
"juice",
"milk",
"tea",
"coffee",
"blood",
"salt",
"water",
"\n"
};
/*
* Various arrays we count so we can check the world files. These
* must be at the bottom of the file so they're pre-declared.
*/
size_t room_bits_count = sizeof(room_bits) / sizeof(room_bits[0]) - 1,
action_bits_count = sizeof(action_bits) / sizeof(action_bits[0]) - 1,
affected_bits_count = sizeof(affected_bits) / sizeof(affected_bits[0]) - 1,
extra_bits_count = sizeof(extra_bits) / sizeof(extra_bits[0]) - 1,
wear_bits_count = sizeof(wear_bits) / sizeof(wear_bits[0]) - 1;
/*
* Loading code
*/
#define ZCMD zone_table[zone].cmd[cmd_no]
void boot_world(void) {
log("Loading zone table.");
index_boot(DB_BOOT_ZON);
log("Loading rooms.");
index_boot(DB_BOOT_WLD);
log("Renumbering rooms.");
renum_world();
// log("Checking start rooms.");
// check_start_rooms();
log("Loading mobs and generating index.");
index_boot(DB_BOOT_MOB);
log("Loading objs and generating index.");
index_boot(DB_BOOT_OBJ);
// log("Renumbering zone table.");
// renum_zone_table();
// if (!no_specials) {
log("Loading shops.");
index_boot(DB_BOOT_SHP);
// }
}
void index_boot(int mode)
{
const char *index_filename, *prefix = NULL; /* NULL or egcs 1.1 complains */
FILE *db_index, *db_file;
int rec_count = 0, size[2];
char buf2[PATH_MAX], buf1[MAX_STRING_LENGTH];
switch (mode) {
case DB_BOOT_WLD:
prefix = WLD_PREFIX;
break;
case DB_BOOT_MOB:
prefix = MOB_PREFIX;
break;
case DB_BOOT_OBJ:
prefix = OBJ_PREFIX;
break;
case DB_BOOT_ZON:
prefix = ZON_PREFIX;
break;
case DB_BOOT_SHP:
prefix = SHP_PREFIX;
break;
case DB_BOOT_HLP:
prefix = HLP_PREFIX;
break;
default:
log("SYSERR: Unknown subcommand %d to index_boot!", mode);
exit(1);
}
/*
* -- EDIT HERE! We don't want to convert minimud. =P
*/
index_filename = INDEX_FILE;
/*
* -- END EDIT
*/
snprintf(buf2, sizeof(buf2), "%s%s", prefix, index_filename);
if (!(db_index = fopen(buf2, "r"))) {
log("SYSERR: opening index file '%s': %s", buf2, strerror(errno));
exit(1);
}
/* first, count the number of records in the file so we can malloc */
fscanf(db_index, "%s\n", buf1);
while (*buf1 != '$') {
snprintf(buf2, sizeof(buf2), "%s%s", prefix, buf1);
if (!(db_file = fopen(buf2, "r"))) {
log("SYSERR: File '%s' listed in '%s/%s': %s", buf2, prefix,
index_filename, strerror(errno));
fscanf(db_index, "%s\n", buf1);
continue;
} else {
if (mode == DB_BOOT_ZON)
rec_count++;
else if (mode == DB_BOOT_HLP)
rec_count += count_alias_records(db_file);
else
rec_count += count_hash_records(db_file);
}
fclose(db_file);
fscanf(db_index, "%s\n", buf1);
}
/* Exit if 0 records, unless this is shops */
if (!rec_count) {
if (mode == DB_BOOT_SHP)
return;
log("SYSERR: boot error - 0 records counted in %s/%s.", prefix,
index_filename);
exit(1);
}
/*
* NOTE: "bytes" does _not_ include strings or other later malloc'd things.
*/
switch (mode) {
case DB_BOOT_WLD:
CREATE(world, struct room_data, rec_count);
size[0] = sizeof(struct room_data) * rec_count;
log(" %d rooms, %d bytes.", rec_count, size[0]);
break;
case DB_BOOT_MOB:
CREATE(mob_proto, struct char_data, rec_count);
CREATE(mob_index, struct index_data, rec_count);
size[0] = sizeof(struct index_data) * rec_count;
size[1] = sizeof(struct char_data) * rec_count;
log(" %d mobs, %d bytes in index, %d bytes in prototypes.", rec_count, size[0], size[1]);
break;
case DB_BOOT_OBJ:
CREATE(obj_proto, struct obj_data, rec_count);
CREATE(obj_index, struct index_data, rec_count);
size[0] = sizeof(struct index_data) * rec_count;
size[1] = sizeof(struct obj_data) * rec_count;
log(" %d objs, %d bytes in index, %d bytes in prototypes.", rec_count, size[0], size[1]);
break;
case DB_BOOT_ZON:
CREATE(zone_table, struct zone_data, rec_count);
size[0] = sizeof(struct zone_data) * rec_count;
log(" %d zones, %d bytes.", rec_count, size[0]);
break;
case DB_BOOT_HLP:
/*
* We're not converting HELP files either
*
CREATE(help_table, struct help_index_element, rec_count);
size[0] = sizeof(struct help_index_element) * rec_count;
log(" %d entries, %d bytes.", rec_count, size[0]);
*/
break;
}
rewind(db_index);
fscanf(db_index, "%s\n", buf1);
while (*buf1 != '$') {
snprintf(buf2, sizeof(buf2), "%s%s", prefix, buf1);
if (!(db_file = fopen(buf2, "r"))) {
log("SYSERR: %s: %s", buf2, strerror(errno));
exit(1);
}
switch (mode) {
case DB_BOOT_WLD:
case DB_BOOT_OBJ:
case DB_BOOT_MOB:
discrete_load(db_file, mode, buf2);
break;
case DB_BOOT_ZON:
load_zones(db_file, buf2);
break;
case DB_BOOT_HLP:
/*
* If you think about it, we have a race here. Although, this is the
* "point-the-gun-at-your-own-foot" type of race.
*/
/*
* Again, not loading help files
*
* load_help(db_file);
*/
break;
case DB_BOOT_SHP:
boot_the_shops(db_file, buf2, rec_count);
break;
}
fclose(db_file);
fscanf(db_index, "%s\n", buf1);
}
fclose(db_index);
/* sort the help index */
if (mode == DB_BOOT_HLP) {
/*
*
*
qsort(help_table, top_of_helpt, sizeof(struct help_index_element), hsort);
top_of_helpt--;
*/
}
}
/*
* Thanks to Andrey (andrey@alex-ua.com) for this bit of code, although I
* did add the 'goto' and changed some "while()" into "do { } while()".
* -gg 6/24/98 (technically 6/25/98, but I care not.)
*/
int count_alias_records(FILE *fl)
{
char key[READ_SIZE], next_key[READ_SIZE];
char line[READ_SIZE], *scan;
int total_keywords = 0;
/* get the first keyword line */
get_one_line(fl, key);
while (*key != '$') {
/* skip the text */
do {
get_one_line(fl, line);
if (feof(fl))
goto ackeof;
} while (*line != '#');
/* now count keywords */
scan = key;
do {
scan = one_word(scan, next_key);
if (*next_key)
++total_keywords;
} while (*next_key);
/* get next keyword line (or $) */
get_one_line(fl, key);
if (feof(fl))
goto ackeof;
}
return (total_keywords);
/* No, they are not evil. -gg 6/24/98 */
ackeof:
log("SYSERR: Unexpected end of help file.");
exit(1); /* Some day we hope to handle these things better... */
}
/* function to count how many hash-mark delimited records exist in a file */
int count_hash_records(FILE *fl)
{
char buf[128];
int count = 0;
while (fgets(buf, 128, fl))
if (*buf == '#')
count++;
return (count);
}
void get_one_line(FILE *fl, char *buf)
{
if (fgets(buf, READ_SIZE, fl) == NULL) {
log("SYSERR: error reading help file: not terminated with $?");
exit(1);
}
buf[strlen(buf) - 1] = '\0'; /* take off the trailing \n */
}
void discrete_load(FILE *fl, int mode, char *filename)
{
int nr = -1, last;
char line[READ_SIZE];
const char *modes[] = {"world", "mob", "obj"};
for (;;) {
/*
* we have to do special processing with the obj files because they have
* no end-of-record marker :(
*/
if (mode != DB_BOOT_OBJ || nr < 0)
if (!get_line(fl, line)) {
if (nr == -1) {
log("SYSERR: %s file %s is empty!", modes[mode], filename);
} else {
log("SYSERR: Format error in %s after %s #%d\n"
"...expecting a new %s, but file ended!\n"
"(maybe the file is not terminated with '$'?)", filename,
modes[mode], nr, modes[mode]);
}
exit(1);
}
if (*line == '$')
return;
if (*line == '#') {
last = nr;
if (sscanf(line, "#%d", &nr) != 1) {
log("SYSERR: Format error after %s #%d", modes[mode], last);
exit(1);
}
if (nr >= 99999)
return;
else
switch (mode) {
case DB_BOOT_WLD:
parse_room(fl, nr);
break;
case DB_BOOT_MOB:
parse_mobile(fl, nr);
break;
case DB_BOOT_OBJ:
strlcpy(line, parse_object(fl, nr), sizeof(line));
break;
}
} else {
log("SYSERR: Format error in %s file %s near %s #%d", modes[mode],
filename, modes[mode], nr);
log("SYSERR: ... offending line: '%s'", line);
exit(1);
}
}
}
#define Z zone_table[zone]
/* load the zone table and command tables */
void load_zones(FILE *fl, char *zonename)
{
static zone_rnum zone = 0;
int cmd_no, num_of_cmds = 0, line_num = 0, tmp, error;
char *ptr, buf[READ_SIZE], zname[READ_SIZE], buf2[MAX_STRING_LENGTH];
strlcpy(zname, zonename, sizeof(zname));
/* Skip first 3 lines lest we mistake the zone name for a command. */
for (tmp = 0; tmp < 3; tmp++)
get_line(fl, buf);
/* More accurate count. Previous was always 4 or 5 too high. -gg 2001/1/17
* Note that if a new zone command is added to reset_zone(), this string
* will need to be updated to suit. - ae.
*/
while (get_line(fl, buf))
if ((strchr("MOPGERD", buf[0]) && buf[1] == ' ') || (buf[0] == 'S' && buf[1] == '\0'))
num_of_cmds++;
rewind(fl);
if (num_of_cmds == 0) {
log("SYSERR: %s is empty!", zname);
exit(1);
} else
CREATE(Z.cmd, struct reset_com, num_of_cmds);
line_num += get_line(fl, buf);
if (sscanf(buf, "#%hd", &Z.number) != 1) {
log("SYSERR: Format error in %s, line %d", zname, line_num);
exit(1);
}
snprintf(buf2, sizeof(buf2), "beginning of zone #%d", Z.number);
line_num += get_line(fl, buf);
if ((ptr = strchr(buf, '~')) != NULL) /* take off the '~' if it's there */
*ptr = '\0';
Z.name = strdup(buf);
line_num += get_line(fl, buf);
if (sscanf(buf, " %hd %hd %d %d ", &Z.bot, &Z.top, &Z.lifespan, &Z.reset_mode) != 4) {
log("SYSERR: Format error in numeric constant line of %s", zname);
exit(1);
}
if (Z.bot > Z.top) {
log("SYSERR: Zone %d bottom (%d) > top (%d).", Z.number, Z.bot, Z.top);
exit(1);
}
cmd_no = 0;
for (;;) {
if ((tmp = get_line(fl, buf)) == 0) {
log("SYSERR: Format error in %s - premature end of file", zname);
exit(1);
}
line_num += tmp;
ptr = buf;
skip_spaces(&ptr);
if ((ZCMD.command = *ptr) == '*')
continue;
ptr++;
if (ZCMD.command == 'S' || ZCMD.command == '$') {
ZCMD.command = 'S';
break;
}
error = 0;
if (strchr("MOEPD", ZCMD.command) == NULL) { /* a 3-arg command */
if (sscanf(ptr, " %d %d %d ", &tmp, &ZCMD.arg1, &ZCMD.arg2) != 3)
error = 1;
} else {
if (sscanf(ptr, " %d %d %d %d ", &tmp, &ZCMD.arg1, &ZCMD.arg2,
&ZCMD.arg3) != 4)
error = 1;
}
ZCMD.if_flag = tmp;
if (error) {
log("SYSERR: Format error in %s, line %d: '%s'", zname, line_num, buf);
exit(1);
}
ZCMD.line = line_num;
cmd_no++;
}
if (num_of_cmds != cmd_no + 1) {
log("SYSERR: Zone command count mismatch for %s. Estimated: %d, Actual: %d", zname, num_of_cmds, cmd_no + 1);
exit(1);
}
top_of_zone_table = zone++;
}
#undef Z
/* load the rooms */
void parse_room(FILE *fl, int virtual_nr)
{
static int room_nr = 0, zone = 0;
int t[10], i;
char line[READ_SIZE], flags[128], buf2[MAX_STRING_LENGTH], buf[128];
struct extra_descr_data *new_descr;
/* This really had better fit or there are other problems. */
snprintf(buf2, sizeof(buf2), "room #%d", virtual_nr);
if (virtual_nr < zone_table[zone].bot) {
log("SYSERR: Room #%d is below zone %d.", virtual_nr, zone);
exit(1);
}
while (virtual_nr > zone_table[zone].top)
if (++zone > top_of_zone_table) {
log("SYSERR: Room %d is outside of any zone.", virtual_nr);
exit(1);
}
world[room_nr].zone = zone;
world[room_nr].number = virtual_nr;
world[room_nr].name = fread_string(fl, buf2);
world[room_nr].description = fread_string(fl, buf2);
if (!get_line(fl, line)) {
log("SYSERR: Expecting roomflags/sector type of room #%d but file ended!",
virtual_nr);
exit(1);
}
if (sscanf(line, " %d %s %d ", t, flags, t + 2) != 3) {
log("SYSERR: Format error in roomflags/sector type of room #%d",
virtual_nr);
exit(1);
}
/* t[0] is the zone number; ignored with the zone-file system */
world[room_nr].room_flags = asciiflag_conv(flags);
sprintf(flags, "object #%d", virtual_nr); /* sprintf: OK (until 399-bit integers) */
check_bitvector_names(world[room_nr].room_flags, room_bits_count, flags, "room");
world[room_nr].sector_type = t[2];
world[room_nr].func = NULL;
world[room_nr].contents = NULL;
world[room_nr].people = NULL;
world[room_nr].light = 0; /* Zero light sources */
for (i = 0; i < NUM_OF_DIRS; i++)
world[room_nr].dir_option[i] = NULL;
world[room_nr].ex_description = NULL;
snprintf(buf, sizeof(buf), "SYSERR: Format error in room #%d (expecting D/E/S)", virtual_nr);
for (;;) {
if (!get_line(fl, line)) {
log("%s", buf);
exit(1);
}
switch (*line) {
case 'D':
setup_dir(fl, room_nr, atoi(line + 1));
break;
case 'E':
CREATE(new_descr, struct extra_descr_data, 1);
new_descr->keyword = fread_string(fl, buf2);
new_descr->description = fread_string(fl, buf2);
new_descr->next = world[room_nr].ex_description;
world[room_nr].ex_description = new_descr;
break;
case 'S': /* end of room */
top_of_world = room_nr++;
return;
default:
log("%s", buf);
exit(1);
}
}
}
void parse_mobile(FILE *mob_f, int nr)
{
static int i = 0;
int j, t[10];
char line[READ_SIZE], *tmpptr, letter;
char f1[128], f2[128], buf2[128];
mob_index[i].vnum = nr;
mob_index[i].number = 0;
mob_index[i].func = NULL;
clear_char(mob_proto + i);
/*
* Mobiles should NEVER use anything in the 'player_specials' structure.
* The only reason we have every mob in the game share this copy of the
* structure is to save newbie coders from themselves. -gg 2/25/98
*/
mob_proto[i].player_specials = &dummy_mob;
sprintf(buf2, "mob vnum %d", nr); /* sprintf: OK (for 'buf2 >= 19') */
/***** String data *****/
mob_proto[i].player.name = fread_string(mob_f, buf2);
tmpptr = mob_proto[i].player.short_descr = fread_string(mob_f, buf2);
if (tmpptr && *tmpptr)
if (!str_cmp(fname(tmpptr), "a") || !str_cmp(fname(tmpptr), "an") ||
!str_cmp(fname(tmpptr), "the"))
*tmpptr = LOWER(*tmpptr);
mob_proto[i].player.long_descr = fread_string(mob_f, buf2);
mob_proto[i].player.description = fread_string(mob_f, buf2);
GET_TITLE(mob_proto + i) = NULL;
/* *** Numeric data *** */
if (!get_line(mob_f, line)) {
log("SYSERR: Format error after string section of mob #%d\n"
"...expecting line of form '# # # {S | E}', but file ended!", nr);
exit(1);
}
#ifdef CIRCLE_ACORN /* Ugh. */
if (sscanf(line, "%s %s %d %s", f1, f2, t + 2, &letter) != 4) {
#else
if (sscanf(line, "%s %s %d %c", f1, f2, t + 2, &letter) != 4) {
#endif
log("SYSERR: Format error after string section of mob #%d\n"
"...expecting line of form '# # # {S | E}'", nr);
exit(1);
}
MOB_FLAGS(mob_proto + i) = asciiflag_conv(f1);
SET_BIT(MOB_FLAGS(mob_proto + i), MOB_ISNPC);
if (MOB_FLAGGED(mob_proto + i, MOB_NOTDEADYET)) {
/* Rather bad to load mobiles with this bit already set. */
log("SYSERR: Mob #%d has reserved bit MOB_NOTDEADYET set.", nr);
REMOVE_BIT(MOB_FLAGS(mob_proto + i), MOB_NOTDEADYET);
}
check_bitvector_names(MOB_FLAGS(mob_proto + i), action_bits_count, buf2, "mobile");
AFF_FLAGS(mob_proto + i) = asciiflag_conv(f2);
check_bitvector_names(AFF_FLAGS(mob_proto + i), affected_bits_count, buf2, "mobile affect");
GET_ALIGNMENT(mob_proto + i) = t[2];
/* AGGR_TO_ALIGN is ignored if the mob is AGGRESSIVE. */
if (MOB_FLAGGED(mob_proto + i, MOB_AGGRESSIVE) && MOB_FLAGGED(mob_proto + i, MOB_AGGR_GOOD | MOB_AGGR_EVIL | MOB_AGGR_NEUTRAL))
log("SYSERR: Mob #%d both Aggressive and Aggressive_to_Alignment.", nr);
switch (UPPER(letter)) {
case 'S': /* Simple monsters */
parse_simple_mob(mob_f, i, nr);
break;
case 'E': /* Circle3 Enhanced monsters */
parse_enhanced_mob(mob_f, i, nr);
break;
/* add new mob types here.. */
default:
log("SYSERR: Unsupported mob type '%c' in mob #%d", letter, nr);
exit(1);
}
mob_proto[i].aff_abils = mob_proto[i].real_abils;
for (j = 0; j < NUM_WEARS; j++)
mob_proto[i].equipment[j] = NULL;
mob_proto[i].nr = i;
mob_proto[i].desc = NULL;
top_of_mobt = i++;
}
/* read all objects from obj file; generate index and prototypes */
char *parse_object(FILE *obj_f, int nr)
{
static int i = 0;
static char line[READ_SIZE];
int t[10], j, retval;
char *tmpptr;
char f1[READ_SIZE], f2[READ_SIZE], buf2[128];
struct extra_descr_data *new_descr;
obj_index[i].vnum = nr;
obj_index[i].number = 0;
obj_index[i].func = NULL;
clear_object(obj_proto + i);
obj_proto[i].item_number = i;
sprintf(buf2, "object #%d", nr); /* sprintf: OK (for 'buf2 >= 19') */
/* *** string data *** */
if ((obj_proto[i].name = fread_string(obj_f, buf2)) == NULL) {
log("SYSERR: Null obj name or format error at or near %s", buf2);
exit(1);
}
tmpptr = obj_proto[i].short_description = fread_string(obj_f, buf2);
if (tmpptr && *tmpptr)
if (!str_cmp(fname(tmpptr), "a") || !str_cmp(fname(tmpptr), "an") ||
!str_cmp(fname(tmpptr), "the"))
*tmpptr = LOWER(*tmpptr);
tmpptr = obj_proto[i].description = fread_string(obj_f, buf2);
if (tmpptr && *tmpptr)
CAP(tmpptr);
obj_proto[i].action_description = fread_string(obj_f, buf2);
/* *** numeric data *** */
if (!get_line(obj_f, line)) {
log("SYSERR: Expecting first numeric line of %s, but file ended!", buf2);
exit(1);
}
if ((retval = sscanf(line, " %d %s %s", t, f1, f2)) != 3) {
log("SYSERR: Format error in first numeric line (expecting 3 args, got %d), %s", retval, buf2);
exit(1);
}
/* Object flags checked in check_object(). */
GET_OBJ_TYPE(obj_proto + i) = t[0];
GET_OBJ_EXTRA(obj_proto + i) = asciiflag_conv(f1);
GET_OBJ_WEAR(obj_proto + i) = asciiflag_conv(f2);
if (!get_line(obj_f, line)) {
log("SYSERR: Expecting second numeric line of %s, but file ended!", buf2);
exit(1);
}
if ((retval = sscanf(line, "%d %d %d %d", t, t + 1, t + 2, t + 3)) != 4) {
log("SYSERR: Format error in second numeric line (expecting 4 args, got %d), %s", retval, buf2);
exit(1);
}
GET_OBJ_VAL(obj_proto + i, 0) = t[0];
GET_OBJ_VAL(obj_proto + i, 1) = t[1];
GET_OBJ_VAL(obj_proto + i, 2) = t[2];
GET_OBJ_VAL(obj_proto + i, 3) = t[3];
if (!get_line(obj_f, line)) {
log("SYSERR: Expecting third numeric line of %s, but file ended!", buf2);
exit(1);
}
if ((retval = sscanf(line, "%d %d %d", t, t + 1, t + 2)) != 3) {
log("SYSERR: Format error in third numeric line (expecting 3 args, got %d), %s", retval, buf2);
exit(1);
}
GET_OBJ_WEIGHT(obj_proto + i) = t[0];
GET_OBJ_COST(obj_proto + i) = t[1];
GET_OBJ_RENT(obj_proto + i) = t[2];
/* check to make sure that weight of containers exceeds curr. quantity */
if (GET_OBJ_TYPE(obj_proto + i) == ITEM_DRINKCON || GET_OBJ_TYPE(obj_proto + i) == ITEM_FOUNTAIN) {
if (GET_OBJ_WEIGHT(obj_proto + i) < GET_OBJ_VAL(obj_proto + i, 1))
GET_OBJ_WEIGHT(obj_proto + i) = GET_OBJ_VAL(obj_proto + i, 1) + 5;
}
/* *** extra descriptions and affect fields *** */
for (j = 0; j < MAX_OBJ_AFFECT; j++) {
obj_proto[i].affected[j].location = APPLY_NONE;
obj_proto[i].affected[j].modifier = 0;
}
strcat(buf2, ", after numeric constants\n" /* strcat: OK (for 'buf2 >= 87') */
"...expecting 'E', 'A', '$', or next object number");
j = 0;
for (;;) {
if (!get_line(obj_f, line)) {
log("SYSERR: Format error in %s", buf2);
exit(1);
}
switch (*line) {
case 'E':
CREATE(new_descr, struct extra_descr_data, 1);
new_descr->keyword = fread_string(obj_f, buf2);
new_descr->description = fread_string(obj_f, buf2);
new_descr->next = obj_proto[i].ex_description;
obj_proto[i].ex_description = new_descr;
break;
case 'A':
if (j >= MAX_OBJ_AFFECT) {
log("SYSERR: Too many A fields (%d max), %s", MAX_OBJ_AFFECT, buf2);
exit(1);
}
if (!get_line(obj_f, line)) {
log("SYSERR: Format error in 'A' field, %s\n"
"...expecting 2 numeric constants but file ended!", buf2);
exit(1);
}
if ((retval = sscanf(line, " %d %d ", t, t + 1)) != 2) {
log("SYSERR: Format error in 'A' field, %s\n"
"...expecting 2 numeric arguments, got %d\n"
"...offending line: '%s'", buf2, retval, line);
exit(1);
}
obj_proto[i].affected[j].location = t[0];
obj_proto[i].affected[j].modifier = t[1];
j++;
break;
case '$':
case '#':
check_object(obj_proto + i);
top_of_objt = i++;
return (line);
default:
log("SYSERR: Format error in (%c): %s", *line, buf2);
exit(1);
}
}
}
/* read and allocate space for a '~'-terminated string from a given file */
char *fread_string(FILE *fl, const char *error)
{
char buf[MAX_STRING_LENGTH], tmp[513];
char *point;
int done = 0, length = 0, templength;
*buf = '\0';
do {
if (!fgets(tmp, 512, fl)) {
log("SYSERR: fread_string: format error at or near %s", error);
exit(1);
}
/* If there is a '~', end the string; else put an "\r\n" over the '\n'. */
if ((point = strchr(tmp, '~')) != NULL) {
*point = '\0';
done = 1;
} else {
point = tmp + strlen(tmp) - 1;
*(point++) = '\r';
*(point++) = '\n';
*point = '\0';
}
templength = strlen(tmp);
if (length + templength >= MAX_STRING_LENGTH) {
log("SYSERR: fread_string: string too large (db.c)");
log("%s", error);
exit(1);
} else {
strcat(buf + length, tmp); /* strcat: OK (size checked above) */
length += templength;
}
} while (!done);
/* allocate space for the new string and copy it */
return (strlen(buf) ? strdup(buf) : NULL);
}
int asciiflag_conv(char *flag)
{
int flags = 0;
int is_num = TRUE;
char *p;
for (p = flag; *p; p++) {
if (islower(*p))
flags |= 1 << (*p - 'a');
else if (isupper(*p))
flags |= 1 << (26 + (*p - 'A'));
if (!isdigit(*p))
is_num = FALSE;
}
if (is_num)
flags = atol(flag);
return (flags);
}
int check_bitvector_names(int bits, size_t namecount, const char *whatami, const char *whatbits)
{
unsigned int flagnum;
bool error = FALSE;
/* See if any bits are set above the ones we know about. */
if (bits <= (~(int)0 >> (sizeof(int) * 8 - namecount)))
return (FALSE);
for (flagnum = namecount; flagnum < sizeof(int) * 8; flagnum++)
if ((1 << flagnum) & bits) {
log("SYSERR: %s has unknown %s flag, bit %d (0 through %d known).", whatami, whatbits, flagnum, namecount - 1);
error = TRUE;
}
return (error);
}
/* read direction data */
void setup_dir(FILE *fl, int room, int dir)
{
int t[5];
char line[READ_SIZE], buf2[128];
snprintf(buf2, sizeof(buf2), "room #%d, direction D%d", GET_ROOM_VNUM(room), dir);
CREATE(world[room].dir_option[dir], struct room_direction_data, 1);
world[room].dir_option[dir]->general_description = fread_string(fl, buf2);
world[room].dir_option[dir]->keyword = fread_string(fl, buf2);
if (!get_line(fl, line)) {
log("SYSERR: Format error, %s", buf2);
exit(1);
}
if (sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
log("SYSERR: Format error, %s", buf2);
exit(1);
}
if (t[0] == 1)
world[room].dir_option[dir]->exit_info = EX_ISDOOR;
else if (t[0] == 2)
world[room].dir_option[dir]->exit_info = EX_ISDOOR | EX_PICKPROOF;
else
world[room].dir_option[dir]->exit_info = 0;
world[room].dir_option[dir]->key = t[1];
world[room].dir_option[dir]->to_room = t[2];
}
/* clear ALL the working variables of a char; do NOT free any space alloc'ed */
void clear_char(struct char_data *ch)
{
memset((char *) ch, 0, sizeof(struct char_data));
IN_ROOM(ch) = NOWHERE;
GET_PFILEPOS(ch) = -1;
GET_MOB_RNUM(ch) = NOBODY;
GET_WAS_IN(ch) = NOWHERE;
GET_POS(ch) = POS_STANDING;
ch->mob_specials.default_pos = POS_STANDING;
GET_AC(ch) = 100; /* Basic Armor */
if (ch->points.max_mana < 100)
ch->points.max_mana = 100;
}
void clear_object(struct obj_data *obj)
{
memset((char *) obj, 0, sizeof(struct obj_data));
obj->item_number = NOTHING;
IN_ROOM(obj) = NOWHERE;
obj->worn_on = NOWHERE;
}
/*
* Extend later to include more checks.
*
* TODO: Add checks for unknown bitvectors.
*/
int check_object(struct obj_data *obj)
{
char objname[MAX_INPUT_LENGTH + 32];
int error = FALSE;
if (GET_OBJ_WEIGHT(obj) < 0 && (error = TRUE))
log("SYSERR: Object #%d (%s) has negative weight (%d).",
GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_WEIGHT(obj));
if (GET_OBJ_RENT(obj) < 0 && (error = TRUE))
log("SYSERR: Object #%d (%s) has negative cost/day (%d).",
GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_RENT(obj));
snprintf(objname, sizeof(objname), "Object #%d (%s)", GET_OBJ_VNUM(obj), obj->short_description);
error |= check_bitvector_names(GET_OBJ_WEAR(obj), wear_bits_count, objname, "object wear");
error |= check_bitvector_names(GET_OBJ_EXTRA(obj), extra_bits_count, objname, "object extra");
error |= check_bitvector_names(GET_OBJ_AFFECT(obj), affected_bits_count, objname, "object affect");
switch (GET_OBJ_TYPE(obj)) {
case ITEM_DRINKCON:
{
char onealias[MAX_INPUT_LENGTH], *space = strrchr(obj->name, ' ');
strlcpy(onealias, space ? space + 1 : obj->name, sizeof(onealias));
if (search_block(onealias, drinknames, TRUE) < 0 && (error = TRUE))
log("SYSERR: Object #%d (%s) doesn't have drink type as last alias. (%s)",
GET_OBJ_VNUM(obj), obj->short_description, obj->name);
}
/* Fall through. */
case ITEM_FOUNTAIN:
if (GET_OBJ_VAL(obj, 1) > GET_OBJ_VAL(obj, 0) && (error = TRUE))
log("SYSERR: Object #%d (%s) contains (%d) more than maximum (%d).",
GET_OBJ_VNUM(obj), obj->short_description,
GET_OBJ_VAL(obj, 1), GET_OBJ_VAL(obj, 0));
break;
case ITEM_SCROLL:
case ITEM_POTION:
error |= check_object_level(obj, 0);
error |= check_object_spell_number(obj, 1);
error |= check_object_spell_number(obj, 2);
error |= check_object_spell_number(obj, 3);
break;
case ITEM_WAND:
case ITEM_STAFF:
error |= check_object_level(obj, 0);
error |= check_object_spell_number(obj, 3);
if (GET_OBJ_VAL(obj, 2) > GET_OBJ_VAL(obj, 1) && (error = TRUE))
log("SYSERR: Object #%d (%s) has more charges (%d) than maximum (%d).",
GET_OBJ_VNUM(obj), obj->short_description,
GET_OBJ_VAL(obj, 2), GET_OBJ_VAL(obj, 1));
break;
}
return (error);
}
int check_object_spell_number(struct obj_data *obj, int val)
{
int error = FALSE;
const char *spellname;
if (GET_OBJ_VAL(obj, val) == -1) /* i.e.: no spell */
return (error);
/*
* Check for negative spells, spells beyond the top define, and any
* spell which is actually a skill.
*/
if (GET_OBJ_VAL(obj, val) < 0)
error = TRUE;
if (GET_OBJ_VAL(obj, val) > TOP_SPELL_DEFINE)
error = TRUE;
if (GET_OBJ_VAL(obj, val) > MAX_SPELLS && GET_OBJ_VAL(obj, val) <= MAX_SKILLS)
error = TRUE;
if (error)
log("SYSERR: Object #%d (%s) has out of range spell #%d.",
GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_VAL(obj, val));
/*
* This bug has been fixed, but if you don't like the special behavior...
*/
#if 0
if (GET_OBJ_TYPE(obj) == ITEM_STAFF &&
HAS_SPELL_ROUTINE(GET_OBJ_VAL(obj, val), MAG_AREAS | MAG_MASSES))
log("... '%s' (#%d) uses %s spell '%s'.",
obj->short_description, GET_OBJ_VNUM(obj),
HAS_SPELL_ROUTINE(GET_OBJ_VAL(obj, val), MAG_AREAS) ? "area" : "mass",
skill_name(GET_OBJ_VAL(obj, val)));
#endif
if (scheck) /* Spell names don't exist in syntax check mode. */
return (error);
/* Now check for unnamed spells. */
spellname = skill_name(GET_OBJ_VAL(obj, val));
if ((spellname == unused_spellname || !str_cmp("UNDEFINED", spellname)) && (error = TRUE))
log("SYSERR: Object #%d (%s) uses '%s' spell #%d.",
GET_OBJ_VNUM(obj), obj->short_description, spellname,
GET_OBJ_VAL(obj, val));
return (error);
}
int check_object_level(struct obj_data *obj, int val)
{
int error = FALSE;
if ((GET_OBJ_VAL(obj, val) < 0 || GET_OBJ_VAL(obj, val) > LVL_IMPL) && (error = TRUE))
log("SYSERR: Object #%d (%s) has out of range level #%d.",
GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_VAL(obj, val));
return (error);
}
void parse_simple_mob(FILE *mob_f, int i, int nr)
{
int j, t[10];
char line[READ_SIZE];
mob_proto[i].real_abils.str = 11;
mob_proto[i].real_abils.intel = 11;
mob_proto[i].real_abils.wis = 11;
mob_proto[i].real_abils.dex = 11;
mob_proto[i].real_abils.con = 11;
mob_proto[i].real_abils.cha = 11;
if (!get_line(mob_f, line)) {
log("SYSERR: Format error in mob #%d, file ended after S flag!", nr);
exit(1);
}
if (sscanf(line, " %d %d %d %dd%d+%d %dd%d+%d ",
t, t + 1, t + 2, t + 3, t + 4, t + 5, t + 6, t + 7, t + 8) != 9) {
log("SYSERR: Format error in mob #%d, first line after S flag\n"
"...expecting line of form '# # # #d#+# #d#+#'", nr);
exit(1);
}
GET_LEVEL(mob_proto + i) = t[0];
GET_HITROLL(mob_proto + i) = 20 - t[1];
GET_AC(mob_proto + i) = 10 * t[2];
/* max hit = 0 is a flag that H, M, V is xdy+z */
GET_MAX_HIT(mob_proto + i) = 0;
GET_HIT(mob_proto + i) = t[3];
GET_MANA(mob_proto + i) = t[4];
GET_MOVE(mob_proto + i) = t[5];
GET_MAX_MANA(mob_proto + i) = 10;
GET_MAX_MOVE(mob_proto + i) = 50;
mob_proto[i].mob_specials.damnodice = t[6];
mob_proto[i].mob_specials.damsizedice = t[7];
GET_DAMROLL(mob_proto + i) = t[8];
if (!get_line(mob_f, line)) {
log("SYSERR: Format error in mob #%d, second line after S flag\n"
"...expecting line of form '# #', but file ended!", nr);
exit(1);
}
if (sscanf(line, " %d %d ", t, t + 1) != 2) {
log("SYSERR: Format error in mob #%d, second line after S flag\n"
"...expecting line of form '# #'", nr);
exit(1);
}
GET_GOLD(mob_proto + i) = t[0];
GET_EXP(mob_proto + i) = t[1];
if (!get_line(mob_f, line)) {
log("SYSERR: Format error in last line of mob #%d\n"
"...expecting line of form '# # #', but file ended!", nr);
exit(1);
}
if (sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
log("SYSERR: Format error in last line of mob #%d\n"
"...expecting line of form '# # #'", nr);
exit(1);
}
GET_POS(mob_proto + i) = t[0];
GET_DEFAULT_POS(mob_proto + i) = t[1];
GET_SEX(mob_proto + i) = t[2];
GET_CLASS(mob_proto + i) = 0;
GET_WEIGHT(mob_proto + i) = 200;
GET_HEIGHT(mob_proto + i) = 198;
/*
* these are now save applies; base save numbers for MOBs are now from
* the warrior save table.
*/
for (j = 0; j < 5; j++)
GET_SAVE(mob_proto + i, j) = 0;
}
/*
* interpret_espec is the function that takes espec keywords and values
* and assigns the correct value to the mob as appropriate. Adding new
* e-specs is absurdly easy -- just add a new CASE statement to this
* function! No other changes need to be made anywhere in the code.
*
* CASE : Requires a parameter through 'value'.
* BOOL_CASE : Being specified at all is its value.
*/
#define CASE(test) \
if (value && !matched && !str_cmp(keyword, test) && (matched = TRUE))
#define BOOL_CASE(test) \
if (!value && !matched && !str_cmp(keyword, test) && (matched = TRUE))
#define RANGE(low, high) \
(num_arg = MAX((low), MIN((high), (num_arg))))
void interpret_espec(const char *keyword, const char *value, int i, int nr)
{
int num_arg = 0, matched = FALSE;
/*
* If there isn't a colon, there is no value. While Boolean options are
* possible, we don't actually have any. Feel free to make some.
*/
if (value)
num_arg = atoi(value);
CASE("BareHandAttack") {
RANGE(0, 99);
mob_proto[i].mob_specials.attack_type = num_arg;
}
CASE("Str") {
RANGE(3, 25);
mob_proto[i].real_abils.str = num_arg;
}
CASE("StrAdd") {
RANGE(0, 100);
mob_proto[i].real_abils.str_add = num_arg;
}
CASE("Int") {
RANGE(3, 25);
mob_proto[i].real_abils.intel = num_arg;
}
CASE("Wis") {
RANGE(3, 25);
mob_proto[i].real_abils.wis = num_arg;
}
CASE("Dex") {
RANGE(3, 25);
mob_proto[i].real_abils.dex = num_arg;
}
CASE("Con") {
RANGE(3, 25);
mob_proto[i].real_abils.con = num_arg;
}
CASE("Cha") {
RANGE(3, 25);
mob_proto[i].real_abils.cha = num_arg;
}
if (!matched) {
log("SYSERR: Warning: unrecognized espec keyword %s in mob #%d",
keyword, nr);
}
}
#undef CASE
#undef BOOL_CASE
#undef RANGE
void parse_espec(char *buf, int i, int nr)
{
char *ptr;
if ((ptr = strchr(buf, ':')) != NULL) {
*(ptr++) = '\0';
while (isspace(*ptr))
ptr++;
}
interpret_espec(buf, ptr, i, nr);
}
void parse_enhanced_mob(FILE *mob_f, int i, int nr)
{
char line[READ_SIZE];
parse_simple_mob(mob_f, i, nr);
while (get_line(mob_f, line)) {
if (!strcmp(line, "E")) /* end of the enhanced section */
return;
else if (*line == '#') { /* we've hit the next mob, maybe? */
log("SYSERR: Unterminated E section in mob #%d", nr);
exit(1);
} else
parse_espec(line, i, nr);
}
log("SYSERR: Unexpected end of file reached after mob #%d", nr);
exit(1);
}
/*
* This function should be used anytime you are not 100% sure that you have
* a valid spell/skill number. A typical for() loop would not need to use
* this because you can guarantee > 0 and <= TOP_SPELL_DEFINE.
*/
const char *skill_name(int num)
{
if (num > 0 && num <= TOP_SPELL_DEFINE)
return (spell_info[num].name);
else if (num == -1)
return ("UNUSED");
else
return ("UNDEFINED");
}
/* Assign the spells on boot up */
void spello(int spl, const char *name, int max_mana, int min_mana,
int mana_change, int minpos, int targets, int violent, int routines, const char *wearoff)
{
int i;
for (i = 0; i < NUM_CLASSES; i++)
spell_info[spl].min_level[i] = LVL_IMMORT;
spell_info[spl].mana_max = max_mana;
spell_info[spl].mana_min = min_mana;
spell_info[spl].mana_change = mana_change;
spell_info[spl].min_position = minpos;
spell_info[spl].targets = targets;
spell_info[spl].violent = violent;
spell_info[spl].routines = routines;
spell_info[spl].name = name;
spell_info[spl].wear_off_msg = wearoff;
}
void unused_spell(int spl)
{
int i;
for (i = 0; i < NUM_CLASSES; i++)
spell_info[spl].min_level[i] = LVL_IMPL + 1;
spell_info[spl].mana_max = 0;
spell_info[spl].mana_min = 0;
spell_info[spl].mana_change = 0;
spell_info[spl].min_position = 0;
spell_info[spl].targets = 0;
spell_info[spl].violent = 0;
spell_info[spl].routines = 0;
spell_info[spl].name = unused_spellname;
}
#define skillo(skill, name) spello(skill, name, 0, 0, 0, 0, 0, 0, 0, NULL);
void mag_assign_spells(void)
{
int i;
/* Do not change the loop below. */
for (i = 0; i <= TOP_SPELL_DEFINE; i++)
unused_spell(i);
/* Do not change the loop above. */
spello(SPELL_ANIMATE_DEAD, "animate dead", 35, 10, 3, POS_STANDING,
TAR_OBJ_ROOM, FALSE, MAG_SUMMONS,
NULL);
spello(SPELL_ARMOR, "armor", 30, 15, 3, POS_FIGHTING,
TAR_CHAR_ROOM, FALSE, MAG_AFFECTS,
"You feel less protected.");
spello(SPELL_BLESS, "bless", 35, 5, 3, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV, FALSE, MAG_AFFECTS | MAG_ALTER_OBJS,
"You feel less righteous.");
spello(SPELL_BLINDNESS, "blindness", 35, 25, 1, POS_STANDING,
TAR_CHAR_ROOM | TAR_NOT_SELF, FALSE, MAG_AFFECTS,
"You feel a cloak of blindness dissolve.");
spello(SPELL_BURNING_HANDS, "burning hands", 30, 10, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_CALL_LIGHTNING, "call lightning", 40, 25, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_CHARM, "charm person", 75, 50, 2, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_NOT_SELF, TRUE, MAG_MANUAL,
"You feel more self-confident.");
spello(SPELL_CHILL_TOUCH, "chill touch", 30, 10, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE | MAG_AFFECTS,
"You feel your strength return.");
spello(SPELL_CLONE, "clone", 80, 65, 5, POS_STANDING,
TAR_SELF_ONLY, FALSE, MAG_SUMMONS,
NULL);
spello(SPELL_COLOR_SPRAY, "color spray", 30, 15, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_CONTROL_WEATHER, "control weather", 75, 25, 5, POS_STANDING,
TAR_IGNORE, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_CREATE_FOOD, "create food", 30, 5, 4, POS_STANDING,
TAR_IGNORE, FALSE, MAG_CREATIONS,
NULL);
spello(SPELL_CREATE_WATER, "create water", 30, 5, 4, POS_STANDING,
TAR_OBJ_INV | TAR_OBJ_EQUIP, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_CURE_BLIND, "cure blind", 30, 5, 2, POS_STANDING,
TAR_CHAR_ROOM, FALSE, MAG_UNAFFECTS,
NULL);
spello(SPELL_CURE_CRITIC, "cure critic", 30, 10, 2, POS_FIGHTING,
TAR_CHAR_ROOM, FALSE, MAG_POINTS,
NULL);
spello(SPELL_CURE_LIGHT, "cure light", 30, 10, 2, POS_FIGHTING,
TAR_CHAR_ROOM, FALSE, MAG_POINTS,
NULL);
spello(SPELL_CURSE, "curse", 80, 50, 2, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV, TRUE, MAG_AFFECTS | MAG_ALTER_OBJS,
"You feel more optimistic.");
spello(SPELL_DETECT_ALIGN, "detect alignment", 20, 10, 2, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"You feel less aware.");
spello(SPELL_DETECT_INVIS, "detect invisibility", 20, 10, 2, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"Your eyes stop tingling.");
spello(SPELL_DETECT_MAGIC, "detect magic", 20, 10, 2, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"The detect magic wears off.");
spello(SPELL_DETECT_POISON, "detect poison", 15, 5, 1, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_ROOM, FALSE, MAG_MANUAL,
"The detect poison wears off.");
spello(SPELL_DISPEL_EVIL, "dispel evil", 40, 25, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_DISPEL_GOOD, "dispel good", 40, 25, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_EARTHQUAKE, "earthquake", 40, 25, 3, POS_FIGHTING,
TAR_IGNORE, TRUE, MAG_AREAS,
NULL);
spello(SPELL_ENCHANT_WEAPON, "enchant weapon", 150, 100, 10, POS_STANDING,
TAR_OBJ_INV, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_ENERGY_DRAIN, "energy drain", 40, 25, 1, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE | MAG_MANUAL,
NULL);
spello(SPELL_GROUP_ARMOR, "group armor", 50, 30, 2, POS_STANDING,
TAR_IGNORE, FALSE, MAG_GROUPS,
NULL);
spello(SPELL_FIREBALL, "fireball", 40, 30, 2, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_GROUP_HEAL, "group heal", 80, 60, 5, POS_STANDING,
TAR_IGNORE, FALSE, MAG_GROUPS,
NULL);
spello(SPELL_HARM, "harm", 75, 45, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_HEAL, "heal", 60, 40, 3, POS_FIGHTING,
TAR_CHAR_ROOM, FALSE, MAG_POINTS | MAG_UNAFFECTS,
NULL);
spello(SPELL_INFRAVISION, "infravision", 25, 10, 1, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"Your night vision seems to fade.");
spello(SPELL_INVISIBLE, "invisibility", 35, 25, 1, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_ROOM, FALSE, MAG_AFFECTS | MAG_ALTER_OBJS,
"You feel yourself exposed.");
spello(SPELL_LIGHTNING_BOLT, "lightning bolt", 30, 15, 1, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_LOCATE_OBJECT, "locate object", 25, 20, 1, POS_STANDING,
TAR_OBJ_WORLD, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_MAGIC_MISSILE, "magic missile", 25, 10, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_POISON, "poison", 50, 20, 3, POS_STANDING,
TAR_CHAR_ROOM | TAR_NOT_SELF | TAR_OBJ_INV, TRUE,
MAG_AFFECTS | MAG_ALTER_OBJS,
"You feel less sick.");
spello(SPELL_PROT_FROM_EVIL, "protection from evil", 40, 10, 3, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"You feel less protected.");
spello(SPELL_REMOVE_CURSE, "remove curse", 45, 25, 5, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_EQUIP, FALSE,
MAG_UNAFFECTS | MAG_ALTER_OBJS,
NULL);
spello(SPELL_REMOVE_POISON, "remove poison", 40, 8, 4, POS_STANDING,
TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_ROOM, FALSE, MAG_UNAFFECTS | MAG_ALTER_OBJS,
NULL);
spello(SPELL_SANCTUARY, "sanctuary", 110, 85, 5, POS_STANDING,
TAR_CHAR_ROOM, FALSE, MAG_AFFECTS,
"The white aura around your body fades.");
spello(SPELL_SENSE_LIFE, "sense life", 20, 10, 2, POS_STANDING,
TAR_CHAR_ROOM | TAR_SELF_ONLY, FALSE, MAG_AFFECTS,
"You feel less aware of your surroundings.");
spello(SPELL_SHOCKING_GRASP, "shocking grasp", 30, 15, 3, POS_FIGHTING,
TAR_CHAR_ROOM | TAR_FIGHT_VICT, TRUE, MAG_DAMAGE,
NULL);
spello(SPELL_SLEEP, "sleep", 40, 25, 5, POS_STANDING,
TAR_CHAR_ROOM, TRUE, MAG_AFFECTS,
"You feel less tired.");
spello(SPELL_STRENGTH, "strength", 35, 30, 1, POS_STANDING,
TAR_CHAR_ROOM, FALSE, MAG_AFFECTS,
"You feel weaker.");
spello(SPELL_SUMMON, "summon", 75, 50, 3, POS_STANDING,
TAR_CHAR_WORLD | TAR_NOT_SELF, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_TELEPORT, "teleport", 75, 50, 3, POS_STANDING,
TAR_CHAR_ROOM, FALSE, MAG_MANUAL,
NULL);
spello(SPELL_WATERWALK, "waterwalk", 40, 20, 2, POS_STANDING,
TAR_CHAR_ROOM, FALSE, MAG_AFFECTS,
"Your feet seem less buoyant.");
spello(SPELL_WORD_OF_RECALL, "word of recall", 20, 10, 2, POS_FIGHTING,
TAR_CHAR_ROOM, FALSE, MAG_MANUAL,
NULL);
/* NON-castable spells should appear below here. */
spello(SPELL_IDENTIFY, "identify", 0, 0, 0, 0,
TAR_CHAR_ROOM | TAR_OBJ_INV | TAR_OBJ_ROOM, FALSE, MAG_MANUAL,
NULL);
/*
* These spells are currently not used, not implemented, and not castable.
* Values for the 'breath' spells are filled in assuming a dragon's breath.
*/
spello(SPELL_FIRE_BREATH, "fire breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
spello(SPELL_GAS_BREATH, "gas breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
spello(SPELL_FROST_BREATH, "frost breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
spello(SPELL_ACID_BREATH, "acid breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
spello(SPELL_ACID_BREATH, "acid breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
spello(SPELL_LIGHTNING_BREATH, "lightning breath", 0, 0, 0, POS_SITTING,
TAR_IGNORE, TRUE, 0,
NULL);
/*
* Declaration of skills - this actually doesn't do anything except
* set it up so that immortals can use these skills by default. The
* min level to use the skill for other classes is set up in class.c.
*/
skillo(SKILL_BACKSTAB, "backstab");
skillo(SKILL_BASH, "bash");
skillo(SKILL_HIDE, "hide");
skillo(SKILL_KICK, "kick");
skillo(SKILL_PICK_LOCK, "pick lock");
skillo(SKILL_RESCUE, "rescue");
skillo(SKILL_SNEAK, "sneak");
skillo(SKILL_STEAL, "steal");
skillo(SKILL_TRACK, "track");
}
/* resolve all vnums into rnums in the world */
void renum_world(void)
{
int room, door;
for (room = 0; room <= top_of_world; room++)
for (door = 0; door < NUM_OF_DIRS; door++)
if (world[room].dir_option[door])
if (world[room].dir_option[door]->to_room != NOWHERE)
world[room].dir_option[door]->to_room =
real_room(world[room].dir_option[door]->to_room);
}
#define ZCMD zone_table[zone].cmd[cmd_no]
/*
* "resulve vnums into rnums in the zone reset tables"
*
* Or in English: Once all of the zone reset tables have been loaded, we
* resolve the virtual numbers into real numbers all at once so we don't have
* to do it repeatedly while the game is running. This does make adding any
* room, mobile, or object a little more difficult while the game is running.
*
* NOTE 1: Assumes NOWHERE == NOBODY == NOTHING.
* NOTE 2: Assumes sizeof(room_rnum) >= (sizeof(mob_rnum) and sizeof(obj_rnum))
*/
void renum_zone_table(void)
{
int cmd_no;
room_rnum a, b, c, olda, oldb, oldc;
zone_rnum zone;
char buf[128];
for (zone = 0; zone <= top_of_zone_table; zone++)
for (cmd_no = 0; ZCMD.command != 'S'; cmd_no++) {
a = b = c = 0;
olda = ZCMD.arg1;
oldb = ZCMD.arg2;
oldc = ZCMD.arg3;
switch (ZCMD.command) {
case 'M':
a = ZCMD.arg1 = real_mobile(ZCMD.arg1);
c = ZCMD.arg3 = real_room(ZCMD.arg3);
break;
case 'O':
a = ZCMD.arg1 = real_object(ZCMD.arg1);
if (ZCMD.arg3 != NOWHERE)
c = ZCMD.arg3 = real_room(ZCMD.arg3);
break;
case 'G':
a = ZCMD.arg1 = real_object(ZCMD.arg1);
break;
case 'E':
a = ZCMD.arg1 = real_object(ZCMD.arg1);
break;
case 'P':
a = ZCMD.arg1 = real_object(ZCMD.arg1);
c = ZCMD.arg3 = real_object(ZCMD.arg3);
break;
case 'D':
a = ZCMD.arg1 = real_room(ZCMD.arg1);
break;
case 'R': /* rem obj from room */
a = ZCMD.arg1 = real_room(ZCMD.arg1);
b = ZCMD.arg2 = real_object(ZCMD.arg2);
break;
}
if (a == NOWHERE || b == NOWHERE || c == NOWHERE) {
// if (!mini_mud) {
snprintf(buf, sizeof(buf), "Invalid vnum %d, cmd disabled",
a == NOWHERE ? olda : b == NOWHERE ? oldb : oldc);
log_zone_error(zone, cmd_no, buf);
// }
ZCMD.command = '*';
}
}
}
/* returns the real number of the room with given virtual number */
room_rnum real_room(room_vnum vnum)
{
room_rnum bot, top, mid;
bot = 0;
top = top_of_world;
/* perform binary search on world-table */
for (;;) {
mid = (bot + top) / 2;
if ((world + mid)->number == vnum)
return (mid);
if (bot >= top)
return (NOWHERE);
if ((world + mid)->number > vnum)
top = mid - 1;
else
bot = mid + 1;
}
}
/* returns the real number of the monster with given virtual number */
mob_rnum real_mobile(mob_vnum vnum)
{
mob_rnum bot, top, mid;
bot = 0;
top = top_of_mobt;
/* perform binary search on mob-table */
for (;;) {
mid = (bot + top) / 2;
if ((mob_index + mid)->vnum == vnum)
return (mid);
if (bot >= top)
return (NOBODY);
if ((mob_index + mid)->vnum > vnum)
top = mid - 1;
else
bot = mid + 1;
}
}
/* returns the real number of the object with given virtual number */
obj_rnum real_object(obj_vnum vnum)
{
obj_rnum bot, top, mid;
bot = 0;
top = top_of_objt;
/* perform binary search on obj-table */
for (;;) {
mid = (bot + top) / 2;
if ((obj_index + mid)->vnum == vnum)
return (mid);
if (bot >= top)
return (NOTHING);
if ((obj_index + mid)->vnum > vnum)
top = mid - 1;
else
bot = mid + 1;
}
}
/* returns the real number of the zone with given virtual number */
room_rnum real_zone(room_vnum vnum)
{
room_rnum bot, top, mid;
bot = 0;
top = top_of_zone_table;
/* perform binary search on zone-table */
for (;;) {
mid = (bot + top) / 2;
if ((zone_table + mid)->number == vnum)
return (mid);
if (bot >= top)
return (NOWHERE);
if ((zone_table + mid)->number > vnum)
top = mid - 1;
else
bot = mid + 1;
}
}
void log_zone_error(zone_rnum zone, int cmd_no, const char *message)
{
log("SYSERR: zone file: %s", message);
log("SYSERR: ...offending cmd: '%c' cmd in zone #%d, line %d", ZCMD.command, zone_table[zone].number, ZCMD.line);
}