Generic room

    This object determines the behavior of rooms.

    Informational public methods:

        dark()                          True if room is dark
        link_ok()                       True if room is link_ok
        exits()                         Get exits

    Public methods to perform an action:

        tell_contents(s)                Tell contents a message
        verb_changed_inside()           Notification

    Owner methods:

        set_dark(val)                   Set whether room is dark
        set_link_ok(val)                Set whether room is link_ok
        add_exit(obj)                   Add an exit

    Commands:

        look_cmd()                      Look at room
        say_cmd(s)                      Say something to the room
        pose_cmd(s)                     Emote something to the room

    Exit protocol methods:

        will_attach(actor)              Indicates impending exit attach
        did_attach(actor)               Indicates completed exit attach
        will_link(actor)                Indicates impending exit link
        did_link(actor)                 Indicates completed exit link

    Private methods:

        invalidate_verb_caches()        Invalidate caches of contents

parent container
parent has_commands
object room

var root name 'room
var room inited 0
var room exits 0
var room dark 0
var room link_ok 0

method init_room
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");
    exits = [];
    dark = 0;
    link_ok = 0;
.

method uninit_room
    if (caller() != $root)
        throw(~perm, "Caller is not $root.");

    // Do something intelligent with exits.
    exits = 0;
    dark = 0;
    link_ok = 0;
.

eval
    .initialize();
    .set_vr_name("Generic room");
    .add_command("l?ook", 'look_cmd);
    .add_command("say *", 'say_cmd);
    .add_command("emote|pose|do *", 'pose_cmd);
    .add_shortcut("\"*", 'say_cmd, ["say", 1]);
    .add_shortcut(":*", 'pose_cmd, ["pose", 1]);
    .set_fertile(1);
.

method environment
    return pass() + exits;
.

method dark
    return dark;
.

method set_dark
    arg val;

    if (!.is_owned_by(sender()))
        throw(~perm, "Sender not an owner.");
    dark = val ? 1 | 0;
.

method link_ok
    return link_ok;
.

method set_link_ok
    arg val;

    if (!.is_owned_by(sender()))
        throw(~perm, "Sender not an owner.");
    link_ok = val ? 1 | 0;
.

method exits
    return exits;
.

method add_exit
    disallow_overrides;

    if (caller() != $exit)
        throw(~perm, "Caller is not $exit.");
    exits = setadd(exits, sender());
.

method remove_exit
    disallow_overrides;

    if (caller() != $exit)
        throw(~perm, "Caller is not $exit.");
    exits = setremove(exits, sender());
.

method full_description
    arg [args];
    var obj, users, awake, objects, object_names, userstr, objstr, desc;

    if (dark)
        return [.vr_name(), .description()];

    // Separate contents into users and objects.
    users = [];
    objects = [];
    for obj in (.contents()) {
        if (obj.has_ancestor($user))
            users = [@users, obj];
        else
            objects = [@objects, obj];
    }

    // Figure out who's awake, leaving out anyone in args.
    awake = [];
    for obj in (users) {
        if (obj.connected() && !(obj in args))
            awake = [@awake, obj];
    }

    // Build user string.
    if (listlen(awake) >= 2)
        userstr = "" + $list.to_english($list.map(awake, 'vr_name)) + " are here.";
    else if (awake)
        userstr = "" + awake[1].vr_name() + " is here.";
    else
        userstr = "";

    // Build object string.
    if (objects) {
        object_names = [];
        for obj in (objects) {
            if (obj.vr_name()[1] in "aeiou")
                object_names = [@object_names, "an " + obj.vr_name()];
            else
                object_names = [@object_names, "a " + obj.vr_name()];
        }
        objstr = "You can see " + $list.to_english(object_names) + ".";
    } else {
        objstr = "";
    }

    desc = [.vr_name(), .description()];
    if (userstr)
        desc = [@desc, userstr];
    if (objstr)
        desc = [@desc, objstr];
    return desc;
.

method tell_contents
    arg str, [except];
    var obj;

    for obj in (.contents()) {
        if (!(obj in except))
            (| obj.tell(str) |);
    }
.

method did_arrive
    arg [args];

    (> pass(@args) <);
    if (!dark && sender().has_ancestor($user))
        .tell_contents(sender().vr_name() + " has arrived.", sender());
.

method did_leave
    arg [args];

    (> pass(@args) <);
    if (!dark && sender().has_ancestor($user))
        .tell_contents(sender().vr_name() + " has left.", sender());
.

method did_connect
    if (!dark)
        .tell_contents(sender().vr_name() + " has connected.", sender());
.

method did_disconnect
    if (!dark)
        .tell_contents(sender().vr_name() + " has disconnected.", sender());
.

method look_cmd
    arg dummy;
    var actor;

    actor = sender();
    actor.tell(.full_description(actor));
.

method say_cmd
    arg dummy1, str;
    var actor;

    actor = sender();
    .tell_contents(actor.vr_name() + " says, \"" + str + "\"", actor);
    actor.tell("You say, \"" + str + "\"");
.

method pose_cmd
    arg dummy1, str;
    var actor;

    actor = sender();
    if (str && str[1] == ":")
        .tell_contents(actor.vr_name() + substr(str, 2));
    else
        .tell_contents(actor.vr_name() + " " + str);
.

method will_attach
    arg obj;

    if (!.is_owned_by(obj))
        throw(~perm, "Object does not own room.");
.

method will_link
    arg obj;

    if (!link_ok && !.is_owned_by(obj))
        throw(~perm, "Object does not own non-link_ok room.");
.