lima-1.0b5/
lima-1.0b5/driver/
lima-1.0b5/driver/ChangeLog.old/
lima-1.0b5/driver/Win32/
lima-1.0b5/driver/compat/
lima-1.0b5/driver/include/
lima-1.0b5/driver/testsuite/
lima-1.0b5/driver/testsuite/clone/
lima-1.0b5/driver/testsuite/command/
lima-1.0b5/driver/testsuite/data/
lima-1.0b5/driver/testsuite/etc/
lima-1.0b5/driver/testsuite/include/
lima-1.0b5/driver/testsuite/inherit/
lima-1.0b5/driver/testsuite/inherit/master/
lima-1.0b5/driver/testsuite/log/
lima-1.0b5/driver/testsuite/single/
lima-1.0b5/driver/testsuite/single/tests/compiler/
lima-1.0b5/driver/testsuite/single/tests/efuns/
lima-1.0b5/driver/testsuite/single/tests/operators/
lima-1.0b5/driver/testsuite/u/
lima-1.0b5/driver/tmp/
lima-1.0b5/etc/
lima-1.0b5/lib/WWW/help/
lima-1.0b5/lib/cmds/
lima-1.0b5/lib/cmds/create/
lima-1.0b5/lib/cmds/player/attic/
lima-1.0b5/lib/contrib/bboard/
lima-1.0b5/lib/contrib/boards/
lima-1.0b5/lib/contrib/marriage/
lima-1.0b5/lib/contrib/roommaker/
lima-1.0b5/lib/contrib/transient_effect/
lima-1.0b5/lib/daemons/channel/
lima-1.0b5/lib/daemons/imud/
lima-1.0b5/lib/data/
lima-1.0b5/lib/data/config/
lima-1.0b5/lib/data/links/
lima-1.0b5/lib/data/news/
lima-1.0b5/lib/data/players/
lima-1.0b5/lib/data/secure/
lima-1.0b5/lib/domains/
lima-1.0b5/lib/domains/std/2.4.5/maze1/
lima-1.0b5/lib/domains/std/2.4.5/npc/
lima-1.0b5/lib/domains/std/2.4.5/post_dir/
lima-1.0b5/lib/domains/std/2.4.5/sub/
lima-1.0b5/lib/domains/std/camera/
lima-1.0b5/lib/domains/std/config/
lima-1.0b5/lib/domains/std/cult/
lima-1.0b5/lib/domains/std/effects/
lima-1.0b5/lib/domains/std/misc/
lima-1.0b5/lib/domains/std/monsters/
lima-1.0b5/lib/domains/std/recorder/
lima-1.0b5/lib/domains/std/rooms/
lima-1.0b5/lib/domains/std/rooms/beach/
lima-1.0b5/lib/domains/std/rooms/labyrinth/
lima-1.0b5/lib/domains/std/school/
lima-1.0b5/lib/domains/std/school/O/
lima-1.0b5/lib/domains/std/spells/
lima-1.0b5/lib/domains/std/spells/stock-mage/
lima-1.0b5/lib/domains/std/spells/stock-priest/
lima-1.0b5/lib/help/
lima-1.0b5/lib/help/admin/
lima-1.0b5/lib/help/hints/General_Questions/
lima-1.0b5/lib/help/hints/Pirate_Quest/
lima-1.0b5/lib/help/player/
lima-1.0b5/lib/help/player/bin/
lima-1.0b5/lib/help/player/quests/
lima-1.0b5/lib/help/wizard/
lima-1.0b5/lib/help/wizard/coding/guilds/
lima-1.0b5/lib/help/wizard/coding/rooms/
lima-1.0b5/lib/help/wizard/lib/daemons/
lima-1.0b5/lib/help/wizard/lib/lfun/
lima-1.0b5/lib/help/wizard/lib/std/
lima-1.0b5/lib/help/wizard/mudos_doc/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/interactive/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/concepts/
lima-1.0b5/lib/help/wizard/mudos_doc/driver/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/arrays/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/buffers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/compile/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/filesystem/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/floats/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/functions/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/general/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mappings/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mixed/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/numbers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/constructs/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/types/
lima-1.0b5/lib/include/driver/
lima-1.0b5/lib/log/
lima-1.0b5/lib/obj/admtool/
lima-1.0b5/lib/obj/admtool/internal/
lima-1.0b5/lib/obj/admtool/mudinfo/
lima-1.0b5/lib/obj/admtool/secure/
lima-1.0b5/lib/obj/secure/
lima-1.0b5/lib/obj/secure/cmd/
lima-1.0b5/lib/obj/secure/mailers/
lima-1.0b5/lib/obj/secure/shell/
lima-1.0b5/lib/obj/secure/shell/classes/
lima-1.0b5/lib/obj/tasktool/
lima-1.0b5/lib/obj/tasktool/internal/
lima-1.0b5/lib/open/
lima-1.0b5/lib/secure/
lima-1.0b5/lib/secure/cgi/
lima-1.0b5/lib/secure/modules/
lima-1.0b5/lib/secure/simul_efun/
lima-1.0b5/lib/std/adversary/
lima-1.0b5/lib/std/adversary/advancement/
lima-1.0b5/lib/std/adversary/armor/
lima-1.0b5/lib/std/adversary/blows/
lima-1.0b5/lib/std/adversary/death/
lima-1.0b5/lib/std/adversary/formula/
lima-1.0b5/lib/std/adversary/health/
lima-1.0b5/lib/std/adversary/pulse/
lima-1.0b5/lib/std/adversary/wield/
lima-1.0b5/lib/std/classes/event_info/
lima-1.0b5/lib/std/container/
lima-1.0b5/lib/std/living/
lima-1.0b5/lib/std/modules/contrib/
lima-1.0b5/lib/std/patterns/
lima-1.0b5/lib/std/race/
lima-1.0b5/lib/std/race/restricted/
lima-1.0b5/lib/std/room/
lima-1.0b5/lib/tmp/
lima-1.0b5/lib/trans/
lima-1.0b5/lib/trans/admincmds/
lima-1.0b5/lib/trans/obj/
lima-1.0b5/lib/wiz/
/* Do not remove the headers from this file! see /USAGE for more info. */

