/* ************************************************************************
* File: objsave.c EmpireMUD AD 1.0 *
* Usage: loading/saving player objects for rent and crash-save *
* *
* All rights reserved. See license.doc for complete information. *
* *
* Code base by Paul Clarke. EmpireMUD Project, a tbgMUD Production. *
* Based upon CircleMUD 3.0, beta patch level 17, by Jeremy Elson. *
* *
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. *
************************************************************************ */
/*
* AutoEQ by Burkhard Knopf <burkhard.knopf@informatik.tu-clausthal.de>
*/
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "comm.h"
#include "handler.h"
#include "db.h"
#include "interpreter.h"
#include "utils.h"
/* these factors should be unique integers */
#define RENT_FACTOR 1
#define CRYO_FACTOR 4
#define LOC_INVENTORY 0
#define MAX_BAG_ROWS 5
extern struct player_index_element *player_table;
extern int top_of_p_table;
extern int obj_file_timeout;
/* Extern functions */
ACMD(do_tell);
/* local functions */
int Obj_to_store(Object obj, FILE * fl, int location);
void update_obj_file(void);
int gen_receptionist(Creature ch, Creature recep, int cmd, char *arg, int mode);
void Crash_restore_weight(Object obj);
void Crash_extract_objs(Object obj);
int Crash_is_unrentable(Object obj);
void Crash_extract_norents(Object obj);
void Crash_rentsave(Creature ch);
Object Obj_from_store(struct obj_file_elem object, int *location) {
Object obj;
int j;
*location = 0;
if (real_object(object.item_number) >= 0) {
obj = read_object(object.item_number, VIRTUAL);
*location = object.location;
GET_OBJ_VAL(obj, 0) = object.value[0];
GET_OBJ_VAL(obj, 1) = object.value[1];
GET_OBJ_VAL(obj, 2) = object.value[2];
GET_OBJ_EXTRA(obj) = object.extra_flags;
GET_OBJ_WEIGHT(obj) = object.weight;
GET_OBJ_TIMER(obj) = object.timer;
obj->obj_flags.bitvector = object.bitvector;
for (j = 0; j < MAX_OBJ_AFFECT; j++)
obj->affected[j] = object.affected[j];
return (obj);
}
else
return (NULL);
}
int Obj_to_store(Object obj, FILE * fl, int location)
{
int j;
struct obj_file_elem object;
object.item_number = GET_OBJ_VNUM(obj);
object.location = location;
object.value[0] = GET_OBJ_VAL(obj, 0);
object.value[1] = GET_OBJ_VAL(obj, 1);
object.value[2] = GET_OBJ_VAL(obj, 2);
object.extra_flags = GET_OBJ_EXTRA(obj);
object.weight = GET_OBJ_WEIGHT(obj);
object.timer = GET_OBJ_TIMER(obj);
object.bitvector = obj->obj_flags.bitvector;
for (j = 0; j < MAX_OBJ_AFFECT; j++)
object.affected[j] = obj->affected[j];
if (fwrite(&object, sizeof(struct obj_file_elem), 1, fl) < 1) {
perror("SYSERR: error writing object in Obj_to_store");
return (0);
}
return (1);
}
void auto_equip(Creature ch, Object obj, int *location) {
int j;
/* Lots of checks... */
if (*location > 0) { /* Was wearing it. */
switch (j = (*location - 1)) {
case WEAR_FINGER_R:
case WEAR_FINGER_L: if (!CAN_WEAR(obj, ITEM_WEAR_FINGER)) *location = LOC_INVENTORY; break;
case WEAR_NECK: if (!CAN_WEAR(obj, ITEM_WEAR_NECK)) *location = LOC_INVENTORY; break;
case WEAR_QUIVER: if (!CAN_WEAR(obj, ITEM_WEAR_QUIVER)) *location = LOC_INVENTORY; break;
case WEAR_EARS: if (!CAN_WEAR(obj, ITEM_WEAR_EARS)) *location = LOC_INVENTORY; break;
case WEAR_BODY: if (!CAN_WEAR(obj, ITEM_WEAR_BODY)) *location = LOC_INVENTORY; break;
case WEAR_ARMOR: if (!CAN_WEAR(obj, ITEM_WEAR_ARMOR)) *location = LOC_INVENTORY; break;
case WEAR_HEAD: if (!CAN_WEAR(obj, ITEM_WEAR_HEAD)) *location = LOC_INVENTORY; break;
case WEAR_LEGS: if (!CAN_WEAR(obj, ITEM_WEAR_LEGS)) *location = LOC_INVENTORY; break;
case WEAR_FEET: if (!CAN_WEAR(obj, ITEM_WEAR_FEET)) *location = LOC_INVENTORY; break;
case WEAR_HANDS: if (!CAN_WEAR(obj, ITEM_WEAR_HANDS)) *location = LOC_INVENTORY; break;
case WEAR_ARMS: if (!CAN_WEAR(obj, ITEM_WEAR_ARMS)) *location = LOC_INVENTORY; break;
case WEAR_ABOUT: if (!CAN_WEAR(obj, ITEM_WEAR_ABOUT)) *location = LOC_INVENTORY; break;
case WEAR_WAIST: if (!CAN_WEAR(obj, ITEM_WEAR_WAIST)) *location = LOC_INVENTORY; break;
case WEAR_SHEATH_1: if (!CAN_WEAR(obj, ITEM_WEAR_SHEATH)) *location = LOC_INVENTORY; break;
case WEAR_SHEATH_2: if (!CAN_WEAR(obj, ITEM_WEAR_SHEATH)) *location = LOC_INVENTORY; break;
case WEAR_SHEATH_3: if (!CAN_WEAR(obj, ITEM_WEAR_SHEATH)) *location = LOC_INVENTORY; break;
case WEAR_IN_SHEATH_1: if (!CAN_WEAR(obj, ITEM_WEAR_WIELD)) *location = LOC_INVENTORY; break;
case WEAR_IN_SHEATH_2: if (!CAN_WEAR(obj, ITEM_WEAR_WIELD)) *location = LOC_INVENTORY; break;
case WEAR_IN_SHEATH_3: if (!CAN_WEAR(obj, ITEM_WEAR_WIELD)) *location = LOC_INVENTORY; break;
case WEAR_WIELD: if (!CAN_WEAR(obj, ITEM_WEAR_WIELD)) *location = LOC_INVENTORY; break;
case WEAR_HOLD: if (!CAN_WEAR(obj, ITEM_WEAR_HOLD)) *location = LOC_INVENTORY; break;
default: *location = LOC_INVENTORY;
}
if (*location > 0) { /* Wearable. */
if (!GET_EQ(ch,j)) {
equip_char(ch, obj, j);
}
else { /* Oops, saved a player with double equipment? */
syslog(0, TRUE, "SYSERR: autoeq: '%s' already equipped in position %d.", GET_NAME(ch), *location);
*location = LOC_INVENTORY;
}
}
}
if (*location <= 0) /* Inventory */
obj_to_char(obj, ch);
}
int Crash_delete_file(char *name) {
char filename[50];
FILE *fl;
if (!get_filename(name, filename, CRASH_FILE))
return (0);
if (!(fl = fopen(filename, "rb"))) {
if (errno != ENOENT) /* if it fails but NOT because of no file */
log("SYSERR: deleting crash file %s (1): %s", filename, strerror(errno));
return (0);
}
fclose(fl);
/* if it fails, NOT because of no file */
if (remove(filename) < 0 && errno != ENOENT)
log("SYSERR: deleting crash file %s (2): %s", filename, strerror(errno));
return (1);
}
int Crash_delete_crashfile(Creature ch) {
char fname[MAX_INPUT_LENGTH];
struct rent_info rent;
FILE *fl;
if (!get_filename(GET_NAME(ch), fname, CRASH_FILE))
return (0);
if (!(fl = fopen(fname, "rb"))) {
if (errno != ENOENT) /* if it fails, NOT because of no file */
log("SYSERR: checking for crash file %s (3): %s", fname, strerror(errno));
return (0);
}
if (!feof(fl))
fread(&rent, sizeof(struct rent_info), 1, fl);
fclose(fl);
if (rent.rentcode == RENT_CRASH)
Crash_delete_file(GET_NAME(ch));
return (1);
}
int Crash_clean_file(char *name) {
char fname[MAX_STRING_LENGTH], filetype[20];
struct rent_info rent;
FILE *fl;
if (!get_filename(name, fname, CRASH_FILE))
return (0);
/*
* open for write so that permission problems will be flagged now, at boot
* time.
*/
if (!(fl = fopen(fname, "r+b"))) {
if (errno != ENOENT) /* if it fails, NOT because of no file */
log("SYSERR: OPENING OBJECT FILE %s (4): %s", fname, strerror(errno));
return (0);
}
if (!feof(fl))
fread(&rent, sizeof(struct rent_info), 1, fl);
fclose(fl);
if (rent.time < time(0) - (obj_file_timeout * SECS_PER_REAL_DAY)) {
Crash_delete_file(name);
switch (rent.rentcode) {
case RENT_CRASH:
strcpy(filetype, "crash");
break;
case RENT_RENTED:
strcpy(filetype, "rent");
break;
default:
strcpy(filetype, "UNKNOWN!");
break;
}
log(" Deleting %s's %s file.", name, filetype);
return (1);
}
return (0);
}
void update_obj_file(void) {
int i;
for (i = 0; i <= top_of_p_table; i++)
if (*player_table[i].name)
Crash_clean_file(player_table[i].name);
}
void Crash_listrent(Creature ch, char *name) {
FILE *fl;
char fname[MAX_INPUT_LENGTH], buf[MAX_STRING_LENGTH];
struct obj_file_elem object;
Object obj;
struct rent_info rent;
if (!get_filename(name, fname, CRASH_FILE))
return;
if (!(fl = fopen(fname, "rb"))) {
sprintf(buf, "%s has no rent file.\r\n", name);
send_to_char(buf, ch);
return;
}
sprintf(buf, "%s\r\n", fname);
if (!feof(fl))
fread(&rent, sizeof(struct rent_info), 1, fl);
switch (rent.rentcode) {
case RENT_RENTED:
strcat(buf, "Rent\r\n");
break;
case RENT_CRASH:
strcat(buf, "Crash\r\n");
break;
default:
strcat(buf, "Undef\r\n");
break;
}
while (!feof(fl)) {
fread(&object, sizeof(struct obj_file_elem), 1, fl);
if (ferror(fl)) {
fclose(fl);
return;
}
if (!feof(fl))
if (real_object(object.item_number) > -1) {
obj = read_object(object.item_number, VIRTUAL);
sprintf(buf + strlen(buf), " [%5d] (%5dau) <%2d> %-20s\r\n", object.item_number, GET_OBJ_RENT(obj), object.location, obj->short_description);
extract_obj(obj);
if (strlen(buf) > MAX_STRING_LENGTH - 80) {
strcat(buf, "** Excessive rent listing. **\r\n");
break;
}
}
}
send_to_char(buf, ch);
fclose(fl);
}
int Crash_write_rentcode(Creature ch, FILE * fl, struct rent_info * rent) {
if (fwrite(rent, sizeof(struct rent_info), 1, fl) < 1) {
perror("SYSERR: writing rent code");
return (0);
}
return (1);
}
/*
* Return values:
* 0 - successful load, keep char in rent room.
* 1 - load failure or load of crash items -- put char in temple.
*/
int Crash_load(Creature ch, int dolog) {
FILE *fl;
char fname[MAX_STRING_LENGTH];
struct obj_file_elem object;
struct rent_info rent;
int orig_rent_code, num_objs = 0, j;
/* AutoEQ addition. */
Object obj, obj2, cont_row[MAX_BAG_ROWS];
int location;
/* Empty all of the container lists (you never know ...) */
for (j = 0; j < MAX_BAG_ROWS; j++)
cont_row[j] = NULL;
if (!get_filename(GET_NAME(ch), fname, CRASH_FILE))
return (1);
if (!(fl = fopen(fname, "r+b"))) {
if (errno != ENOENT) { /* if it fails, NOT because of no file */
log("SYSERR: READING OBJECT FILE %s (5): %s", fname, strerror(errno));
send_to_char("\r\n********************* NOTICE *********************\r\n"
"There was a problem loading your objects from disk.\r\n"
"Contact a God for assistance.\r\n", ch);
}
if (dolog) {
syslog(GET_INVIS_LEV(ch), TRUE, "%s entering game with no equipment.", GET_NAME(ch));
if (GET_INVIS_LEV(ch) == 0)
mortlog(ch, "%s has entered the game", PERS(ch, ch, 1));
}
return (1);
}
if (!feof(fl))
fread(&rent, sizeof(struct rent_info), 1, fl);
else {
log("SYSERR: Crash_load: %s's rent file was empty!", GET_NAME(ch));
return (1);
}
switch (orig_rent_code = rent.rentcode) {
case RENT_RENTED:
if (dolog)
syslog(GET_INVIS_LEV(ch), TRUE, "%s un-renting and entering game.", GET_NAME(ch));
break;
case RENT_CRASH:
if (dolog)
syslog(GET_INVIS_LEV(ch), TRUE, "%s retrieving crash-saved items and entering game.", GET_NAME(ch));
break;
default:
syslog(GET_INVIS_LEV(ch), TRUE, "SYSERR: %s entering game with undefined rent code %d.", GET_NAME(ch), rent.rentcode);
break;
}
if (dolog && GET_INVIS_LEV(ch) == 0)
mortlog(ch, "%s has entered the game", PERS(ch, ch, 1));
while (!feof(fl)) {
fread(&object, sizeof(struct obj_file_elem), 1, fl);
if (ferror(fl)) {
perror("SYSERR: Reading crash file: Crash_load");
fclose(fl);
return (1);
}
if (feof(fl))
break;
++num_objs;
if ((obj = Obj_from_store(object, &location)) == NULL)
continue;
auto_equip(ch, obj, &location);
/*
* What to do with a new loaded item:
*
* If there's a list with location less than 1 below this, then its
* container has disappeared from the file so we put the list back into
* the character's inventory. (Equipped items are 0 here.)
*
* If there's a list of contents with location of 1 below this, then we
* check if it is a container:
* - Yes: Get it from the character, fill it, and give it back so we
* have the correct weight.
* - No: The container is missing so we put everything back into the
* character's inventory.
*
* For items with negative location, we check if there is already a list
* of contents with the same location. If so, we put it there and if not,
* we start a new list.
*
* Since location for contents is < 0, the list indices are switched to
* non-negative.
*
* This looks ugly, but it works.
*/
if (location > 0) { /* Equipped */
for (j = MAX_BAG_ROWS - 1; j > 0; j--) {
if (cont_row[j]) { /* No container, back to inventory. */
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_char(cont_row[j], ch);
}
cont_row[j] = NULL;
}
}
if (cont_row[0]) { /* Content list existing. */
if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER || IS_CORPSE(obj)) {
/* Remove object, fill it, equip again. */
obj = unequip_char(ch, location - 1);
obj->contains = NULL; /* Should be NULL anyway, but just in case. */
for (; cont_row[0]; cont_row[0] = obj2) {
obj2 = cont_row[0]->next_content;
obj_to_obj(cont_row[0], obj);
}
equip_char(ch, obj, location - 1);
}
else { /* Object isn't container, empty the list. */
for (; cont_row[0]; cont_row[0] = obj2) {
obj2 = cont_row[0]->next_content;
obj_to_char(cont_row[0], ch);
}
cont_row[0] = NULL;
}
}
}
else { /* location <= 0 */
for (j = MAX_BAG_ROWS - 1; j > -location; j--) {
if (cont_row[j]) { /* No container, back to inventory. */
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_char(cont_row[j], ch);
}
cont_row[j] = NULL;
}
}
if (j == -location && cont_row[j]) { /* Content list exists. */
if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER || IS_CORPSE(obj)) {
/* Take the item, fill it, and give it back. */
obj_from_char(obj);
obj->contains = NULL;
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_obj(cont_row[j], obj);
}
obj_to_char(obj, ch); /* Add to inventory first. */
}
else { /* Object isn't container, empty content list. */
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_char(cont_row[j], ch);
}
cont_row[j] = NULL;
}
}
if (location < 0 && location >= -MAX_BAG_ROWS) {
/*
* Let the object be part of the content list but put it at the
* list's end. Thus having the items in the same order as before
* the character rented.
*/
obj_from_char(obj);
if ((obj2 = cont_row[-location - 1]) != NULL) {
while (obj2->next_content)
obj2 = obj2->next_content;
obj2->next_content = obj;
}
else
cont_row[-location - 1] = obj;
}
}
}
/* turn this into a crash file by re-writing the control block */
rent.rentcode = RENT_CRASH;
rent.time = time(0);
rewind(fl);
Crash_write_rentcode(ch, fl, &rent);
fclose(fl);
if (orig_rent_code == RENT_RENTED)
return (0);
else
return (1);
}
int Crash_save(Object obj, FILE *fp, int location) {
Object tmp;
int result;
if (obj) {
Crash_save(obj->next_content, fp, location);
Crash_save(obj->contains, fp, MIN(0, location) - 1);
result = Obj_to_store(obj, fp, location);
for (tmp = obj->in_obj; tmp; tmp = tmp->in_obj)
GET_OBJ_WEIGHT(tmp) -= GET_OBJ_WEIGHT(obj);
if (!result)
return (0);
}
return (TRUE);
}
void Crash_restore_weight(Object obj) {
if (obj) {
Crash_restore_weight(obj->contains);
Crash_restore_weight(obj->next_content);
if (obj->in_obj)
GET_OBJ_WEIGHT(obj->in_obj) += GET_OBJ_WEIGHT(obj);
}
}
/*
* Get !RENT items from equipment to inventory and
* extract !RENT out of worn containers.
*/
void Crash_extract_norent_eq(Creature ch) {
int j;
for (j = 0; j < NUM_WEARS; j++) {
if (GET_EQ(ch, j) == NULL)
continue;
if (Crash_is_unrentable(GET_EQ(ch, j)))
obj_to_char(unequip_char(ch, j), ch);
else
Crash_extract_norents(GET_EQ(ch, j));
}
}
void Crash_extract_objs(Object obj) {
if (obj) {
Crash_extract_objs(obj->contains);
Crash_extract_objs(obj->next_content);
extract_obj(obj);
}
}
int Crash_is_unrentable(Object obj) {
if (!obj)
return (0);
if (GET_OBJ_RNUM(obj) <= NOTHING)
return (1);
return (0);
}
void Crash_extract_norents(Object obj) {
if (obj) {
Crash_extract_norents(obj->contains);
Crash_extract_norents(obj->next_content);
if (Crash_is_unrentable(obj))
extract_obj(obj);
}
}
void Crash_crashsave(Creature ch) {
char buf[MAX_INPUT_LENGTH];
struct rent_info rent;
int j;
FILE *fp;
if (IS_NPC(ch))
return;
if (!get_filename(GET_NAME(ch), buf, CRASH_FILE))
return;
if (!(fp = fopen(buf, "wb")))
return;
rent.rentcode = RENT_CRASH;
rent.time = time(0);
if (!Crash_write_rentcode(ch, fp, &rent)) {
fclose(fp);
return;
}
for (j = 0; j < NUM_WEARS; j++)
if (GET_EQ(ch, j)) {
if (!Crash_save(GET_EQ(ch, j), fp, j + 1)) {
fclose(fp);
return;
}
Crash_restore_weight(GET_EQ(ch, j));
}
if (!Crash_save(ch->carrying, fp, 0)) {
fclose(fp);
return;
}
Crash_restore_weight(ch->carrying);
fclose(fp);
}
void Crash_rentsave(Creature ch) {
char buf[MAX_INPUT_LENGTH];
struct rent_info rent;
int j;
FILE *fp;
if (IS_NPC(ch))
return;
if (!get_filename(GET_NAME(ch), buf, CRASH_FILE))
return;
if (!(fp = fopen(buf, "wb")))
return;
Crash_extract_norent_eq(ch);
Crash_extract_norents(ch->carrying);
rent.rentcode = RENT_RENTED;
rent.time = time(0);
if (!Crash_write_rentcode(ch, fp, &rent)) {
fclose(fp);
return;
}
for (j = 0; j < NUM_WEARS; j++)
if (GET_EQ(ch, j)) {
if (!Crash_save(GET_EQ(ch,j), fp, j + 1)) {
fclose(fp);
return;
}
Crash_restore_weight(GET_EQ(ch, j));
Crash_extract_objs(GET_EQ(ch, j));
}
if (!Crash_save(ch->carrying, fp, 0)) {
fclose(fp);
return;
}
fclose(fp);
Crash_extract_objs(ch->carrying);
}
void Crash_save_all(void) {
void write_aliases(Creature ch);
Descr d;
for (d = descriptor_list; d; d = d->next) {
if ((STATE(d) == CON_PLAYING) && !IS_NPC(d->character)) {
Crash_crashsave(d->character);
write_aliases(d->character);
SAVE_CHAR(d->character);
}
}
}
/*
* objpacks are based ENTIRELY on EmpireMUD's standard object saving/loading
* code, but with a little of my own personal finesse. Some comments have
* been removed because I think they're excessive, but I've made an attempt
* not to remove any credits. Just in case I do, this paragraph indicates
* the original authors. Don't be fooled, though. This is my own genius
* work. - PC 10/13/00
*/
/* Objects in room */
int objpack_save_room(int rnum) {
struct rent_info rent[1];
FILE *fp;
if (!world[rnum].contents)
return 0;
sprintf(buf, "%s%d.%s", LIB_OBJPACK, world[rnum].number, SUF_PACK);
if (!(fp = fopen(buf, "wb")))
return 0;
rent[0].rentcode = RENT_CRASH;
rent[0].time = time(0);
if (fwrite(rent, sizeof(struct rent_info), 1, fp) < 1) {
perror("SYSERR: writing rent code");
fclose(fp);
return 0;
}
if (!Crash_save(world[rnum].contents, fp, 0)) {
fclose(fp);
return 0;
}
Crash_restore_weight(world[rnum].contents);
fclose(fp);
return 1;
}
void objpack_load_room(int rnum) {
FILE *fl;
char fname[MAX_STRING_LENGTH];
struct obj_file_elem object;
struct rent_info rent;
int num_objs = 0, j;
/* AutoEQ addition. */
Object obj, obj2, cont_row[MAX_BAG_ROWS];
int location;
/* Empty all of the container lists (you never know ...) */
for (j = 0; j < MAX_BAG_ROWS; j++)
cont_row[j] = NULL;
sprintf(fname, "%s%d.%s", LIB_OBJPACK, world[rnum].number, SUF_PACK);
if (!(fl = fopen(fname, "r+b"))) {
if (errno != ENOENT)
log("SYSERR: READING OBJECT FILE %s (5): %s", fname, strerror(errno));
return;
}
if (!feof(fl))
fread(&rent, sizeof(struct rent_info), 1, fl);
else {
log("SYSERR: Crash_load: '%s' rent file was empty!", fname);
return;
}
while (!feof(fl)) {
fread(&object, sizeof(struct obj_file_elem), 1, fl);
if (ferror(fl)) {
perror("SYSERR: Reading crash file: Crash_load");
fclose(fl);
return;
}
if (feof(fl))
break;
++num_objs;
if ((obj = Obj_from_store(object, &location)) == NULL)
continue;
if (location > 0)
location = LOC_INVENTORY; /* Not really an inventory, but same idea. */
obj_to_room(obj, rnum);
for (j = MAX_BAG_ROWS - 1; j > -location; j--) {
if (cont_row[j]) { /* No container, back to room. */
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_room(cont_row[j], rnum);
}
cont_row[j] = NULL;
}
}
if (j == -location && cont_row[j]) { /* Content list exists. */
if (GET_OBJ_TYPE(obj) == ITEM_CONTAINER || IS_CORPSE(obj)) {
/* Take the item, fill it, and give it back. */
obj_from_room(obj);
obj->contains = NULL;
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_obj(cont_row[j], obj);
}
obj_to_room(obj, rnum); /* Add to room first. */
}
else { /* Object isn't container, empty content list. */
for (; cont_row[j]; cont_row[j] = obj2) {
obj2 = cont_row[j]->next_content;
obj_to_room(cont_row[j], rnum);
}
cont_row[j] = NULL;
}
}
if (location < 0 && location >= -MAX_BAG_ROWS) {
obj_from_room(obj);
if ((obj2 = cont_row[-location - 1]) != NULL) {
while (obj2->next_content)
obj2 = obj2->next_content;
obj2->next_content = obj;
}
else
cont_row[-location - 1] = obj;
}
}
fclose(fl);
}