[go: up one dir, main page]

Menu

[897c37]: / mlib / room / room.lpc  Maximize  Restore  History

Download this file

1818 lines (1640 with data), 45.4 kB

/*
 * Shattered World's Standard Room Template.
 *
 * This should be used by inheriting from the appropriate room.
 * ie. inherit "room/room";
 *
 * See /doc/example/simple_room.c for an example of how to use this file.
 *
 * Modified from the original proposal at Genesis by
 *  rendell@bruce.cs.monash.oz.au
 *  geoff@bruce.cs.monash.oz.au
 *  mmcg@bruce.cs.monash.oz.au
 */

/*
 * History:
 *  950219 Hunter    Cleaned up code and comments
 *  950221 Hunter    Added support for multiple id's for items()
 *  950225 Hunter    Added OLDCODE trap for 'dest_dir = .. '
 *  951121 Hunter	shifted 'medium' code to RO/base/medium.c
 *  960901 Belgarion Added "static" to several global variables that
 *			don't really need to save; changed show_stats()
 *			to _not_ generate a massive array through addition.
 *  981211 Jenna	Added short() to long desc, at player request
 *			Also added hilite capability to short().
 *  000429 Dredd    Added in item persistence.
 *  000514 Jenna    Overhauled doors	
 *  010822 Jenna    Persistent rooms (eg shops) now save on player exit()	
 */

inherit "RO/base_object";
inherit "RO/base/medium";

#include <Objects.h>

//#define PERSISTENT 

/* weather stuff */
const CLOUD=0;
const PRECIPITATION=1;
const HUMIDITY=2;
const WIND=3;
const TEMPERATURE=4;
const NIGHT=5;

string short_desc;     /* Short description of the room */
string long_desc;      /* Long description of the room */

/* 
 * Special items in the room. "table", "A nice table", "window", "A window" 
 */
array items;

/* 
 * An array with destinations and directions: "room/church", "north" ... 
 *
 * Made static by Jenna May 2000
 */
static array dest_dir;

/*
 * Doorways: added by Jenna May 2000
 * A paired array of directions and door objects (or direction and 0
 * to indicate an empty doorway).
 */
static array doorways;
#define JEN(x)	catch tell_room(load_object("players/jenna/door/bug_room"), "[room:"+file_name(this_object())+"] "+x+"\n");
#define DOOR	"obj/door"

static int light;        /* light value of this location */

/*
 * outdoorness defines how 'outdoors' a location is for weather purposes:
 * 0: no effect from weather
 * 1: indoors, can hear weather effects on the roof etc
 * 2: indoors, but can see outdoors
 * 3: outdoors, under cover
 * 4: completely exposed
 */

int outdoorness;

const w_none     = 0;
const w_inside   = 1;
const w_enclosed = 2;
const w_covered  = 3;
const w_outdoors = 4;

/*
 * int set_dest_dir_flag is a hack to find all the code that
 * directly assigns to dest_dir : Hunter
 *
 * Static because most saved rooms reset their dest_dir on more than
 * one occasion - frequently after restoring from the save file.
 *   - Belgarion.
 */
static int set_dest_dir_flag;

int last_night;
static object weather_obj;

/*
 * This is the area controller, if any.  It should be set before the
 * dest_dir is set; then, when dest dir is set, this object won't add itself
 * to the mapknower (it expects the room controller to do all that for it).
 */

static (int|string|object) area_controller;
static int last_init_time = time();

/*
 * call_out_functions is an array of function names that are called
 * periodically, with 2 args: a state arg and the player.  The function
 * can return 0 (to halt the call_out sequence) or an array, the first
 * element specifying the time value of the next call_out, the second
 * being the new state variable.
 */

static array call_out_functions;

/*
 * foot prints varibles see footprints doc and end of file for a description
 * of the variables.
 */
static int prints_status;
static int prints;
static string prints_desc_long;
static string prints_desc_short;
static int prints_time;

/* debug info (added by Hunter of Shattered Worlds) */
array
show_stats()
{
    int count;
    array ret, tmp;
    int pos;

    /* Force these global variables into sane states. */
    if (!pointerp(dest_dir)) dest_dir = ({ });
    if (!pointerp(items)) items = ({ });
    if (!pointerp(call_out_functions)) call_out_functions = ({ });
    if (!arrayp(doorways)) doorways = [];

    ret = "RO/base/medium"::show_stats();
    tmp = "RO/base_object"::show_stats();
    if (tmp) ret += tmp;
    ret += allocate(sizeof(dest_dir) / 2 + sizeof(items) / 2 + 8 +
	    sizeof(call_out_functions) + sizeof(doorways) / 2);
    pos = index(ret, 0);
    ret[pos++] = "short desc: "+short_desc;
    ret[pos++] = "dest_dir: "+(sizeof(dest_dir)?"":"<none>");
    if (sizeof(dest_dir))
        for (count = 0; count < sizeof(dest_dir); count += 2)
        {
            if (pointerp(dest_dir[count]))
                ret[pos++] = "x,y,z:     "+dest_dir[count][0]+","
                +dest_dir[count][1] +","+dest_dir[count][2]+", "
                +dest_dir[count+1];
            else if (objectp(dest_dir[count]))
                ret[pos++] = " ob: " +dest_dir[count]->short()+ ", "
                +dest_dir[count+1];
            else
                ret[pos++] = "     "+dest_dir[count]+", "+dest_dir[count+1];
        }
    ret[pos++] = "doorways: "+(sizeof(doorways)?"":"<none>");
    if (sizeof(doorways))
	for (count = 0; count < sizeof(doorways); count += 2)
	{
	    ret[pos] = "     "+doorways[count]+": ";
	    if (arrayp(doorways[count+1]))
		ret[pos] += doorways[count+1][1][0];
	    else if (objectp(doorways[count+1]))
		ret[pos] +=  doorways[count+1]->query_short();
	    else if (!doorways[count+1])
		ret[pos] += "<empty>";
	    pos++;
	}
    if (sizeof(items))
    {
        ret[pos++] = "items:";
        for (count = 0; count < sizeof(items); count += 2)
            ret[pos++] = "     \""+(stringp(items[count])?items[count]
            :(implode(items[count], "\" \"")))
            +"\", \""+items[count+1]+"\"";
    }
    ret[pos++] = "outdoorness: "+outdoorness+"\t"
        +"light: "+query_light()+"\t"
/*    write("in game light: "+query_glight()+"\t"); */
        +"last_night: "+last_night;
    ret[pos++] = "weather_obj: "+(objectp(weather_obj)?file_name(weather_obj)
        :"<none>");
    ret[pos++] = "area controller: "+
        (objectp(area_controller)?file_name(area_controller):
         stringp(area_controller)?area_controller:
         MAPKNOWER->query_room_known(file_name(this_object()))? "<MapKnower>" :
         "<none>");
    if (sizeof(call_out_functions))
    {
        ret[pos++] = "call_out_functions:";
        for (count = 0; count < sizeof(call_out_functions); count++)
            ret[pos++] = "   "+call_out_functions[count];
    }
#if 0
    write("Map info: ");
    MAPKNOWER->show_room(file_name(this_object()));
#endif
    /* compact the array */
    ret = ret[..(pos - 1)];
    return ret;
}

