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