// container.c
//
// possibly the grossest piece of code in the mudlib.
// (not as bad as the code that describes Rust's mom, of course)
// Reworked a bit 2-17-99 by Tigran.  
// Reworking included merging CONTAINER and COMPLEX_CONTAINER

/* 
 * INCLUDES
 */
#include <move.h>
#include <setbit.h>
#include <driver/origin.h>
#include <hooks.h>
#include <clean_up.h>

/* 
 * INHERIT
 */
inherit OBJ;
inherit "/std/container/vsupport";

#ifdef SAY_HISTORY_IN_ROOMS
inherit "/std/body/history";
#endif 

/* 
 * CLASS DEFINITIONS
 */
class relation_data
{
  object array contents;           /* All of the objects associated with that relation */
  int max_capacity;                /* The maximum capacity of that relation. */
  int hidden;                      /* Whether the contents in this relation can be seen */
  int attached;                    /* Whether the contents of the relation will be added to 
                                      the relation's mass/size when calc'd */
  mixed hidden_func;               /* Function or int to be evaluated to determine visibility */
  mapping create_on_reset;         /* A mapping of filenames, quantities and parameters needed for
                                      objects to be updated as required every reset() in the relation */
  mapping create_unique_on_reset;  /* Mapping similar to that above, but the entire game is checked for the
                                      existance of the object before cloning */
}

/* 
 * VARIABLES
 */
private mapping relations = ([ ]);
private mapping relation_aliases=([]);/* Sometimes "in" and "on" etc mean the 
				       * same thing, for example on the bed. */
private string default_relation; /* The default relation used for this 
				       * container if none is specified.
				       * It should usually be "in" but 
				       * not necessarily */

int contained_light;
int contained_light_added;
mixed all_hidden_func;
varargs string introduce_contents(string relation);