init()
{
    int i,sz;
    object item;
    object wet;
    array DestDir = this_object()->query_dest_dir();

    last_init_time = time();    /* auto-decay on room boxes */

	/* ported from ~rhys/room/sea_box - hunter 931210 */
    if (this_object()->query_medium("water"))
    {
        if (wet = present("wet_obj", this_player()))
        wet->reset_wet();
        else
        {
            wet = clone_object(WET_OBJ);
            if (transfer(wet, this_player()))
                destruct(wet);
        }
    }
    add_light(this_player()->query_light());
    this_player()->set_room(this_object());
    this_player()->test_breath();
    if (weather_obj)
    {
        int chance;
        if (outdoorness >= 4)
            add_action("snow_make", "make");
        update_weather(-1);
        chance = 0;
        if (outdoorness == 4 && weather_obj->query_precipitation() >= 2)
            chance = 10 - weather_obj->query_precipitation()*2;
        if (chance)
            if (check_wet(chance, this_player()))
            {
                call_out("test_func", chance*5,
			({"check_wet", chance, this_player()}));
            }
    }
    if (call_out_functions)
    {
        for (i = 0; i < sizeof(call_out_functions); i++)
            call_out("test_func", 1, ({call_out_functions[i], 0, this_player()}));
    }

// Jenna 14-Feb-01 - put back this since non-cardinal exits
// stopped working - temp fix while waiting for Dredd
#if 1
    /* no dest_dir set */
    if (!DestDir) return;	

    /* check if this_player is present*/
    if (environment(this_player()) == this_object()) 
    {
        i = 1;
        sz = sizeof(DestDir);
        while(i < sz)
	    {
            string dirn;
            dirn = explode(DestDir[i], "#")[0];
            if (!DIRECTION->is_standard(dirn))
            {
                add_action("redirect_move", dirn);
                // LOG->log("DIRECTIONS", file_name(this_object()) + ": " + dirn);
            }
            i += 2;
        }
    }
#endif
}

/*
 * should propogate upward - adding light to objects it's within
 * Hence we can have dark object which don't light the area
 * around them - must remember to remove light whenever a
 * transfer() is called.
 */
add_light(x)
{
    if (!intp(x)) return light;

    if (!intp(light)) light = 0;

    light = light + x;
    if (environment())
        environment()->add_light(x);
    return light;
}

query_light() { return light; }
query_total_light() { return light; }

set_light(int x)
{
    light += x;
    return light;
}

id(str)
{
    if (query_item_index(str) > -1) return 1;
    return ::id(str);
}

query_room() { return 1; }

query_look(Exception)
{
    return query_room_look() +
     LOOK->query_contents_look(this_object(), Exception);
}

query_room_look()
{
/* this is a backward compatible hack to catch any code using this dirrectly */
    LOG->log("OLDCODE", "query_room_look() (room/room) in " + file_name(this_object()) + "\n");
	return this_player()->query_soul("generic/perception")->query_room_look();
}


long(str)
{
/* this is a hack to make the code backwards compatible 
	this should be logged in the future */
    LOG->log("OLDCODE", "long() (room/room) in " + file_name(this_object()) + "\n");
    return this_player()->query_soul("generic/perception")->room_long(str);
}

describe_exits()
{
int myint, i, x;
object door;
array Exits, EmptyDoorways;
string ret;
int hilite = 0;

    if (!doorways)
        doorways = [];
    if (this_player())
    {
        myint = this_player()->query_int();
        hilite = ANSI->hilite(this_player(), "exits");
    }
    else
        myint = 0;

    if (!dest_dir || sizeof(dest_dir) == 0)
        ret =  "There are no obvious exits.\n";
    else
    {
        if (sizeof(dest_dir) == 2)
            ret = "The one obvious exit is ";
        else
            ret = "There are " +
                NUMBERS_SERV->int_to_english(sizeof(dest_dir)/2, myint, 5) +
                " obvious exits:\n  ";
        for (i = 1, Exits = [], EmptyDoorways = []; i < sizeof(dest_dir); i += 2)
        {
            x = index(doorways, dest_dir[i]);
            if (x == -1)
                Exits += [ dest_dir[i] ];
            else if (!doorways[x+1])
		EmptyDoorways += [ dest_dir[i] ];
            else if (arrayp(doorways[x+1]))
                Exits += [ doorways[x+1][1][0] + " leading " +
                           dest_dir[i] ];
            else if (objectp(doorways[x+1]))
            {
                string tmp = doorways[x+1]->is_open() ? " (open)" : "";
                Exits += [ "a " + doorways[x+1]->describe_strength() +
                   " door" + tmp + " leading " + dest_dir[i] ];
            }
        }
	if (sizeof(EmptyDoorways))
	{
	    string tmp = "a doorway leading ";
	    if (sizeof(EmptyDoorways) > 1)
		tmp = "doorways leading ";
	    Exits += [ tmp + STRINGS->array2string(EmptyDoorways) ];
	}
        ret += STRINGS->array2string(Exits) + ".\n";
    }
#ifdef NORTH_MUDLIB
    ret = STRINGS->indent( ret, 4, 6, 72 ) + "\n";
#endif

    ret = ANSI->scolor(hilite,ret);

    return ret;
}

