/ circle3.1 / src / objsave.c
objsave.c
   1  /* ************************************************************************
   2  *   File: objsave.c                                     Part of CircleMUD *
   3  *  Usage: loading/saving player objects for rent and crash-save           *
   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  #include "conf.h"
  12  #include "sysdep.h"
  13  
  14  
  15  #include "structs.h"
  16  #include "comm.h"
  17  #include "handler.h"
  18  #include "db.h"
  19  #include "interpreter.h"
  20  #include "utils.h"
  21  #include "spells.h"
  22  
  23  /* these factors should be unique integers */
  24  #define RENT_FACTOR 	1
  25  #define CRYO_FACTOR 	4
  26  
  27  #define LOC_INVENTORY	0
  28  #define MAX_BAG_ROWS	5
  29  
  30  /* external variables */
  31  extern struct player_index_element *player_table;
  32  extern int top_of_p_table;
  33  extern int rent_file_timeout, crash_file_timeout;
  34  extern int free_rent;
  35  extern int min_rent_cost;
  36  extern int max_obj_save;	/* change in config.c */
  37  
  38  /* Extern functions */
  39  ACMD(do_action);
  40  ACMD(do_tell);
  41  SPECIAL(receptionist);
  42  SPECIAL(cryogenicist);
  43  int invalid_class(struct char_data *ch, struct obj_data *obj);
  44  
  45  /* local functions */
  46  void Crash_extract_norent_eq(struct char_data *ch);
  47  void auto_equip(struct char_data *ch, struct obj_data *obj, int location);
  48  int Crash_offer_rent(struct char_data *ch, struct char_data *recep, int display, int factor);
  49  int Crash_report_unrentables(struct char_data *ch, struct char_data *recep, struct obj_data *obj);
  50  void Crash_report_rent(struct char_data *ch, struct char_data *recep, struct obj_data *obj, long *cost, long *nitems, int display, int factor);
  51  struct obj_data *Obj_from_store(struct obj_file_elem object, int *location);
  52  int Obj_to_store(struct obj_data *obj, FILE *fl, int location);
  53  void update_obj_file(void);
  54  int Crash_write_rentcode(struct char_data *ch, FILE *fl, struct rent_info *rent);
  55  int gen_receptionist(struct char_data *ch, struct char_data *recep, int cmd, char *arg, int mode);
  56  int Crash_save(struct obj_data *obj, FILE *fp, int location);
  57  void Crash_rent_deadline(struct char_data *ch, struct char_data *recep, long cost);
  58  void Crash_restore_weight(struct obj_data *obj);
  59  void Crash_extract_objs(struct obj_data *obj);
  60  int Crash_is_unrentable(struct obj_data *obj);
  61  void Crash_extract_norents(struct obj_data *obj);
  62  void Crash_extract_expensive(struct obj_data *obj);
  63  void Crash_calculate_rent(struct obj_data *obj, int *cost);
  64  void Crash_rentsave(struct char_data *ch, int cost);
  65  void Crash_cryosave(struct char_data *ch, int cost);
  66  
  67  
  68  struct obj_data *Obj_from_store(struct obj_file_elem object, int *location)
  69  {
  70    struct obj_data *obj;
  71    obj_rnum itemnum;
  72    int j;
  73  
  74    *location = 0;
  75    if ((itemnum = real_object(object.item_number)) == NOTHING)
  76      return (NULL);
  77  
  78    obj = read_object(itemnum, REAL);
  79  #if USE_AUTOEQ
  80    *location = object.location;
  81  #endif
  82    GET_OBJ_VAL(obj, 0) = object.value[0];
  83    GET_OBJ_VAL(obj, 1) = object.value[1];
  84    GET_OBJ_VAL(obj, 2) = object.value[2];
  85    GET_OBJ_VAL(obj, 3) = object.value[3];
  86    GET_OBJ_EXTRA(obj) = object.extra_flags;
  87    GET_OBJ_WEIGHT(obj) = object.weight;
  88    GET_OBJ_TIMER(obj) = object.timer;
  89    GET_OBJ_AFFECT(obj) = object.bitvector;
  90  
  91    for (j = 0; j < MAX_OBJ_AFFECT; j++)
  92      obj->affected[j] = object.affected[j];
  93  
  94    return (obj);
  95  }
  96  
  97  
  98  
  99  int Obj_to_store(struct obj_data *obj, FILE *fl, int location)
 100  {
 101    int j;
 102    struct obj_file_elem object;
 103  
 104    object.item_number = GET_OBJ_VNUM(obj);
 105  #if USE_AUTOEQ
 106    object.location = location;
 107  #endif
 108    object.value[0] = GET_OBJ_VAL(obj, 0);
 109    object.value[1] = GET_OBJ_VAL(obj, 1);
 110    object.value[2] = GET_OBJ_VAL(obj, 2);
 111    object.value[3] = GET_OBJ_VAL(obj, 3);
 112    object.extra_flags = GET_OBJ_EXTRA(obj);
 113    object.weight = GET_OBJ_WEIGHT(obj);
 114    object.timer = GET_OBJ_TIMER(obj);
 115    object.bitvector = GET_OBJ_AFFECT(obj);
 116    for (j = 0; j < MAX_OBJ_AFFECT; j++)
 117      object.affected[j] = obj->affected[j];
 118  
 119    if (fwrite(&object, sizeof(struct obj_file_elem), 1, fl) < 1) {
 120      perror("SYSERR: error writing object in Obj_to_store");
 121      return (0);
 122    }
 123    return (1);
 124  }
 125  
 126  /*
 127   * AutoEQ by Burkhard Knopf <burkhard.knopf@informatik.tu-clausthal.de>
 128   */
 129  void auto_equip(struct char_data *ch, struct obj_data *obj, int location)
 130  {
 131    int j;
 132  
 133    /* Lots of checks... */
 134    if (location > 0) {	/* Was wearing it. */
 135      switch (j = (location - 1)) {
 136      case WEAR_LIGHT:
 137        break;
 138      case WEAR_FINGER_R:
 139      case WEAR_FINGER_L:
 140        if (!CAN_WEAR(obj, ITEM_WEAR_FINGER)) /* not fitting :( */
 141          location = LOC_INVENTORY;
 142        break;
 143      case WEAR_NECK_1:
 144      case WEAR_NECK_2:
 145        if (!CAN_WEAR(obj, ITEM_WEAR_NECK))
 146          location = LOC_INVENTORY;
 147        break;
 148      case WEAR_BODY:
 149        if (!CAN_WEAR(obj, ITEM_WEAR_BODY))
 150          location = LOC_INVENTORY;
 151        break;
 152      case WEAR_HEAD:
 153        if (!CAN_WEAR(obj, ITEM_WEAR_HEAD))
 154          location = LOC_INVENTORY;
 155        break;
 156      case WEAR_LEGS:
 157        if (!CAN_WEAR(obj, ITEM_WEAR_LEGS))
 158          location = LOC_INVENTORY;
 159        break;
 160      case WEAR_FEET:
 161        if (!CAN_WEAR(obj, ITEM_WEAR_FEET))
 162          location = LOC_INVENTORY;
 163        break;
 164      case WEAR_HANDS:
 165        if (!CAN_WEAR(obj, ITEM_WEAR_HANDS))
 166          location = LOC_INVENTORY;
 167        break;
 168      case WEAR_ARMS:
 169        if (!CAN_WEAR(obj, ITEM_WEAR_ARMS))
 170          location = LOC_INVENTORY;
 171        break;
 172      case WEAR_SHIELD:
 173        if (!CAN_WEAR(obj, ITEM_WEAR_SHIELD))
 174          location = LOC_INVENTORY;
 175        break;
 176      case WEAR_ABOUT:
 177        if (!CAN_WEAR(obj, ITEM_WEAR_ABOUT))
 178          location = LOC_INVENTORY;
 179        break;
 180      case WEAR_WAIST:
 181        if (!CAN_WEAR(obj, ITEM_WEAR_WAIST))
 182          location = LOC_INVENTORY;
 183        break;
 184      case WEAR_WRIST_R:
 185      case WEAR_WRIST_L:
 186        if (!CAN_WEAR(obj, ITEM_WEAR_WRIST))
 187          location = LOC_INVENTORY;
 188        break;
 189      case WEAR_WIELD:
 190        if (!CAN_WEAR(obj, ITEM_WEAR_WIELD))
 191          location = LOC_INVENTORY;
 192        break;
 193      case WEAR_HOLD:
 194        if (CAN_WEAR(obj, ITEM_WEAR_HOLD))
 195  	break;
 196        if (IS_WARRIOR(ch) && CAN_WEAR(obj, ITEM_WEAR_WIELD) && GET_OBJ_TYPE(obj) == ITEM_WEAPON)
 197  	break;
 198        location = LOC_INVENTORY;
 199        break;
 200      default:
 201        location = LOC_INVENTORY;
 202      }
 203  
 204      if (location > 0) {	    /* Wearable. */
 205        if (!GET_EQ(ch,j)) {
 206  	/*
 207  	 * Check the characters's alignment to prevent them from being
 208  	 * zapped through the auto-equipping.
 209           */
 210           if (invalid_align(ch, obj) || invalid_class(ch, obj))
 211            location = LOC_INVENTORY;
 212          else
 213            equip_char(ch, obj, j);
 214        } else {	/* Oops, saved a player with double equipment? */
 215          mudlog(BRF, LVL_IMMORT, TRUE, "SYSERR: autoeq: '%s' already equipped in position %d.", GET_NAME(ch), location);
 216          location = LOC_INVENTORY;
 217        }
 218      }
 219    }
 220    if (location <= 0)	/* Inventory */
 221      obj_to_char(obj, ch);
 222  }
 223  
 224  
 225  int Crash_delete_file(char *name)
 226  {
 227    char filename[50];
 228    FILE *fl;
 229  
 230    if (!get_filename(filename, sizeof(filename), CRASH_FILE, name))
 231      return (0);
 232    if (!(fl = fopen(filename, "rb"))) {
 233      if (errno != ENOENT)	/* if it fails but NOT because of no file */
 234        log("SYSERR: deleting crash file %s (1): %s", filename, strerror(errno));
 235      return (0);
 236    }
 237    fclose(fl);
 238  
 239    /* if it fails, NOT because of no file */
 240    if (remove(filename) < 0 && errno != ENOENT)
 241      log("SYSERR: deleting crash file %s (2): %s", filename, strerror(errno));
 242  
 243    return (1);
 244  }
 245  
 246  
 247  int Crash_delete_crashfile(struct char_data *ch)
 248  {
 249    char filename[MAX_INPUT_LENGTH];
 250    struct rent_info rent;
 251    int numread;
 252    FILE *fl;
 253  
 254    if (!get_filename(filename, sizeof(filename), CRASH_FILE, GET_NAME(ch)))
 255      return (0);
 256    if (!(fl = fopen(filename, "rb"))) {
 257      if (errno != ENOENT)	/* if it fails, NOT because of no file */
 258        log("SYSERR: checking for crash file %s (3): %s", filename, strerror(errno));
 259      return (0);
 260    }
 261    numread = fread(&rent, sizeof(struct rent_info), 1, fl);
 262    fclose(fl);
 263  
 264    if (numread == 0)
 265      return (0);
 266  
 267    if (rent.rentcode == RENT_CRASH)
 268      Crash_delete_file(GET_NAME(ch));
 269  
 270    return (1);
 271  }
 272  
 273  
 274  int Crash_clean_file(char *name)
 275  {
 276    char filename[MAX_STRING_LENGTH];
 277    struct rent_info rent;
 278    int numread;
 279    FILE *fl;
 280  
 281    if (!get_filename(filename, sizeof(filename), CRASH_FILE, name))
 282      return (0);
 283    /*
 284     * open for write so that permission problems will be flagged now, at boot
 285     * time.
 286     */
 287    if (!(fl = fopen(filename, "r+b"))) {
 288      if (errno != ENOENT)	/* if it fails, NOT because of no file */
 289        log("SYSERR: OPENING OBJECT FILE %s (4): %s", filename, strerror(errno));
 290      return (0);
 291    }
 292    numread = fread(&rent, sizeof(struct rent_info), 1, fl);
 293    fclose(fl);
 294  
 295    if (numread == 0)
 296      return (0);
 297  
 298    if ((rent.rentcode == RENT_CRASH) ||
 299        (rent.rentcode == RENT_FORCED) || (rent.rentcode == RENT_TIMEDOUT)) {
 300      if (rent.time < time(0) - (crash_file_timeout * SECS_PER_REAL_DAY)) {
 301        const char *filetype;
 302  
 303        Crash_delete_file(name);
 304        switch (rent.rentcode) {
 305        case RENT_CRASH:
 306          filetype = "crash";
 307          break;
 308        case RENT_FORCED:
 309          filetype = "forced rent";
 310          break;
 311        case RENT_TIMEDOUT:
 312          filetype = "idlesave";
 313          break;
 314        default:
 315          filetype = "UNKNOWN!";
 316          break;
 317        }
 318        log("    Deleting %s's %s file.", name, filetype);
 319        return (1);
 320      }
 321      /* Must retrieve rented items w/in 30 days */
 322    } else if (rent.rentcode == RENT_RENTED)
 323      if (rent.time < time(0) - (rent_file_timeout * SECS_PER_REAL_DAY)) {
 324        Crash_delete_file(name);
 325        log("    Deleting %s's rent file.", name);
 326        return (1);
 327      }
 328    return (0);
 329  }
 330  
 331  
 332  void update_obj_file(void)
 333  {
 334    int i;
 335  
 336    for (i = 0; i <= top_of_p_table; i++)
 337      if (*player_table[i].name)
 338        Crash_clean_file(player_table[i].name);
 339  }
 340  
 341  
 342  void Crash_listrent(struct char_data *ch, char *name)
 343  {
 344    FILE *fl;
 345    char filename[MAX_INPUT_LENGTH];
 346    struct obj_file_elem object;
 347    struct obj_data *obj;
 348    struct rent_info rent;
 349    int numread;
 350  
 351    if (!get_filename(filename, sizeof(filename), CRASH_FILE, name))
 352      return;
 353    if (!(fl = fopen(filename, "rb"))) {
 354      send_to_char(ch, "%s has no rent file.\r\n", name);
 355      return;
 356    }
 357    numread = fread(&rent, sizeof(struct rent_info), 1, fl);
 358  
 359    /* Oops, can't get the data, punt. */
 360    if (numread == 0) {
 361      send_to_char(ch, "Error reading rent information.\r\n");
 362      fclose(fl);
 363      return;
 364    }
 365  
 366    send_to_char(ch, "%s\r\n", filename);
 367    switch (rent.rentcode) {
 368    case RENT_RENTED:
 369      send_to_char(ch, "Rent\r\n");
 370      break;
 371    case RENT_CRASH:
 372      send_to_char(ch, "Crash\r\n");
 373      break;
 374    case RENT_CRYO:
 375      send_to_char(ch, "Cryo\r\n");
 376      break;
 377    case RENT_TIMEDOUT:
 378    case RENT_FORCED:
 379      send_to_char(ch, "TimedOut\r\n");
 380      break;
 381    default:
 382      send_to_char(ch, "Undef\r\n");
 383      break;
 384    }
 385    while (!feof(fl)) {
 386      fread(&object, sizeof(struct obj_file_elem), 1, fl);
 387      if (ferror(fl)) {
 388        fclose(fl);
 389        return;
 390      }
 391      if (!feof(fl))
 392        if (real_object(object.item_number) != NOTHING) {
 393  	obj = read_object(object.item_number, VIRTUAL);
 394  #if USE_AUTOEQ
 395  	send_to_char(ch, " [%5d] (%5dau) <%2d> %-20s\r\n",
 396  		object.item_number, GET_OBJ_RENT(obj),
 397  		object.location, obj->short_description);
 398  #else
 399  	send_to_char(ch, " [%5d] (%5dau) %-20s\r\n",
 400  		object.item_number, GET_OBJ_RENT(obj),
 401  		obj->short_description);
 402  #endif
 403  	extract_obj(obj);
 404        }
 405    }
 406    fclose(fl);
 407  }
 408  
 409  
 410  int Crash_write_rentcode(struct char_data *ch, FILE *fl, struct rent_info *rent)
 411  {
 412    if (fwrite(rent, sizeof(struct rent_info), 1, fl) < 1) {
 413      perror("SYSERR: writing rent code");
 414      return (0);
 415    }
 416    return (1);
 417  }
 418  
 419  
 420  /*
 421   * Return values:
 422   *  0 - successful load, keep char in rent room.
 423   *  1 - load failure or load of crash items -- put char in temple.
 424   *  2 - rented equipment lost (no $)
 425   */
 426  int Crash_load(struct char_data *ch)
 427  {
 428    FILE *fl;
 429    char filename[MAX_STRING_LENGTH];
 430    struct obj_file_elem object;
 431    struct rent_info rent;
 432    int cost, orig_rent_code, num_objs = 0, j;
 433    float num_of_days;
 434    /* AutoEQ addition. */
 435    struct obj_data *obj, *obj2, *cont_row[MAX_BAG_ROWS];
 436    int location;
 437  
 438    /* Empty all of the container lists (you never know ...) */
 439    for (j = 0; j < MAX_BAG_ROWS; j++)
 440      cont_row[j] = NULL;
 441  
 442    if (!get_filename(filename, sizeof(filename), CRASH_FILE, GET_NAME(ch)))
 443      return (1);
 444    if (!(fl = fopen(filename, "r+b"))) {
 445      if (errno != ENOENT) {	/* if it fails, NOT because of no file */
 446        log("SYSERR: READING OBJECT FILE %s (5): %s", filename, strerror(errno));
 447        send_to_char(ch,
 448  		"\r\n********************* NOTICE *********************\r\n"
 449  		"There was a problem loading your objects from disk.\r\n"
 450  		"Contact a God for assistance.\r\n");
 451      }
 452      mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s entering game with no equipment.", GET_NAME(ch));
 453      return (1);
 454    }
 455    if (!feof(fl))
 456      fread(&rent, sizeof(struct rent_info), 1, fl);
 457    else {
 458      log("SYSERR: Crash_load: %s's rent file was empty!", GET_NAME(ch));
 459      return (1);
 460    }
 461  
 462    if (rent.rentcode == RENT_RENTED || rent.rentcode == RENT_TIMEDOUT) {
 463      num_of_days = (float) (time(0) - rent.time) / SECS_PER_REAL_DAY;
 464      cost = (int) (rent.net_cost_per_diem * num_of_days);
 465      if (cost > GET_GOLD(ch) + GET_BANK_GOLD(ch)) {
 466        fclose(fl);
 467        mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s entering game, rented equipment lost (no $).", GET_NAME(ch));
 468        Crash_crashsave(ch);
 469        return (2);
 470      } else {
 471        GET_BANK_GOLD(ch) -= MAX(cost - GET_GOLD(ch), 0);
 472        GET_GOLD(ch) = MAX(GET_GOLD(ch) - cost, 0);
 473        save_char(ch);
 474      }
 475    }
 476    switch (orig_rent_code = rent.rentcode) {
 477    case RENT_RENTED:
 478      mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s un-renting and entering game.", GET_NAME(ch));
 479      break;
 480    case RENT_CRASH:
 481      mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s retrieving crash-saved items and entering game.", GET_NAME(ch));
 482      break;
 483    case RENT_CRYO:
 484      mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s un-cryo'ing and entering game.", GET_NAME(ch));
 485      break;
 486    case RENT_FORCED:
 487    case RENT_TIMEDOUT:
 488      mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s retrieving force-saved items and entering game.", GET_NAME(ch));
 489      break;
 490    default:
 491      mudlog(BRF, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE,
 492  		"SYSERR: %s entering game with undefined rent code %d.", GET_NAME(ch), rent.rentcode);
 493      break;
 494    }
 495  
 496    while (!feof(fl)) {
 497      fread(&object, sizeof(struct obj_file_elem), 1, fl);
 498      if (ferror(fl)) {
 499        perror("SYSERR: Reading crash file: Crash_load");
 500        fclose(fl);
 501        return (1);
 502      }
 503      if (feof(fl))
 504        break;
 505      ++num_objs;
 506      if ((obj = Obj_from_store(object, &location)) == NULL)
 507        continue;
 508  
 509      auto_equip(ch, obj, location);
 510      /*
 511       * What to do with a new loaded item:
 512       *
 513       * If there's a list with location less than 1 below this, then its
 514       * container has disappeared from the file so we put the list back into
 515       * the character's inventory. (Equipped items are 0 here.)
 516       *
 517       * If there's a list of contents with location of 1 below this, then we
 518       * check if it is a container:
 519       *   - Yes: Get it from the character, fill it, and give it back so we
 520       *          have the correct weight.
 521       *   -  No: The container is missing so we put everything back into the
 522       *          character's inventory.
 523       *
 524       * For items with negative location, we check if there is already a list
 525       * of contents with the same location.  If so, we put it there and if not,
 526       * we start a new list.
 527       *
 528       * Since location for contents is < 0, the list indices are switched to
 529       * non-negative.
 530       *
 531       * This looks ugly, but it works.
 532       */
 533      if (location > 0) {		/* Equipped */
 534        for (j = MAX_BAG_ROWS - 1; j > 0; j--) {
 535          if (cont_row[j]) {	/* No container, back to inventory. */
 536            for (; cont_row[j]; cont_row[j] = obj2) {
 537              obj2 = cont_row[j]->next_content;
 538              obj_to_char(cont_row[j], ch);
 539            }
 540            cont_row[j] = NULL;
 541          }
 542        }
 543        if (cont_row[0]) {	/* Content list existing. */
 544          if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER) {
 545  	/* Remove object, fill it, equip again. */
 546            obj = unequip_char(ch, location - 1);
 547            obj->contains = NULL;	/* Should be NULL anyway, but just in case. */
 548            for (; cont_row[0]; cont_row[0] = obj2) {
 549              obj2 = cont_row[0]->next_content;
 550              obj_to_obj(cont_row[0], obj);
 551            }
 552            equip_char(ch, obj, location - 1);
 553          } else {			/* Object isn't container, empty the list. */
 554            for (; cont_row[0]; cont_row[0] = obj2) {
 555              obj2 = cont_row[0]->next_content;
 556              obj_to_char(cont_row[0], ch);
 557            }
 558            cont_row[0] = NULL;
 559          }
 560        }
 561      } else {	/* location <= 0 */
 562        for (j = MAX_BAG_ROWS - 1; j > -location; j--) {
 563          if (cont_row[j]) {	/* No container, back to inventory. */
 564            for (; cont_row[j]; cont_row[j] = obj2) {
 565              obj2 = cont_row[j]->next_content;
 566              obj_to_char(cont_row[j], ch);
 567            }
 568            cont_row[j] = NULL;
 569          }
 570        }
 571        if (j == -location && cont_row[j]) {	/* Content list exists. */
 572          if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER) {
 573  		/* Take the item, fill it, and give it back. */
 574            obj_from_char(obj);
 575            obj->contains = NULL;
 576            for (; cont_row[j]; cont_row[j] = obj2) {
 577              obj2 = cont_row[j]->next_content;
 578              obj_to_obj(cont_row[j], obj);
 579            }
 580            obj_to_char(obj, ch);	/* Add to inventory first. */
 581          } else {	/* Object isn't container, empty content list. */
 582            for (; cont_row[j]; cont_row[j] = obj2) {
 583              obj2 = cont_row[j]->next_content;
 584              obj_to_char(cont_row[j], ch);
 585            }
 586            cont_row[j] = NULL;
 587          }
 588        }
 589        if (location < 0 && location >= -MAX_BAG_ROWS) {
 590          /*
 591           * Let the object be part of the content list but put it at the
 592           * list's end.  Thus having the items in the same order as before
 593           * the character rented.
 594           */
 595          obj_from_char(obj);
 596          if ((obj2 = cont_row[-location - 1]) != NULL) {
 597            while (obj2->next_content)
 598              obj2 = obj2->next_content;
 599            obj2->next_content = obj;
 600          } else
 601            cont_row[-location - 1] = obj;
 602        }
 603      }
 604    }
 605  
 606    /* Little hoarding check. -gg 3/1/98 */
 607    mudlog(NRM, MAX(GET_INVIS_LEV(ch), LVL_GOD), TRUE, "%s (level %d) has %d object%s (max %d).",
 608  	GET_NAME(ch), GET_LEVEL(ch), num_objs, num_objs != 1 ? "s" : "", max_obj_save);
 609  
 610    /* turn this into a crash file by re-writing the control block */
 611    rent.rentcode = RENT_CRASH;
 612    rent.time = time(0);
 613    rewind(fl);
 614    Crash_write_rentcode(ch, fl, &rent);
 615  
 616    fclose(fl);
 617  
 618    if ((orig_rent_code == RENT_RENTED) || (orig_rent_code == RENT_CRYO))
 619      return (0);
 620    else
 621      return (1);
 622  }
 623  
 624  
 625  
 626  int Crash_save(struct obj_data *obj, FILE *fp, int location)
 627  {
 628    struct obj_data *tmp;
 629    int result;
 630  
 631    if (obj) {
 632      Crash_save(obj->next_content, fp, location);
 633      Crash_save(obj->contains, fp, MIN(0, location) - 1);
 634      result = Obj_to_store(obj, fp, location);
 635  
 636      for (tmp = obj->in_obj; tmp; tmp = tmp->in_obj)
 637        GET_OBJ_WEIGHT(tmp) -= GET_OBJ_WEIGHT(obj);
 638  
 639      if (!result)
 640        return (0);
 641    }
 642    return (TRUE);
 643  }
 644  
 645  
 646  void Crash_restore_weight(struct obj_data *obj)
 647  {
 648    if (obj) {
 649      Crash_restore_weight(obj->contains);
 650      Crash_restore_weight(obj->next_content);
 651      if (obj->in_obj)
 652        GET_OBJ_WEIGHT(obj->in_obj) += GET_OBJ_WEIGHT(obj);
 653    }
 654  }
 655  
 656  /*
 657   * Get !RENT items from equipment to inventory and
 658   * extract !RENT out of worn containers.
 659   */
 660  void Crash_extract_norent_eq(struct char_data *ch)
 661  {
 662    int j;
 663  
 664    for (j = 0; j < NUM_WEARS; j++) {
 665      if (GET_EQ(ch, j) == NULL)
 666        continue;
 667  
 668      if (Crash_is_unrentable(GET_EQ(ch, j)))
 669        obj_to_char(unequip_char(ch, j), ch);
 670      else
 671        Crash_extract_norents(GET_EQ(ch, j));
 672    }
 673  }
 674  
 675  void Crash_extract_objs(struct obj_data *obj)
 676  {
 677    if (obj) {
 678      Crash_extract_objs(obj->contains);
 679      Crash_extract_objs(obj->next_content);
 680      extract_obj(obj);
 681    }
 682  }
 683  
 684  
 685  int Crash_is_unrentable(struct obj_data *obj)
 686  {
 687    if (!obj)
 688      return (0);
 689  
 690    if (OBJ_FLAGGED(obj, ITEM_NORENT) || GET_OBJ_RENT(obj) < 0 ||
 691        GET_OBJ_RNUM(obj) == NOTHING || GET_OBJ_TYPE(obj) == ITEM_KEY)
 692      return (1);
 693  
 694    return (0);
 695  }
 696  
 697  
 698  void Crash_extract_norents(struct obj_data *obj)
 699  {
 700    if (obj) {
 701      Crash_extract_norents(obj->contains);
 702      Crash_extract_norents(obj->next_content);
 703      if (Crash_is_unrentable(obj))
 704        extract_obj(obj);
 705    }
 706  }
 707  
 708  
 709  void Crash_extract_expensive(struct obj_data *obj)
 710  {
 711    struct obj_data *tobj, *max;
 712  
 713    max = obj;
 714    for (tobj = obj; tobj; tobj = tobj->next_content)
 715      if (GET_OBJ_RENT(tobj) > GET_OBJ_RENT(max))
 716        max = tobj;
 717    extract_obj(max);
 718  }
 719  
 720  
 721  
 722  void Crash_calculate_rent(struct obj_data *obj, int *cost)
 723  {
 724    if (obj) {
 725      *cost += MAX(0, GET_OBJ_RENT(obj));
 726      Crash_calculate_rent(obj->contains, cost);
 727      Crash_calculate_rent(obj->next_content, cost);
 728    }
 729  }
 730  
 731  
 732  void Crash_crashsave(struct char_data *ch)
 733  {
 734    char buf[MAX_INPUT_LENGTH];
 735    struct rent_info rent;
 736    int j;
 737    FILE *fp;
 738  
 739    if (IS_NPC(ch))
 740      return;
 741  
 742    if (!get_filename(buf, sizeof(buf), CRASH_FILE, GET_NAME(ch)))
 743      return;
 744    if (!(fp = fopen(buf, "wb")))
 745      return;
 746  
 747    rent.rentcode = RENT_CRASH;
 748    rent.time = time(0);
 749    if (!Crash_write_rentcode(ch, fp, &rent)) {
 750      fclose(fp);
 751      return;
 752    }
 753  
 754    for (j = 0; j < NUM_WEARS; j++)
 755      if (GET_EQ(ch, j)) {
 756        if (!Crash_save(GET_EQ(ch, j), fp, j + 1)) {
 757  	fclose(fp);
 758  	return;
 759        }
 760        Crash_restore_weight(GET_EQ(ch, j));
 761      }
 762  
 763    if (!Crash_save(ch->carrying, fp, 0)) {
 764      fclose(fp);
 765      return;
 766    }
 767    Crash_restore_weight(ch->carrying);
 768  
 769    fclose(fp);
 770    REMOVE_BIT(PLR_FLAGS(ch), PLR_CRASH);
 771  }
 772  
 773  
 774  void Crash_idlesave(struct char_data *ch)
 775  {
 776    char buf[MAX_INPUT_LENGTH];
 777    struct rent_info rent;
 778    int j;
 779    int cost, cost_eq;
 780    FILE *fp;
 781  
 782    if (IS_NPC(ch))
 783      return;
 784  
 785    if (!get_filename(buf, sizeof(buf), CRASH_FILE, GET_NAME(ch)))
 786      return;
 787    if (!(fp = fopen(buf, "wb")))
 788      return;
 789  
 790    Crash_extract_norent_eq(ch);
 791    Crash_extract_norents(ch->carrying);
 792  
 793    cost = 0;
 794    Crash_calculate_rent(ch->carrying, &cost);
 795  
 796    cost_eq = 0;
 797    for (j = 0; j < NUM_WEARS; j++)
 798      Crash_calculate_rent(GET_EQ(ch, j), &cost_eq);
 799  
 800    cost += cost_eq;
 801    cost *= 2;			/* forcerent cost is 2x normal rent */
 802  
 803    if (cost > GET_GOLD(ch) + GET_BANK_GOLD(ch)) {
 804      for (j = 0; j < NUM_WEARS; j++)	/* Unequip players with low gold. */
 805        if (GET_EQ(ch, j))
 806          obj_to_char(unequip_char(ch, j), ch);
 807  
 808      while ((cost > GET_GOLD(ch) + GET_BANK_GOLD(ch)) && ch->carrying) {
 809        Crash_extract_expensive(ch->carrying);
 810        cost = 0;
 811        Crash_calculate_rent(ch->carrying, &cost);
 812        cost *= 2;
 813      }
 814    }
 815  
 816    if (ch->carrying == NULL) {
 817      for (j = 0; j < NUM_WEARS && GET_EQ(ch, j) == NULL; j++) /* Nothing */ ;
 818      if (j == NUM_WEARS) {	/* No equipment or inventory. */
 819        fclose(fp);
 820        Crash_delete_file(GET_NAME(ch));
 821        return;
 822      }
 823    }
 824    rent.net_cost_per_diem = cost;
 825  
 826    rent.rentcode = RENT_TIMEDOUT;
 827    rent.time = time(0);
 828    rent.gold = GET_GOLD(ch);
 829    rent.account = GET_BANK_GOLD(ch);
 830    if (!Crash_write_rentcode(ch, fp, &rent)) {
 831      fclose(fp);
 832      return;
 833    }
 834    for (j = 0; j < NUM_WEARS; j++) {
 835      if (GET_EQ(ch, j)) {
 836        if (!Crash_save(GET_EQ(ch, j), fp, j + 1)) {
 837          fclose(fp);
 838          return;
 839        }
 840        Crash_restore_weight(GET_EQ(ch, j));
 841        Crash_extract_objs(GET_EQ(ch, j));
 842      }
 843    }
 844    if (!Crash_save(ch->carrying, fp, 0)) {
 845      fclose(fp);
 846      return;
 847    }
 848    fclose(fp);
 849  
 850    Crash_extract_objs(ch->carrying);
 851  }
 852  
 853  
 854  void Crash_rentsave(struct char_data *ch, int cost)
 855  {
 856    char buf[MAX_INPUT_LENGTH];
 857    struct rent_info rent;
 858    int j;
 859    FILE *fp;
 860  
 861    if (IS_NPC(ch))
 862      return;
 863  
 864    if (!get_filename(buf, sizeof(buf), CRASH_FILE, GET_NAME(ch)))
 865      return;
 866    if (!(fp = fopen(buf, "wb")))
 867      return;
 868  
 869    Crash_extract_norent_eq(ch);
 870    Crash_extract_norents(ch->carrying);
 871  
 872    rent.net_cost_per_diem = cost;
 873    rent.rentcode = RENT_RENTED;
 874    rent.time = time(0);
 875    rent.gold = GET_GOLD(ch);
 876    rent.account = GET_BANK_GOLD(ch);
 877    if (!Crash_write_rentcode(ch, fp, &rent)) {
 878      fclose(fp);
 879      return;
 880    }
 881    for (j = 0; j < NUM_WEARS; j++)
 882      if (GET_EQ(ch, j)) {
 883        if (!Crash_save(GET_EQ(ch,j), fp, j + 1)) {
 884          fclose(fp);
 885          return;
 886        }
 887        Crash_restore_weight(GET_EQ(ch, j));
 888        Crash_extract_objs(GET_EQ(ch, j));
 889      }
 890    if (!Crash_save(ch->carrying, fp, 0)) {
 891      fclose(fp);
 892      return;
 893    }
 894    fclose(fp);
 895  
 896    Crash_extract_objs(ch->carrying);
 897  }
 898  
 899  
 900  void Crash_cryosave(struct char_data *ch, int cost)
 901  {
 902    char buf[MAX_INPUT_LENGTH];
 903    struct rent_info rent;
 904    int j;
 905    FILE *fp;
 906  
 907    if (IS_NPC(ch))
 908      return;
 909  
 910    if (!get_filename(buf, sizeof(buf), CRASH_FILE, GET_NAME(ch)))
 911      return;
 912    if (!(fp = fopen(buf, "wb")))
 913      return;
 914  
 915    Crash_extract_norent_eq(ch);
 916    Crash_extract_norents(ch->carrying);
 917  
 918    GET_GOLD(ch) = MAX(0, GET_GOLD(ch) - cost);
 919  
 920    rent.rentcode = RENT_CRYO;
 921    rent.time = time(0);
 922    rent.gold = GET_GOLD(ch);
 923    rent.account = GET_BANK_GOLD(ch);
 924    rent.net_cost_per_diem = 0;
 925    if (!Crash_write_rentcode(ch, fp, &rent)) {
 926      fclose(fp);
 927      return;
 928    }
 929    for (j = 0; j < NUM_WEARS; j++)
 930      if (GET_EQ(ch, j)) {
 931        if (!Crash_save(GET_EQ(ch, j), fp, j + 1)) {
 932          fclose(fp);
 933          return;
 934        }
 935        Crash_restore_weight(GET_EQ(ch, j));
 936        Crash_extract_objs(GET_EQ(ch, j));
 937      }
 938    if (!Crash_save(ch->carrying, fp, 0)) {
 939      fclose(fp);
 940      return;
 941    }
 942    fclose(fp);
 943  
 944    Crash_extract_objs(ch->carrying);
 945    SET_BIT(PLR_FLAGS(ch), PLR_CRYO);
 946  }
 947  
 948  
 949  /* ************************************************************************
 950  * Routines used for the receptionist					  *
 951  ************************************************************************* */
 952  
 953  void Crash_rent_deadline(struct char_data *ch, struct char_data *recep,
 954  			      long cost)
 955  {
 956    char buf[256];
 957    long rent_deadline;
 958  
 959    if (!cost)
 960      return;
 961  
 962    rent_deadline = ((GET_GOLD(ch) + GET_BANK_GOLD(ch)) / cost);
 963    snprintf(buf, sizeof(buf), "$n tells you, 'You can rent for %ld day%s with the gold you have\r\n"
 964  	  "on hand and in the bank.'\r\n", rent_deadline, rent_deadline != 1 ? "s" : "");
 965    act(buf, FALSE, recep, 0, ch, TO_VICT);
 966  }
 967  
 968  int Crash_report_unrentables(struct char_data *ch, struct char_data *recep,
 969  			         struct obj_data *obj)
 970  {
 971    int has_norents = 0;
 972  
 973    if (obj) {
 974      if (Crash_is_unrentable(obj)) {
 975        char buf[128];
 976  
 977        has_norents = 1;
 978        snprintf(buf, sizeof(buf), "$n tells you, 'You cannot store %s.'", OBJS(obj, ch));
 979        act(buf, FALSE, recep, 0, ch, TO_VICT);
 980      }
 981      has_norents += Crash_report_unrentables(ch, recep, obj->contains);
 982      has_norents += Crash_report_unrentables(ch, recep, obj->next_content);
 983    }
 984    return (has_norents);
 985  }
 986  
 987  
 988  
 989  void Crash_report_rent(struct char_data *ch, struct char_data *recep,
 990  		            struct obj_data *obj, long *cost, long *nitems, int display, int factor)
 991  {
 992    if (obj) {
 993      if (!Crash_is_unrentable(obj)) {
 994        (*nitems)++;
 995        *cost += MAX(0, (GET_OBJ_RENT(obj) * factor));
 996        if (display) {
 997          char buf[256];
 998  
 999  	snprintf(buf, sizeof(buf), "$n tells you, '%5d coins for %s..'", GET_OBJ_RENT(obj) * factor, OBJS(obj, ch));
1000  	act(buf, FALSE, recep, 0, ch, TO_VICT);
1001        }
1002      }
1003      Crash_report_rent(ch, recep, obj->contains, cost, nitems, display, factor);
1004      Crash_report_rent(ch, recep, obj->next_content, cost, nitems, display, factor);
1005    }
1006  }
1007  
1008  
1009  
1010  int Crash_offer_rent(struct char_data *ch, struct char_data *recep,
1011  		         int display, int factor)
1012  {
1013    int i;
1014    long totalcost = 0, numitems = 0, norent;
1015  
1016    norent = Crash_report_unrentables(ch, recep, ch->carrying);
1017    for (i = 0; i < NUM_WEARS; i++)
1018      norent += Crash_report_unrentables(ch, recep, GET_EQ(ch, i));
1019  
1020    if (norent)
1021      return (0);
1022  
1023    totalcost = min_rent_cost * factor;
1024  
1025    Crash_report_rent(ch, recep, ch->carrying, &totalcost, &numitems, display, factor);
1026  
1027    for (i = 0; i < NUM_WEARS; i++)
1028      Crash_report_rent(ch, recep, GET_EQ(ch, i), &totalcost, &numitems, display, factor);
1029  
1030    if (!numitems) {
1031      act("$n tells you, 'But you are not carrying anything!  Just quit!'",
1032  	FALSE, recep, 0, ch, TO_VICT);
1033      return (0);
1034    }
1035    if (numitems > max_obj_save) {
1036      char buf[256];
1037  
1038      snprintf(buf, sizeof(buf), "$n tells you, 'Sorry, but I cannot store more than %d items.'", max_obj_save);
1039      act(buf, FALSE, recep, 0, ch, TO_VICT);
1040      return (0);
1041    }
1042    if (display) {
1043      char buf[256];
1044  
1045      snprintf(buf, sizeof(buf), "$n tells you, 'Plus, my %d coin fee..'", min_rent_cost * factor);
1046      act(buf, FALSE, recep, 0, ch, TO_VICT);
1047  
1048      snprintf(buf, sizeof(buf), "$n tells you, 'For a total of %ld coins%s.'", totalcost, factor == RENT_FACTOR ? " per day" : "");
1049      act(buf, FALSE, recep, 0, ch, TO_VICT);
1050  
1051      if (totalcost > GET_GOLD(ch) + GET_BANK_GOLD(ch)) {
1052        act("$n tells you, '...which I see you can't afford.'", FALSE, recep, 0, ch, TO_VICT);
1053        return (0);
1054      } else if (factor == RENT_FACTOR)
1055        Crash_rent_deadline(ch, recep, totalcost);
1056    }
1057    return (totalcost);
1058  }
1059  
1060  
1061  
1062  int gen_receptionist(struct char_data *ch, struct char_data *recep,
1063  		         int cmd, char *arg, int mode)
1064  {
1065    int cost;
1066    const char *action_table[] = { "smile", "dance", "sigh", "blush", "burp",
1067  	  "cough", "fart", "twiddle", "yawn" };
1068  
1069    if (!cmd && !rand_number(0, 5)) {
1070      do_action(recep, NULL, find_command(action_table[rand_number(0, 8)]), 0);
1071      return (FALSE);
1072    }
1073  
1074    if (!ch->desc || IS_NPC(ch))
1075      return (FALSE);
1076  
1077    if (!CMD_IS("offer") && !CMD_IS("rent"))
1078      return (FALSE);
1079  
1080    if (!AWAKE(recep)) {
1081      send_to_char(ch, "%s is unable to talk to you...\r\n", HSSH(recep));
1082      return (TRUE);
1083    }
1084  
1085    if (!CAN_SEE(recep, ch)) {
1086      act("$n says, 'I don't deal with people I can't see!'", FALSE, recep, 0, 0, TO_ROOM);
1087      return (TRUE);
1088    }
1089  
1090    if (free_rent) {
1091      act("$n tells you, 'Rent is free here.  Just quit, and your objects will be saved!'",
1092  	FALSE, recep, 0, ch, TO_VICT);
1093      return (1);
1094    }
1095  
1096    if (CMD_IS("rent")) {
1097      char buf[128];
1098  
1099      if (!(cost = Crash_offer_rent(ch, recep, FALSE, mode)))
1100        return (TRUE);
1101      if (mode == RENT_FACTOR)
1102        snprintf(buf, sizeof(buf), "$n tells you, 'Rent will cost you %d gold coins per day.'", cost);
1103      else if (mode == CRYO_FACTOR)
1104        snprintf(buf, sizeof(buf), "$n tells you, 'It will cost you %d gold coins to be frozen.'", cost);
1105      act(buf, FALSE, recep, 0, ch, TO_VICT);
1106  
1107      if (cost > GET_GOLD(ch) + GET_BANK_GOLD(ch)) {
1108        act("$n tells you, '...which I see you can't afford.'",
1109  	  FALSE, recep, 0, ch, TO_VICT);
1110        return (TRUE);
1111      }
1112      if (cost && (mode == RENT_FACTOR))
1113        Crash_rent_deadline(ch, recep, cost);
1114  
1115      if (mode == RENT_FACTOR) {
1116        act("$n stores your belongings and helps you into your private chamber.", FALSE, recep, 0, ch, TO_VICT);
1117        Crash_rentsave(ch, cost);
1118        mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s has rented (%d/day, %d tot.)",
1119  		GET_NAME(ch), cost, GET_GOLD(ch) + GET_BANK_GOLD(ch));
1120      } else {			/* cryo */
1121        act("$n stores your belongings and helps you into your private chamber.\r\n"
1122  	  "A white mist appears in the room, chilling you to the bone...\r\n"
1123  	  "You begin to lose consciousness...",
1124  	  FALSE, recep, 0, ch, TO_VICT);
1125        Crash_cryosave(ch, cost);
1126        mudlog(NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE, "%s has cryo-rented.", GET_NAME(ch));
1127        SET_BIT(PLR_FLAGS(ch), PLR_CRYO);
1128      }
1129  
1130      act("$n helps $N into $S private chamber.", FALSE, recep, 0, ch, TO_NOTVICT);
1131  
1132      GET_LOADROOM(ch) = GET_ROOM_VNUM(IN_ROOM(ch));
1133      extract_char(ch);	/* It saves. */
1134    } else {
1135      Crash_offer_rent(ch, recep, TRUE, mode);
1136      act("$N gives $n an offer.", FALSE, ch, 0, recep, TO_ROOM);
1137    }
1138    return (TRUE);
1139  }
1140  
1141  
1142  SPECIAL(receptionist)
1143  {
1144    return (gen_receptionist(ch, (struct char_data *)me, cmd, argument, RENT_FACTOR));
1145  }
1146  
1147  
1148  SPECIAL(cryogenicist)
1149  {
1150    return (gen_receptionist(ch, (struct char_data *)me, cmd, argument, CRYO_FACTOR));
1151  }
1152  
1153  
1154  void Crash_save_all(void)
1155  {
1156    struct descriptor_data *d;
1157    for (d = descriptor_list; d; d = d->next) {
1158      if ((STATE(d) == CON_PLAYING) && !IS_NPC(d->character)) {
1159        if (PLR_FLAGGED(d->character, PLR_CRASH)) {
1160  	Crash_crashsave(d->character);
1161  	save_char(d->character);
1162  	REMOVE_BIT(PLR_FLAGS(d->character), PLR_CRASH);
1163        }
1164      }
1165    }
1166  }