/* 
 * PROTOTYPES
 */
string simple_long();
int inventory_visible();

/* 
 * FUNCTIONS
 */

/********    Relations    ********/


/* Return 1 if the relation is valid, else 0 */
private nomask int valid_relation(string relation)
{
  relation=PREPOSITION_D->translate_preposition(relation);
  if(member_array(relation,keys(relations))==-1)
    return 0;
  return 1;
}

//:FUNCTION query_relation
//Return the relation which the given object is conatained by
string query_relation(object ob)
{
  foreach(string test,class relation_data values in relations)
    if(member_array(ob,values->contents)>-1)
      return test;
}

//:FUNCTION add_relation
//Add a relation to the complex container.
varargs void add_relation(string relation,int max_capacity,int hidden)
{
  class relation_data new_relation=new(class relation_data);
  relation=PREPOSITION_D->translate_preposition(relation);
  new_relation->max_capacity=max_capacity;
  new_relation->hidden=hidden;
  new_relation->contents=({});
  new_relation->create_on_reset=([]);
  new_relation->create_unique_on_reset=([]);
  relations[relation]=new_relation;
}

//:FUNCTION remove_relations
//Remove relations from an object.  Relations can only successfully be removed 
//if they are unoccupied.
void remove_relations(string array rels...)
{
  foreach(string rel in rels)
  {
    rel=PREPOSITION_D->translate_preposition(rel);
    if(sizeof(relations[rel]->contents))
      continue;
    map_delete(relations,rel);
  }
}

//:FUNCTION set_relations
//Set the relations which are legal for a complex container.  Example:
//set_relations("on", "in", "under").  The first one is the default
//relation (the one used by set_objects(), etc)
void set_relations(string array rels...)
{
/* Ok, a bit tricky here.  We can't remove relations if there is an
   object occupying that, so we remove all relations with no objects,
   and add the new ones */
/* Cheat and remove_relations() instead, it handles this already -- Tigran */
  remove_relations(keys(relations)...);
  foreach(string rel in rels)
  add_relation(rel,VERY_LARGE);
}

//:FUNCTION get_relations
//Return all of the possible relations for an object
string array get_relations(){ return keys(relations); }

//:FUNCTION is_relation_alias
//Determine whether or not the relation is valid and return which relation
//the alias is associated with.
string is_relation_alias(string test)
{
  foreach(string relation,mixed aliases in relation_aliases)
  {
    if(member_array(test,aliases)>-1)
      return relation;
  }
  return 0;
}

//:FUNCTION set_relation_alias
//Set the aliases that a relation has
void set_relation_alias(string relation,string aliases...)
{
  string aliased_to;
  relation=PREPOSITION_D->translate_preposition(relation);
  aliased_to=is_relation_alias(relation);
  if(!valid_relation(relation))
  {
    if(!aliased_to)
      error("Cannot set a relation alias to a nonexistant relation");
    relation=aliased_to;
  }
  relation_aliases[relation]=flatten_array(aliases);
}

//:FUNCTION add_relation_alias
//Add additional aliases that a relation has.
void add_relation_alias(string relation,string aliases...)
{
  string aliased_to;
  relation=PREPOSITION_D->translate_preposition(relation);
  aliased_to=is_relation_alias(relation);
  if(!valid_relation(relation))
  {
    if(!aliased_to)
      error("Cannot add a relation alias to a nonexistant relation");
    relation=aliased_to;
  }
  if(!sizeof(relation_aliases[relation]))
    set_relation_alias(relation,aliases...);
  else
    relation_aliases[relation]=flatten_array(relation_aliases[relation]+aliases);
}

//FUNCTION remove_relation_alias
//Remove aliases that a relation has.
void remove_relation_alias(string relation,string aliases...)
{
  relation_aliases[relation]-=aliases;
  if(!sizeof(relation_aliases))
    map_delete(relation_aliases,relation);
}

//:FUNCTION query_relation_aliases
//Return the array of aliases that a relation has.
string array query_relation_aliases(string relation)
{
  return relation_aliases[relation];
}