redirect_move(str)
{
    object ms = this_player()->query_soul("generic/movement");
    string qv = query_verb();
    if (!ms) return 0;
    if (str) 
	qv += " " + str;
    return ms->move(qv);
}

/* 
 * Called by obj/souls/movement to see if we can really move. Returns
 * the destination if movement is allowed, else sets a notify_fail() 
 * and returns 0.
 *
 * Jenna May 2000
 */
move(string direction, (int|string) str, object pob)
{
    int i;
    string verb, door_name, dest;
    object door;

    if (!pob)
    {
	throw("Type error in room/room::move(dir, str, pob) - not sent correct args\n");
        pob = this_player();
        LOG->log("jenna.move", file_name(this_object()));
    }
    dest = query_exit(direction);

    if (!dest)
    {
        pob->notify_fail("There is no exit to the "+direction+".\n");
        return 0;
    }

    door = query_door(direction);
    if (door && !door->is_open() && pob->query_wiz() < 20)
    {
        tell_object(pob, "The door is closed!\n");
        return 1;
    }

    return dest;
}


/* this should query perception soul on weather a player can see rather
   than the light level */
/* Modified by Jenna for hilites Dec 98 */
short()
{
    int hilite = 0;
    string ret;

    if (this_player())
	hilite = ANSI->hilite(this_player(), "roomnames");
    if (this_object()->query_light() > 0)
        ret = short_desc;
    else
	ret = "Dark area";
    return ANSI->scolor(hilite, ret);
}

search(str)
{
    /* Hidden object stuff by Hunter */
    array stuff;
    int count, found;

    if (stuff = contents(this_object()) )
    {
        found = 0;
        for (count = 0; count < sizeof(stuff); count ++)
            if (stuff[count]->query_hidden())
                found +=  stuff[count]->set_hidden(0);
        if (found)
        {
            write("You find "+found+" object"+(found ==1?"":"s")+".\n");
            return 1;
        }
    }
    notify_fail("You search around for a while, but don't find anything interesting.\n");
    return 0;
}

/*
 * outdoorness    {none, indoors, indoors(windows), under cover, exposed}
 *
 * cloud cover    {clear, light, heavy, overcast}
 * precipitation    {none, spitting, light, storm, thunder storm}
 * humidity    {none, lots, fog, mist}
 * wind        {still, light, breezy, windy, strong wind, gale force}
 * temperature    {cold, chilly, temperate, warm, hot}
 * night       {not night, night}
 */

reset(arg)
{
    "RO/base_object"::reset(arg);

    /* Footprints/tracking stuff */
    if (query_prints()) clean_prints();
	if (!is_cloned(this_object())) set_prints(1); 

    weather_obj = load_object("obj/weather");
    last_night = 1;
    update_weather(-1);

#ifdef PERSISTENT
    /* Persistent stuff */
    if (!arg)
    {
        catch W_DATABASE->restore_items(file_name(this_object()), this_object());;
    }
#endif
}

query_weather()
{
    return weather_obj->query_sky_weather(outdoorness);
}

sky_look(arg)
{
array DestDir = this_object()->query_dest_dir();

    if (arg == "footprints" || arg == "prints" || arg == "floor")
    {
        if (query_prints())
	{
            write(query_prints_long());
            return 1;
        }
	else 
	{
	    write("Your feet don't seem to leave any marks here.\n"); 
	}
    }
    if (weather_obj && outdoorness > 1)
        return weather_obj->sky_query_long(arg, outdoorness);
    if (index(DestDir, arg) > -1)
        return this_player()->peer(arg);
    return 0;
}

set_outdoors(n) { outdoorness = n; }
query_outdoors() { return outdoorness; }
set_weather_obj(n) { weather_obj = n; }
query_weather_obj() { return weather_obj; }

