/* ************************************************************************ * 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); }