//:FUNCTION list_relation_aliases
//List all of the relation alias information
mapping list_relation_aliases()
{
  return relation_aliases;
}

//:FUNCTION set_default_relation
//Sets the default relation for the container.  This relation is used if no 
//relation is specified on many functions
void set_default_relation(string set)
{
  string aliased_to;
  set=PREPOSITION_D->translate_preposition(set);
  aliased_to=is_relation_alias(set);
  if(!valid_relation(set))
  {
    if(!aliased_to)
      error("Cannot set a nonexistant relation as default");
    default_relation=aliased_to;
  }
  default_relation=set;
}

//:FUNCTION query_default_relation
//Returns the default relation for the container.  See set_default_relation.
string query_default_relation(){ return default_relation; }


/********    Capacity   ********/


//:FUNCTION query_capacity
//Returns the amount of mass currently attached to a container
varargs int query_capacity(string relation)
{
  int cap;
  string aliased_to;
/* Need a little special handling for #CLONE# */
  if(!relation||relation==""||relation=="#CLONE#")
    relation=query_default_relation();
  relation=PREPOSITION_D->translate_preposition(relation);
  aliased_to=is_relation_alias(relation);
  if(!valid_relation(relation))
  {
    if(!aliased_to)
      return 0;
    relation=aliased_to;
  }
  foreach(object ob in relations[relation]->contents)
  {
    if(!ob)
    {
      relations[relation]->contents-=({ob});
      continue;
    }
#ifdef USE_SIZE 
    cap+=ob->query_size();
#else
    cap+=ob->query_mass();
#endif
  }
  return cap;
}

//:FUNCTION set_max_capacity
//Set the maximum capacity for a given relation.
varargs void set_max_capacity(int cap, string relation)
{
    string aliased_to;
    if(!relation||relation=="")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    aliased_to=is_relation_alias(relation);
    if(!valid_relation(relation))
    {
	if(!aliased_to)
	    error("Invalid relation");
	relation=aliased_to;
    }
    relations[relation]->max_capacity=cap;
}

//:FUNCTION query_max_capacity
//Returns the maximum capacity for a given relation
varargs int query_max_capacity(string relation)
{
    string aliased_to;
    if(!relation||relation=="")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    aliased_to=is_relation_alias(relation);
    if(!valid_relation(relation))
    {
	if(!aliased_to)
	    return 0;
	relation=aliased_to;
    }
    return relations[relation]->max_capacity;
}

//### Yo! finish this -- Tigran 
//:FUNCTION query_total_capacity
//Returns the capacity directly attributed to the container.  This should 
//normally include anything attached or within the container.
int query_total_capacity()
{

}
#ifdef USE_MASS
//:FUNCTION query_mass
int query_mass()
{
    return query_total_capacity() + ::query_mass();
}
#endif

#ifdef USE_SIZE
int query_aggregate_size()
{
    return query_total_capacity() + ::query_size();
}
#endif


//:FUNCTION receive_object
//Determine whether we will accept having an object moved into us;
//returns a value from <move.h> if there is an error
mixed receive_object( object target, string relation )
{
    int x, m;
    string aliased_to;
    if(!relation||relation==""||relation=="#CLONE#")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    aliased_to=is_relation_alias(relation);
    if( target == this_object() )
	return "You can't move an object inside itself.\n";

    /* Have to be a bit stricter here to keep relations[] sane */
    if (!valid_relation(relation))
    {
	if(!aliased_to)
	    return "You can't put things " + relation + " that.\n";
	relation=aliased_to;
    }


#ifdef USE_SIZE
    x = target->query_size();
#else
    x=target->query_mass();
#endif

    if ( (m=(query_capacity(relation))+x) > query_max_capacity(relation) )
    {
	return MOVE_NO_ROOM;
    }
    relations[relation]->contents += ({ target });
    return 1;
}