sky_change_weather(weather_type, value)
{
    int current;
    string thing;

    if (outdoorness == 0)
        return;
    if (weather_type == CLOUD && outdoorness > 1)
    {
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            return appropriate(value, weather_obj->query_cloud(),
                "The sky is starting to cloud over.\n",
                "The sky is getting more cloudy.\n",
                "The sky is starting to clear.\n",
                3);
        }
        else
        {
            return appropriate(value, weather_obj->query_cloud(),
                "The sky is now cloudy.\n",
                "The cloud cover gets heavier.\n",
                "The sky clears.\n",
                "The cloud cover decreases.\n");
        }
    }
    if (weather_type == PRECIPITATION)
    {
        current = weather_obj->query_temperature();
        if (current == 0)
            thing = " snow";
        else if (current == 1)
            thing = " hail";
        else
            thing = " rain";
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            if (outdoorness > 1)
            {
                ud_prints(); /* to see if prints are washed away */
                return appropriate(value, weather_obj->query_precipitation(),
                    "It looks like"+thing+" is on the way.\n",
                    "The"+thing+" seems to be getting heavier.\n",
                    "The"+thing+" seems to be getting lighter.\n",
                    3);
            }
        }
        else
        {
            if (outdoorness == 1)
            {
                if (weather_obj->query_temperature() > 0)
                    return appropriate(value, weather_obj->query_precipitation(),
                        "A noise starts above your head.\n",
                        "The noise above your head gets louder.\n",
                        "The noise above your head stops.\n",
                        "The noise above your head becomes quieter.\n");
            }
            else
            {
                ud_prints(); /* to see if prints are washed away */
                return appropriate(value, weather_obj->query_precipitation(),
                    "It starts to"+thing+".\n",
                    "The"+thing+" gets heavier.\n",
                    "The"+thing+" stops.\n",
                    "The"+thing+" eases up.\n");
            }
        }
    }
    if (weather_type == HUMIDITY && outdoorness > 1)
    {
        current = weather_obj->query_humidity();
        if (current == 1 || (current == 0 && value == 1))
            thing = " mist ";
        else if (current == 2 || (current == 0 && value == 2))
            thing = " fog ";
        else if (thing == 0) thing = " fog ";
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            return appropriate(value, weather_obj->query_humidity(),
                "A"+thing+"starts to rise.\n",
                "The"+thing+"seems to be getting thicker.\n",
                "The"+thing+"seems to be getting lighter.\n",
                3);
        }
        else
        {
            return appropriate(value, weather_obj->query_humidity(),
                "A"+thing+"settles on the land.\n",
                "The"+thing+"gets thicker.\n",
                "The"+thing+"clears.\n",
                "The"+thing+"thins somewhat.\n");
        }
    }
    if (weather_type == WIND && outdoorness > 1)
    {
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            ud_prints(); /* to see if prints are washed away */
            return appropriate(value, weather_obj->query_wind(),
                "The air is starting to move.\n",
                "The wind seems to be picking up.\n",
                "The wind seems to be easing off.\n",
                3);
        }
        else
        {
            ud_prints(); /* to see if prints are washed away */
               return appropriate(value, weather_obj->query_wind(),
                "A wind starts to blow.\n",
                "The wind gets stronger.\n",
                "The wind stills.\n",
                "The wind eases somewhat.\n");
        }
    }
    if (weather_type == TEMPERATURE)
    {
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            if (outdoorness > 1)
                return appropriate(value, weather_obj->query_temperature(),
                    "The air seems to be getting warmer.\n",
                    1,
                    "The temperature seems to be dropping.\n",
                    3);
        }
        else
        {
            if (outdoorness == 1)
            {
                if (value > 0 || (value < 0 && weather_obj->query_precipitation() > 0))
                    return appropriate(value, weather_obj->query_temperature(),
                        "A sudden noise begins above your head.\n",
                        "",
                        "The noise above your head fades to a whisper, then stops.\n",
                        "");
            }
            else
                return appropriate(value, weather_obj->query_temperature(),
                    "The air feels warmer.\n",
                    1,
                    "The air feels cooler.\n",
                    3);
        }
    }
    if (weather_type == NIGHT && outdoorness > 1)
    {
        if (value >= 100)
        {
            while (value >= 100)
                value -= 100;
            return appropriate(value, weather_obj->query_night(),
                "The sun edges down past the western horizon.\n",
                1,
                "The eastern sky glows with early dawn.\n",
                3);
        }
        else
        {
            if (weather_obj->query_night() < value)
                return "The sun sets.\n";
            else if (weather_obj->query_night() > value)
                return "The sun rises.\n";
            update_weather(value);
        }
    }
}

query_last_night() { return last_night; }

update_weather(current)
{
    if (!weather_obj)
        return;
    if (current < 0)
        current = weather_obj->query_night();
    if (outdoorness < 2)
        current = 1; /* treat as night if indoors */
    add_light(last_night - current);
    last_night = current;
    ud_prints(); /* for footprints */
}

appropriate(new, current, st1, st2, st3, st4)
{
    if (intp(st1))
        st1 = nth(st1, st1, st2, st3, st4);
    if (intp(st2))
        st2 = nth(st2, st1, st2, st3, st4);
    if (intp(st3))
        st3 = nth(st3, st1, st2, st3, st4);
    if (intp(st4))
        st4 = nth(st4, st1, st2, st3, st4);
    if (new > current)
        if (current == 0)
            return st1;
        else
            return st2;
    else if (new < current)
        if (new == 0)
            return st3;
        else
            return st4;
}

nth(num, s1, s2, s3, s4)
{
    if (num == 1)
        return s1;
    else if (num == 2)
        return s2;
    else if (num == 3)
        return s3;
    else if (num == 4)
        return s4;
    return 0;
}

/*
 * This should be in the perception soul.
 */

peer(dir)
{
    /* this function has been moved to the perception soul. The stub has
    been left here for backward compatability. This should be logged - Zaph */
    LOG->log("OLDCODE", "direct peer (room/room) in " + file_name(this_object()) + "\n");
	return this_player()->query_soul("generic/perception")->peer(dir);
}

test_func(kludge)
{
    array ret;
    string func, arg;
    object who;

    if (!pointerp(kludge))
    {
       catch tell_object(find_player("dredd"), "Kludge is fucked = "+kludge+".\n") ;;
       return;
    }
    func = kludge[0];
    arg = kludge[1];
    who = kludge[2];
    if (!who)
        return;
    if (environment(who) != this_object())
        return;
    ret = this_object()->(func)(arg, who);
    if (ret != 0 && ret[0] != 0)
        call_out("test_func", ret[0], ({func, ret[1], who}));
}

check_wet(chance, who)
{
    object wet;

    if (who)
    {
        if (!random(chance))
        {
            wet = present("wet_obj", who);
            if (wet)
                wet->reset_wet();
            else
            {
                wet = clone_object(WET_OBJ);
                if (transfer(wet, who))
                    destruct(wet);
            }
        }
        else
            return ({chance*5, chance});
    }
}

