/ circle3.1 / src / db.c
db.c
   1  /* ************************************************************************
   2  *   File: db.c                                          Part of CircleMUD *
   3  *  Usage: Loading/saving chars, booting/resetting world, internal funcs   *
   4  *                                                                         *
   5  *  All rights reserved.  See license.doc for complete information.        *
   6  *                                                                         *
   7  *  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
   8  *  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
   9  ************************************************************************ */
  10  
  11  #define __DB_C__
  12  
  13  #include "conf.h"
  14  #include "sysdep.h"
  15  
  16  
  17  #include "structs.h"
  18  #include "utils.h"
  19  #include "db.h"
  20  #include "comm.h"
  21  #include "handler.h"
  22  #include "spells.h"
  23  #include "mail.h"
  24  #include "interpreter.h"
  25  #include "house.h"
  26  #include "constants.h"
  27  
  28  /**************************************************************************
  29  *  declarations of most of the 'global' variables                         *
  30  **************************************************************************/
  31  
  32  struct room_data *world = NULL;	/* array of rooms		 */
  33  room_rnum top_of_world = 0;	/* ref to top element of world	 */
  34  
  35  struct char_data *character_list = NULL;	/* global linked list of
  36  						 * chars	 */
  37  struct index_data *mob_index;	/* index table for mobile file	 */
  38  struct char_data *mob_proto;	/* prototypes for mobs		 */
  39  mob_rnum top_of_mobt = 0;	/* top of mobile index table	 */
  40  
  41  struct obj_data *object_list = NULL;	/* global linked list of objs	 */
  42  struct index_data *obj_index;	/* index table for object file	 */
  43  struct obj_data *obj_proto;	/* prototypes for objs		 */
  44  obj_rnum top_of_objt = 0;	/* top of object index table	 */
  45  
  46  struct zone_data *zone_table;	/* zone table			 */
  47  zone_rnum top_of_zone_table = 0;/* top element of zone tab	 */
  48  struct message_list fight_messages[MAX_MESSAGES];	/* fighting messages	 */
  49  
  50  struct player_index_element *player_table = NULL;	/* index to plr file	 */
  51  FILE *player_fl = NULL;		/* file desc of player file	 */
  52  int top_of_p_table = 0;		/* ref to top of table		 */
  53  long top_idnum = 0;		/* highest idnum in use		 */
  54  
  55  int no_mail = 0;		/* mail disabled?		 */
  56  int mini_mud = 0;		/* mini-mud mode?		 */
  57  int no_rent_check = 0;		/* skip rent check on boot?	 */
  58  time_t boot_time = 0;		/* time of mud boot		 */
  59  int circle_restrict = 0;	/* level of game restriction	 */
  60  room_rnum r_mortal_start_room;	/* rnum of mortal start room	 */
  61  room_rnum r_immort_start_room;	/* rnum of immort start room	 */
  62  room_rnum r_frozen_start_room;	/* rnum of frozen start room	 */
  63  
  64  char *credits = NULL;		/* game credits			 */
  65  char *news = NULL;		/* mud news			 */
  66  char *motd = NULL;		/* message of the day - mortals */
  67  char *imotd = NULL;		/* message of the day - immorts */
  68  char *GREETINGS = NULL;		/* opening credits screen	*/
  69  char *help = NULL;		/* help screen			 */
  70  char *info = NULL;		/* info page			 */
  71  char *wizlist = NULL;		/* list of higher gods		 */
  72  char *immlist = NULL;		/* list of peon gods		 */
  73  char *background = NULL;	/* background story		 */
  74  char *handbook = NULL;		/* handbook for new immortals	 */
  75  char *policies = NULL;		/* policies page		 */
  76  
  77  struct help_index_element *help_table = 0;	/* the help table	 */
  78  int top_of_helpt = 0;		/* top of help index table	 */
  79  
  80  struct time_info_data time_info;/* the infomation about the time    */
  81  struct weather_data weather_info;	/* the infomation about the weather */
  82  struct player_special_data dummy_mob;	/* dummy spec area for mobs	*/
  83  struct reset_q_type reset_q;	/* queue of zones to be reset	 */
  84  
  85  /* local functions */
  86  int check_bitvector_names(bitvector_t bits, size_t namecount, const char *whatami, const char *whatbits);
  87  int check_object_spell_number(struct obj_data *obj, int val);
  88  int check_object_level(struct obj_data *obj, int val);
  89  void setup_dir(FILE *fl, int room, int dir);
  90  void index_boot(int mode);
  91  void discrete_load(FILE *fl, int mode, char *filename);
  92  int check_object(struct obj_data *);
  93  void parse_room(FILE *fl, int virtual_nr);
  94  void parse_mobile(FILE *mob_f, int nr);
  95  char *parse_object(FILE *obj_f, int nr);
  96  void load_zones(FILE *fl, char *zonename);
  97  void load_help(FILE *fl);
  98  void assign_mobiles(void);
  99  void assign_objects(void);
 100  void assign_rooms(void);
 101  void assign_the_shopkeepers(void);
 102  void build_player_index(void);
 103  int is_empty(zone_rnum zone_nr);
 104  void reset_zone(zone_rnum zone);
 105  int file_to_string(const char *name, char *buf);
 106  int file_to_string_alloc(const char *name, char **buf);
 107  void reboot_wizlists(void);
 108  ACMD(do_reboot);
 109  void boot_world(void);
 110  int count_alias_records(FILE *fl);
 111  int count_hash_records(FILE *fl);
 112  bitvector_t asciiflag_conv(char *flag);
 113  void parse_simple_mob(FILE *mob_f, int i, int nr);
 114  void interpret_espec(const char *keyword, const char *value, int i, int nr);
 115  void parse_espec(char *buf, int i, int nr);
 116  void parse_enhanced_mob(FILE *mob_f, int i, int nr);
 117  void get_one_line(FILE *fl, char *buf);
 118  void save_etext(struct char_data *ch);
 119  void check_start_rooms(void);
 120  void renum_world(void);
 121  void renum_zone_table(void);
 122  void log_zone_error(zone_rnum zone, int cmd_no, const char *message);
 123  void reset_time(void);
 124  long get_ptable_by_name(const char *name);
 125  
 126  /* external functions */
 127  void paginate_string(char *str, struct descriptor_data *d);
 128  struct time_info_data *mud_time_passed(time_t t2, time_t t1);
 129  void free_alias(struct alias_data *a);
 130  void load_messages(void);
 131  void weather_and_time(int mode);
 132  void mag_assign_spells(void);
 133  void boot_social_messages(void);
 134  void update_obj_file(void);	/* In objsave.c */
 135  void sort_commands(void);
 136  void sort_spells(void);
 137  void load_banned(void);
 138  void Read_Invalid_List(void);
 139  void boot_the_shops(FILE *shop_f, char *filename, int rec_count);
 140  int hsort(const void *a, const void *b);
 141  void prune_crlf(char *txt);
 142  void destroy_shops(void);
 143  
 144  /* external vars */
 145  extern int no_specials;
 146  extern int scheck;
 147  extern room_vnum mortal_start_room;
 148  extern room_vnum immort_start_room;
 149  extern room_vnum frozen_start_room;
 150  extern struct descriptor_data *descriptor_list;
 151  extern const char *unused_spellname;	/* spell_parser.c */
 152  
 153  /*************************************************************************
 154  *  routines for booting the system                                       *
 155  *************************************************************************/
 156  
 157  /* this is necessary for the autowiz system */
 158  void reboot_wizlists(void)
 159  {
 160    file_to_string_alloc(WIZLIST_FILE, &wizlist);
 161    file_to_string_alloc(IMMLIST_FILE, &immlist);
 162  }
 163  
 164  
 165  /* Wipe out all the loaded text files, for shutting down. */
 166  void free_text_files(void)
 167  {
 168    char **textfiles[] = {
 169  	&wizlist, &immlist, &news, &credits, &motd, &imotd, &help, &info,
 170  	&policies, &handbook, &background, &GREETINGS, NULL
 171    };
 172    int rf;
 173  
 174    for (rf = 0; textfiles[rf]; rf++)
 175      if (*textfiles[rf]) {
 176        free(*textfiles[rf]);
 177        *textfiles[rf] = NULL;
 178      }
 179  }
 180  
 181  
 182  /*
 183   * Too bad it doesn't check the return values to let the user
 184   * know about -1 values.  This will result in an 'Okay.' to a
 185   * 'reload' command even when the string was not replaced.
 186   * To fix later, if desired. -gg 6/24/99
 187   */
 188  ACMD(do_reboot)
 189  {
 190    char arg[MAX_INPUT_LENGTH];
 191  
 192    one_argument(argument, arg);
 193  
 194    if (!str_cmp(arg, "all") || *arg == '*') {
 195      if (file_to_string_alloc(GREETINGS_FILE, &GREETINGS) == 0)
 196        prune_crlf(GREETINGS);
 197      file_to_string_alloc(WIZLIST_FILE, &wizlist);
 198      file_to_string_alloc(IMMLIST_FILE, &immlist);
 199      file_to_string_alloc(NEWS_FILE, &news);
 200      file_to_string_alloc(CREDITS_FILE, &credits);
 201      file_to_string_alloc(MOTD_FILE, &motd);
 202      file_to_string_alloc(IMOTD_FILE, &imotd);
 203      file_to_string_alloc(HELP_PAGE_FILE, &help);
 204      file_to_string_alloc(INFO_FILE, &info);
 205      file_to_string_alloc(POLICIES_FILE, &policies);
 206      file_to_string_alloc(HANDBOOK_FILE, &handbook);
 207      file_to_string_alloc(BACKGROUND_FILE, &background);
 208    } else if (!str_cmp(arg, "wizlist"))
 209      file_to_string_alloc(WIZLIST_FILE, &wizlist);
 210    else if (!str_cmp(arg, "immlist"))
 211      file_to_string_alloc(IMMLIST_FILE, &immlist);
 212    else if (!str_cmp(arg, "news"))
 213      file_to_string_alloc(NEWS_FILE, &news);
 214    else if (!str_cmp(arg, "credits"))
 215      file_to_string_alloc(CREDITS_FILE, &credits);
 216    else if (!str_cmp(arg, "motd"))
 217      file_to_string_alloc(MOTD_FILE, &motd);
 218    else if (!str_cmp(arg, "imotd"))
 219      file_to_string_alloc(IMOTD_FILE, &imotd);
 220    else if (!str_cmp(arg, "help"))
 221      file_to_string_alloc(HELP_PAGE_FILE, &help);
 222    else if (!str_cmp(arg, "info"))
 223      file_to_string_alloc(INFO_FILE, &info);
 224    else if (!str_cmp(arg, "policy"))
 225      file_to_string_alloc(POLICIES_FILE, &policies);
 226    else if (!str_cmp(arg, "handbook"))
 227      file_to_string_alloc(HANDBOOK_FILE, &handbook);
 228    else if (!str_cmp(arg, "background"))
 229      file_to_string_alloc(BACKGROUND_FILE, &background);
 230    else if (!str_cmp(arg, "greetings")) {
 231      if (file_to_string_alloc(GREETINGS_FILE, &GREETINGS) == 0)
 232        prune_crlf(GREETINGS);
 233    } else if (!str_cmp(arg, "xhelp")) {
 234      if (help_table)
 235        free_help();
 236      index_boot(DB_BOOT_HLP);
 237    } else {
 238      send_to_char(ch, "Unknown reload option.\r\n");
 239      return;
 240    }
 241  
 242    send_to_char(ch, "%s", OK);
 243  }
 244  
 245  
 246  void boot_world(void)
 247  {
 248    log("Loading zone table.");
 249    index_boot(DB_BOOT_ZON);
 250  
 251    log("Loading rooms.");
 252    index_boot(DB_BOOT_WLD);
 253  
 254    log("Renumbering rooms.");
 255    renum_world();
 256  
 257    log("Checking start rooms.");
 258    check_start_rooms();
 259  
 260    log("Loading mobs and generating index.");
 261    index_boot(DB_BOOT_MOB);
 262  
 263    log("Loading objs and generating index.");
 264    index_boot(DB_BOOT_OBJ);
 265  
 266    log("Renumbering zone table.");
 267    renum_zone_table();
 268  
 269    if (!no_specials) {
 270      log("Loading shops.");
 271      index_boot(DB_BOOT_SHP);
 272    }
 273  }
 274  
 275  
 276  void free_extra_descriptions(struct extra_descr_data *edesc)
 277  {
 278    struct extra_descr_data *enext;
 279  
 280    for (; edesc; edesc = enext) {
 281      enext = edesc->next;
 282  
 283      free(edesc->keyword);
 284      free(edesc->description);
 285      free(edesc);
 286    }
 287  }
 288  
 289  
 290  /* Free the world, in a memory allocation sense. */
 291  void destroy_db(void)
 292  {
 293    ssize_t cnt, itr;
 294    struct char_data *chtmp;
 295    struct obj_data *objtmp;
 296  
 297    /* Active Mobiles & Players */
 298    while (character_list) {
 299      chtmp = character_list;
 300      character_list = character_list->next;
 301      free_char(chtmp);
 302    }
 303  
 304    /* Active Objects */
 305    while (object_list) {
 306      objtmp = object_list;
 307      object_list = object_list->next;
 308      free_obj(objtmp);
 309    }
 310  
 311    /* Rooms */
 312    for (cnt = 0; cnt <= top_of_world; cnt++) {
 313      if (world[cnt].name)
 314        free(world[cnt].name);
 315      if (world[cnt].description)
 316        free(world[cnt].description);
 317      free_extra_descriptions(world[cnt].ex_description);
 318  
 319      for (itr = 0; itr < NUM_OF_DIRS; itr++) {
 320        if (!world[cnt].dir_option[itr])
 321          continue;
 322  
 323        if (world[cnt].dir_option[itr]->general_description)
 324          free(world[cnt].dir_option[itr]->general_description);
 325        if (world[cnt].dir_option[itr]->keyword)
 326          free(world[cnt].dir_option[itr]->keyword);
 327        free(world[cnt].dir_option[itr]);
 328      }
 329    }
 330    free(world);
 331  
 332    /* Objects */
 333    for (cnt = 0; cnt <= top_of_objt; cnt++) {
 334      if (obj_proto[cnt].name)
 335        free(obj_proto[cnt].name);
 336      if (obj_proto[cnt].description)
 337        free(obj_proto[cnt].description);
 338      if (obj_proto[cnt].short_description)
 339        free(obj_proto[cnt].short_description);
 340      if (obj_proto[cnt].action_description)
 341        free(obj_proto[cnt].action_description);
 342      free_extra_descriptions(obj_proto[cnt].ex_description);
 343    }
 344    free(obj_proto);
 345    free(obj_index);
 346  
 347    /* Mobiles */
 348    for (cnt = 0; cnt <= top_of_mobt; cnt++) {
 349      if (mob_proto[cnt].player.name)
 350        free(mob_proto[cnt].player.name);
 351      if (mob_proto[cnt].player.title)
 352        free(mob_proto[cnt].player.title);
 353      if (mob_proto[cnt].player.short_descr)
 354        free(mob_proto[cnt].player.short_descr);
 355      if (mob_proto[cnt].player.long_descr)
 356        free(mob_proto[cnt].player.long_descr);
 357      if (mob_proto[cnt].player.description)
 358        free(mob_proto[cnt].player.description);
 359  
 360      while (mob_proto[cnt].affected)
 361        affect_remove(&mob_proto[cnt], mob_proto[cnt].affected);
 362    }
 363    free(mob_proto);
 364    free(mob_index);
 365  
 366    /* Shops */
 367    destroy_shops();
 368  
 369    /* Zones */
 370    for (cnt = 0; cnt <= top_of_zone_table; cnt++) {
 371      if (zone_table[cnt].name)
 372        free(zone_table[cnt].name);
 373      if (zone_table[cnt].cmd)
 374        free(zone_table[cnt].cmd);
 375    }
 376    free(zone_table);
 377  }
 378  
 379  
 380  /* body of the booting system */
 381  void boot_db(void)
 382  {
 383    zone_rnum i;
 384  
 385    log("Boot db -- BEGIN.");
 386  
 387    log("Resetting the game time:");
 388    reset_time();
 389  
 390    log("Reading news, credits, help, bground, info & motds.");
 391    file_to_string_alloc(NEWS_FILE, &news);
 392    file_to_string_alloc(CREDITS_FILE, &credits);
 393    file_to_string_alloc(MOTD_FILE, &motd);
 394    file_to_string_alloc(IMOTD_FILE, &imotd);
 395    file_to_string_alloc(HELP_PAGE_FILE, &help);
 396    file_to_string_alloc(INFO_FILE, &info);
 397    file_to_string_alloc(WIZLIST_FILE, &wizlist);
 398    file_to_string_alloc(IMMLIST_FILE, &immlist);
 399    file_to_string_alloc(POLICIES_FILE, &policies);
 400    file_to_string_alloc(HANDBOOK_FILE, &handbook);
 401    file_to_string_alloc(BACKGROUND_FILE, &background);
 402    if (file_to_string_alloc(GREETINGS_FILE, &GREETINGS) == 0)
 403      prune_crlf(GREETINGS);
 404  
 405    log("Loading spell definitions.");
 406    mag_assign_spells();
 407  
 408    boot_world();
 409  
 410    log("Loading help entries.");
 411    index_boot(DB_BOOT_HLP);
 412  
 413    log("Generating player index.");
 414    build_player_index();
 415  
 416    log("Loading fight messages.");
 417    load_messages();
 418  
 419    log("Loading social messages.");
 420    boot_social_messages();
 421  
 422    log("Assigning function pointers:");
 423  
 424    if (!no_specials) {
 425      log("   Mobiles.");
 426      assign_mobiles();
 427      log("   Shopkeepers.");
 428      assign_the_shopkeepers();
 429      log("   Objects.");
 430      assign_objects();
 431      log("   Rooms.");
 432      assign_rooms();
 433    }
 434  
 435    log("Assigning spell and skill levels.");
 436    init_spell_levels();
 437  
 438    log("Sorting command list and spells.");
 439    sort_commands();
 440    sort_spells();
 441  
 442    log("Booting mail system.");
 443    if (!scan_file()) {
 444      log("    Mail boot failed -- Mail system disabled");
 445      no_mail = 1;
 446    }
 447    log("Reading banned site and invalid-name list.");
 448    load_banned();
 449    Read_Invalid_List();
 450  
 451    if (!no_rent_check) {
 452      log("Deleting timed-out crash and rent files:");
 453      update_obj_file();
 454      log("   Done.");
 455    }
 456  
 457    /* Moved here so the object limit code works. -gg 6/24/98 */
 458    if (!mini_mud) {
 459      log("Booting houses.");
 460      House_boot();
 461    }
 462  
 463    for (i = 0; i <= top_of_zone_table; i++) {
 464      log("Resetting #%d: %s (rooms %d-%d).", zone_table[i].number,
 465  	zone_table[i].name, zone_table[i].bot, zone_table[i].top);
 466      reset_zone(i);
 467    }
 468  
 469    reset_q.head = reset_q.tail = NULL;
 470  
 471    boot_time = time(0);
 472  
 473    log("Boot db -- DONE.");
 474  }
 475  
 476  
 477  /* reset the time in the game from file */
 478  void reset_time(void)
 479  {
 480    time_t beginning_of_time = 0;
 481    FILE *bgtime;
 482  
 483    if ((bgtime = fopen(TIME_FILE, "r")) == NULL)
 484      log("SYSERR: Can't read from '%s' time file.", TIME_FILE);
 485    else {
 486      fscanf(bgtime, "%ld\n", &beginning_of_time);
 487      fclose(bgtime);
 488    }
 489    if (beginning_of_time == 0)
 490      beginning_of_time = 650336715;
 491  
 492    time_info = *mud_time_passed(time(0), beginning_of_time);
 493  
 494    if (time_info.hours <= 4)
 495      weather_info.sunlight = SUN_DARK;
 496    else if (time_info.hours == 5)
 497      weather_info.sunlight = SUN_RISE;
 498    else if (time_info.hours <= 20)
 499      weather_info.sunlight = SUN_LIGHT;
 500    else if (time_info.hours == 21)
 501      weather_info.sunlight = SUN_SET;
 502    else
 503      weather_info.sunlight = SUN_DARK;
 504  
 505    log("   Current Gametime: %dH %dD %dM %dY.", time_info.hours,
 506  	  time_info.day, time_info.month, time_info.year);
 507  
 508    weather_info.pressure = 960;
 509    if ((time_info.month >= 7) && (time_info.month <= 12))
 510      weather_info.pressure += dice(1, 50);
 511    else
 512      weather_info.pressure += dice(1, 80);
 513  
 514    weather_info.change = 0;
 515  
 516    if (weather_info.pressure <= 980)
 517      weather_info.sky = SKY_LIGHTNING;
 518    else if (weather_info.pressure <= 1000)
 519      weather_info.sky = SKY_RAINING;
 520    else if (weather_info.pressure <= 1020)
 521      weather_info.sky = SKY_CLOUDY;
 522    else
 523      weather_info.sky = SKY_CLOUDLESS;
 524  }
 525  
 526  
 527  /* Write the time in 'when' to the MUD-time file. */
 528  void save_mud_time(struct time_info_data *when)
 529  {
 530    FILE *bgtime;
 531  
 532    if ((bgtime = fopen(TIME_FILE, "w")) == NULL)
 533      log("SYSERR: Can't write to '%s' time file.", TIME_FILE);
 534    else {
 535      fprintf(bgtime, "%ld\n", mud_time_to_secs(when));
 536      fclose(bgtime);
 537    }
 538  }
 539  
 540  
 541  void free_player_index(void)
 542  {
 543    int tp;
 544  
 545    if (!player_table)
 546      return;
 547  
 548    for (tp = 0; tp <= top_of_p_table; tp++)
 549      if (player_table[tp].name)
 550        free(player_table[tp].name);
 551  
 552    free(player_table);
 553    player_table = NULL;
 554    top_of_p_table = 0;
 555  }
 556  
 557  
 558  /* generate index table for the player file */
 559  void build_player_index(void)
 560  {
 561    int nr = -1, i;
 562    long size, recs;
 563    struct char_file_u dummy;
 564  
 565    if (!(player_fl = fopen(PLAYER_FILE, "r+b"))) {
 566      if (errno != ENOENT) {
 567        perror("SYSERR: fatal error opening playerfile");
 568        exit(1);
 569      } else {
 570        log("No playerfile.  Creating a new one.");
 571        touch(PLAYER_FILE);
 572        if (!(player_fl = fopen(PLAYER_FILE, "r+b"))) {
 573  	perror("SYSERR: fatal error opening playerfile");
 574  	exit(1);
 575        }
 576      }
 577    }
 578  
 579    fseek(player_fl, 0L, SEEK_END);
 580    size = ftell(player_fl);
 581    rewind(player_fl);
 582    if (size % sizeof(struct char_file_u))
 583      log("\aWARNING:  PLAYERFILE IS PROBABLY CORRUPT!");
 584    recs = size / sizeof(struct char_file_u);
 585    if (recs) {
 586      log("   %ld players in database.", recs);
 587      CREATE(player_table, struct player_index_element, recs);
 588    } else {
 589      player_table = NULL;
 590      top_of_p_table = -1;
 591      return;
 592    }
 593  
 594    for (;;) {
 595      fread(&dummy, sizeof(struct char_file_u), 1, player_fl);
 596      if (feof(player_fl))
 597        break;
 598  
 599      /* new record */
 600      nr++;
 601      CREATE(player_table[nr].name, char, strlen(dummy.name) + 1);
 602      for (i = 0; (*(player_table[nr].name + i) = LOWER(*(dummy.name + i))); i++)
 603        ;
 604      player_table[nr].id = dummy.char_specials_saved.idnum;
 605      top_idnum = MAX(top_idnum, dummy.char_specials_saved.idnum);
 606    }
 607  
 608    top_of_p_table = nr;
 609  }
 610  
 611  /*
 612   * Thanks to Andrey (andrey@alex-ua.com) for this bit of code, although I
 613   * did add the 'goto' and changed some "while()" into "do { } while()".
 614   *	-gg 6/24/98 (technically 6/25/98, but I care not.)
 615   */
 616  int count_alias_records(FILE *fl)
 617  {
 618    char key[READ_SIZE], next_key[READ_SIZE];
 619    char line[READ_SIZE], *scan;
 620    int total_keywords = 0;
 621  
 622    /* get the first keyword line */
 623    get_one_line(fl, key);
 624  
 625    while (*key != '$') {
 626      /* skip the text */
 627      do {
 628        get_one_line(fl, line);
 629        if (feof(fl))
 630  	goto ackeof;
 631      } while (*line != '#');
 632  
 633      /* now count keywords */
 634      scan = key;
 635      do {
 636        scan = one_word(scan, next_key);
 637        if (*next_key)
 638          ++total_keywords;
 639      } while (*next_key);
 640  
 641      /* get next keyword line (or $) */
 642      get_one_line(fl, key);
 643  
 644      if (feof(fl))
 645        goto ackeof;
 646    }
 647  
 648    return (total_keywords);
 649  
 650    /* No, they are not evil. -gg 6/24/98 */
 651  ackeof:	
 652    log("SYSERR: Unexpected end of help file.");
 653    exit(1);	/* Some day we hope to handle these things better... */
 654  }
 655  
 656  /* function to count how many hash-mark delimited records exist in a file */
 657  int count_hash_records(FILE *fl)
 658  {
 659    char buf[128];
 660    int count = 0;
 661  
 662    while (fgets(buf, 128, fl))
 663      if (*buf == '#')
 664        count++;
 665  
 666    return (count);
 667  }
 668  
 669  
 670  
 671  void index_boot(int mode)
 672  {
 673    const char *index_filename, *prefix = NULL;	/* NULL or egcs 1.1 complains */
 674    FILE *db_index, *db_file;
 675    int rec_count = 0, size[2];
 676    char buf2[PATH_MAX], buf1[MAX_STRING_LENGTH];
 677  
 678    switch (mode) {
 679    case DB_BOOT_WLD:
 680      prefix = WLD_PREFIX;
 681      break;
 682    case DB_BOOT_MOB:
 683      prefix = MOB_PREFIX;
 684      break;
 685    case DB_BOOT_OBJ:
 686      prefix = OBJ_PREFIX;
 687      break;
 688    case DB_BOOT_ZON:
 689      prefix = ZON_PREFIX;
 690      break;
 691    case DB_BOOT_SHP:
 692      prefix = SHP_PREFIX;
 693      break;
 694    case DB_BOOT_HLP:
 695      prefix = HLP_PREFIX;
 696      break;
 697    default:
 698      log("SYSERR: Unknown subcommand %d to index_boot!", mode);
 699      exit(1);
 700    }
 701  
 702    if (mini_mud)
 703      index_filename = MINDEX_FILE;
 704    else
 705      index_filename = INDEX_FILE;
 706  
 707    snprintf(buf2, sizeof(buf2), "%s%s", prefix, index_filename);
 708    if (!(db_index = fopen(buf2, "r"))) {
 709      log("SYSERR: opening index file '%s': %s", buf2, strerror(errno));
 710      exit(1);
 711    }
 712  
 713    /* first, count the number of records in the file so we can malloc */
 714    fscanf(db_index, "%s\n", buf1);
 715    while (*buf1 != '$') {
 716      snprintf(buf2, sizeof(buf2), "%s%s", prefix, buf1);
 717      if (!(db_file = fopen(buf2, "r"))) {
 718        log("SYSERR: File '%s' listed in '%s/%s': %s", buf2, prefix,
 719  	  index_filename, strerror(errno));
 720        fscanf(db_index, "%s\n", buf1);
 721        continue;
 722      } else {
 723        if (mode == DB_BOOT_ZON)
 724  	rec_count++;
 725        else if (mode == DB_BOOT_HLP)
 726  	rec_count += count_alias_records(db_file);
 727        else
 728  	rec_count += count_hash_records(db_file);
 729      }
 730  
 731      fclose(db_file);
 732      fscanf(db_index, "%s\n", buf1);
 733    }
 734  
 735    /* Exit if 0 records, unless this is shops */
 736    if (!rec_count) {
 737      if (mode == DB_BOOT_SHP)
 738        return;
 739      log("SYSERR: boot error - 0 records counted in %s/%s.", prefix,
 740  	index_filename);
 741      exit(1);
 742    }
 743  
 744    /*
 745     * NOTE: "bytes" does _not_ include strings or other later malloc'd things.
 746     */
 747    switch (mode) {
 748    case DB_BOOT_WLD:
 749      CREATE(world, struct room_data, rec_count);
 750      size[0] = sizeof(struct room_data) * rec_count;
 751      log("   %d rooms, %d bytes.", rec_count, size[0]);
 752      break;
 753    case DB_BOOT_MOB:
 754      CREATE(mob_proto, struct char_data, rec_count);
 755      CREATE(mob_index, struct index_data, rec_count);
 756      size[0] = sizeof(struct index_data) * rec_count;
 757      size[1] = sizeof(struct char_data) * rec_count;
 758      log("   %d mobs, %d bytes in index, %d bytes in prototypes.", rec_count, size[0], size[1]);
 759      break;
 760    case DB_BOOT_OBJ:
 761      CREATE(obj_proto, struct obj_data, rec_count);
 762      CREATE(obj_index, struct index_data, rec_count);
 763      size[0] = sizeof(struct index_data) * rec_count;
 764      size[1] = sizeof(struct obj_data) * rec_count;
 765      log("   %d objs, %d bytes in index, %d bytes in prototypes.", rec_count, size[0], size[1]);
 766      break;
 767    case DB_BOOT_ZON:
 768      CREATE(zone_table, struct zone_data, rec_count);
 769      size[0] = sizeof(struct zone_data) * rec_count;
 770      log("   %d zones, %d bytes.", rec_count, size[0]);
 771      break;
 772    case DB_BOOT_HLP:
 773      CREATE(help_table, struct help_index_element, rec_count);
 774      size[0] = sizeof(struct help_index_element) * rec_count;
 775      log("   %d entries, %d bytes.", rec_count, size[0]);
 776      break;
 777    }
 778  
 779    rewind(db_index);
 780    fscanf(db_index, "%s\n", buf1);
 781    while (*buf1 != '$') {
 782      snprintf(buf2, sizeof(buf2), "%s%s", prefix, buf1);
 783      if (!(db_file = fopen(buf2, "r"))) {
 784        log("SYSERR: %s: %s", buf2, strerror(errno));
 785        exit(1);
 786      }
 787      switch (mode) {
 788      case DB_BOOT_WLD:
 789      case DB_BOOT_OBJ:
 790      case DB_BOOT_MOB:
 791        discrete_load(db_file, mode, buf2);
 792        break;
 793      case DB_BOOT_ZON:
 794        load_zones(db_file, buf2);
 795        break;
 796      case DB_BOOT_HLP:
 797        /*
 798         * If you think about it, we have a race here.  Although, this is the
 799         * "point-the-gun-at-your-own-foot" type of race.
 800         */
 801        load_help(db_file);
 802        break;
 803      case DB_BOOT_SHP:
 804        boot_the_shops(db_file, buf2, rec_count);
 805        break;
 806      }
 807  
 808      fclose(db_file);
 809      fscanf(db_index, "%s\n", buf1);
 810    }
 811    fclose(db_index);
 812  
 813    /* sort the help index */
 814    if (mode == DB_BOOT_HLP) {
 815      qsort(help_table, top_of_helpt, sizeof(struct help_index_element), hsort);
 816      top_of_helpt--;
 817    }
 818  }
 819  
 820  
 821  void discrete_load(FILE *fl, int mode, char *filename)
 822  {
 823    int nr = -1, last;
 824    char line[READ_SIZE];
 825  
 826    const char *modes[] = {"world", "mob", "obj"};
 827  
 828    for (;;) {
 829      /*
 830       * we have to do special processing with the obj files because they have
 831       * no end-of-record marker :(
 832       */
 833      if (mode != DB_BOOT_OBJ || nr < 0)
 834        if (!get_line(fl, line)) {
 835  	if (nr == -1) {
 836  	  log("SYSERR: %s file %s is empty!", modes[mode], filename);
 837  	} else {
 838  	  log("SYSERR: Format error in %s after %s #%d\n"
 839  	      "...expecting a new %s, but file ended!\n"
 840  	      "(maybe the file is not terminated with '$'?)", filename,
 841  	      modes[mode], nr, modes[mode]);
 842  	}
 843  	exit(1);
 844        }
 845      if (*line == '$')
 846        return;
 847  
 848      if (*line == '#') {
 849        last = nr;
 850        if (sscanf(line, "#%d", &nr) != 1) {
 851  	log("SYSERR: Format error after %s #%d", modes[mode], last);
 852  	exit(1);
 853        }
 854        if (nr >= 99999)
 855  	return;
 856        else
 857  	switch (mode) {
 858  	case DB_BOOT_WLD:
 859  	  parse_room(fl, nr);
 860  	  break;
 861  	case DB_BOOT_MOB:
 862  	  parse_mobile(fl, nr);
 863  	  break;
 864  	case DB_BOOT_OBJ:
 865  	  strlcpy(line, parse_object(fl, nr), sizeof(line));
 866  	  break;
 867  	}
 868      } else {
 869        log("SYSERR: Format error in %s file %s near %s #%d", modes[mode],
 870  	  filename, modes[mode], nr);
 871        log("SYSERR: ... offending line: '%s'", line);
 872        exit(1);
 873      }
 874    }
 875  }
 876  
 877  bitvector_t asciiflag_conv(char *flag)
 878  {
 879    bitvector_t flags = 0;
 880    int is_num = TRUE;
 881    char *p;
 882  
 883    for (p = flag; *p; p++) {
 884      if (islower(*p))
 885        flags |= 1 << (*p - 'a');
 886      else if (isupper(*p))
 887        flags |= 1 << (26 + (*p - 'A'));
 888  
 889      if (!isdigit(*p))
 890        is_num = FALSE;
 891    }
 892  
 893    if (is_num)
 894      flags = atol(flag);
 895  
 896    return (flags);
 897  }
 898  
 899  
 900  /* load the rooms */
 901  void parse_room(FILE *fl, int virtual_nr)
 902  {
 903    static int room_nr = 0, zone = 0;
 904    int t[10], i;
 905    char line[READ_SIZE], flags[128], buf2[MAX_STRING_LENGTH], buf[128];
 906    struct extra_descr_data *new_descr;
 907  
 908    /* This really had better fit or there are other problems. */
 909    snprintf(buf2, sizeof(buf2), "room #%d", virtual_nr);
 910  
 911    if (virtual_nr < zone_table[zone].bot) {
 912      log("SYSERR: Room #%d is below zone %d.", virtual_nr, zone);
 913      exit(1);
 914    }
 915    while (virtual_nr > zone_table[zone].top)
 916      if (++zone > top_of_zone_table) {
 917        log("SYSERR: Room %d is outside of any zone.", virtual_nr);
 918        exit(1);
 919      }
 920    world[room_nr].zone = zone;
 921    world[room_nr].number = virtual_nr;
 922    world[room_nr].name = fread_string(fl, buf2);
 923    world[room_nr].description = fread_string(fl, buf2);
 924  
 925    if (!get_line(fl, line)) {
 926      log("SYSERR: Expecting roomflags/sector type of room #%d but file ended!",
 927  	virtual_nr);
 928      exit(1);
 929    }
 930  
 931    if (sscanf(line, " %d %s %d ", t, flags, t + 2) != 3) {
 932      log("SYSERR: Format error in roomflags/sector type of room #%d",
 933  	virtual_nr);
 934      exit(1);
 935    }
 936    /* t[0] is the zone number; ignored with the zone-file system */
 937  
 938    world[room_nr].room_flags = asciiflag_conv(flags);
 939    sprintf(flags, "object #%d", virtual_nr);	/* sprintf: OK (until 399-bit integers) */
 940    check_bitvector_names(world[room_nr].room_flags, room_bits_count, flags, "room");
 941  
 942    world[room_nr].sector_type = t[2];
 943  
 944    world[room_nr].func = NULL;
 945    world[room_nr].contents = NULL;
 946    world[room_nr].people = NULL;
 947    world[room_nr].light = 0;	/* Zero light sources */
 948  
 949    for (i = 0; i < NUM_OF_DIRS; i++)
 950      world[room_nr].dir_option[i] = NULL;
 951  
 952    world[room_nr].ex_description = NULL;
 953  
 954    snprintf(buf, sizeof(buf), "SYSERR: Format error in room #%d (expecting D/E/S)", virtual_nr);
 955  
 956    for (;;) {
 957      if (!get_line(fl, line)) {
 958        log("%s", buf);
 959        exit(1);
 960      }
 961      switch (*line) {
 962      case 'D':
 963        setup_dir(fl, room_nr, atoi(line + 1));
 964        break;
 965      case 'E':
 966        CREATE(new_descr, struct extra_descr_data, 1);
 967        new_descr->keyword = fread_string(fl, buf2);
 968        new_descr->description = fread_string(fl, buf2);
 969        new_descr->next = world[room_nr].ex_description;
 970        world[room_nr].ex_description = new_descr;
 971        break;
 972      case 'S':			/* end of room */
 973        top_of_world = room_nr++;
 974        return;
 975      default:
 976        log("%s", buf);
 977        exit(1);
 978      }
 979    }
 980  }
 981  
 982  
 983  
 984  /* read direction data */
 985  void setup_dir(FILE *fl, int room, int dir)
 986  {
 987    int t[5];
 988    char line[READ_SIZE], buf2[128];
 989  
 990    snprintf(buf2, sizeof(buf2), "room #%d, direction D%d", GET_ROOM_VNUM(room), dir);
 991  
 992    CREATE(world[room].dir_option[dir], struct room_direction_data, 1);
 993    world[room].dir_option[dir]->general_description = fread_string(fl, buf2);
 994    world[room].dir_option[dir]->keyword = fread_string(fl, buf2);
 995  
 996    if (!get_line(fl, line)) {
 997      log("SYSERR: Format error, %s", buf2);
 998      exit(1);
 999    }
1000    if (sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
1001      log("SYSERR: Format error, %s", buf2);
1002      exit(1);
1003    }
1004    if (t[0] == 1)
1005      world[room].dir_option[dir]->exit_info = EX_ISDOOR;
1006    else if (t[0] == 2)
1007      world[room].dir_option[dir]->exit_info = EX_ISDOOR | EX_PICKPROOF;
1008    else
1009      world[room].dir_option[dir]->exit_info = 0;
1010  
1011    world[room].dir_option[dir]->key = t[1];
1012    world[room].dir_option[dir]->to_room = t[2];
1013  }
1014  
1015  
1016  /* make sure the start rooms exist & resolve their vnums to rnums */
1017  void check_start_rooms(void)
1018  {
1019    if ((r_mortal_start_room = real_room(mortal_start_room)) == NOWHERE) {
1020      log("SYSERR:  Mortal start room does not exist.  Change in config.c.");
1021      exit(1);
1022    }
1023    if ((r_immort_start_room = real_room(immort_start_room)) == NOWHERE) {
1024      if (!mini_mud)
1025        log("SYSERR:  Warning: Immort start room does not exist.  Change in config.c.");
1026      r_immort_start_room = r_mortal_start_room;
1027    }
1028    if ((r_frozen_start_room = real_room(frozen_start_room)) == NOWHERE) {
1029      if (!mini_mud)
1030        log("SYSERR:  Warning: Frozen start room does not exist.  Change in config.c.");
1031      r_frozen_start_room = r_mortal_start_room;
1032    }
1033  }
1034  
1035  
1036  /* resolve all vnums into rnums in the world */
1037  void renum_world(void)
1038  {
1039    int room, door;
1040  
1041    for (room = 0; room <= top_of_world; room++)
1042      for (door = 0; door < NUM_OF_DIRS; door++)
1043        if (world[room].dir_option[door])
1044  	if (world[room].dir_option[door]->to_room != NOWHERE)
1045  	  world[room].dir_option[door]->to_room =
1046  	    real_room(world[room].dir_option[door]->to_room);
1047  }
1048  
1049  
1050  #define ZCMD zone_table[zone].cmd[cmd_no]
1051  
1052  /*
1053   * "resulve vnums into rnums in the zone reset tables"
1054   *
1055   * Or in English: Once all of the zone reset tables have been loaded, we
1056   * resolve the virtual numbers into real numbers all at once so we don't have
1057   * to do it repeatedly while the game is running.  This does make adding any
1058   * room, mobile, or object a little more difficult while the game is running.
1059   *
1060   * NOTE 1: Assumes NOWHERE == NOBODY == NOTHING.
1061   * NOTE 2: Assumes sizeof(room_rnum) >= (sizeof(mob_rnum) and sizeof(obj_rnum))
1062   */
1063  void renum_zone_table(void)
1064  {
1065    int cmd_no;
1066    room_rnum a, b, c, olda, oldb, oldc;
1067    zone_rnum zone;
1068    char buf[128];
1069  
1070    for (zone = 0; zone <= top_of_zone_table; zone++)
1071      for (cmd_no = 0; ZCMD.command != 'S'; cmd_no++) {
1072        a = b = c = 0;
1073        olda = ZCMD.arg1;
1074        oldb = ZCMD.arg2;
1075        oldc = ZCMD.arg3;
1076        switch (ZCMD.command) {
1077        case 'M':
1078  	a = ZCMD.arg1 = real_mobile(ZCMD.arg1);
1079  	c = ZCMD.arg3 = real_room(ZCMD.arg3);
1080  	break;
1081        case 'O':
1082  	a = ZCMD.arg1 = real_object(ZCMD.arg1);
1083  	if (ZCMD.arg3 != NOWHERE)
1084  	  c = ZCMD.arg3 = real_room(ZCMD.arg3);
1085  	break;
1086        case 'G':
1087  	a = ZCMD.arg1 = real_object(ZCMD.arg1);
1088  	break;
1089        case 'E':
1090  	a = ZCMD.arg1 = real_object(ZCMD.arg1);
1091  	break;
1092        case 'P':
1093  	a = ZCMD.arg1 = real_object(ZCMD.arg1);
1094  	c = ZCMD.arg3 = real_object(ZCMD.arg3);
1095  	break;
1096        case 'D':
1097  	a = ZCMD.arg1 = real_room(ZCMD.arg1);
1098  	break;
1099        case 'R': /* rem obj from room */
1100          a = ZCMD.arg1 = real_room(ZCMD.arg1);
1101  	b = ZCMD.arg2 = real_object(ZCMD.arg2);
1102          break;
1103        }
1104        if (a == NOWHERE || b == NOWHERE || c == NOWHERE) {
1105  	if (!mini_mud) {
1106  	  snprintf(buf, sizeof(buf), "Invalid vnum %d, cmd disabled",
1107  			 a == NOWHERE ? olda : b == NOWHERE ? oldb : oldc);
1108  	  log_zone_error(zone, cmd_no, buf);
1109  	}
1110  	ZCMD.command = '*';
1111        }
1112      }
1113  }
1114  
1115  
1116  
1117  void parse_simple_mob(FILE *mob_f, int i, int nr)
1118  {
1119    int j, t[10];
1120    char line[READ_SIZE];
1121  
1122    mob_proto[i].real_abils.str = 11;
1123    mob_proto[i].real_abils.intel = 11;
1124    mob_proto[i].real_abils.wis = 11;
1125    mob_proto[i].real_abils.dex = 11;
1126    mob_proto[i].real_abils.con = 11;
1127    mob_proto[i].real_abils.cha = 11;
1128  
1129    if (!get_line(mob_f, line)) {
1130      log("SYSERR: Format error in mob #%d, file ended after S flag!", nr);
1131      exit(1);
1132    }
1133  
1134    if (sscanf(line, " %d %d %d %dd%d+%d %dd%d+%d ",
1135  	  t, t + 1, t + 2, t + 3, t + 4, t + 5, t + 6, t + 7, t + 8) != 9) {
1136      log("SYSERR: Format error in mob #%d, first line after S flag\n"
1137  	"...expecting line of form '# # # #d#+# #d#+#'", nr);
1138      exit(1);
1139    }
1140  
1141    GET_LEVEL(mob_proto + i) = t[0];
1142    GET_HITROLL(mob_proto + i) = 20 - t[1];
1143    GET_AC(mob_proto + i) = 10 * t[2];
1144  
1145    /* max hit = 0 is a flag that H, M, V is xdy+z */
1146    GET_MAX_HIT(mob_proto + i) = 0;
1147    GET_HIT(mob_proto + i) = t[3];
1148    GET_MANA(mob_proto + i) = t[4];
1149    GET_MOVE(mob_proto + i) = t[5];
1150  
1151    GET_MAX_MANA(mob_proto + i) = 10;
1152    GET_MAX_MOVE(mob_proto + i) = 50;
1153  
1154    mob_proto[i].mob_specials.damnodice = t[6];
1155    mob_proto[i].mob_specials.damsizedice = t[7];
1156    GET_DAMROLL(mob_proto + i) = t[8];
1157  
1158    if (!get_line(mob_f, line)) {
1159        log("SYSERR: Format error in mob #%d, second line after S flag\n"
1160  	  "...expecting line of form '# #', but file ended!", nr);
1161        exit(1);
1162      }
1163  
1164    if (sscanf(line, " %d %d ", t, t + 1) != 2) {
1165      log("SYSERR: Format error in mob #%d, second line after S flag\n"
1166  	"...expecting line of form '# #'", nr);
1167      exit(1);
1168    }
1169  
1170    GET_GOLD(mob_proto + i) = t[0];
1171    GET_EXP(mob_proto + i) = t[1];
1172  
1173    if (!get_line(mob_f, line)) {
1174      log("SYSERR: Format error in last line of mob #%d\n"
1175  	"...expecting line of form '# # #', but file ended!", nr);
1176      exit(1);
1177    }
1178  
1179    if (sscanf(line, " %d %d %d ", t, t + 1, t + 2) != 3) {
1180      log("SYSERR: Format error in last line of mob #%d\n"
1181  	"...expecting line of form '# # #'", nr);
1182      exit(1);
1183    }
1184  
1185    GET_POS(mob_proto + i) = t[0];
1186    GET_DEFAULT_POS(mob_proto + i) = t[1];
1187    GET_SEX(mob_proto + i) = t[2];
1188  
1189    GET_CLASS(mob_proto + i) = 0;
1190    GET_WEIGHT(mob_proto + i) = 200;
1191    GET_HEIGHT(mob_proto + i) = 198;
1192  
1193    /*
1194     * these are now save applies; base save numbers for MOBs are now from
1195     * the warrior save table.
1196     */
1197    for (j = 0; j < 5; j++)
1198      GET_SAVE(mob_proto + i, j) = 0;
1199  }
1200  
1201  
1202  /*
1203   * interpret_espec is the function that takes espec keywords and values
1204   * and assigns the correct value to the mob as appropriate.  Adding new
1205   * e-specs is absurdly easy -- just add a new CASE statement to this
1206   * function!  No other changes need to be made anywhere in the code.
1207   *
1208   * CASE		: Requires a parameter through 'value'.
1209   * BOOL_CASE	: Being specified at all is its value.
1210   */
1211  
1212  #define CASE(test)	\
1213  	if (value && !matched && !str_cmp(keyword, test) && (matched = TRUE))
1214  
1215  #define BOOL_CASE(test)	\
1216  	if (!value && !matched && !str_cmp(keyword, test) && (matched = TRUE))
1217  
1218  #define RANGE(low, high)	\
1219  	(num_arg = MAX((low), MIN((high), (num_arg))))
1220  
1221  void interpret_espec(const char *keyword, const char *value, int i, int nr)
1222  {
1223    int num_arg = 0, matched = FALSE;
1224  
1225    /*
1226     * If there isn't a colon, there is no value.  While Boolean options are
1227     * possible, we don't actually have any.  Feel free to make some.
1228    */
1229    if (value)
1230      num_arg = atoi(value);
1231  
1232    CASE("BareHandAttack") {
1233      RANGE(0, 99);
1234      mob_proto[i].mob_specials.attack_type = num_arg;
1235    }
1236  
1237    CASE("Str") {
1238      RANGE(3, 25);
1239      mob_proto[i].real_abils.str = num_arg;
1240    }
1241  
1242    CASE("StrAdd") {
1243      RANGE(0, 100);
1244      mob_proto[i].real_abils.str_add = num_arg;    
1245    }
1246  
1247    CASE("Int") {
1248      RANGE(3, 25);
1249      mob_proto[i].real_abils.intel = num_arg;
1250    }
1251  
1252    CASE("Wis") {
1253      RANGE(3, 25);
1254      mob_proto[i].real_abils.wis = num_arg;
1255    }
1256  
1257    CASE("Dex") {
1258      RANGE(3, 25);
1259      mob_proto[i].real_abils.dex = num_arg;
1260    }
1261  
1262    CASE("Con") {
1263      RANGE(3, 25);
1264      mob_proto[i].real_abils.con = num_arg;
1265    }
1266  
1267    CASE("Cha") {
1268      RANGE(3, 25);
1269      mob_proto[i].real_abils.cha = num_arg;
1270    }
1271  
1272    if (!matched) {
1273      log("SYSERR: Warning: unrecognized espec keyword %s in mob #%d",
1274  	    keyword, nr);
1275    }    
1276  }
1277  
1278  #undef CASE
1279  #undef BOOL_CASE
1280  #undef RANGE
1281  
1282  void parse_espec(char *buf, int i, int nr)
1283  {
1284    char *ptr;
1285  
1286    if ((ptr = strchr(buf, ':')) != NULL) {
1287      *(ptr++) = '\0';
1288      while (isspace(*ptr))
1289        ptr++;
1290    }
1291    interpret_espec(buf, ptr, i, nr);
1292  }
1293  
1294  
1295  void parse_enhanced_mob(FILE *mob_f, int i, int nr)
1296  {
1297    char line[READ_SIZE];
1298  
1299    parse_simple_mob(mob_f, i, nr);
1300  
1301    while (get_line(mob_f, line)) {
1302      if (!strcmp(line, "E"))	/* end of the enhanced section */
1303        return;
1304      else if (*line == '#') {	/* we've hit the next mob, maybe? */
1305        log("SYSERR: Unterminated E section in mob #%d", nr);
1306        exit(1);
1307      } else
1308        parse_espec(line, i, nr);
1309    }
1310  
1311    log("SYSERR: Unexpected end of file reached after mob #%d", nr);
1312    exit(1);
1313  }
1314  
1315  
1316  void parse_mobile(FILE *mob_f, int nr)
1317  {
1318    static int i = 0;
1319    int j, t[10];
1320    char line[READ_SIZE], *tmpptr, letter;
1321    char f1[128], f2[128], buf2[128];
1322  
1323    mob_index[i].vnum = nr;
1324    mob_index[i].number = 0;
1325    mob_index[i].func = NULL;
1326  
1327    clear_char(mob_proto + i);
1328  
1329    /*
1330     * Mobiles should NEVER use anything in the 'player_specials' structure.
1331     * The only reason we have every mob in the game share this copy of the
1332     * structure is to save newbie coders from themselves. -gg 2/25/98
1333     */
1334    mob_proto[i].player_specials = &dummy_mob;
1335    sprintf(buf2, "mob vnum %d", nr);	/* sprintf: OK (for 'buf2 >= 19') */
1336  
1337    /***** String data *****/
1338    mob_proto[i].player.name = fread_string(mob_f, buf2);
1339    tmpptr = mob_proto[i].player.short_descr = fread_string(mob_f, buf2);
1340    if (tmpptr && *tmpptr)
1341      if (!str_cmp(fname(tmpptr), "a") || !str_cmp(fname(tmpptr), "an") ||
1342  	!str_cmp(fname(tmpptr), "the"))
1343        *tmpptr = LOWER(*tmpptr);
1344    mob_proto[i].player.long_descr = fread_string(mob_f, buf2);
1345    mob_proto[i].player.description = fread_string(mob_f, buf2);
1346    GET_TITLE(mob_proto + i) = NULL;
1347  
1348    /* *** Numeric data *** */
1349    if (!get_line(mob_f, line)) {
1350      log("SYSERR: Format error after string section of mob #%d\n"
1351  	"...expecting line of form '# # # {S | E}', but file ended!", nr);
1352      exit(1);
1353    }
1354  
1355  #ifdef CIRCLE_ACORN	/* Ugh. */
1356    if (sscanf(line, "%s %s %d %s", f1, f2, t + 2, &letter) != 4) {
1357  #else
1358    if (sscanf(line, "%s %s %d %c", f1, f2, t + 2, &letter) != 4) {
1359  #endif
1360      log("SYSERR: Format error after string section of mob #%d\n"
1361  	"...expecting line of form '# # # {S | E}'", nr);
1362      exit(1);
1363    }
1364  
1365    MOB_FLAGS(mob_proto + i) = asciiflag_conv(f1);
1366    SET_BIT(MOB_FLAGS(mob_proto + i), MOB_ISNPC);
1367    if (MOB_FLAGGED(mob_proto + i, MOB_NOTDEADYET)) {
1368      /* Rather bad to load mobiles with this bit already set. */
1369      log("SYSERR: Mob #%d has reserved bit MOB_NOTDEADYET set.", nr);
1370      REMOVE_BIT(MOB_FLAGS(mob_proto + i), MOB_NOTDEADYET);
1371    }
1372    check_bitvector_names(MOB_FLAGS(mob_proto + i), action_bits_count, buf2, "mobile");
1373  
1374    AFF_FLAGS(mob_proto + i) = asciiflag_conv(f2);
1375    check_bitvector_names(AFF_FLAGS(mob_proto + i), affected_bits_count, buf2, "mobile affect");
1376  
1377    GET_ALIGNMENT(mob_proto + i) = t[2];
1378  
1379    /* AGGR_TO_ALIGN is ignored if the mob is AGGRESSIVE. */
1380    if (MOB_FLAGGED(mob_proto + i, MOB_AGGRESSIVE) && MOB_FLAGGED(mob_proto + i, MOB_AGGR_GOOD | MOB_AGGR_EVIL | MOB_AGGR_NEUTRAL))
1381      log("SYSERR: Mob #%d both Aggressive and Aggressive_to_Alignment.", nr);
1382  
1383    switch (UPPER(letter)) {
1384    case 'S':	/* Simple monsters */
1385      parse_simple_mob(mob_f, i, nr);
1386      break;
1387    case 'E':	/* Circle3 Enhanced monsters */
1388      parse_enhanced_mob(mob_f, i, nr);
1389      break;
1390    /* add new mob types here.. */
1391    default:
1392      log("SYSERR: Unsupported mob type '%c' in mob #%d", letter, nr);
1393      exit(1);
1394    }
1395  
1396    mob_proto[i].aff_abils = mob_proto[i].real_abils;
1397  
1398    for (j = 0; j < NUM_WEARS; j++)
1399      mob_proto[i].equipment[j] = NULL;
1400  
1401    mob_proto[i].nr = i;
1402    mob_proto[i].desc = NULL;
1403  
1404    top_of_mobt = i++;
1405  }
1406  
1407  
1408  
1409  
1410  /* read all objects from obj file; generate index and prototypes */
1411  char *parse_object(FILE *obj_f, int nr)
1412  {
1413    static int i = 0;
1414    static char line[READ_SIZE];
1415    int t[10], j, retval;
1416    char *tmpptr;
1417    char f1[READ_SIZE], f2[READ_SIZE], buf2[128];
1418    struct extra_descr_data *new_descr;
1419  
1420    obj_index[i].vnum = nr;
1421    obj_index[i].number = 0;
1422    obj_index[i].func = NULL;
1423  
1424    clear_object(obj_proto + i);
1425    obj_proto[i].item_number = i;
1426  
1427    sprintf(buf2, "object #%d", nr);	/* sprintf: OK (for 'buf2 >= 19') */
1428  
1429    /* *** string data *** */
1430    if ((obj_proto[i].name = fread_string(obj_f, buf2)) == NULL) {
1431      log("SYSERR: Null obj name or format error at or near %s", buf2);
1432      exit(1);
1433    }
1434    tmpptr = obj_proto[i].short_description = fread_string(obj_f, buf2);
1435    if (tmpptr && *tmpptr)
1436      if (!str_cmp(fname(tmpptr), "a") || !str_cmp(fname(tmpptr), "an") ||
1437  	!str_cmp(fname(tmpptr), "the"))
1438        *tmpptr = LOWER(*tmpptr);
1439  
1440    tmpptr = obj_proto[i].description = fread_string(obj_f, buf2);
1441    if (tmpptr && *tmpptr)
1442      CAP(tmpptr);
1443    obj_proto[i].action_description = fread_string(obj_f, buf2);
1444  
1445    /* *** numeric data *** */
1446    if (!get_line(obj_f, line)) {
1447      log("SYSERR: Expecting first numeric line of %s, but file ended!", buf2);
1448      exit(1);
1449    }
1450    if ((retval = sscanf(line, " %d %s %s", t, f1, f2)) != 3) {
1451      log("SYSERR: Format error in first numeric line (expecting 3 args, got %d), %s", retval, buf2);
1452      exit(1);
1453    }
1454  
1455    /* Object flags checked in check_object(). */
1456    GET_OBJ_TYPE(obj_proto + i) = t[0];
1457    GET_OBJ_EXTRA(obj_proto + i) = asciiflag_conv(f1);
1458    GET_OBJ_WEAR(obj_proto + i) = asciiflag_conv(f2);
1459  
1460    if (!get_line(obj_f, line)) {
1461      log("SYSERR: Expecting second numeric line of %s, but file ended!", buf2);
1462      exit(1);
1463    }
1464    if ((retval = sscanf(line, "%d %d %d %d", t, t + 1, t + 2, t + 3)) != 4) {
1465      log("SYSERR: Format error in second numeric line (expecting 4 args, got %d), %s", retval, buf2);
1466      exit(1);
1467    }
1468    GET_OBJ_VAL(obj_proto + i, 0) = t[0];
1469    GET_OBJ_VAL(obj_proto + i, 1) = t[1];
1470    GET_OBJ_VAL(obj_proto + i, 2) = t[2];
1471    GET_OBJ_VAL(obj_proto + i, 3) = t[3];
1472  
1473    if (!get_line(obj_f, line)) {
1474      log("SYSERR: Expecting third numeric line of %s, but file ended!", buf2);
1475      exit(1);
1476    }
1477    if ((retval = sscanf(line, "%d %d %d", t, t + 1, t + 2)) != 3) {
1478      log("SYSERR: Format error in third numeric line (expecting 3 args, got %d), %s", retval, buf2);
1479      exit(1);
1480    }
1481    GET_OBJ_WEIGHT(obj_proto + i) = t[0];
1482    GET_OBJ_COST(obj_proto + i) = t[1];
1483    GET_OBJ_RENT(obj_proto + i) = t[2];
1484  
1485    /* check to make sure that weight of containers exceeds curr. quantity */
1486    if (GET_OBJ_TYPE(obj_proto + i) == ITEM_DRINKCON || GET_OBJ_TYPE(obj_proto + i) == ITEM_FOUNTAIN) {
1487      if (GET_OBJ_WEIGHT(obj_proto + i) < GET_OBJ_VAL(obj_proto + i, 1))
1488        GET_OBJ_WEIGHT(obj_proto + i) = GET_OBJ_VAL(obj_proto + i, 1) + 5;
1489    }
1490  
1491    /* *** extra descriptions and affect fields *** */
1492  
1493    for (j = 0; j < MAX_OBJ_AFFECT; j++) {
1494      obj_proto[i].affected[j].location = APPLY_NONE;
1495      obj_proto[i].affected[j].modifier = 0;
1496    }
1497  
1498    strcat(buf2, ", after numeric constants\n"	/* strcat: OK (for 'buf2 >= 87') */
1499  	 "...expecting 'E', 'A', '$', or next object number");
1500    j = 0;
1501  
1502    for (;;) {
1503      if (!get_line(obj_f, line)) {
1504        log("SYSERR: Format error in %s", buf2);
1505        exit(1);
1506      }
1507      switch (*line) {
1508      case 'E':
1509        CREATE(new_descr, struct extra_descr_data, 1);
1510        new_descr->keyword = fread_string(obj_f, buf2);
1511        new_descr->description = fread_string(obj_f, buf2);
1512        new_descr->next = obj_proto[i].ex_description;
1513        obj_proto[i].ex_description = new_descr;
1514        break;
1515      case 'A':
1516        if (j >= MAX_OBJ_AFFECT) {
1517  	log("SYSERR: Too many A fields (%d max), %s", MAX_OBJ_AFFECT, buf2);
1518  	exit(1);
1519        }
1520        if (!get_line(obj_f, line)) {
1521  	log("SYSERR: Format error in 'A' field, %s\n"
1522  	    "...expecting 2 numeric constants but file ended!", buf2);
1523  	exit(1);
1524        }
1525  
1526        if ((retval = sscanf(line, " %d %d ", t, t + 1)) != 2) {
1527  	log("SYSERR: Format error in 'A' field, %s\n"
1528  	    "...expecting 2 numeric arguments, got %d\n"
1529  	    "...offending line: '%s'", buf2, retval, line);
1530  	exit(1);
1531        }
1532        obj_proto[i].affected[j].location = t[0];
1533        obj_proto[i].affected[j].modifier = t[1];
1534        j++;
1535        break;
1536      case '$':
1537      case '#':
1538        check_object(obj_proto + i);
1539        top_of_objt = i++;
1540        return (line);
1541      default:
1542        log("SYSERR: Format error in (%c): %s", *line, buf2);
1543        exit(1);
1544      }
1545    }
1546  }
1547  
1548  
1549  #define Z	zone_table[zone]
1550  
1551  /* load the zone table and command tables */
1552  void load_zones(FILE *fl, char *zonename)
1553  {
1554    static zone_rnum zone = 0;
1555    int cmd_no, num_of_cmds = 0, line_num = 0, tmp, error;
1556    char *ptr, buf[READ_SIZE], zname[READ_SIZE], buf2[MAX_STRING_LENGTH];
1557  
1558    strlcpy(zname, zonename, sizeof(zname));
1559  
1560    /* Skip first 3 lines lest we mistake the zone name for a command. */
1561    for (tmp = 0; tmp < 3; tmp++)
1562      get_line(fl, buf);
1563  
1564    /*  More accurate count. Previous was always 4 or 5 too high. -gg 2001/1/17
1565     *  Note that if a new zone command is added to reset_zone(), this string
1566     *  will need to be updated to suit. - ae.
1567     */
1568    while (get_line(fl, buf))
1569      if ((strchr("MOPGERD", buf[0]) && buf[1] == ' ') || (buf[0] == 'S' && buf[1] == '\0'))
1570        num_of_cmds++;
1571  
1572    rewind(fl);
1573  
1574    if (num_of_cmds == 0) {
1575      log("SYSERR: %s is empty!", zname);
1576      exit(1);
1577    } else
1578      CREATE(Z.cmd, struct reset_com, num_of_cmds);
1579  
1580    line_num += get_line(fl, buf);
1581  
1582    if (sscanf(buf, "#%hd", &Z.number) != 1) {
1583      log("SYSERR: Format error in %s, line %d", zname, line_num);
1584      exit(1);
1585    }
1586    snprintf(buf2, sizeof(buf2), "beginning of zone #%d", Z.number);
1587  
1588    line_num += get_line(fl, buf);
1589    if ((ptr = strchr(buf, '~')) != NULL)	/* take off the '~' if it's there */
1590      *ptr = '\0';
1591    Z.name = strdup(buf);
1592  
1593    line_num += get_line(fl, buf);
1594    if (sscanf(buf, " %hd %hd %d %d ", &Z.bot, &Z.top, &Z.lifespan, &Z.reset_mode) != 4) {
1595      log("SYSERR: Format error in numeric constant line of %s", zname);
1596      exit(1);
1597    }
1598    if (Z.bot > Z.top) {
1599      log("SYSERR: Zone %d bottom (%d) > top (%d).", Z.number, Z.bot, Z.top);
1600      exit(1);
1601    }
1602  
1603    cmd_no = 0;
1604  
1605    for (;;) {
1606      if ((tmp = get_line(fl, buf)) == 0) {
1607        log("SYSERR: Format error in %s - premature end of file", zname);
1608        exit(1);
1609      }
1610      line_num += tmp;
1611      ptr = buf;
1612      skip_spaces(&ptr);
1613  
1614      if ((ZCMD.command = *ptr) == '*')
1615        continue;
1616  
1617      ptr++;
1618  
1619      if (ZCMD.command == 'S' || ZCMD.command == '$') {
1620        ZCMD.command = 'S';
1621        break;
1622      }
1623      error = 0;
1624      if (strchr("MOEPD", ZCMD.command) == NULL) {	/* a 3-arg command */
1625        if (sscanf(ptr, " %d %d %d ", &tmp, &ZCMD.arg1, &ZCMD.arg2) != 3)
1626  	error = 1;
1627      } else {
1628        if (sscanf(ptr, " %d %d %d %d ", &tmp, &ZCMD.arg1, &ZCMD.arg2,
1629  		 &ZCMD.arg3) != 4)
1630  	error = 1;
1631      }
1632  
1633      ZCMD.if_flag = tmp;
1634  
1635      if (error) {
1636        log("SYSERR: Format error in %s, line %d: '%s'", zname, line_num, buf);
1637        exit(1);
1638      }
1639      ZCMD.line = line_num;
1640      cmd_no++;
1641    }
1642  
1643    if (num_of_cmds != cmd_no + 1) {
1644      log("SYSERR: Zone command count mismatch for %s. Estimated: %d, Actual: %d", zname, num_of_cmds, cmd_no + 1);
1645      exit(1);
1646    }
1647  
1648    top_of_zone_table = zone++;
1649  }
1650  
1651  #undef Z
1652  
1653  
1654  void get_one_line(FILE *fl, char *buf)
1655  {
1656    if (fgets(buf, READ_SIZE, fl) == NULL) {
1657      log("SYSERR: error reading help file: not terminated with $?");
1658      exit(1);
1659    }
1660  
1661    buf[strlen(buf) - 1] = '\0'; /* take off the trailing \n */
1662  }
1663  
1664  
1665  void free_help(void)
1666  {
1667    int hp;
1668  
1669    if (!help_table)
1670       return;
1671  
1672    for (hp = 0; hp <= top_of_helpt; hp++) {
1673      if (help_table[hp].keyword)
1674        free(help_table[hp].keyword);
1675      if (help_table[hp].entry && !help_table[hp].duplicate)
1676        free(help_table[hp].entry);
1677    }
1678  
1679    free(help_table);
1680    help_table = NULL;
1681    top_of_helpt = 0;
1682  }
1683  
1684  
1685  void load_help(FILE *fl)
1686  {
1687  #if defined(CIRCLE_MACINTOSH)
1688    static char key[READ_SIZE + 1], next_key[READ_SIZE + 1], entry[32384]; /* too big for stack? */
1689  #else
1690    char key[READ_SIZE + 1], next_key[READ_SIZE + 1], entry[32384];
1691  #endif
1692    size_t entrylen;
1693    char line[READ_SIZE + 1], *scan;
1694    struct help_index_element el;
1695  
1696    /* get the first keyword line */
1697    get_one_line(fl, key);
1698    while (*key != '$') {
1699      strcat(key, "\r\n");	/* strcat: OK (READ_SIZE - "\n" + "\r\n" == READ_SIZE + 1) */
1700      entrylen = strlcpy(entry, key, sizeof(entry));
1701  
1702      /* read in the corresponding help entry */
1703      get_one_line(fl, line);
1704      while (*line != '#' && entrylen < sizeof(entry) - 1) {
1705        entrylen += strlcpy(entry + entrylen, line, sizeof(entry) - entrylen);
1706  
1707        if (entrylen + 2 < sizeof(entry) - 1) {
1708          strcpy(entry + entrylen, "\r\n");	/* strcpy: OK (size checked above) */
1709          entrylen += 2;
1710        }
1711        get_one_line(fl, line);
1712      }
1713  
1714      if (entrylen >= sizeof(entry) - 1) {
1715        int keysize;
1716        const char *truncmsg = "\r\n*TRUNCATED*\r\n";
1717  
1718        strcpy(entry + sizeof(entry) - strlen(truncmsg) - 1, truncmsg);	/* strcpy: OK (assuming sane 'entry' size) */
1719  
1720        keysize = strlen(key) - 2;
1721        log("SYSERR: Help entry exceeded buffer space: %.*s", keysize, key);
1722  
1723        /* If we ran out of buffer space, eat the rest of the entry. */
1724        while (*line != '#')
1725          get_one_line(fl, line);
1726      }
1727  
1728      /* now, add the entry to the index with each keyword on the keyword line */
1729      el.duplicate = 0;
1730      el.entry = strdup(entry);
1731      scan = one_word(key, next_key);
1732      while (*next_key) {
1733        el.keyword = strdup(next_key);
1734        help_table[top_of_helpt++] = el;
1735        el.duplicate++;
1736        scan = one_word(scan, next_key);
1737      }
1738  
1739      /* get next keyword line (or $) */
1740      get_one_line(fl, key);
1741    }
1742  }
1743  
1744  
1745  int hsort(const void *a, const void *b)
1746  {
1747    const struct help_index_element *a1, *b1;
1748  
1749    a1 = (const struct help_index_element *) a;
1750    b1 = (const struct help_index_element *) b;
1751  
1752    return (str_cmp(a1->keyword, b1->keyword));
1753  }
1754  
1755  
1756  /*************************************************************************
1757  *  procedures for resetting, both play-time and boot-time	 	 *
1758  *************************************************************************/
1759  
1760  
1761  int vnum_mobile(char *searchname, struct char_data *ch)
1762  {
1763    int nr, found = 0;
1764  
1765    for (nr = 0; nr <= top_of_mobt; nr++)
1766      if (isname(searchname, mob_proto[nr].player.name))
1767        send_to_char(ch, "%3d. [%5d] %s\r\n", ++found, mob_index[nr].vnum, mob_proto[nr].player.short_descr);
1768  
1769    return (found);
1770  }
1771  
1772  
1773  
1774  int vnum_object(char *searchname, struct char_data *ch)
1775  {
1776    int nr, found = 0;
1777  
1778    for (nr = 0; nr <= top_of_objt; nr++)
1779      if (isname(searchname, obj_proto[nr].name))
1780        send_to_char(ch, "%3d. [%5d] %s\r\n", ++found, obj_index[nr].vnum, obj_proto[nr].short_description);
1781  
1782    return (found);
1783  }
1784  
1785  
1786  /* create a character, and add it to the char list */
1787  struct char_data *create_char(void)
1788  {
1789    struct char_data *ch;
1790  
1791    CREATE(ch, struct char_data, 1);
1792    clear_char(ch);
1793    ch->next = character_list;
1794    character_list = ch;
1795  
1796    return (ch);
1797  }
1798  
1799  
1800  /* create a new mobile from a prototype */
1801  struct char_data *read_mobile(mob_vnum nr, int type) /* and mob_rnum */
1802  {
1803    mob_rnum i;
1804    struct char_data *mob;
1805  
1806    if (type == VIRTUAL) {
1807      if ((i = real_mobile(nr)) == NOBODY) {
1808        log("WARNING: Mobile vnum %d does not exist in database.", nr);
1809        return (NULL);
1810      }
1811    } else
1812      i = nr;
1813  
1814    CREATE(mob, struct char_data, 1);
1815    clear_char(mob);
1816    *mob = mob_proto[i];
1817    mob->next = character_list;
1818    character_list = mob;
1819  
1820    if (!mob->points.max_hit) {
1821      mob->points.max_hit = dice(mob->points.hit, mob->points.mana) +
1822        mob->points.move;
1823    } else
1824      mob->points.max_hit = rand_number(mob->points.hit, mob->points.mana);
1825  
1826    mob->points.hit = mob->points.max_hit;
1827    mob->points.mana = mob->points.max_mana;
1828    mob->points.move = mob->points.max_move;
1829  
1830    mob->player.time.birth = time(0);
1831    mob->player.time.played = 0;
1832    mob->player.time.logon = time(0);
1833  
1834    mob_index[i].number++;
1835  
1836    return (mob);
1837  }
1838  
1839  
1840  /* create an object, and add it to the object list */
1841  struct obj_data *create_obj(void)
1842  {
1843    struct obj_data *obj;
1844  
1845    CREATE(obj, struct obj_data, 1);
1846    clear_object(obj);
1847    obj->next = object_list;
1848    object_list = obj;
1849  
1850    return (obj);
1851  }
1852  
1853  
1854  /* create a new object from a prototype */
1855  struct obj_data *read_object(obj_vnum nr, int type) /* and obj_rnum */
1856  {
1857    struct obj_data *obj;
1858    obj_rnum i = type == VIRTUAL ? real_object(nr) : nr;
1859  
1860    if (i == NOTHING || i > top_of_objt) {
1861      log("Object (%c) %d does not exist in database.", type == VIRTUAL ? 'V' : 'R', nr);
1862      return (NULL);
1863    }
1864  
1865    CREATE(obj, struct obj_data, 1);
1866    clear_object(obj);
1867    *obj = obj_proto[i];
1868    obj->next = object_list;
1869    object_list = obj;
1870  
1871    obj_index[i].number++;
1872  
1873    return (obj);
1874  }
1875  
1876  
1877  
1878  #define ZO_DEAD  999
1879  
1880  /* update zone ages, queue for reset if necessary, and dequeue when possible */
1881  void zone_update(void)
1882  {
1883    int i;
1884    struct reset_q_element *update_u, *temp;
1885    static int timer = 0;
1886  
1887    /* jelson 10/22/92 */
1888    if (((++timer * PULSE_ZONE) / PASSES_PER_SEC) >= 60) {
1889      /* one minute has passed */
1890      /*
1891       * NOT accurate unless PULSE_ZONE is a multiple of PASSES_PER_SEC or a
1892       * factor of 60
1893       */
1894  
1895      timer = 0;
1896  
1897      /* since one minute has passed, increment zone ages */
1898      for (i = 0; i <= top_of_zone_table; i++) {
1899        if (zone_table[i].age < zone_table[i].lifespan &&
1900  	  zone_table[i].reset_mode)
1901  	(zone_table[i].age)++;
1902  
1903        if (zone_table[i].age >= zone_table[i].lifespan &&
1904  	  zone_table[i].age < ZO_DEAD && zone_table[i].reset_mode) {
1905  	/* enqueue zone */
1906  
1907  	CREATE(update_u, struct reset_q_element, 1);
1908  
1909  	update_u->zone_to_reset = i;
1910  	update_u->next = 0;
1911  
1912  	if (!reset_q.head)
1913  	  reset_q.head = reset_q.tail = update_u;
1914  	else {
1915  	  reset_q.tail->next = update_u;
1916  	  reset_q.tail = update_u;
1917  	}
1918  
1919  	zone_table[i].age = ZO_DEAD;
1920        }
1921      }
1922    }	/* end - one minute has passed */
1923  
1924  
1925    /* dequeue zones (if possible) and reset */
1926    /* this code is executed every 10 seconds (i.e. PULSE_ZONE) */
1927    for (update_u = reset_q.head; update_u; update_u = update_u->next)
1928      if (zone_table[update_u->zone_to_reset].reset_mode == 2 ||
1929  	is_empty(update_u->zone_to_reset)) {
1930        reset_zone(update_u->zone_to_reset);
1931        mudlog(CMP, LVL_GOD, FALSE, "Auto zone reset: %s", zone_table[update_u->zone_to_reset].name);
1932        /* dequeue */
1933        if (update_u == reset_q.head)
1934  	reset_q.head = reset_q.head->next;
1935        else {
1936  	for (temp = reset_q.head; temp->next != update_u;
1937  	     temp = temp->next);
1938  
1939  	if (!update_u->next)
1940  	  reset_q.tail = temp;
1941  
1942  	temp->next = update_u->next;
1943        }
1944  
1945        free(update_u);
1946        break;
1947      }
1948  }
1949  
1950  void log_zone_error(zone_rnum zone, int cmd_no, const char *message)
1951  {
1952    mudlog(NRM, LVL_GOD, TRUE, "SYSERR: zone file: %s", message);
1953    mudlog(NRM, LVL_GOD, TRUE, "SYSERR: ...offending cmd: '%c' cmd in zone #%d, line %d",
1954  	ZCMD.command, zone_table[zone].number, ZCMD.line);
1955  }
1956  
1957  #define ZONE_ERROR(message) \
1958  	{ log_zone_error(zone, cmd_no, message); last_cmd = 0; }
1959  
1960  /* execute the reset command table of a given zone */
1961  void reset_zone(zone_rnum zone)
1962  {
1963    int cmd_no, last_cmd = 0;
1964    struct char_data *mob = NULL;
1965    struct obj_data *obj, *obj_to;
1966  
1967    for (cmd_no = 0; ZCMD.command != 'S'; cmd_no++) {
1968  
1969      if (ZCMD.if_flag && !last_cmd)
1970        continue;
1971  
1972      /*  This is the list of actual zone commands.  If any new
1973       *  zone commands are added to the game, be certain to update
1974       *  the list of commands in load_zone() so that the counting
1975       *  will still be correct. - ae.
1976       */
1977      switch (ZCMD.command) {
1978      case '*':			/* ignore command */
1979        last_cmd = 0;
1980        break;
1981  
1982      case 'M':			/* read a mobile */
1983        if (mob_index[ZCMD.arg1].number < ZCMD.arg2) {
1984  	mob = read_mobile(ZCMD.arg1, REAL);
1985  	char_to_room(mob, ZCMD.arg3);
1986  	last_cmd = 1;
1987        } else
1988  	last_cmd = 0;
1989        break;
1990  
1991      case 'O':			/* read an object */
1992        if (obj_index[ZCMD.arg1].number < ZCMD.arg2) {
1993  	if (ZCMD.arg3 != NOWHERE) {
1994  	  obj = read_object(ZCMD.arg1, REAL);
1995  	  obj_to_room(obj, ZCMD.arg3);
1996  	  last_cmd = 1;
1997  	} else {
1998  	  obj = read_object(ZCMD.arg1, REAL);
1999  	  IN_ROOM(obj) = NOWHERE;
2000  	  last_cmd = 1;
2001  	}
2002        } else
2003  	last_cmd = 0;
2004        break;
2005  
2006      case 'P':			/* object to object */
2007        if (obj_index[ZCMD.arg1].number < ZCMD.arg2) {
2008  	obj = read_object(ZCMD.arg1, REAL);
2009  	if (!(obj_to = get_obj_num(ZCMD.arg3))) {
2010  	  ZONE_ERROR("target obj not found, command disabled");
2011  	  ZCMD.command = '*';
2012  	  break;
2013  	}
2014  	obj_to_obj(obj, obj_to);
2015  	last_cmd = 1;
2016        } else
2017  	last_cmd = 0;
2018        break;
2019  
2020      case 'G':			/* obj_to_char */
2021        if (!mob) {
2022  	ZONE_ERROR("attempt to give obj to non-existant mob, command disabled");
2023  	ZCMD.command = '*';
2024  	break;
2025        }
2026        if (obj_index[ZCMD.arg1].number < ZCMD.arg2) {
2027  	obj = read_object(ZCMD.arg1, REAL);
2028  	obj_to_char(obj, mob);
2029  	last_cmd = 1;
2030        } else
2031  	last_cmd = 0;
2032        break;
2033  
2034      case 'E':			/* object to equipment list */
2035        if (!mob) {
2036  	ZONE_ERROR("trying to equip non-existant mob, command disabled");
2037  	ZCMD.command = '*';
2038  	break;
2039        }
2040        if (obj_index[ZCMD.arg1].number < ZCMD.arg2) {
2041  	if (ZCMD.arg3 < 0 || ZCMD.arg3 >= NUM_WEARS) {
2042  	  ZONE_ERROR("invalid equipment pos number");
2043  	} else {
2044  	  obj = read_object(ZCMD.arg1, REAL);
2045  	  equip_char(mob, obj, ZCMD.arg3);
2046  	  last_cmd = 1;
2047  	}
2048        } else
2049  	last_cmd = 0;
2050        break;
2051  
2052      case 'R': /* rem obj from room */
2053        if ((obj = get_obj_in_list_num(ZCMD.arg2, world[ZCMD.arg1].contents)) != NULL)
2054          extract_obj(obj);
2055        last_cmd = 1;
2056        break;
2057  
2058  
2059      case 'D':			/* set state of door */
2060        if (ZCMD.arg2 < 0 || ZCMD.arg2 >= NUM_OF_DIRS ||
2061  	  (world[ZCMD.arg1].dir_option[ZCMD.arg2] == NULL)) {
2062  	ZONE_ERROR("door does not exist, command disabled");
2063  	ZCMD.command = '*';
2064        } else
2065  	switch (ZCMD.arg3) {
2066  	case 0:
2067  	  REMOVE_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2068  		     EX_LOCKED);
2069  	  REMOVE_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2070  		     EX_CLOSED);
2071  	  break;
2072  	case 1:
2073  	  SET_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2074  		  EX_CLOSED);
2075  	  REMOVE_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2076  		     EX_LOCKED);
2077  	  break;
2078  	case 2:
2079  	  SET_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2080  		  EX_LOCKED);
2081  	  SET_BIT(world[ZCMD.arg1].dir_option[ZCMD.arg2]->exit_info,
2082  		  EX_CLOSED);
2083  	  break;
2084  	}
2085        last_cmd = 1;
2086        break;
2087  
2088      default:
2089        ZONE_ERROR("unknown cmd in reset table; cmd disabled");
2090        ZCMD.command = '*';
2091        break;
2092      }
2093    }
2094  
2095    zone_table[zone].age = 0;
2096  }
2097  
2098  
2099  
2100  /* for use in reset_zone; return TRUE if zone 'nr' is free of PC's  */
2101  int is_empty(zone_rnum zone_nr)
2102  {
2103    struct descriptor_data *i;
2104  
2105    for (i = descriptor_list; i; i = i->next) {
2106      if (STATE(i) != CON_PLAYING)
2107        continue;
2108      if (IN_ROOM(i->character) == NOWHERE)
2109        continue;
2110      if (GET_LEVEL(i->character) >= LVL_IMMORT)
2111        continue;
2112      if (world[IN_ROOM(i->character)].zone != zone_nr)
2113        continue;
2114  
2115      return (0);
2116    }
2117  
2118    return (1);
2119  }
2120  
2121  
2122  
2123  
2124  
2125  /*************************************************************************
2126  *  stuff related to the save/load player system				 *
2127  *************************************************************************/
2128  
2129  
2130  long get_ptable_by_name(const char *name)
2131  {
2132    int i;
2133  
2134    for (i = 0; i <= top_of_p_table; i++)
2135      if (!str_cmp(player_table[i].name, name))
2136        return (i);
2137  
2138    return (-1);
2139  }
2140  
2141  
2142  long get_id_by_name(const char *name)
2143  {
2144    int i;
2145  
2146    for (i = 0; i <= top_of_p_table; i++)
2147      if (!str_cmp(player_table[i].name, name))
2148        return (player_table[i].id);
2149  
2150    return (-1);
2151  }
2152  
2153  
2154  char *get_name_by_id(long id)
2155  {
2156    int i;
2157  
2158    for (i = 0; i <= top_of_p_table; i++)
2159      if (player_table[i].id == id)
2160        return (player_table[i].name);
2161  
2162    return (NULL);
2163  }
2164  
2165  
2166  /* Load a char, TRUE if loaded, FALSE if not */
2167  int load_char(const char *name, struct char_file_u *char_element)
2168  {
2169    int player_i;
2170  
2171    if ((player_i = get_ptable_by_name(name)) >= 0) {
2172      fseek(player_fl, player_i * sizeof(struct char_file_u), SEEK_SET);
2173      fread(char_element, sizeof(struct char_file_u), 1, player_fl);
2174      return (player_i);
2175    } else
2176      return (-1);
2177  }
2178  
2179  
2180  
2181  
2182  /*
2183   * write the vital data of a player to the player file
2184   *
2185   * And that's it! No more fudging around with the load room.
2186   * Unfortunately, 'host' modifying is still here due to lack
2187   * of that variable in the char_data structure.
2188   */
2189  void save_char(struct char_data *ch)
2190  {
2191    struct char_file_u st;
2192  
2193    if (IS_NPC(ch) || !ch->desc || GET_PFILEPOS(ch) < 0)
2194      return;
2195  
2196    char_to_store(ch, &st);
2197  
2198    strncpy(st.host, ch->desc->host, HOST_LENGTH);	/* strncpy: OK (s.host:HOST_LENGTH+1) */
2199    st.host[HOST_LENGTH] = '\0';
2200  
2201    fseek(player_fl, GET_PFILEPOS(ch) * sizeof(struct char_file_u), SEEK_SET);
2202    fwrite(&st, sizeof(struct char_file_u), 1, player_fl);
2203  }
2204  
2205  
2206  
2207  /* copy data from the file structure to a char struct */
2208  void store_to_char(struct char_file_u *st, struct char_data *ch)
2209  {
2210    int i;
2211  
2212    /* to save memory, only PC's -- not MOB's -- have player_specials */
2213    if (ch->player_specials == NULL)
2214      CREATE(ch->player_specials, struct player_special_data, 1);
2215  
2216    GET_SEX(ch) = st->sex;
2217    GET_CLASS(ch) = st->chclass;
2218    GET_LEVEL(ch) = st->level;
2219  
2220    ch->player.short_descr = NULL;
2221    ch->player.long_descr = NULL;
2222    ch->player.title = strdup(st->title);
2223    ch->player.description = strdup(st->description);
2224  
2225    ch->player.hometown = st->hometown;
2226    ch->player.time.birth = st->birth;
2227    ch->player.time.played = st->played;
2228    ch->player.time.logon = time(0);
2229  
2230    ch->player.weight = st->weight;
2231    ch->player.height = st->height;
2232  
2233    ch->real_abils = st->abilities;
2234    ch->aff_abils = st->abilities;
2235    ch->points = st->points;
2236    ch->char_specials.saved = st->char_specials_saved;
2237    ch->player_specials->saved = st->player_specials_saved;
2238    POOFIN(ch) = NULL;
2239    POOFOUT(ch) = NULL;
2240    GET_LAST_TELL(ch) = NOBODY;
2241  
2242    if (ch->points.max_mana < 100)
2243      ch->points.max_mana = 100;
2244  
2245    ch->char_specials.carry_weight = 0;
2246    ch->char_specials.carry_items = 0;
2247    ch->points.armor = 100;
2248    ch->points.hitroll = 0;
2249    ch->points.damroll = 0;
2250  
2251    if (ch->player.name)
2252      free(ch->player.name);
2253    ch->player.name = strdup(st->name);
2254    strlcpy(ch->player.passwd, st->pwd, sizeof(ch->player.passwd));
2255  
2256    /* Add all spell effects */
2257    for (i = 0; i < MAX_AFFECT; i++) {
2258      if (st->affected[i].type)
2259        affect_to_char(ch, &st->affected[i]);
2260    }
2261  
2262    /*
2263     * If you're not poisioned and you've been away for more than an hour of
2264     * real time, we'll set your HMV back to full
2265     */
2266  
2267    if (!AFF_FLAGGED(ch, AFF_POISON) &&
2268  	time(0) - st->last_logon >= SECS_PER_REAL_HOUR) {
2269      GET_HIT(ch) = GET_MAX_HIT(ch);
2270      GET_MOVE(ch) = GET_MAX_MOVE(ch);
2271      GET_MANA(ch) = GET_MAX_MANA(ch);
2272    }
2273  }				/* store_to_char */
2274  
2275  
2276  
2277  
2278  /* copy vital data from a players char-structure to the file structure */
2279  void char_to_store(struct char_data *ch, struct char_file_u *st)
2280  {
2281    int i;
2282    struct affected_type *af;
2283    struct obj_data *char_eq[NUM_WEARS];
2284  
2285    /* Unaffect everything a character can be affected by */
2286  
2287    for (i = 0; i < NUM_WEARS; i++) {
2288      if (GET_EQ(ch, i))
2289        char_eq[i] = unequip_char(ch, i);
2290      else
2291        char_eq[i] = NULL;
2292    }
2293  
2294    for (af = ch->affected, i = 0; i < MAX_AFFECT; i++) {
2295      if (af) {
2296        st->affected[i] = *af;
2297        st->affected[i].next = 0;
2298        af = af->next;
2299      } else {
2300        st->affected[i].type = 0;	/* Zero signifies not used */
2301        st->affected[i].duration = 0;
2302        st->affected[i].modifier = 0;
2303        st->affected[i].location = 0;
2304        st->affected[i].bitvector = 0;
2305        st->affected[i].next = 0;
2306      }
2307    }
2308  
2309  
2310    /*
2311     * remove the affections so that the raw values are stored; otherwise the
2312     * effects are doubled when the char logs back in.
2313     */
2314  
2315    while (ch->affected)
2316      affect_remove(ch, ch->affected);
2317  
2318    if ((i >= MAX_AFFECT) && af && af->next)
2319      log("SYSERR: WARNING: OUT OF STORE ROOM FOR AFFECTED TYPES!!!");
2320  
2321    ch->aff_abils = ch->real_abils;
2322  
2323    st->birth = ch->player.time.birth;
2324    st->played = ch->player.time.played;
2325    st->played += time(0) - ch->player.time.logon;
2326    st->last_logon = time(0);
2327  
2328    ch->player.time.played = st->played;
2329    ch->player.time.logon = time(0);
2330  
2331    st->hometown = ch->player.hometown;
2332    st->weight = GET_WEIGHT(ch);
2333    st->height = GET_HEIGHT(ch);
2334    st->sex = GET_SEX(ch);
2335    st->chclass = GET_CLASS(ch);
2336    st->level = GET_LEVEL(ch);
2337    st->abilities = ch->real_abils;
2338    st->points = ch->points;
2339    st->char_specials_saved = ch->char_specials.saved;
2340    st->player_specials_saved = ch->player_specials->saved;
2341  
2342    st->points.armor = 100;
2343    st->points.hitroll = 0;
2344    st->points.damroll = 0;
2345  
2346    if (GET_TITLE(ch))
2347      strlcpy(st->title, GET_TITLE(ch), MAX_TITLE_LENGTH);
2348    else
2349      *st->title = '\0';
2350  
2351    if (ch->player.description) {
2352      if (strlen(ch->player.description) >= sizeof(st->description)) {
2353        log("SYSERR: char_to_store: %s's description length: %d, max: %d! "
2354           "Truncated.", GET_PC_NAME(ch), strlen(ch->player.description),
2355           sizeof(st->description));
2356        ch->player.description[sizeof(st->description) - 3] = '\0';
2357        strcat(ch->player.description, "\r\n");	/* strcat: OK (previous line makes room) */
2358      }
2359      strcpy(st->description, ch->player.description);	/* strcpy: OK (checked above) */
2360    } else
2361      *st->description = '\0';
2362  
2363    strcpy(st->name, GET_NAME(ch));	/* strcpy: OK (that's what GET_NAME came from) */
2364    strcpy(st->pwd, GET_PASSWD(ch));	/* strcpy: OK (that's what GET_PASSWD came from) */
2365  
2366    /* add spell and eq affections back in now */
2367    for (i = 0; i < MAX_AFFECT; i++) {
2368      if (st->affected[i].type)
2369        affect_to_char(ch, &st->affected[i]);
2370    }
2371  
2372    for (i = 0; i < NUM_WEARS; i++) {
2373      if (char_eq[i])
2374        equip_char(ch, char_eq[i], i);
2375    }
2376  /*   affect_total(ch); unnecessary, I think !?! */
2377  }				/* Char to store */
2378  
2379  
2380  
2381  void save_etext(struct char_data *ch)
2382  {
2383  /* this will be really cool soon */
2384  }
2385  
2386  
2387  /*
2388   * Create a new entry in the in-memory index table for the player file.
2389   * If the name already exists, by overwriting a deleted character, then
2390   * we re-use the old position.
2391   */
2392  int create_entry(char *name)
2393  {
2394    int i, pos;
2395  
2396    if (top_of_p_table == -1) {	/* no table */
2397      CREATE(player_table, struct player_index_element, 1);
2398      pos = top_of_p_table = 0;
2399    } else if ((pos = get_ptable_by_name(name)) == -1) {	/* new name */
2400      i = ++top_of_p_table + 1;
2401  
2402      RECREATE(player_table, struct player_index_element, i);
2403      pos = top_of_p_table;
2404    }
2405  
2406    CREATE(player_table[pos].name, char, strlen(name) + 1);
2407  
2408    /* copy lowercase equivalent of name to table field */
2409    for (i = 0; (player_table[pos].name[i] = LOWER(name[i])); i++)
2410  	/* Nothing */;
2411  
2412    return (pos);
2413  }
2414  
2415  
2416  
2417  /************************************************************************
2418  *  funcs of a (more or less) general utility nature			*
2419  ************************************************************************/
2420  
2421  
2422  /* read and allocate space for a '~'-terminated string from a given file */
2423  char *fread_string(FILE *fl, const char *error)
2424  {
2425    char buf[MAX_STRING_LENGTH], tmp[513];
2426    char *point;
2427    int done = 0, length = 0, templength;
2428  
2429    *buf = '\0';
2430  
2431    do {
2432      if (!fgets(tmp, 512, fl)) {
2433        log("SYSERR: fread_string: format error at or near %s", error);
2434        exit(1);
2435      }
2436      /* If there is a '~', end the string; else put an "\r\n" over the '\n'. */
2437      if ((point = strchr(tmp, '~')) != NULL) {
2438        *point = '\0';
2439        done = 1;
2440      } else {
2441        point = tmp + strlen(tmp) - 1;
2442        *(point++) = '\r';
2443        *(point++) = '\n';
2444        *point = '\0';
2445      }
2446  
2447      templength = strlen(tmp);
2448  
2449      if (length + templength >= MAX_STRING_LENGTH) {
2450        log("SYSERR: fread_string: string too large (db.c)");
2451        log("%s", error);
2452        exit(1);
2453      } else {
2454        strcat(buf + length, tmp);	/* strcat: OK (size checked above) */
2455        length += templength;
2456      }
2457    } while (!done);
2458  
2459    /* allocate space for the new string and copy it */
2460    return (strlen(buf) ? strdup(buf) : NULL);
2461  }
2462  
2463  
2464  /* release memory allocated for a char struct */
2465  void free_char(struct char_data *ch)
2466  {
2467    int i;
2468    struct alias_data *a;
2469  
2470    if (ch->player_specials != NULL && ch->player_specials != &dummy_mob) {
2471      while ((a = GET_ALIASES(ch)) != NULL) {
2472        GET_ALIASES(ch) = (GET_ALIASES(ch))->next;
2473        free_alias(a);
2474      }
2475      if (ch->player_specials->poofin)
2476        free(ch->player_specials->poofin);
2477      if (ch->player_specials->poofout)
2478        free(ch->player_specials->poofout);
2479      free(ch->player_specials);
2480      if (IS_NPC(ch))
2481        log("SYSERR: Mob %s (#%d) had player_specials allocated!", GET_NAME(ch), GET_MOB_VNUM(ch));
2482    }
2483    if (!IS_NPC(ch) || (IS_NPC(ch) && GET_MOB_RNUM(ch) == NOBODY)) {
2484      /* if this is a player, or a non-prototyped non-player, free all */
2485      if (GET_NAME(ch))
2486        free(GET_NAME(ch));
2487      if (ch->player.title)
2488        free(ch->player.title);
2489      if (ch->player.short_descr)
2490        free(ch->player.short_descr);
2491      if (ch->player.long_descr)
2492        free(ch->player.long_descr);
2493      if (ch->player.description)
2494        free(ch->player.description);
2495    } else if ((i = GET_MOB_RNUM(ch)) != NOBODY) {
2496      /* otherwise, free strings only if the string is not pointing at proto */
2497      if (ch->player.name && ch->player.name != mob_proto[i].player.name)
2498        free(ch->player.name);
2499      if (ch->player.title && ch->player.title != mob_proto[i].player.title)
2500        free(ch->player.title);
2501      if (ch->player.short_descr && ch->player.short_descr != mob_proto[i].player.short_descr)
2502        free(ch->player.short_descr);
2503      if (ch->player.long_descr && ch->player.long_descr != mob_proto[i].player.long_descr)
2504        free(ch->player.long_descr);
2505      if (ch->player.description && ch->player.description != mob_proto[i].player.description)
2506        free(ch->player.description);
2507    }
2508    while (ch->affected)
2509      affect_remove(ch, ch->affected);
2510  
2511    if (ch->desc)
2512      ch->desc->character = NULL;
2513  
2514    free(ch);
2515  }
2516  
2517  
2518  
2519  
2520  /* release memory allocated for an obj struct */
2521  void free_obj(struct obj_data *obj)
2522  {
2523    int nr;
2524  
2525    if ((nr = GET_OBJ_RNUM(obj)) == NOTHING) {
2526      if (obj->name)
2527        free(obj->name);
2528      if (obj->description)
2529        free(obj->description);
2530      if (obj->short_description)
2531        free(obj->short_description);
2532      if (obj->action_description)
2533        free(obj->action_description);
2534      if (obj->ex_description)
2535        free_extra_descriptions(obj->ex_description);
2536    } else {
2537      if (obj->name && obj->name != obj_proto[nr].name)
2538        free(obj->name);
2539      if (obj->description && obj->description != obj_proto[nr].description)
2540        free(obj->description);
2541      if (obj->short_description && obj->short_description != obj_proto[nr].short_description)
2542        free(obj->short_description);
2543      if (obj->action_description && obj->action_description != obj_proto[nr].action_description)
2544        free(obj->action_description);
2545      if (obj->ex_description && obj->ex_description != obj_proto[nr].ex_description)
2546        free_extra_descriptions(obj->ex_description);
2547    }
2548  
2549    free(obj);
2550  }
2551  
2552  
2553  /*
2554   * Steps:
2555   *   1: Read contents of a text file.
2556   *   2: Make sure no one is using the pointer in paging.
2557   *   3: Allocate space.
2558   *   4: Point 'buf' to it.
2559   *
2560   * We don't want to free() the string that someone may be
2561   * viewing in the pager.  page_string() keeps the internal
2562   * strdup()'d copy on ->showstr_head and it won't care
2563   * if we delete the original.  Otherwise, strings are kept
2564   * on ->showstr_vector but we'll only match if the pointer
2565   * is to the string we're interested in and not a copy.
2566   *
2567   * If someone is reading a global copy we're trying to
2568   * replace, give everybody using it a different copy so
2569   * as to avoid special cases.
2570   */
2571  int file_to_string_alloc(const char *name, char **buf)
2572  {
2573    int temppage;
2574    char temp[MAX_STRING_LENGTH];
2575    struct descriptor_data *in_use;
2576  
2577    for (in_use = descriptor_list; in_use; in_use = in_use->next)
2578      if (in_use->showstr_vector && *in_use->showstr_vector == *buf)
2579        return (-1);
2580  
2581    /* Lets not free() what used to be there unless we succeeded. */
2582    if (file_to_string(name, temp) < 0)
2583      return (-1);
2584  
2585    for (in_use = descriptor_list; in_use; in_use = in_use->next) {
2586      if (!in_use->showstr_count || *in_use->showstr_vector != *buf)
2587        continue;
2588  
2589      /* Let's be nice and leave them at the page they were on. */
2590      temppage = in_use->showstr_page;
2591      paginate_string((in_use->showstr_head = strdup(*in_use->showstr_vector)), in_use);
2592      in_use->showstr_page = temppage;
2593    }
2594  
2595    if (*buf)
2596      free(*buf);
2597  
2598    *buf = strdup(temp);
2599    return (0);
2600  }
2601  
2602  
2603  /* read contents of a text file, and place in buf */
2604  int file_to_string(const char *name, char *buf)
2605  {
2606    FILE *fl;
2607    char tmp[READ_SIZE + 3];
2608    int len;
2609  
2610    *buf = '\0';
2611  
2612    if (!(fl = fopen(name, "r"))) {
2613      log("SYSERR: reading %s: %s", name, strerror(errno));
2614      return (-1);
2615    }
2616  
2617    for (;;) {
2618      if (!fgets(tmp, READ_SIZE, fl))	/* EOF check */
2619        break;
2620      if ((len = strlen(tmp)) > 0)
2621        tmp[len - 1] = '\0'; /* take off the trailing \n */
2622      strcat(tmp, "\r\n");	/* strcat: OK (tmp:READ_SIZE+3) */
2623  
2624      if (strlen(buf) + strlen(tmp) + 1 > MAX_STRING_LENGTH) {
2625        log("SYSERR: %s: string too big (%d max)", name, MAX_STRING_LENGTH);
2626        *buf = '\0';
2627        fclose(fl);
2628        return (-1);
2629      }
2630      strcat(buf, tmp);	/* strcat: OK (size checked above) */
2631    }
2632  
2633    fclose(fl);
2634  
2635    return (0);
2636  }
2637  
2638  
2639  
2640  /* clear some of the the working variables of a char */
2641  void reset_char(struct char_data *ch)
2642  {
2643    int i;
2644  
2645    for (i = 0; i < NUM_WEARS; i++)
2646      GET_EQ(ch, i) = NULL;
2647  
2648    ch->followers = NULL;
2649    ch->master = NULL;
2650    IN_ROOM(ch) = NOWHERE;
2651    ch->carrying = NULL;
2652    ch->next = NULL;
2653    ch->next_fighting = NULL;
2654    ch->next_in_room = NULL;
2655    FIGHTING(ch) = NULL;
2656    ch->char_specials.position = POS_STANDING;
2657    ch->mob_specials.default_pos = POS_STANDING;
2658    ch->char_specials.carry_weight = 0;
2659    ch->char_specials.carry_items = 0;
2660  
2661    if (GET_HIT(ch) <= 0)
2662      GET_HIT(ch) = 1;
2663    if (GET_MOVE(ch) <= 0)
2664      GET_MOVE(ch) = 1;
2665    if (GET_MANA(ch) <= 0)
2666      GET_MANA(ch) = 1;
2667  
2668    GET_LAST_TELL(ch) = NOBODY;
2669  }
2670  
2671  
2672  
2673  /* clear ALL the working variables of a char; do NOT free any space alloc'ed */
2674  void clear_char(struct char_data *ch)
2675  {
2676    memset((char *) ch, 0, sizeof(struct char_data));
2677  
2678    IN_ROOM(ch) = NOWHERE;
2679    GET_PFILEPOS(ch) = -1;
2680    GET_MOB_RNUM(ch) = NOBODY;
2681    GET_WAS_IN(ch) = NOWHERE;
2682    GET_POS(ch) = POS_STANDING;
2683    ch->mob_specials.default_pos = POS_STANDING;
2684  
2685    GET_AC(ch) = 100;		/* Basic Armor */
2686    if (ch->points.max_mana < 100)
2687      ch->points.max_mana = 100;
2688  }
2689  
2690  
2691  void clear_object(struct obj_data *obj)
2692  {
2693    memset((char *) obj, 0, sizeof(struct obj_data));
2694  
2695    obj->item_number = NOTHING;
2696    IN_ROOM(obj) = NOWHERE;
2697    obj->worn_on = NOWHERE;
2698  }
2699  
2700  
2701  
2702  
2703  /*
2704   * Called during character creation after picking character class
2705   * (and then never again for that character).
2706   */
2707  void init_char(struct char_data *ch)
2708  {
2709    int i;
2710  
2711    /* create a player_special structure */
2712    if (ch->player_specials == NULL)
2713      CREATE(ch->player_specials, struct player_special_data, 1);
2714  
2715    /* *** if this is our first player --- he be God *** */
2716    if (top_of_p_table == 0) {
2717      GET_LEVEL(ch) = LVL_IMPL;
2718      GET_EXP(ch) = 7000000;
2719  
2720      /* The implementor never goes through do_start(). */
2721      GET_MAX_HIT(ch) = 500;
2722      GET_MAX_MANA(ch) = 100;
2723      GET_MAX_MOVE(ch) = 82;
2724      GET_HIT(ch) = GET_MAX_HIT(ch);
2725      GET_MANA(ch) = GET_MAX_MANA(ch);
2726      GET_MOVE(ch) = GET_MAX_MOVE(ch);
2727    }
2728  
2729    set_title(ch, NULL);
2730    ch->player.short_descr = NULL;
2731    ch->player.long_descr = NULL;
2732    ch->player.description = NULL;
2733  
2734    ch->player.time.birth = time(0);
2735    ch->player.time.logon = time(0);
2736    ch->player.time.played = 0;
2737  
2738    GET_HOME(ch) = 1;
2739    GET_AC(ch) = 100;
2740  
2741    for (i = 0; i < MAX_TONGUE; i++)
2742      GET_TALK(ch, i) = 0;
2743  
2744    /*
2745     * make favors for sex -- or in English, we bias the height and weight of the
2746     * character depending on what gender they've chosen for themselves. While it
2747     * is possible to have a tall, heavy female it's not as likely as a male.
2748     *
2749     * Height is in centimeters. Weight is in pounds.  The only place they're
2750     * ever printed (in stock code) is SPELL_IDENTIFY.
2751     */
2752    if (GET_SEX(ch) == SEX_MALE) {
2753      GET_WEIGHT(ch) = rand_number(120, 180);
2754      GET_HEIGHT(ch) = rand_number(160, 200); /* 5'4" - 6'8" */
2755    } else {
2756      GET_WEIGHT(ch) = rand_number(100, 160);
2757      GET_HEIGHT(ch) = rand_number(150, 180); /* 5'0" - 6'0" */
2758    }
2759  
2760    if ((i = get_ptable_by_name(GET_NAME(ch))) != -1)
2761      player_table[i].id = GET_IDNUM(ch) = ++top_idnum;
2762    else
2763      log("SYSERR: init_char: Character '%s' not found in player table.", GET_NAME(ch));
2764  
2765    for (i = 1; i <= MAX_SKILLS; i++) {
2766      if (GET_LEVEL(ch) < LVL_IMPL)
2767        SET_SKILL(ch, i, 0);
2768      else
2769        SET_SKILL(ch, i, 100);
2770    }
2771  
2772    AFF_FLAGS(ch) = 0;
2773  
2774    for (i = 0; i < 5; i++)
2775      GET_SAVE(ch, i) = 0;
2776  
2777    ch->real_abils.intel = 25;
2778    ch->real_abils.wis = 25;
2779    ch->real_abils.dex = 25;
2780    ch->real_abils.str = 25;
2781    ch->real_abils.str_add = 100;
2782    ch->real_abils.con = 25;
2783    ch->real_abils.cha = 25;
2784  
2785    for (i = 0; i < 3; i++)
2786      GET_COND(ch, i) = (GET_LEVEL(ch) == LVL_IMPL ? -1 : 24);
2787  
2788    GET_LOADROOM(ch) = NOWHERE;
2789  }
2790  
2791  
2792  
2793  /* returns the real number of the room with given virtual number */
2794  room_rnum real_room(room_vnum vnum)
2795  {
2796    room_rnum bot, top, mid;
2797  
2798    bot = 0;
2799    top = top_of_world;
2800  
2801    /* perform binary search on world-table */
2802    for (;;) {
2803      mid = (bot + top) / 2;
2804  
2805      if ((world + mid)->number == vnum)
2806        return (mid);
2807      if (bot >= top)
2808        return (NOWHERE);
2809      if ((world + mid)->number > vnum)
2810        top = mid - 1;
2811      else
2812        bot = mid + 1;
2813    }
2814  }
2815  
2816  
2817  
2818  /* returns the real number of the monster with given virtual number */
2819  mob_rnum real_mobile(mob_vnum vnum)
2820  {
2821    mob_rnum bot, top, mid;
2822  
2823    bot = 0;
2824    top = top_of_mobt;
2825  
2826    /* perform binary search on mob-table */
2827    for (;;) {
2828      mid = (bot + top) / 2;
2829  
2830      if ((mob_index + mid)->vnum == vnum)
2831        return (mid);
2832      if (bot >= top)
2833        return (NOBODY);
2834      if ((mob_index + mid)->vnum > vnum)
2835        top = mid - 1;
2836      else
2837        bot = mid + 1;
2838    }
2839  }
2840  
2841  
2842  /* returns the real number of the object with given virtual number */
2843  obj_rnum real_object(obj_vnum vnum)
2844  {
2845    obj_rnum bot, top, mid;
2846  
2847    bot = 0;
2848    top = top_of_objt;
2849  
2850    /* perform binary search on obj-table */
2851    for (;;) {
2852      mid = (bot + top) / 2;
2853  
2854      if ((obj_index + mid)->vnum == vnum)
2855        return (mid);
2856      if (bot >= top)
2857        return (NOTHING);
2858      if ((obj_index + mid)->vnum > vnum)
2859        top = mid - 1;
2860      else
2861        bot = mid + 1;
2862    }
2863  }
2864  
2865  
2866  /* returns the real number of the zone with given virtual number */
2867  room_rnum real_zone(room_vnum vnum)
2868  {
2869    room_rnum bot, top, mid;
2870  
2871    bot = 0;
2872    top = top_of_zone_table;
2873  
2874    /* perform binary search on zone-table */
2875    for (;;) {
2876      mid = (bot + top) / 2;
2877  
2878      if ((zone_table + mid)->number == vnum)
2879        return (mid);
2880      if (bot >= top)
2881        return (NOWHERE);
2882      if ((zone_table + mid)->number > vnum)
2883        top = mid - 1;
2884      else
2885        bot = mid + 1;
2886    }
2887  }
2888  
2889  
2890  /*
2891   * Extend later to include more checks.
2892   *
2893   * TODO: Add checks for unknown bitvectors.
2894   */
2895  int check_object(struct obj_data *obj)
2896  {
2897    char objname[MAX_INPUT_LENGTH + 32];
2898    int error = FALSE;
2899  
2900    if (GET_OBJ_WEIGHT(obj) < 0 && (error = TRUE))
2901      log("SYSERR: Object #%d (%s) has negative weight (%d).",
2902  	GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_WEIGHT(obj));
2903  
2904    if (GET_OBJ_RENT(obj) < 0 && (error = TRUE))
2905      log("SYSERR: Object #%d (%s) has negative cost/day (%d).",
2906  	GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_RENT(obj));
2907  
2908    snprintf(objname, sizeof(objname), "Object #%d (%s)", GET_OBJ_VNUM(obj), obj->short_description);
2909    error |= check_bitvector_names(GET_OBJ_WEAR(obj), wear_bits_count, objname, "object wear");
2910    error |= check_bitvector_names(GET_OBJ_EXTRA(obj), extra_bits_count, objname, "object extra");
2911    error |= check_bitvector_names(GET_OBJ_AFFECT(obj), affected_bits_count, objname, "object affect");
2912  
2913    switch (GET_OBJ_TYPE(obj)) {
2914    case ITEM_DRINKCON:
2915    {
2916      char onealias[MAX_INPUT_LENGTH], *space = strrchr(obj->name, ' ');
2917  
2918      strlcpy(onealias, space ? space + 1 : obj->name, sizeof(onealias));
2919      if (search_block(onealias, drinknames, TRUE) < 0 && (error = TRUE))
2920        log("SYSERR: Object #%d (%s) doesn't have drink type as last alias. (%s)",
2921  		GET_OBJ_VNUM(obj), obj->short_description, obj->name);
2922    }
2923    /* Fall through. */
2924    case ITEM_FOUNTAIN:
2925      if (GET_OBJ_VAL(obj, 1) > GET_OBJ_VAL(obj, 0) && (error = TRUE))
2926        log("SYSERR: Object #%d (%s) contains (%d) more than maximum (%d).",
2927  		GET_OBJ_VNUM(obj), obj->short_description,
2928  		GET_OBJ_VAL(obj, 1), GET_OBJ_VAL(obj, 0));
2929      break;
2930    case ITEM_SCROLL:
2931    case ITEM_POTION:
2932      error |= check_object_level(obj, 0);
2933      error |= check_object_spell_number(obj, 1);
2934      error |= check_object_spell_number(obj, 2);
2935      error |= check_object_spell_number(obj, 3);
2936      break;
2937    case ITEM_WAND:
2938    case ITEM_STAFF:
2939      error |= check_object_level(obj, 0);
2940      error |= check_object_spell_number(obj, 3);
2941      if (GET_OBJ_VAL(obj, 2) > GET_OBJ_VAL(obj, 1) && (error = TRUE))
2942        log("SYSERR: Object #%d (%s) has more charges (%d) than maximum (%d).",
2943  		GET_OBJ_VNUM(obj), obj->short_description,
2944  		GET_OBJ_VAL(obj, 2), GET_OBJ_VAL(obj, 1));
2945      break;
2946   }
2947  
2948    return (error);
2949  }
2950  
2951  int check_object_spell_number(struct obj_data *obj, int val)
2952  {
2953    int error = FALSE;
2954    const char *spellname;
2955  
2956    if (GET_OBJ_VAL(obj, val) == -1)	/* i.e.: no spell */
2957      return (error);
2958  
2959    /*
2960     * Check for negative spells, spells beyond the top define, and any
2961     * spell which is actually a skill.
2962     */
2963    if (GET_OBJ_VAL(obj, val) < 0)
2964      error = TRUE;
2965    if (GET_OBJ_VAL(obj, val) > TOP_SPELL_DEFINE)
2966      error = TRUE;
2967    if (GET_OBJ_VAL(obj, val) > MAX_SPELLS && GET_OBJ_VAL(obj, val) <= MAX_SKILLS)
2968      error = TRUE;
2969    if (error)
2970      log("SYSERR: Object #%d (%s) has out of range spell #%d.",
2971  	GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_VAL(obj, val));
2972  
2973    /*
2974     * This bug has been fixed, but if you don't like the special behavior...
2975     */
2976  #if 0
2977    if (GET_OBJ_TYPE(obj) == ITEM_STAFF &&
2978  	HAS_SPELL_ROUTINE(GET_OBJ_VAL(obj, val), MAG_AREAS | MAG_MASSES))
2979      log("... '%s' (#%d) uses %s spell '%s'.",
2980  	obj->short_description,	GET_OBJ_VNUM(obj),
2981  	HAS_SPELL_ROUTINE(GET_OBJ_VAL(obj, val), MAG_AREAS) ? "area" : "mass",
2982  	skill_name(GET_OBJ_VAL(obj, val)));
2983  #endif
2984  
2985    if (scheck)		/* Spell names don't exist in syntax check mode. */
2986      return (error);
2987  
2988    /* Now check for unnamed spells. */
2989    spellname = skill_name(GET_OBJ_VAL(obj, val));
2990  
2991    if ((spellname == unused_spellname || !str_cmp("UNDEFINED", spellname)) && (error = TRUE))
2992      log("SYSERR: Object #%d (%s) uses '%s' spell #%d.",
2993  		GET_OBJ_VNUM(obj), obj->short_description, spellname,
2994  		GET_OBJ_VAL(obj, val));
2995  
2996    return (error);
2997  }
2998  
2999  int check_object_level(struct obj_data *obj, int val)
3000  {
3001    int error = FALSE;
3002  
3003    if ((GET_OBJ_VAL(obj, val) < 0 || GET_OBJ_VAL(obj, val) > LVL_IMPL) && (error = TRUE))
3004      log("SYSERR: Object #%d (%s) has out of range level #%d.",
3005  	GET_OBJ_VNUM(obj), obj->short_description, GET_OBJ_VAL(obj, val));
3006  
3007    return (error);
3008  }
3009  
3010  int check_bitvector_names(bitvector_t bits, size_t namecount, const char *whatami, const char *whatbits)
3011  {
3012    unsigned int flagnum;
3013    bool error = FALSE;
3014  
3015    /* See if any bits are set above the ones we know about. */
3016    if (bits <= (~(bitvector_t)0 >> (sizeof(bitvector_t) * 8 - namecount)))
3017      return (FALSE);
3018  
3019    for (flagnum = namecount; flagnum < sizeof(bitvector_t) * 8; flagnum++)
3020      if ((1 << flagnum) & bits) {
3021        log("SYSERR: %s has unknown %s flag, bit %d (0 through %d known).", whatami, whatbits, flagnum, namecount - 1);
3022        error = TRUE;
3023      }
3024  
3025    return (error);
3026  }
3027