//:FUNCTION release_object
//Prepare for an object to be moved out of us; the object isn't allowed
//to leave if we return zero or a string (error message)
varargs mixed release_object( object target, int force )
{
    string relation;
    if(!target||force)
	return 1;
    relation=query_relation(target);
    if(!relation&&!force)
	return 0;
    relations[relation]->contents-=({target});
    return 1;
}


void reinsert_object( object target, string relation )
{
    if(!relation)
	relation = query_default_relation();
    relations[relation]->contents += ({ target });
}


/********   Descriptions    ********/

string long()
{
  string res;
  string contents;

  res = simple_long();
  if (!inventory_visible())
    return res;

  foreach (string rel,class relation_data data in relations)
  {
    contents = inv_list(data->contents, 1);
    if (contents)
    {
	    res += introduce_contents(rel) + contents;
    }
  }
  return res;
}

//:FUNCTION look_in
//returns a string containing the result of looking inside (or optionally
//a different relation) of the object
string look_in( string relation )
{
    string inv;
    mixed ex;
    string aliased_to;
    if(!relation||relation=="")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    aliased_to=is_relation_alias(relation);
    //    if (!relation) relation = query_prep();

    //:HOOK prevent_look
    //A set of yes/no/error hooks which can prevent looking <relation> OBJ
    //The actual hook is prevent_look_<relation>, so to prevent looking
    //in something use prevent_look_in.
    ex = call_hooks("prevent_look_" + relation, HOOK_YES_NO_ERROR);
    if(!ex)
	ex = call_hooks("prevent_look_all", HOOK_YES_NO_ERROR);
    if (!ex) ex = "That doesn't seem possible.";
    if (stringp(ex))
	return ex;

    if (!valid_relation(relation))
    {
	if(!aliased_to)
	    return "There is nothing there.\n";
	relation=aliased_to;
    }

    inv = inv_list(relations[relation]->contents);
    if ( !inv )
	inv = "  nothing";

    return (sprintf( "%s %s you see: \n%s\n",
	capitalize(relation),
	short(),
	inv ));
}


varargs void set_hide_contents( mixed hide, string relation )
{
    string aliased_to;
    if(hide)
    {
	if(!relation)
	{
	    all_hidden_func=hide;
	    add_hook( "prevent_look_all", all_hidden_func );
	}
	else
	{
	    relation=PREPOSITION_D->translate_preposition(relation);
	    aliased_to=is_relation_alias(relation);
	    if(!valid_relation(relation))
	    {
		if(!aliased_to)
		    error("Attempted to hide contents of a container with a "
		      "nonexistant relation.");
		relation=aliased_to;
	    }
	    relations[relation]->hidden_func=hide;
	    add_hook ("prevent_look_"+relation,relations[relation]->hidden_func);
	}
    }
    else 
    {
	if(!relation)
	{
	    remove_hook( "prevent_look_all", all_hidden_func );
	    all_hidden_func=0;
	}
	relation=PREPOSITION_D->translate_preposition(relation);
	if(!valid_relation(relation))
	{
	    if(!aliased_to)
		error("Attempted to unhide contents of a container with a nonexistant "
		  "relation");
	    relation=aliased_to;
	}
	remove_hook( "prevent_look_"+relation, relations[relation]->hidden_func );
	relations[relation]->hidden_func=0;
    }   
}

mixed query_hide_contents(string relation)
{
    string aliased_to;
    if(!relation)
	return all_hidden_func;
    relation=PREPOSITION_D->translate_preposition(relation);
    aliased_to=is_relation_alias(relation);
    if(!valid_relation(relation))
    {
	if(!aliased_to)
	    return 0;
	relation=aliased_to;
    }
    return relations[relation]->hidden_func;
}


//:FUNCTION simple_long
//Return the long description without the inventory list.
string simple_long() {
    return ::long();
}


//:FUNCTION ob_state
//Determine whether an object should be grouped with other objects of the
//same kind as it.  -1 is unique, otherwise if objects will be grouped
//according to the return value of the function.
mixed ob_state() {
    /* if we have an inventory, and it can be seen, we should be unique */
    if (first_inventory(this_object()) && inventory_visible()) return -1;
    //### hack
    if (this_object()->query_closed()) return "#closed#";
    return ::ob_state();
}