add_exit(string dest1, string dir1)
{
    array tmp1, co;
    int y;

    tmp1 = this_object()->query_dest_dir();
    if (!tmp1) tmp1 = [];
    set_dest_dir(tmp1 + [ dest1, dir1 ]);

    /* hack follows -> to force init() for everything in the room. */
    co = contents(this_object());
    for (y = 0; y < sizeof(co); y ++)
        move_object(co[y], this_object());
    return 1;
}

remove_exit(string dest1)
{
    array tmp1, tmp2;
    int x;

    if (!pointerp(dest_dir)) return 0;

    x = index(dest_dir, dest1);
    if (x > -1 && (x / 2 == (x+1)/2))
    {
        tmp1 = dest_dir[..x-1];
        tmp2 = dest_dir[x+2..];
        dest_dir = tmp1 + tmp2;
    }
    else if (x > -1)
    {
        if (x == 1)
            dest_dir = dest_dir[2..sizeof(dest_dir)-1];
        else
        {
            tmp1 = dest_dir[..x-2];
            tmp2 = dest_dir[x+1..];
            dest_dir = tmp1 + tmp2;
        }
    }
    return 0;
}

/*
 * This is always called using this_object()->query_exit(), and so can
 * easily be overridden in an inherited file.
 */

query_exit(str, int open) 
{
    int x;
    array DestDir = this_object()->query_dest_dir();
    array door;

    if (!str) return 0;
    if (!pointerp(DestDir)) return;

    x = index(DestDir, str);
    if (x < 1)
    {    
        /* rare */
        for (x = 1; x < sizeof(DestDir); x+=2)
        {
            door = explode(DestDir[x], "#");
            if (door[0] == str)
            {
                if (open && (atoi(door[1]) != 0))
                {
                    return 0;
                }
                return DestDir[x-1];
            }
        }
        return 0;
    }
    /* matched it in an exit slot */
    if (x % 2)  return DestDir[x-1];
    return DestDir[x+1];       /* searching for a filename's exit */
}

/* Jenna March 2000 */
query_direction(str, int open)
{
    int i, x;
    array DestDir = this_object()->query_dest_dir();
    array door;

    if (!str) return 0;
    if (!pointerp(DestDir)) return;
    for (i = 1; i < sizeof(DestDir); i+= 2) {
	x = index(DestDir[i], "#");
	if (x != -1)
	    DestDir[i] = DestDir[i][..x-1];
	}
    x = index(DestDir, str);
    return x == -1 ? 0 : DestDir[x+1];
}

set_long(str) { long_desc = str; }
set_short(str) { short_desc = str; }
query_long_desc() { return long_desc; }
query_short_desc() { return short_desc; } /* redundant to short, but hey... */
/* Jenna Dec 98 */
query_long() { return short() + "\n" + long_desc; }
query_short() { return short(); }

set_dest_dir(array arr)
{
    int x, Count;
    array props, locks, old_doors;

    /* check the direction and make sure it doesn't have a leading slash */
    /* Also: new backwards compatability stuff for doors - Jenna May 2000 */
    for (Count = 0, old_doors = []; Count < sizeof(arr); Count+=2)
    {
        if ((arr[Count])[0] == '/')
            arr[Count] = arr[Count][1..];
        x = index(arr[Count+1], "#"); /* old door setup, a la "east#2doorcode" */
        if (x != -1)
        {
	    JEN("Got old door setup.");
            props = [];
            locks = 0;
            if (arr[Count+1][x+1] == '0')
                props += [ "open" ];
            else if (arr[Count+1][x+1] == '1')
                props += [ "unlocked" ];
            if (arr[Count+1][x+2..] != "")
                locks = [ arr[Count+1][x+2..], 2];
            arr[Count+1] = arr[Count+1][..x-1];
	    old_doors += [ arr[Count+1], props, locks ];
        }
    }

    /* a hack for the detection of direct assignments to dest_dir : Hunter */
    set_dest_dir_flag = 1;

    dest_dir = arr;
/*
 * If this is a cloned room, we do _not_ want to add ourselves to
 * the mapper - we let our area_controller do it (area controller should
 * only add names of rooms with constant dest dirs, and manage all others
 * itself).
 * Note: almost all virtual objects are cloned - hence the is_cloned hack.
 */

    for (Count = 0; Count < sizeof(old_doors); Count += 3)
	make_door(old_doors[Count], 2, old_doors[Count+1], old_doors[Count+2]);

    call_out("notify_mapper", 0);
#if 0
    // actually we probably want to add it (and check if it connects
    // to a non virtual area?
    if ((!area_controller) && (!is_cloned(this_object())))
    {
        call_out("notify_mapper", 0);
    }
#endif
}

notify_mapper()
{
    //catch
    MAPKNOWER->add_location(file_name(this_object()), dest_dir, area_controller);
    //;
}

/*
 * return a _COPY_ of the dest_dir array
 *
 * Some rooms may override this for special effects. Hence always refer to
 * query_dest_dir(), instead of dest_dir, in inherited objects
 */
query_dest_dir()
{
    return (pointerp(dest_dir)?( dest_dir+({ }) ):({ }) );
}

/* 
 * Doorways
 */ 

make_door(direction, strength, props, locks, magicks)
{
    array door = [ "", strength, 0, 0, 0, 0, 0, magicks, 0 ];
    int i, x, unlocked, hinges_this_side;
    string neighbour;

    JEN("make_door in direction: "+direction+"."); 
    if (!dest_dir || index(this_object()->query_dest_dir(), direction) == -1)
	return 0;
    if (strength == "auto")
    {
	object or = load_object(query_exit(direction));
	array dw = or->query_doorways();
	string od = or->query_direction(file_name(this_object()));  
	int x2;
	x = index(dw, od);
	if (x == -1) return 0;
	add_doorway(direction); /* adds doorway if not already present */
        x2 = index(doorways, direction);
        doorways[x2+1] = dw[x+1];
	return 1;
    }
    door[0] = "a " + DOOR->describe_strength(strength) + " door";
    if (arrayp(props))
    {
	x = index(props, "unlocked");
	if (x != -1)
	{
	    unlocked = 1;
	    props = props[..x-1] + props[x+1..];
	}
	x = index(props, "open");
	if (x != -1)
	{
	    door[2] = 1;
	    door[0] += " (open)";
	    unlocked = 1;	
	    props = props[..x-1] + props[x+1..];
	}
	x = index(props, "hinges_this_side");
	if (x != -1)
	{
	    hinges_this_side = 1;
	    props = props[..x-1] + props[x+1..];
	}
	if (sizeof(props))
	    door[8] = props;
    }
    neighbour = query_exit(direction);
    if (hinges_this_side)
	door[5] = [ file_name(this_object()), neighbour ];
    else
	door[5] = [ neighbour, file_name(this_object()) ];
    if (arrayp(locks))
    {
	door[6] = [ locks[0], locks[1], !unlocked ];
	for (i = 2; i < sizeof(locks); i+= 2)
	    door[6] += [ locks[i], locks[i+1], !unlocked ];
    }
    add_doorway(direction); /* adds doorway if not already present */
    x = index(doorways, direction);
    doorways[x+1] = [ DOOR, door ];
    return 1;
}

fix_doors()
{
    object door;
    int i;

    set_doorways(0);
    for (i = 1; i < sizeof(dest_dir); i += 2)
	make_door(dest_dir[i], "auto");
    write("Reset room's doors according to neighbours.\n");
    return 1;
}

query_doorways() { return doorways; }

/* Returns 1 if any doors exist (but doesn't load them),
 * else 0.
 */
has_doors()
{
    int i;

    if (!doorways || !sizeof(doorways))
	return 0;
    for (i = 1; i < sizeof(doorways); i += 2)
	if (doorways[i])
	    return 1;
    return 0;
}

query_doors()
{

    int i;
    array ret;
    object door;

    if (doorways)
	for (i = 0, ret = []; i < sizeof(doorways); i+=2)
	{
	    door = query_door(doorways[i]);
	    if(door)
		ret += [ door ];
	}
    return ret;
}

add_doorways(arg) { return set_doorways(arg); }

set_doorways(arg)
{
    int i;
    array tmp;

    if (stringp(arg))
	return add_doorway(arg);
    else if (arrayp(arg))
	for (i = 0; i < sizeof(arg); i++)
	    add_doorway(arg[i]);
    else if (!arg)
	doorways = 0;
}

add_doorway(string dir)
{
    JEN("add_doorway(\""+dir+"\").");
    if (!arrayp(doorways))
	doorways = [];
    if (index(doorways, dir) == -1)
	doorways += [ dir, 0 ];
}

internal_add_door(string dir, door) /* called by init_arg */
{
    int x;
    if (!arrayp(doorways))
	doorways = [];
    x = index(doorways, dir);
    if (x == -1)
	doorways += [ dir, door ];
    else
	doorways[x+1] = door;
}

add_door(string dir, object door)
{
    int x;

    if (!arrayp(doorways))
	doorways = [];
    x = index(doorways, dir);
    if (x == -1)
	doorways += [ dir, door ];
    else
	doorways[x+1] = door;
    JEN("Added door "+file_name(door));

}

remove_door(dir_or_door)
{
    int x;

    if (!arrayp(doorways))
	doorways = [];
    x = index(doorways, dir_or_door);
    if (x != -1)
	doorways[x+1-(x%2)] = 0;
}

remove_doorway(string dir)
{
    int x;

    if (!arrayp(doorways))
	doorways = [];
    x = index(doorways, dir);
    if (x != -1)
	doorways = doorways[..x-1] + doorways[x+2..];
}

query_door(string dir, int flag)
{
    int x;

    if (!arrayp(doorways))
        doorways = [];
    x = index(doorways, dir);
    if (x == -1)
        return 0;
    if (arrayp(doorways[x+1]))
	doorways[x+1] = load_door(doorways[x+1]);
    if (objectp(doorways[x+1]))
        return doorways[x+1];
    return flag;
}

load_door(array door_auto_load)
{
    string my_dir;
    object other_room;
    array installed;
    object door = clone_object(door_auto_load[0]);
    door->init_arg(door_auto_load[1]);
    installed = door->query_installed();
    JEN("Loaded door "+file_name(door));
    if (installed)
    {
	other_room = (installed[0] == file_name(this_object()) ?
			load_object(installed[1]) :
			load_object(installed[0]));
	my_dir = other_room->query_direction(file_name(this_object()));
	JEN("calling other_room->add_door in direction \""+my_dir+"\"");
	if (my_dir)
	    other_room->add_door(my_dir, door);
    }
    return door;
}

query_doorway(string dir)
{
    return query_door(dir, 1);
}

/* 
 * do_door() interprets commands for installed doors, stripping out
 * any directional command (e.g. "kick east door") and sending it
 * to the appropriate door object (e.g. east_door->kick("door")).
 * - Jenna May 2000
 */