//:FUNCTION parent_environment_accessible
//Return 1 if the parser should include the outside world in its
//decisions, overloaded in non_room descendants
int parent_environment_accessible() {
    return 0;
}


/********    Inventory    ********/


//:FUNCTION inventory_visible
//Return 1 if the contents of this object can be seen, zero otherwise
int inventory_visible()
{
    if ( !is_visible() )
	return 0;

    //### this should go!! short() should never return 0
    if (!short()) return 0;

    if ( test_flag(TRANSPARENT) )
	return 1;

    return !this_object()->query_closed();
}

/* Function which creates objects on reset if they are needed. */

mixed array make_objects_if_needed()
{
    mixed *objs = ({});

    /* Start by looping through each of the relations of the object */
    foreach(string relation,class relation_data object_data in relations)
    {
	/* Loop through each element of the mapping */
	object_data->contents-=({0});
	foreach(string file,mixed parameters in object_data->create_on_reset)
	{
	    int num=1;
	    mixed array rest=({});
	    object array matches=({});
	    /* Allow use of relative paths, relative to the container. */
	    file = absolute_path(file, this_object());
	    /* If the only parameter is an integer, it is the quantity of the
	     * object that needs to be in this relation */
	    if (intp(parameters)) 
		num = parameters;
	    else
	    if (arrayp(parameters)) 
	    {
		/* Check the first argument for an integer value, if it is
		 * then it is the quantity fo the object to be in the 
		 * relation */
		if(intp(parameters[0]))
		{
		    num = parameters[0];
		    rest = parameters[1..];
		}
		/* Everything else is parameters passed to create() */
		else
		    rest = parameters;
	    }
	    else
		continue;
	    if(sizeof(object_data->contents))
	    {
		matches=filter(object_data->contents,(:cannonical_form ($1)==$(file) :) );
	    }
	    while(sizeof(matches)<num)
	    {
		int ret;
		object ob = new(absolute_path(file), rest...);
		if(!ob)
		    error("Couldn't find file '" + file + "' to clone!\n");
		ret = ob->move(this_object(), "#CLONE#");
		if ( ret != MOVE_OK )
		    error("Initial clone failed for '" + file +"': " + ret + "\n");
		ob->on_clone( rest... );
		matches+=({ob});
	    }
	    objs+=matches;
	}
    }
    return objs;
}

mixed array make_unique_objects_if_needed()
{
    mixed array objs=({});

    /* Loop through each relation */
    foreach(string relation,class relation_data object_data in relations)
    {
	/* Loop through each file in the mapping. */
	foreach (string file, mixed parameters in relations[relation]->create_unique_on_reset)
	{
	    mixed array rest=({});
	    int num;
	    object array matches=({});
	    /* Allow use of relative paths, relative to the container. */
	    file = absolute_path(file, this_object());
	    /* If the only parameter is an integer, it is the quantity of the
	     * object that needs to be in this relation */
	    if (intp(parameters)) 
		num = parameters;
	    else
	    if (arrayp(parameters)) 
	    {
		/* Check the first argument for an integer value, if it is
		 * then it is the quantity for the object to be in the 
		 * relation */
		if(intp(parameters[0]))
		{
		    num = parameters[0];
		    rest = parameters[1..];
		}
		/* Everything else is parameters passed to create() */
		else
		    rest = parameters;
	    }
	    else
		continue;
	    matches=children(file);
	    matches = filter(matches, (: clonep($1) :));
	    /* Clone x of the object to catch it up to the number of objects 
	     * requested by the mapping */
	    while(sizeof(matches)<num)
	    {
		int ret;
		object ob = new(absolute_path(file), rest...);
		if(!ob)
		    error("Couldn't find file '" + file + "' to clone!\n");
		/* Test for uniqueness in the object by calling test_unique() */
		if(ob->test_unique())
		    break;
		ret = ob->move(this_object(), "#CLONE#");
		if ( ret != MOVE_OK )
		    error("Initial clone failed for '" + file +"': " + ret + "\n");
		ob->on_clone( rest... );
		matches+=({ob});
	    }
	    objs+=matches;
	}
    }
    return objs;
}