do_door(str)
{
    string verb, direction, stripped;
    array args, doors;
    object door;

    if (!doorways || !sizeof(doorways))
        return 0;
    verb = query_verb();
    notify_fail("Which door do you wish to " + verb + "? \n");
    if (!str)
        return 0;
    args = STRINGS->split_first(str, "door");
    if (!args)
        return 0;
    if (this_player()->query_ghost())
    {
	notify_fail("Your hand passes straight through the door!\n");
	return 0;
    }
    direction = STRINGS->strip_whitespace(args[0]);
    if (verb == "knock" && args[0][..2] == "on ")
    {
        notify_fail("On which door do you wish to knock?\n");
        direction = STRINGS->strip_whitespace(args[0][3..]);
    }
    else if (verb == "prise" && args[0][..9] == "lock from ")
    {
	notify_fail("Which of the doors do you want to prise a lock from?\n");
	direction = STRINGS->strip_whitespace(args[0][10..]);
    }
    if (verb == "break")
        verb = "smash";
    if (direction && direction != "")
    {
        array tmp;
        door = query_door(direction);
        if (!door)
        {
            notify_fail("There is no door to the "+direction+".\n");
            return 0;
        }
        tmp = STRINGS->split_around(str, [ direction + " " ] );
        stripped = tmp[0] + tmp[1];
    }
    else
    {
        doors = query_doors();
        if (!doors)
        {
            notify_fail("This room has no door.\n");
            return 0;
        }
        else if (sizeof(doors) != 1)
            return 0;
        door = doors[0];
        stripped = str;
    }
    if (!door)
        return 0;
    JEN("Calling "+verb+"(\""+stripped+"\")");
    return door->(verb)(stripped);
}

/* 
 * An ID function that tries to find a matching door for the
 * string - e.g. "east door", "wooden door". If found, it
 * returns the door object.
 *
 * Jenna May 2000
 */
find_door(str)
{
    array a, qd;
    int s, x, i;
    string desc;
    object door;

    if (!str)
	return 0;
    a = explode(str, " ");
    s = sizeof(a);
    if (a[s-1] != "door")
    {
	str += " door";
	a += [ "door" ];
	s++;
    }
    qd = query_doors();
    if (!qd || !sizeof(qd))
	return 0;
    if (s == 1)
    {
	door = qd[0];
    }
    else
    {
	x = index(doorways, a[s-2]);

	if (x != -1 && doorways[x+1])
	{
	    door = doorways[x+1];
	}
    }
    if (!door)
    {
	for (i = 0; i < sizeof(qd); i++)
	{
	    if (qd[i]->id(str))
		return qd[i];
	}
    }
    return (door ? door : 0);
}

owner(dir)
{
    string who = "nobody";
    (int|array|string) o;

    if (!dir) return who;
    o = query_exit(dir)->query_owner();
    if (o) {
        if (stringp(o))
            who = capitalize(o);
        else
            who = implode(o, ", ");
        }
    return who;
}

query_auto_load()
{
    array ret;
    int i, c;

    if (doorways && sizeof(doorways))
    {
	ret = allocate(sizeof(doorways));
	for (c = 0, i = 1; i < sizeof(doorways); i += 2)
	{
	    if (doorways[i])
	    {
		ret[c++] = doorways[i-1];
		if (objectp(doorways[i]))
		    ret[c++] = doorways[i]->query_auto_load();
		else
		    ret[c++] = doorways[i];
	    }
	}
	ret = ret[..c-1];
	if (sizeof(ret))
	    return [ "doorways", ret ];
    }
}

init_arg(args)
{
    int x, i;
    JEN("init_arg.");
    if (arrayp(args))
    {
	x = index(args, "doorways"); 
	if (x != -1)
	{
	    JEN("init_arg provided "+sizeof(args[x+1])+ "/2 doorways.");
	    for (i = 0; i < sizeof(args[x+1]); i += 2)
		internal_add_door(args[x+1][i], args[x+1][i+1]);
	}
    }
}

/* End doorways */

set_items(arr) { items = arr; }
query_items() { return items; }
/*
 * find the specified object in the items array
 */
int
query_item_index( string id )
{
    int i;

    if(!arrayp(items)) return -1;
    for (i = 0; i < sizeof(items); i += 2)
    {
	    if (stringp(items[i]) && items[i] == id)
		return i;
	    if (arrayp(items[i]) && index(items[i], id) != -1)
		return i;
    }
    return -1;
}

query_item_description(id) 
{
    int id_pos;

	id_pos = query_item_index(id);
	if (id_pos < 0) return 0;
	else 
    {
        if (items[id_pos+1][0] == '@')
        {
            return this_object()->(items[id_pos+1][1..])(id);
        }
	    return items[id_pos+1];
    }
}	
    
add_item(item, desc)
{
    int i;

    if (!items) items = ({item, desc});
    else
    {
        i = query_item_index(item);
        if (i == -1)
            items += ({item, desc});
        else
            items[i+1] = desc;
    }
    return 1;
}

remove_item(item)
{
    int it;
    it = query_item_index(item);
    if (it < 0) return 0;
    items = items[..it-1] + items[it+2..];
    return 1;
}


/*
 * Coded by Roam, August 1991
 */
snow_make(str)
{
object ball;
    notify_fail("Make what?\n");
    if (!str)
        return 0;
    if (str != "snowball" && str != "snow ball" && str != "a snowball" &&
        str != "a snow ball")
    {
	return 0;
    }
    if (weather_obj->query_precipitation()<2 ||
                  weather_obj->query_temperature())
    {
        notify_fail("There's no snow here.\n");
        return 0;
    }
/* ok to make a snow ball */
    write("You pack some snow into a snow ball.\n");
    say(this_player()->query_name()+" makes a snow ball.\n");
    ball = clone_object("players/nostromo/obj/snow_ball");
    transfer(ball, this_player());
    return 1;
}

query_destruct() { return 0; }

/*
 * FOOTPRINTS.
 * Coded by Zaphoid of Shattered World.
 * 26/4/92
 *
 * prints has two states:-
 *
 * 0 = prints off.
 * 1 = prints on.
 *
 * prints_status has three states.
 *
 * 0 = no prints.
 * 1 = one set of prints in the room.
 * 2 = two or more sets of prints in the room.
 *
 * prints changed to use a timestamp rather than a call_out()
 *
 */

/* this function is called whenever someone leaves the room. */
/* geez - this is FAR to big and complex - Dredd */
/* we'd prefer no exit function whatsoever */