//:FUNCTION set_objects
//Provide a list of objects to be loaded now and at every reset.  The key
//should be the filename of the object, and the value should be the number
//of objects to clone.  The value can also be an array, in which case the
//first element is the number of objects to clone, and the remaining elements
//are arguments that should be passed to create() when the objects are cloned.
//An optional second string argument represents a specific relation which
//should produce objects on reset()
//
//Note:  the number already present is determined by counting the number of
//objects with the same first id, and objects are only cloned to bring the
//count up to that number.
varargs mixed array set_objects(mapping m,string relation) {
    if(!relation||relation=="")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    relations[relation]->create_on_reset = m;
    return make_objects_if_needed();
}

//:FUNCTION set_unique_objects
//Provide a list of objects to be loaded now and at every reset if they
//are not already loaded.  The key should be the filename of the object, 
//and the value should be an array which is passed to create() when the 
//objects are cloned.
//The structure of the mapping should be the same as the structure of the 
//mapping for set_objects().  For unique objects, to be checked, you should
//have a function in the object called test_unique() which will return 1 if 
//uniqueness requirements are met.  The prototype for the function is
//        int test_unique();
//An optional second string argument represents a specific relation which
//should produce objects on reset()
varargs mixed array set_unique_objects(mapping m,string relation) {
    if(!relation||relation=="")
	relation=query_default_relation();
    relation=PREPOSITION_D->translate_preposition(relation);
    relations[relation]->create_unique_on_reset = m;
    return make_unique_objects_if_needed();
}

/* Hrm, are the following two functions really necessary?  Should the relation
 * themselves have more say in this? */

//:FUNCTION introduce_contents
//returns a string appropriate for introduction the contents of an object
//in room descriptions.
varargs string introduce_contents(string relation)
{
  if(!relation||relation=="")
    relation=query_default_relation();
  relation=PREPOSITION_D->translate_preposition(relation);
  switch (relation)
  {
    case "in":
      return capitalize(the_short()) + " contains:\n";
    case "on":
      return "Sitting on "+the_short()+" you see:\n";
    default:
      return capitalize(relation)+" "+the_short()+" you see:\n";
  }
}

/* //:FUNCTION inventory_header */
/* //Returns a string header to put before inventory lists */
/* string inventory_header() { */
/*     switch (main_prep) { */
/*     case "in": return "It contains:\n"; */
/*     case "on": return "Sitting on it you see:\n"; */
/*     default: return capitalize(main_prep)+" it you see:\n"; */
/*     } */
/* } */

varargs string inventory_recurse(int depth, mixed avoid)
{
  string res;
  object array obs;
  int i;
  string str="";
  string tmp;

  if (avoid) 
  {
    if(!arrayp(avoid))
      avoid = ({ avoid });
  } else
    avoid = ({});


  if (!this_object()->is_living())
  {
    obs = all_inventory() - avoid;
    foreach (object ob in obs)
    {
      if (!(ob->is_visible()))
        continue;
	    if (!ob->test_flag(TOUCHED) && ob->untouched_long())
	    {
        str += ob->untouched_long()+"\n";
        if (ob->inventory_visible())
          if (!ob->is_living())
            str += ob->inventory_recurse(0, avoid);
      }
    }
  }
  if (!this_object()->is_living())
  {
    foreach(string key,mixed data in relations)
    {
	    res = introduce_contents(key);
	    tmp = inv_list(data->contents-avoid, 1, depth);
      if (tmp)
      {
        for (i=0; i<depth; i++)
          str += "  ";
        str += res + tmp;
      }
    }
  }
  return str;
}

string show_contents()
{
    return inventory_recurse();
}

//:FUNCTION inventory_accessible
//Return 1 if the contents of this object can be touched, manipulated, etc
int inventory_accessible() {
    return 1;
    /*     if (!is_visible()) return 0; */
    /*     if (!short()) return 0; */
    /*     return !this_object()->query_closed(); */
}


int contents_can_hear()
{
    return 1;
}

int internal_sounds_carry()
{
    return 1;
}

int environment_can_hear()
{
    object env = environment();
    return (internal_sounds_carry() && env && (!env->cant_hear_contents()));
}

// We use this so that bodies can overload this
void do_receive(string msg, int msg_type) {
    receive(msg);
}

// Inside messages propogate upward and downward...
varargs void
receive_inside_msg(string msg, object * exclude, int message_type, 
  mixed other)
{
    object env;
    object array contents;

    do_receive(msg, message_type);

    /* downwards (into our contents) */
    if(contents_can_hear())
    {
	contents = all_inventory(this_object());
	if(arrayp(exclude))
	    contents -= exclude;
	contents->receive_outside_msg(msg, exclude, message_type, other);
    }

    /* upwards (to our environment) */
    if(environment_can_hear() && (env = environment()) && (!arrayp(exclude) || 
	member_array(env, exclude) == -1))
    {
	env->receive_inside_msg(msg, arrayp(exclude) ? exclude + 
	  ({this_object()}) : ({this_object()}), 
	  message_type, other);
    }
}

// Outside messages propogate downward (into our contents)
varargs void
receive_outside_msg(string msg, object * exclude, int message_type,
  mixed other)
{
    object array contents;

    do_receive(msg, message_type);

    if(contents_can_hear())
    {
	contents = all_inventory(this_object());
	if(arrayp(exclude))
	    contents -= exclude;
	contents->receive_outside_msg(msg, exclude, message_type, other);
    }
}

//Remote messages propogate just like an inside message by defauly
varargs void 
receive_remote_msg(string msg, object array exclude, int message_type,
  mixed other)
{
    receive_inside_msg(msg, exclude, message_type, other);
}

varargs void
receive_private_msg(string msg, int message_type, mixed other)
{
    do_receive(msg, message_type);
}

/* INTERNAL MUDLIB USAGE!!! NEVER CALL THIS!!! */
void containee_light_changed(int adjustment)
{
    contained_light += adjustment;

    /*
    ** if the containee is visible, then tweak our own light; this will
    ** propagate on up to the room
    */
    if ( inventory_visible() )
	adjust_light(adjustment);
}

void resync_visibility()
{
    int new_state;

    ::resync_visibility();

    new_state = inventory_visible();

    /* if it didn't change, jump ship */
    if ( new_state == contained_light_added )
	return;

    contained_light_added = new_state;

    if ( new_state )
	adjust_light(contained_light);
    else
	adjust_light(-contained_light);
}


// If this is called, clean_up in /std/object has decided we might be
// useless.  Make sure we don't have any 'people' inside us, though.
int destruct_if_useless() {
    foreach (object ob in deep_inventory(this_object())) {
	object link = ob->query_link();

	if (link && userp(link))
	    return ASK_AGAIN;
    }
    return ::destruct_if_useless();
}

mapping lpscript_attributes()
{
  return ([
    "objects" : ({ LPSCRIPT_OBJECTS }),
    "capacity" : ({ LPSCRIPT_INT, "setup", "set_max_capacity" }),
    "relations" : ({ LPSCRIPT_LIST, "setup", "set_relations" }),
    "default_relation" : ({ LPSCRIPT_STRING, "setup", "set_default_relation" }),
    ]);
}


/********    Miscellaneous    ********/

//:FUNCTION is_container
//Returns 1 if an object is a container
int is_container()  { return 1; }


string stat_me()
{
    return
    "Prepositions: " + implode(keys(relations), ",") + "\n" +
    "It contains:\n"+ show_contents() + "\n" +
    ::stat_me();
}

void reset()
{
    make_objects_if_needed();
    make_unique_objects_if_needed();
}