exit(obj)
{
    string dir;
    string race;
    string leave;

    if (obj) add_light(-1 * obj->query_light());

    this_object()->save_to_database();

    // The following added by Jenna Aug 01
    // Homes/shops need to save if players/mobs drop or pick up items
    // within the room -- previously, if they did this and the shop
    // didn't buy or sell anything before reboot, the item respawned /
    // vanished.
    if (this_object()->is_persistent())
	this_object()->set_needs_update();

    if (query_prints_status() == 0 && query_prints() == 1)
    {
        dir = query_verb();
        if (obj->query_invis()) return 0;

        if ((query_win() || query_precip()) && query_prints_status() > 0 )
        {
	        ud_prints();
            return 0;
        }
        else if (obj->query_race())
        {
            if (obj->query_npc())
            {
                race = capitalize(obj->query_race());
            }
            else
            {
                race = "Player";
            }
            if (index(query_dest_dir(),dir) != -1)
            {
                leave = dir;
            }
            else
            {
                leave = "X";
            }
            add_prints(race, leave);
        }
    }
    else
    {
        if (query_prints_status() == 1)
        {
            prints_desc_short =
		"There are footprints leaving in many directions.\n";
            prints_desc_long = "There many footprints in the room. So many "+
        "in fact that it is impossible to tell in what direction people "+
        "have been leaving.\n";
            prints_status = 2;
	        prints_time = time();	
            ud_prints();
        }
    }
}

/* Separated out from exit() by Jenna May 2000 for use with
 * new door system.
 */
save_to_database()
{
#ifdef PERSISTENT
//    if (!this_object()->is_persistent() && sizeof(contents(this_object())) > 1)
//    {
        catch W_DATABASE->commit_items(this_object(), 1);;
//    }
#endif
}

set_prints(arg)
{
    if (arg != 1 && arg != 0)
       return 0;
    prints = arg;
    return 1;
}

query_prints()
{
    return prints;
}

query_prints_short()
{
  /* returns the current print desc. */
    if (query_prints()) {
	ud_prints();
        return prints_desc_short;
    }	
}

query_prints_long()
{
  /* returns the long description of the footprints. */
    if (query_prints())
    {
	ud_prints();
        if (query_prints_status() == 0)
            return "The floor is completely clear of footprints.\n";
        if (query_prints_status() > 0)
            return prints_desc_long;
    }
}

query_prints_status()
{
    if (query_prints()) {
	ud_prints(); 
        return prints_status;
    }
}


clean_prints()
{
  /* Removes all footprints from the room. */
    if (query_prints())
    {
        prints_desc_long = "";
        prints_desc_short = "";
        prints_status = 0;
	prints_time = 0;
    }
}

add_prints(race, leave)
{
  /* sets the long and short of print discriptions */
    if (query_prints())
    {
	ud_prints();
        if (query_prints_status()== 0)
        {
            if (leave == "X")
            {
                prints_desc_short = "There is a set of footprints here.\n";
                prints_desc_long = "There is a set of footprints that finish "+
        "in the middle of the room. They just stop there.\n";
            }
            else
            {
                prints_desc_short = "There is a set of footprints leading "
		+ leave + ".\n";
                prints_desc_long = "The footprints look as if they have been "+
          "made by a "+race+" and point towards the "+leave+" exit" + ".\n";
            }
        }
        prints_status = 1;
	prints_time = time();
    }
}

query_precip()
{
    if (!weather_obj)
        return 0;
    if (weather_obj -> query_precipitation() > 1 && query_outdoors() >= 2)
        return 1;
}

query_win()
{
    if (!weather_obj)
        return 0;
    if (weather_obj -> query_wind() > 2 && query_outdoors() >= 1)
        return 1;
}

ud_prints()
{
  /* change in the weather calls this so the prints are removed */
    if ((query_precip() || query_win())
       && query_prints() /* && (query_prints_status() > 0)*/ )
    {
        clean_prints();
    }
    if ((time() - prints_time) >= 300) clean_prints();
}

/*
 * Stuff designed to interface with area controllers - G.
 * Note that set_dest_dir() relies on area_controller being set before
 * it is called.
 */

set_area_controller((object|string) x)
{
    if (objectp(x)) x = file_name(x);

    area_controller = x;
    if (set_dest_dir_flag)
    {
        LOG->log("BAD_CONTR", file_name(this_object())
		+ "; dest dir set before controller (" + x + ")\n");
    }
}

query_area_controller()
{
    return area_controller;
}

/*
 * Controllers can set decay times on rooms - if the decay time is 0,
 * the room doesn't decay, otherwise the room will call back its controller
 * if it has been empty of all living items for at least the given decay time,
 * and is currently empty of objects.  If the room has no controller when it decays,
 * it will dest itself.
 */

static int decay_time = 0;

set_room_decay(int t)
{
    if (0 < t && t < 300)
        t = 300;    /* 5 mins minimum */
    if (t < 0)
        t = 0;
    decay_time = t;
    if (find_call_out("room_decay") == (-1))
        call_out("room_decay", t);
}

room_decay()
{
    if (!decay_time)
        return 0;
    if ((time() - last_init_time) < decay_time)
    {
        call_out("room_decay", decay_time - time() + last_init_time);
        return;
    }
    (int|array) c = contents(this_object());
    if (pointerp(c) && !sizeof(c))
    c = 0;
    if (!c)
    {
        if (area_controller)
            area_controller->room_decay(this_object());
        else
            this_object()->clean_up();
    }
    else
        call_out("room_decay", decay_time);
}

/* So we can put things into the room! */
can_put_and_get ()
{
    return 1;
}

/* added so perception can tell the difference between a room.c room and a
   door.c room */

query_room_type() { return "room"; }
 
query_coords() { return [ 0, 0, 0 ]; }