/
CDC-1.2b/
CDC-1.2b/src/
parent $body
parent $interaction
parent $mail_ui
parent $command_aliases
parent $bad_commands
parent $user_data
parent $set_ui
parent $has_settings
parent $help_ui
parent $has_messages
object $user

var $root child_index 47
var $root owners [$user]
var $root fertile 0
var $has_commands commands [["@quit", 'quit_cmd], ["i?nventory", 'inventory_cmd], ["@title *", 'title_cmd], ["@add-name-alias|@ana *", 'add_name_alias_cmd], ["@del-name-alias|@dna *", 'del_name_alias_cmd], ["@rename * to *", 'rename_cmd], ["@time *", 'time_cmd], ["@audit *", 'audit_cmd], ["@who *", 'who_cmd], ["@del-command-a?lias|@dca?lias *", 'del_command_alias_cmd], ["@command-a?liases|@ca?liases *", 'command_aliases_cmd], ["@wrap *", 'wrap_cmd], ["@prompt *", 'prompt_cmd], ["@password *", 'password_cmd], ["@idle", 'idle_cmd], ["@add-command-a?lias|@aca?lias *", 'add_command_alias_cmd], ["@login-watch *", 'watch_logins_cmd], ["@com?mands *", 'commands_cmd], ["@age *", 'age_cmd], ["@status", 'status_cmd], ["@spawn *", 'spawn_cmd], ["@rehash?-commands", 'rehash_cmd], ["@name-aliase?s|@na *", 'name_aliases_cmd], ["@news", 'news_cmd], ["@remember *", 'remember_cmd], ["@remembered *", 'remembered_cmd], ["@forget *", 'forget_cmd], ["@whereis|@where-is *", 'whereis_cmd], ["@ways", 'ways_cmd], ["@prose *", 'prose_cmd], ["@describe *", 'describe_cmd], ["@gender *", 'gender_cmd]]
var $has_commands shortcuts []
var $has_verbs verbs #[]
var $location contents []
var $located location $nowhere
var $root inited 1
var $user password "*"
var $user connected_at 0
var $user last_command_at 0
var $user connections []
var $user linelen 0
var $user title 0
var $user email 0
var $user action 0
var $user creation_time 0
var $user home $body_cave
var $user pagelen 0
var $user activity 0
var $user parsers []
var $user filters []
var $user watch_logins 0
var $user tell_traceback 0
var $user last_name 0
var $root dbref 'user
var $user prompt ""
var $root owned [$user]
var $command_aliases command_aliases []
var $mail_list letters #[]
var $mail_list letters_index #[]
var $mail_list senders 1
var $mail_list readers [$core]
var $mail_list notify [$user]
var $mail_list last_letter 0
var $mail_ui subscribed #[[$user, 791485891]]
var $gendered gender $gender_neuter
var $located obvious 1
var $described prose #[]
var $root manager $user
var $root writable [$user]
var $root readable ['parameters, 'methods, 'code]
var $help_interface current_help $
var $help_interface help_dict #[["*", $], ["help", $], ["**", $]]
var $user context 0
var $named name ['uniq, "Generic User Object"]
var $named name_aliases []
var $user_data user_data #[['real_name, [1, "???"]], ['email, [1, "???"]]]
var $old_has_settings setting_templates #[["terminated-tell", 'boolean], ["Content-type", 'string], ["error-syntax-prefix", 'string], ["error-prefix", 'string]]
var $user remembered_places []
var $old_command_environment verb_cache #[]
var $old_command_environment command_cache [["@quit", 'quit_cmd], ["i?nventory", 'inventory_cmd], ["@title *", 'title_cmd], ["@add-name-alias|@ana *", 'add_name_alias_cmd], ["@del-name-alias|@dna *", 'del_name_alias_cmd], ["@rename * to *", 'rename_cmd], ["@time *", 'time_cmd], ["@audit *", 'audit_cmd], ["@who *", 'who_cmd], ["@del-command-a?lias|@dca?lias *", 'del_command_alias_cmd], ["@command-a?liases|@ca?liases *", 'command_aliases_cmd], ["@wrap *", 'wrap_cmd], ["@prompt *", 'prompt_cmd], ["@password *", 'password_cmd], ["@idle", 'idle_cmd], ["@add-command-a?lias|@aca?lias *", 'add_command_alias_cmd], ["@login-watch *", 'watch_logins_cmd], ["@com?mands *", 'commands_cmd], ["@age *", 'age_cmd], ["@prose|@describe *", 'prose_cmd], ["@status", 'status_cmd], ["@spawn *", 'spawn_cmd], ["@rehash?-commands", 'rehash_cmd], ["@name-aliase?s|@na *", 'name_aliases_cmd], ["@news", 'news_cmd], ["@remember *", 'remember_cmd], ["@remembered *", 'remembered_cmd], ["@forget *", 'forget_cmd], ["@whereis|@where-is *", 'whereis_cmd], ["wh?isper * to *", 'whisper_cmd], ["@register-ch?annel|@reg-ch?annel *", 'register_channel_cmd], ["@delete-ch?annel|@del-ch?annel *", 'delete_channel_cmd], ["say *", 'say_cmd], ["to * say *", 'say_to_cmd], ["@paste *", 'paste_cmd], ["page * with *", 'page_cmd], ["emote *", 'emote_cmd], ["strip *", 'strip_cmd], ["@tuned|@ch?annels *", 'channels_cmd], ["repeat *", 'repeat_cmd], ["@tune in|out *", 'tune_channel_cmd], ["on * say *", 'on_subj_cmd], ["vowel?ize *", 'late_vowels_cmd], ["epage * with *", 'page_emote_cmd], ["@m?ail * on *", 'mail_on_cmd], ["@mail-s?end|@ms?end * to *", 'send_to_cmd], ["@mail-sub?scribe|@msub?scribe *", 'subscribe_cmd], ["@mail-unsub?scribe|@munsub?scribe *", 'unsubscribe_cmd], ["@mail-l?ists|@ml?ists *", 'mail_lists_cmd], ["@m?ail * subject|subject:|s *", 'mail_subj_cmd], ["@mail-r?ead|@mr?ead * on *", 'read_cmd], ["@mail-r?ead|@mr?ead *", 'read_cmd], ["@mail-rem?ove|@mrm * from|on *", 'mail_remove_cmd], ["@mail-rem?ove|@mrm *", 'mail_remove_cmd], ["quit", 'old_command_cmd], ["WHO", 'old_command_cmd], ["@create *", 'old_command_cmd], ["@dig *", 'old_command_cmd], ["news", 'old_command_cmd], ["@finger-data|@user-data * my * is|are *", 'data_is_cmd], ["@finger|@user-data *", 'data_cmd], ["@set-finger|@set-user-data * as|to *", 'set_info_cmd], ["@set *", 'set_cmd], ["@add-setting *", 'add_setting_cmd]]
var $old_command_environment shortcuts_cache [["|*", '_interact, ["|", 1]], ["<*", '_interact, ["<", 1]], ["think *", 'think_cmd, [1]], ["%*", 'think_cmd, [1]], ["#*", 'channel_cmd, ["#", 1]], ["\"*", 'say_cmd, ["", 1]], [":*", 'emote_cmd, ["", 1]], ["!*", 'polite_spoof_cmd, [1]], ["''*", 'say_to_cmd, ["to", "", "say", 1]], ["'* *", 'say_to_cmd, ["to", 1, "say", 2]], ["--*", 'page_cmd, ["page", "", "with", 1]], ["-* *", 'page_cmd, ["page", 1, "with", 2]], ["++*", 'page_emote_cmd, ["epage", "", "with", 1]], ["+* *", 'page_emote_cmd, ["epage", 1, "with", 2]], ["]*", 'right_encapsulate_cmd, ["]", 1]], [")*", 'right_encapsulate_cmd, [")", 1]], ["@m?ail", 'mail_on_cmd, [1, "", "on", "me"]]]
var $has_settings settings 0
var $has_settings setting_types #[["terminated-tell", #[['type, "boolean"], ['check, 'is_boolean], ['get, 'get_direct_setting], ['set, 'set_direct_setting], ['del, 'del_direct_setting], ['set_args, ['name, 'definer, 'value, 'style]], ['get_args, ['name, 'definer]]]], ["content-type", #[['type, "string"], ['check, 'is_anything], ['get, 'get_direct_setting], ['set, 'set_direct_setting], ['del, 'del_direct_setting], ['set_args, ['name, 'definer, 'value, 'style]], ['get_args, ['name, 'definer]]]]]
var $has_settings setting_data #[[$user, #[["content-type", "text/plain"]]]]
var $help_ui current_node $help_node_summary
var $help_ui last_visited #[['pos, 1], ['nodes, [$help_node_summary]]]
var $mail_list mail []
var $mail_ui current #[['location, 0], ['list, $user]]

method init_user
    .perms(caller(), $root);
    password = "*";
    connected_at = 0;
    last_command_at = 0;
    connections = [];
    creation_time = time();
    parsers = [$command_parser, $verb_parser];
    filters = [$ctext_filter];
    action = "";
    prompt = "";
    context = #[];
    .set_quota($sys.get_starting('quota));
    $user_db.insert(.namef(), this());
    .set_readable([]);
    .move_to($body_cave);
.

method uninit_user
    var conn;
    
    (> .perms(caller(), $root) <);
    if (.connected())
        (| (.location()).did_disconnect() |);
    
    // and incase any are lying about
    for conn in (connections)
        (| conn.user_going_away() |);
    password = 0;
    connections = 0;
    (| $user_db.remove(.name()) |);
.

method trusted
    return pass() + ($parsers.children());
.

method will_move
    arg mover, place;
    
    (> pass(mover, place) <);
    if (!(place.has_ancestor($place)))
        throw(~perm, "Players can only move into rooms.");
.

method watch_logins
    return watch_logins;
.

method connected_at
    return connected_at;
.

method last_command_at
    return last_command_at;
.

method tell
    arg what;
    var f;
    
    if (filters) {
        for f in (filters)
            what = f.filter(what);
    }
    ._tell(what);
.

method set_password
    arg str;
    var x, num;
    
    .perms(sender(), 'manager);
    if (strlen(str) < 5)
        throw(~badpasswd, "Passwords must be at least 5 characters long.");
    
    // this is assuming they have alphabetic characters as well (shrug).
    // for x in [1 .. strlen(str)] {
    //   if (str[x] in "1234567890")
    //     num = num + 1;
    // }
    // if (num < 2)
    //   throw(~badpasswd, "Passwords must contain at least 2 numeric characters.");
    password = crypt(str);
.

method check_password
    arg str;
    
    return crypt(str, substr(password, 1, 2)) == password;
.

method did_move
    arg [args];
    var loc;
    
    (> pass(@args) <);
    loc = .location();
    (| loc.look_cmd("look") |);
.

method parsers
    .perms(sender(), 'trusts);
    return parsers;
.

method add_parser
    arg parser, [position];
    
    // adds a new $parser at 'position.
    .perms(sender(), 'parser);
    
    // do this in three steps, first make sure the posistion is valid,
    // then check for it already, then figure its insert position.
    position = [@position, 'first][1];
    if (!(position in ['last, 'first]))
        throw(~type, "Posistion types must be one of: 'last, 'first.");
    
    // does it exist?  If so remove it.
    if (parser in parsers)
        parsers = setremove(parsers, parser);
    
    // figure position
    if (position == 'last)
        position = listlen(parsers) + 1;
    else if (position == 'first)
        position = 1;
    parsers = insert(parsers, position, parser);
.

method del_parser
    arg parser;
    var keepers;
    
    // removes a parser.  Cannot remove $command_parser or $verb_parser
    keepers = [$command_parser, $verb_parser];
    if (!(parser in parsers))
        throw(~parsernf, ($data.unparse(parser)) + " is not in the parser list.");
    if (parser in keepers)
        throw(~twink, ("You must always have " + ($list.map_to_english(keepers, 'namef))) + ".");
    parsers = setremove(parsers, parser);
.

method parse_line
    arg line;
    var result, c;
    
    (> .perms(caller(), $connection) <);
    last_command_at = time();
    catch any {
        result = parsers ? (parsers[1]).parse(this(), line, @sublist(parsers, 2), $null_parser) | 'failed;
        if (type(result) == 'list)
            return (result[1]).(result[2])(@sublist(result, 3));
        switch (result) {
            case 'failed:
                // try one last time to at least give them an intelligent
                // answer...
                for c in (($places.coordinate_shortcuts()).keys()) {
                    if (match_template(c, line))
                        return .tell(("There is no exit " + line) + " here.");
                }
                .tell(("I don't understand \"" + ($string.chop(line, (.linelen()) - 22))) + "\".");
            case 'ok:
            default:
                .tell(tostr(result));
        }
    } with handler {
        if (((traceback()[1])[3]) != 'no_traceback)
            .tell_traceback(traceback(), line, 0, error());
    }
.

method login
    arg connection;
    var loc;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    .tell((("* * * Login successful (" + (connection.address())) + ") * * *").center(.linelen()));
    .tell((("* * Last connection at " + ($time.date(abs(.connected_at())))) + " * *").center(.linelen()));
    connected_at = time();
    last_command_at = time();
    
    //
    loc = .location();
    if (loc == $body_cave) {
        if ((.home()) != $body_cave)
            (| .move_to(.home()) |);
        else
            (| .move_to($places.place('starting)) |);
    } else if ((loc == (.home())) || (loc != $body_cave)) {
        (| loc.look_cmd("look") |);
    } else {
        (| .move_to(.home()) |);
    }
    (| (.location()).did_connect() |);
    (| .login_notify() |);
    (| $login_watcher.did_connect() |);
    (| $user_db.did_connect() |);
    .rehash_command_environment();
.

method login_again
    arg connection;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    last_command_at = time();
    (| .tell($string.center(((("* * * " + ($integer.n_to_nth(connection in (.connections())))) + " Login successful (") + (connection.addr())) + ") * * *", .linelen())) |) || (| .tell($string.center(((("* * * " + ($integer.n_to_nth(connection in (.connections())))) + " Login successful (") + (connection.address())) + ") * * *", .linelen())) |);
.

method logout
    arg connection;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    (| (.location()).did_disconnect() |);
    
    // set this to -last_command so we know they aren't connected
    // (and using last command will be last_login)
    connected_at = -last_command_at;
    
    // user specific things
    if (!($guest in (.parents()))) {
        (| $housekeeper.did_disconnect() |);
        (| $user_db.last_log_disconnect(this()) |);
    } else {
        (| $user_db.last_log_disconnect($guest) |);
    }
    (| $user_db.did_disconnect() |);
    (| $login_watcher.did_disconnect() |);
    .purge_command_environment();
.

method connections
    return connections;
.

method connected
    disallow_overrides;
    
    return connections ? 1 | 0;
.

method who_cmd
    arg com, [line];
    var who, opts, opt, args;
    
    // just the basic who listing
    (> .perms(sender(), 'this) <);
    who = [];
    if (!(line[1])) {
        args = [$user_db.connected(), "Connected Users"];
    } else if (((line[1])[1]) == "@") {
        args = ._who_at_place(substr(line[1], 2));
    } else {
        args = $parse.options(line[1]);
        opts = args[2];
        args = args[1];
        if (opts) {
            for opt in (dict_keys(opts)) {
                switch (opt) {
                    case "p", "programmers":
                        args = ._who_programmers(args, opts);
                        break;
                    case "a", "admins":
                        args = ._who_admins(args, opts);
                        break;
                    case "s", "short":
                        ._who_short();
                        return;
                    default:
                        .tell("Unknown @who option: -" + tostr(opt));
                        return;
                }
            }
        } else {
            args = ._who_is(@line);
        }
    }
    if (!args)
        return;
    .tell($code.generate_listing(@args));
.

method email
    return email || "**no email address**";
.

method quit_cmd
    arg dummy;
    
    .perms(sender(), 'parser);
    return 'disconnect;
.

method inventory_cmd
    arg dummy;
    var i;
    
    .perms(sender(), 'parser);
    if (.contents()) {
        .tell("Carrying:");
        for i in (.contents())
            .tell(" " + (i.namef()));
    } else {
        .tell("You are empty-handed.");
    }
.

method match_env_nice
    arg name, [syntax];
    var obj, args, line;
    
    // calls .match_environment() returns nice errors. as well as stopping if it
    // breaks.  No returns neccessary
    syntax = [@syntax, ""][1];
    catch any {
        obj = .match_environment(name);
    } with handler {
        switch (error()) {
            case ~ambig:
                args = (traceback()[1])[3];
                line = ("\"" + (args[listlen(args)])) + "\" can match any of: ";
                line = line + ($list.map_to_english(args[1], 'namef));
                (> $parse.tell_error(line, syntax) <);
            case ~objnf:
                line = ("Nothing found by the name \"" + ((traceback()[1])[3])) + "\".";
                (> $parse.tell_error(line, syntax) <);
            default:
                line = (traceback()[1])[2];
                (> $parse.tell_error(line, syntax) <);
        }
    }
    return obj;
.

method linelen
    return linelen || 79;
.

method idle_seconds
    return time() - last_command_at;
.

method title
    return title || "";
.

method action
    // different from activity, returns a more accurate second to second action
    if (.connected())
        return action || "";
    else
        return "(asleep)";
.

method fooz
    arg time;
    
    connected_at = time;
.

method title_cmd
    arg com, str;
    
    .perms(sender(), 'parser);
    .tell("Temporary until the real title setup is working");
    catch any {
        .set_title(str);
    } with handler {
        $parse.tell_error((traceback()[1])[2]);
    }
    .tell(("Title Set as: \"" + str) + "\"");
.

method commands_cmd
    arg cmd, args;
    var obj, coms, c, len, lcoms, scoms, ulen, l, what, a, opts, lines;
    
    // returns all commands in a nice format.
    .perms(sender(), 'parser);
    args = explode(args);
    if (!args) {
        what = .ancestors();
        .tell("All commands: (prepare to be spammed)");
    } else {
        what = [.match_env_nice(args[1])];
        .tell("Commands on " + ((what[1]).namef('ref)));
    }
    ulen = .linelen();
    for obj in (what) {
        if (!((obj.has_ancestor($has_commands)) || (obj.has_ancestor($has_verbs))))
            continue;
        coms = [];
        coms = coms + ((| $list.slice(obj.shortcuts(), 1) |) || []);
        coms = coms + ((| $list.slice(obj.commands(), 1) |) || []);
        coms = coms + ((| obj.verb_templates() |) || []);
        if (coms) {
            scoms = [];
            lcoms = [];
            for c in (coms) {
                len = strlen(c) + 2;
                if (len > (ulen / 3))
                    lcoms = [@lcoms, "  " + c];
                else
                    scoms = [@scoms, "  " + c];
            }
            lines = ($list.lcolumnize(scoms, ulen)) + ($list.lcolumnize(lcoms, ulen));
            for l in (lines)
                .tell($string.trim(l, 'right));
            .tell("  -=-");
        }
        if (obj == $has_commands)
            break;
    }
.

method tell_commands
    arg p, ps, pc, space;
    var x, a, c;
    
    // called by $builder.commands_cmd
    .tell(((((space + "Commands on ") + (p.namef('ref))) + " (") + toliteral(p)) + ")");
    pc = ps + pc;
    if (!pc) {
        .tell(space + "  None");
        return;
    }
    for x in [1 .. listlen(pc)] {
        a = pad(tostr((pc[x])[1]), 30) + " ";
        c = pad(toliteral((pc[x])[2]), 20) + " ";
        .tell(((space + "  ") + a) + c);
    }
.

method set_email
    arg email_str;
    var syn, email, host;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender is not an owner.");
    
    // kludgy way to check for semi valid email addresses:
    email = explode(email_str, "@");
    if (listlen(email) < 2)
        throw(~invemail, "Invalid email: " + email_str);
    
    // check hostname for at least 1 subnet and a machine name.
    host = explode(email[2], ".");
    if (listlen(host) < 2)
        throw(~invemail, "Invalid hostname: " + (host[1]));
    
    // email is purposefully constructed this way, as at one point it will
    // be able to automatically tack on your host, if you do not provide it.
    set_var('email, ((email[1]) + "@") + (email[2]));
.

method set_watch_logins
    arg value;
    
    // either have a single reference, or: 'none|'all
    .perms(sender());
    watch_logins = value;
.

method echo_file
    arg str;
    var con;
    
    for con in (connections)
        con.echo_file(str);
.

method set_home
    arg obj;
    
    // if (!obj.is_writable_by(sender()))
    //  return .tell("You do not own " + obj.namef() + ".");
    home = obj;
.

method pagelen
    return pagelen || 24;
.

method set_pagelen
    arg len;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender not an owner.");
    if (type(len) != 'integer)
        throw(~type, "pagelength must be an integer");
    pagelen = len;
.

method set_linelen
    arg len;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender not an owner.");
    if (type(len) != 'integer)
        throw(~type, "Linelength must be an integer");
    linelen = len;
.

method name_aliases_cmd
    arg com, [obj];
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender not owner.");
    obj = .match_env_nice((obj[1]) ? obj[1] | (.dbref()));
    if (!(obj.has_ancestor($named))) {
        .tell(("Object `" + (obj.dbref())) + "' is not a named object.");
        throw(~stop, "", 'no_traceback);
    }
    .tell((("Aliases for " + (obj.namef())) + ": ") + ($list.to_english(obj.name_aliases(), "none")));
.

method activity
    var idle;
    
    // different from action, returns a broader version of your doings
    if (!(.connected()))
        return "asleep";
    if (activity)
        return activity;
    idle = .idle_seconds();
    if (idle < 180)
        return "";
    if (idle < 300)
        return "daydreaming";
    if (idle < 900)
        return "zoned";
    else
        return "long gone";
.

method add_name_alias_cmd
    arg com, [args];
    var syn, obj, what;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender not this");
    syn = ("`" + com) + " <alias> to <object>' (to <object> is optional)";
    args = explode(args[1]);
    what = args[1];
    obj = .match_env_nice([@args, .dbref(), .dbref()][3]);
    if (!what)
        $parse.tell_error("You must name an alias.", syn);
    if (what in (.name_aliases()))
        $parse.tell_error(("You already have the name alias `" + what) + "'");
    catch any {
        obj.add_name_alias(what);
    } with handler {
        switch (error()) {
            case ~methodnf:
                $parse.tell_error((obj.namef('ref)) + " is not a descendant of $xxxxxxxxx", syn);
            default:
                $parse.tell_error((traceback()[1])[2], syn);
        }
    }
    .tell(((("Name Alias `" + what) + "' added to ") + (obj.namef())) + ".");
.

method modes
    return modes;
.

method del_name_alias_cmd
    arg com, [args];
    var syn, obj, what;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender not this");
    syn = ("`" + com) + " <alias> from <object>' (from <object> is optional)";
    args = explode(args[1]);
    what = args[1];
    obj = .match_env_nice([@args, .dbref(), .dbref()][3]);
    if (!what)
        $parse.tell_error("You must name an alias to delete.", syn);
    if (!(what in (.name_aliases())))
        $parse.tell_error((((obj.namef('ref)) + " doesn't have the name alias `") + what) + "'");
    catch any {
        obj.del_name_alias(what);
    } with handler {
        switch (error()) {
            case ~methodnf:
                $parse.tell_error((obj.namef('ref)) + " is not a descendant of $matchable", syn);
            default:
                $parse.tell_error((traceback()[1])[2], syn);
        }
    }
    .tell(((((("Name Alias `" + what) + "' deleted from ") + (obj.namef())) + ", aliases are now: ") + ($list.to_english(.name_aliases()))) + ".");
.

method home
    return home || $body_cave;
.

method set_dbref
    disallow_overrides;
    arg new_dbref;
    
    if ((caller() != $user) && (!(sender() in ($sys.system()))))
        throw(~perm, "User dbrefs can only be changed by $user.");
    (> pass(new_dbref) <);
.

method rename_cmd
    arg com, what, prep, line;
    var syn, x, article, name;
    
    if (sender() != this())
        throw(~perm, "Sender is not this");
    syn = ((com + " <object> ") + prep) + " <newname> [-proper|-unique]";
    what = .match_env_nice(what);
    if (!(what.has_ancestor($named)))
        $parse.tell_error(("Object \"" + ($data.unparse(what))) + "\" is not descended from $named.", syn);
    line = explode(line);
    article = 'uniq;
    for x in [1 .. listlen(line)] {
        if ((line[x]) in "-proper") {
            article = 'prop;
            line = delete(line, x);
            break;
        }
        if ((line[x]) in "-unique") {
            article = 'uniq;
            line = delete(line, x);
            break;
        }
    }
    name = $list.to_string(line);
    catch any {
        what.set_name(name, article);
    } with handler {
        $parse.tell_error((traceback()[1])[2], syn);
    }
    .tell("Name is now: " + (what.name()));
.

method time_cmd
    arg command, [args];
    var rtime, itime, ptime;
    
    .perms(sender(), 'parser);
    
    // rtime = "Terran Time:      " + $time.ltime('12hr, 'ampm) + "  " +
    $time.ltime();
    rtime = "Terran Time:      " + ctime();
    itime = (("Ilraitheen Time:  " + ($dark_time.ilraitheen_time())) + " day ") + tostr($dark_time.day());
    ptime = "Paradisical Time: " + ($dark_time.paradise_time());
    .tell([rtime, itime, ptime]);
.

method audit_cmd
    arg com, [args];
    var owner, obj, col, total, line, syntax, loc, size;
    
    if (sender() != this())
        throw(~perm, "Sender not this.");
    syntax = "";
    owner = (args[1]) ? args[1] | "me";
    owner = .match_env_nice(owner, syntax);
    if (!(owner.owned())) {
        .tell("  None");
    } else {
        col = (.linelen()) / 2;
        line = ("Objects owned by " + (owner.namef('ref))) + ":";
        .tell((pad(line, col) + pad("bytes", -10)) + " Location");
        for obj in (owner.owned()) {
            if (!valid(obj)) {
                .tell(("  ** invalid object (" + toliteral(obj)) + ") **");
                continue;
            }
            line = pad("  " + (obj.namef('xref)), col);
            size = $integer.to_english(obj.size());
            line = (line + pad(size, -((strlen(size) > 10) ? strlen(size) | 10))) + " ";
            loc = (obj.has_ancestor($located)) ? ("[" + ((obj.location()).name())) + "]" | "";
            .tell(pad(line + loc, .linelen()));
            total = total + (obj.size());
        }
    }
    .tell(("Total usage: " + ($integer.to_english(total))) + " bytes");
    size = owner.get_quota();
    line = ("Total quota: " + ($integer.to_english(size))) + " bytes       ";
    line = ((line + "Remaining: ") + ($integer.to_english(size - total))) + " bytes";
    .tell(line);
.

method login_notify
    // called by .login, set items here that will 'notify' a user of various
    // different things, such as new mail, news, etc.
    if ($news.new())
        .tell("There is new News (use `@news` to read it).");
    if ((.last_received_on()) > ((.subscribed())[this()]))
        .tell("You have new mail (use `@help mail` to learn about mail).");
    .subscribed_cmd("");
.

method _set_watch_logins
    arg value;
    
    if (sender() != this())
        throw(~perm, "Invalid call to private method");
    watch_logins = value;
.

method get_mode
    arg mode;
    
    // returns the variable for the mode
    if (mode in dict_keys(modes))
        return modes[mode];
    throw(~modenf, ("Mode \"'" + tostr(mode)) + "\" is not found in the users dictionary");
.

method add_mode
    arg mode, setting;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender is not an owner.");
    if (type(mode) != 'symbol)
        throw(~type, "Modes must be a symbol");
    modes = dict_add(modes, mode, setting);
.

method news_cmd
    arg [args];
    
    (> .perms(sender(), 'parser) <);
    $news.read_vrb("read", "news");
.

method del_mode
    arg mode;
    
    if (!(.is_writable_by(sender())))
        throw(~perm, "Sender is not an owner.");
    if (mode in dict_keys(modes)) {
        modes = dict_del(modes, mode);
        return;
    }
    throw(~modenf, "Mode is not in users modes dictionary");
.

method add_command_alias_cmd
    arg com, input;
    var syn, alias, actual, tmpalias, tmpactual, num;
    
    if (sender() != this())
        throw(~perm, "Sender is not this");
    input = explode(input, "\"");
    
    // hell, i'll be nice and check for a few possibilities:
    syn = com + " \"<alias>\" [to] \"<actual command>\"";
    if (listlen(input) < 2)
        $parse.tell_error("Not enough arguments (enclose each alias in quotes)", syn);
    if (listlen(input) > 3)
        $parse.tell_error("Too many arguments (enclose each alias in quotes)", syn);
    
    // sort and get the alias and actual
    if (listlen(input) == 3)
        input = delete(input, 2);
    alias = input[1];
    actual = input[2];
    
    // make sure the %foo's match up
    tmpalias = alias;
    tmpactual = actual;
    while ("%" in tmpalias) {
        num = tmpalias[("%" in tmpalias) + 1];
        if (!toint(num))
            $parse.tell_error("referece cards must be integers", syn);
        num = "%" + num;
        if (!(num in tmpactual))
            $parse.tell_error("reference cards to not match up", syn);
        tmpalias = strsub(tmpalias, num, "");
        tmpactual = strsub(tmpactual, num, "");
    }
    if ("%" in tmpactual)
        $parse.tell_error("reference cards do not match up", syn);
    .add_command_alias(alias, actual);
    .tell(((("New command alias \"" + alias) + "\" => \"") + actual) + "\" added.");
.

method del_command_alias_cmd
    arg com, template;
    
    if (sender() != this())
        throw(~perm, "Sender is not this");
    template = explode(template, "\"")[1];
    catch ~aliasnf {
        .del_command_alias(template);
    } with handler {
        $parse.tell_error(("No command alias is found matching the template \"" + template) + "\".");
    }
    .tell(("Command alias \"" + template) + "\" deleted.");
.

method command_aliases_cmd
    arg com, what;
    var aliases, a, line;
    
    if (!what)
        what = this();
    else
        what = .match_env_nice(what);
    if ((what != this()) && (!(what.is_writable_by(this()))))
        $parse.tell_error("You are not allowed to read the command aliases on " + (what.namef()));
    .tell(("--- Command aliases on " + (what.namef('xref))) + ":");
    aliases = what.command_aliases();
    if (aliases) {
        for a in (aliases) {
            line = "  " + pad(("\"" + (a[1])) + "\"", ((.linelen()) - 10) / 2);
            .tell(((line + " => \"") + (a[2])) + "\"");
        }
    } else {
        .tell("  <none>");
    }
    .tell("---");
.

method _tell
    arg what;
    var conn, line;
    
    if (type(what) == 'list) {
        for line in (what)
            ._tell(line);
    } else {
        for conn in (connections)
            conn.send(what);
    }
.

method add_filter
    arg filter, [position];
    
    if ((!(sender().has_ancestor($filters))) && (!(.is_writable_by(sender()))))
        throw(~perms, "%O does not have permission to remove filters.", sender());
    
    // do this in three steps, first make sure the posistion is valid,
    // then check for it already, then figure its insert position.
    position = [@position, 'first][1];
    if (!(position in ['last, 'first]))
        throw(~type, "Posistion types must be one of: 'last, 'first.");
    
    // does it exist?  If so remove it.
    if (filter in filters)
        filters = setremove(filters, filter);
    
    // figure position
    if (position == 'last)
        position = listlen(filters) + 1;
    else if (position == 'first)
        position = 1;
    filters = insert(filters, position, filter);
.

method del_filter
    arg filter;
    
    if ((!(sender().has_ancestor($filters))) && (!(.is_writable_by(sender()))))
        throw(~perms, "%O does not have permission to remove filters.", sender());
    if (filters) {
        filters = setremove(filters, filter);
        return;
    } else {
        throw(~nofilters, "You do not have any tell filters.");
    }
.

method wrap_cmd
    arg com, how;
    var filters;
    
    .perms(sender(), 'this);
    if (!(how in ["on", "off"]))
        return .tell("!  You can either turn line wrapping `on' or `off'");
    filters = .filters();
    if (how == "on") {
        if (filters && ($wrap_filter in filters))
            return .tell("!  You already have line wrapping on..");
        .add_filter($wrap_filter);
        return .tell("Line wrapping turned on.");
    } else {
        if (filters && (!($wrap_filter in filters)))
            return .tell("! You dont have line wrapping turned on.");
        .del_filter($wrap_filter);
        return .tell("Line wrapping turned off.");
    }
.

method filters
    .perms(sender());
    return filters;
.

method tell_traceback
    arg traceback, [args];
    var tt, name, eargs, error, str;
    
    // tt = tell_traceback || ['verbose, 0, "! "];
    str = [@args, ""][1];
    eargs = [@args, 0, 0][2];
    error = [@args, 0, 0, 0][3];
    tt = ['verbose, -1, "! "];
    switch (tt[1]) {
        case 'verbose:
            traceback = $parse.traceback(traceback, tt[2], tt[3], error);
            if (args)
                name = (| $list.to_english($list.map(args, 'namef, 'ref)) |);
            if (!name)
                name = "Object";
            .tell(strsub(traceback[1], "%O", name));
            .tell(sublist(traceback, 2));
        default:
            .tell(("! Internal error processing \"" + str) + "\", contact an administrator.");
    }
.

method set_tell_traceback
    arg which, [lines];
    
    .perms(sender(), 'manager);
    if (!(which in ['verbose, 'brief, 'none]))
        throw(~type, "Which style must either be 'verbose, 'brief, or 'none.");
    if (lines && (type(lines[1]) != 'integer))
        throw(~type, "You must specify the max lines as an integer.");
    if (!lines)
        lines = 0;
    else
        lines = lines[1];
    tell_traceback = [which, lines];
.

method namef
    arg [args];
    
    if (!args)
        args = [['name]];
    
    // first check for shortcuts, if so re-call this method correctly.
    if ((args[1]) == 'nactivity) {
        if (.activity())
            args = [['name], " (", ['activity], ")"] + sublist(args, 2);
        else
            args = [['name]] + sublist(args, 2);
    }
    if ((args[1]) == 'titled) {
        if (.title())
            args = [['name], ", ", ['title]] + sublist(args, 2);
        else
            args = [['name]] + sublist(args, 2);
    }
    return pass(@args);
.

method get_prompt
    return prompt;
.

method set_prompt
    arg what;
    
    .perms(sender(), 'this);
    prompt = what;
.

method prompt_cmd
    arg com, [args];
    
    .perms(sender(), 'this);
    if (!args)
        args = [""];
    .set_prompt(args[1]);
    .tell(("Prompt set as \"" + (args[1])) + "\"");
.

method password_cmd
    arg com, [args];
    var syn;
    
    (> .perms(sender(), 'this) <);
    syn = com + " <old password> <new password>";
    args = explode(@args);
    if ((listlen(args) < 2) || (listlen(args) > 2))
        $parse.tell_error("Not enough arguments specified (note: passwords cannot contain spaces).", syn);
    if (!(.check_password(args[1])))
        $parse.tell_error("Sorry.", syn);
    catch any {
        .set_password(args[2]);
    } with handler {
        $parse.tell_error((traceback()[1])[2], syn);
    }
    .tell("Password changed.");
.

method set_title
    arg str;
    
    .perms(sender(), 'manager);
    if (strlen(str) > 30)
        throw(~type, "Titles must be under 30 characters.");
    title = str;
.

method watch_logins_cmd
    arg com, args;
    
    args = explode(args);
    if (!args)
        return .tell("You must specify someone to watch (logging in/out).");
    if ((args[1]) in ["all", "everybody", "everyone"]) {
        .set_watch_logins(1);
        .tell("You are now listening to login messages from everybody.");
    } else if ((args[1]) in ["none", "noone"]) {
        .set_watch_logins(0);
        .tell("You are now ignoring login messages.");
    } else {
        .tell("nothing else on this command is progged yet.");
    }
.

method delimited_tell
    arg text;
    var conn;
    
    // will adjust perms etc later.
    for conn in (.connections())
        conn.enh_tell([text]);
.

method match_context
    arg str;
    
    return context[str];
.

method context
    return context;
.

method match_environment
    arg str;
    var match, gend;
    
    match = (> pass(str) <);
    gend = (| match.gender() |);
    if (gend)
        context = dict_add(context, gend.pronoun('po), match);
    return match;
.

method non_terminated_tell
    arg text;
    var conn;
    
    if (.setting("terminated-tell")) {
        for conn in (.connections())
            conn.enh_tell([text]);
    } else {
        .tell(text);
    }
.

method set_name
    arg new_name, [ignore];
    var old_name, part, sname;
    
    (> .perms(sender(), 'manager) <);
    
    // so it doesnt bomb on .set_dbref
    if ((.name()) == new_name)
        return;
    if ($user_db.valid_name(new_name))
        old_name = .name();
    sname = $string.strip(new_name, $user_db.stripped_characters());
    catch any {
        pass(new_name);
        $user_db.key_changed(old_name, new_name);
        .set_dbref(tosym("user_" + sname));
    } with handler {
        (| pass(old_name) |);
        rethrow(error());
    }
.

method find_object_nice
    arg str, [args];
    var match;
    
    catch any {
        match = .find_object(str, @args);
    } with handler {
        .tell("!  " + ((traceback()[1])[2]));
        throw(~stop, "", 'no_traceback);
    }
    return match;
.

method find_object
    arg str, [args];
    var trace, match;
    
    // comprehensive matching method.
    // args define what to match.
    if (!args)
        args = ['environment];
    while (args) {
        switch (args[1]) {
            case 'environment:
                match = (| .match_environment(str) |);
            case 'user:
                match = (| $user_db.find(str) |);
            case 'grasp:
                match = (| $object.to_dbref(str) |);
        }
        if (match)
            return match;
        args = delete(args, 1);
    }
    throw(~objnf, ("No object found by the reference \"" + str) + "\".");
.

method creation_time
    return creation_time;
.

method age_cmd
    arg com, user;
    var person, time;
    
    .perms(sender(), 'parser);
    person = (| $user_db.find(user) |);
    if (!person) {
        person = (| .match_environment(user) |);
        if (!person)
            return .tell(("No person can be found by the name \"" + user) + "\".");
    }
    time = person.creation_time();
    .tell(((person.namef()) + " was created on ") + ($time.ldate(time)));
    .tell(((((person.gender()).pronoun('psc)) + " is ") + ($time.elapsed(time() - time, 'long))) + " old.");
.

method spawn_cmd
    arg com, args;
    var syn, new_obj, moved, name, parent, builder;
    
    (> .perms(sender(), 'this) <);
    syn = com + " <parent> [named|called] <new object name>";
    args = explode(args);
    builder = $builder in (.ancestors());
    if ("named" in args)
        args = setremove(args, "named");
    if ("called" in args)
        args = setremove(args, "named");
    if (!args)
        $parse.tell_error("No arguments specified.", syn);
    else if ((listlen(args) == 1) && (!builder))
        $parse.tell_error("You must specify both the parent object and the name of the new object.", syn);
    parent = args[1];
    parent = .match_env_nice(parent, syn);
    
    // users shouldn't be creating non VR objs.
    if ((!(parent.has_ancestor($thing))) && (!builder)) {
        .tell(("Only programmers are allowed to spawn non VR objects (anything which isn't descended from " + ($thing.namef('ref))) + ").");
        return;
    }
    name = sublist(args, 2).to_string();
    catch any {
        new_obj = parent.spawn();
        if (name) {
            if (!(new_obj.has_ancestor($named)))
                .tell("Object is not descended from $named.");
            else
                (> new_obj.set_name(name) <);
        }
        moved = (| new_obj.move_to(this()) |);
        .tell(((("Object " + (new_obj.namef('ref))) + " spawned from ") + (parent.namef('ref))) + ".");
    } with handler {
        $parse.tell_error((traceback()[1])[2], syn);
    }
.

method _prose_done
    arg text, obj, which;
    
    .perms(sender(), 'this);
    (> obj.set_prose(which, text) <);
    which = tostr(which);
    .tell(((($string.capitalize(which)) + " prose description for ") + (obj.namef('ref))) + " set.");
.

method prose_cmd
    arg cmd, args;
    var syn, which, obj, prose, long;
    
    (> .perms(sender()) <);
    syn = cmd + " [-long|-short] <object>";
    args = $parse.options(args);
    obj = (args[1]).to_string();
    if (args[2]) {
        // We'll only take the first flag, too bad.
        args = dict_keys(args[2])[1];
        long = match_template("l?ong", args);
    }
    if (long)
        which = 'long;
    else
        which = 'short;
    if (!obj)
        $parse.tell_error("No object specified.", syn);
    obj = .match_env_nice(obj);
    if (!(obj.has_ancestor($described)))
        $parse.tell_error((obj.namef('ref)) + " is not a describeable object!", syn);
    if (!(| obj.perms(this()) |))
        $parse.tell_error(("You do not have permission to write the prose description for " + (obj.namef('ref))) + ".", syn);
    if (which == 'short)
        prose = [.prompt("Enter line for short prose: ")];
    else
        prose = .read("Enter text for long prose (end with a period or abort with `@abort`):");
    if (prose == 'aborted)
        return;
    if ((prose[1]) == "@abort")
        return .tell("Aborted.");
    (> obj.set_prose(which, prose) <);
    which = tostr(which);
    .tell(((($string.capitalize(which)) + " prose description for ") + (obj.namef('ref))) + " set.");
.

method status_cmd
    arg com, [args];
    var line, s;
    
    (> .perms(sender(), 'this) <);
    .tell(((("Name: " + ($motd.server_name())) + ", ") + ($motd.server_title())) + ".");
    .tell("Startup time:     " + ($time.date($sys.server_info('startup_time))));
    .tell(("                  (" + ($time.to_english(time() - ($sys.server_info('startup_time))))) + " ago)");
    .tell("Last backup time: " + ($time.date($sys.server_info('last_backup))));
    .tell(("                  (" + ($time.to_english(time() - ($sys.server_info('last_backup))))) + " ago)");
    .tell("Driver:                         " + ($sys.server_info('driver_version, 'long)));
    .tell("Core:                           " + ($sys.server_info('core_version, 'long)));
    s = $sys.status();
    .tell(((("Driver execution time: " + ($time.to_english(s[1]))) + " seconds (") + tostr(s[2])) + " microsecs)");
    .tell(((("System execution time: " + ($time.to_english(s[3]))) + " seconds (") + tostr(s[4])) + " microsecs)");
    .tell(("Max resident set size utilized: " + tostr(s[5])) + " bytes");
    .tell("Integral shared text size:      " + tostr(s[6]));
    .tell("Integral shared memory size:    " + tostr(s[7]));
    .tell("Integral unshared data size:    " + tostr(s[8]));
    .tell("Integral unshared stack size:   " + tostr(s[9]));
    .tell("Page reclaims:                  " + tostr(s[10]));
    .tell("Page Faults:                    " + tostr(s[11]));
    .tell("Number of times swapped:        " + tostr(s[12]));
    .tell("Block input operations:         " + tostr(s[13]));
    .tell("Block output operations:        " + tostr(s[14]));
    .tell("IPC messages sent:              " + tostr(s[15]));
    .tell("IPC messages received:          " + tostr(s[16]));
    .tell("Signals delivered:              " + tostr(s[17]));
    .tell("Voluntary context switches:     " + tostr(s[18]));
    .tell("In-voluntary context switches:  " + tostr(s[19]));
.

method del_command_alias
    arg alias;
    
    (> .perms(sender()) <);
    (> pass(alias) <);
    if ((!(.command_aliases())) && ($command_aliases_parser in (.parsers()))) {
        .tell("Removing $command_aliases_parser from your list of parsers.");
        .del_parser($command_aliases_parser);
    }
.

method add_command_alias
    arg alias, actual;
    
    (> .perms(sender()) <);
    (> pass(alias, actual) <);
    if ((.command_aliases()) && (!($command_aliases_parser in (.parsers())))) {
        .tell("Adding $command_aliases_parser to your list of parsers..");
        .add_parser($command_aliases_parser, 'first);
    }
.

method logout_connection
    arg connection;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    .tell($string.center(("* * * " + ($integer.n_to_nth(listlen(.connections()) + 1))) + " Logout sucessful * * *", .linelen()));
.

method tell_ctext
    arg ctext, originator;
    var output, type;
    
    type = (.setting("content-type")) || "text/plain";
    output = ctext.translate_to(type);
    .tell(output);
.

method rehash_cmd
    arg cmd;
    
    .tell("Rehashing commands in your environment...");
    .rehash_command_environment();
.

method description
    arg actor, [exclude];
    var out, obj, contents, line;
    
    out = (> pass(actor, @exclude) <);
    contents = .contents();
    line = ((.gender()).pronoun('psc)) + " is ";
    out = [@out, (line + ((.activity()) || "awake")) + "."];
    if (!contents) {
        out = [@out, ((.gender()).pronoun('psc)) + " is holding nothing."];
    } else {
        out = [@out, ((.gender()).pronoun('psc)) + " is holding:"];
        for obj in (contents)
            out = [@out, "  " + (obj.namef())];
    }
    return out;
.

method _who_at_place
    arg str;
    var place, thing, who, args;
    
    (> .perms(sender(), 'this) <);
    
    // This should actually be done with a global place dictionary and
    // a local place dictionary for each user.
    place = $place.match_descendants(str);
    if (!place) {
        .tell(("I do not know where \"" + str) + "\" is.");
        return 0;
    }
    who = [];
    for thing in (place.contents()) {
        if (thing.has_ancestor($user))
            who = [@who, thing];
    }
    if (!who) {
        .tell(("Nobody is in " + (place.namef())) + ".");
        return 0;
    }
    args = [who, "Users in " + (place.namef())];
    args = [@args, [['namef, 'titled], ['time_poll]]];
    args = [@args, ["Name", "Times (idle)"], [1, 1]];
    return args;
.

method _who_is
    arg [args];
    var person, p, who;
    
    (> .perms(sender(), 'this) <);
    who = [];
    args = ($string.explode_english_list(@args)) || [];
    for p in (args) {
        catch any {
            person = $user_db.find(p);
        } with handler {
            switch (error()) {
                case ~ambig:
                    .tell(((("The name \"" + p) + "\" can match any of: ") + ($list.to_english($list.map((traceback()[1])[3], 'namef)))) + ".");
                default:
                    .tell(("I don't know who \"" + p) + "\" is.");
            }
            continue;
        }
        who = [@who, person];
    }
    if (!who)
        return 0;
    return [who, (listlen(who) == 1) ? "User" | "Users"];
.

method _who_programmers
    arg args, opts;
    var out, progs, p, x, t, all;
    
    (> .perms(sender(), 'this) <);
    progs = [];
    opts = dict_keys(opts);
    all = "all" in opts;
    if (args && (!all)) {
        for p in (args) {
            x = $user_db.find(p);
            if (!x)
                .tell(("I don't know who \"" + p) + "\" is.");
            else
                progs = [@progs, x];
        }
        t = (("Programmer" + listlen(progs)) == 1) ? "" | "s";
    } else if (all) {
        t = "All Programmers";
        progs = $programmer.descendants();
        progs = setremove(progs, $admin);
    } else {
        t = "Connected Programmers";
        for p in ($user_db.connected()) {
            if (p.has_ancestor($programmer))
                progs = [@progs, p];
        }
    }
    if (!progs)
        return 0;
    return [progs, t];
.

method _who_admins
    arg args, opts;
    var out, admins, a, x, t, all;
    
    (> .perms(sender(), 'this) <);
    admins = [];
    opts = dict_keys(opts);
    all = "all" in opts;
    if (args && (!all)) {
        for a in (args) {
            x = $user_db.find(a);
            if (!x)
                .tell(("I don't know who \"" + a) + "\" is.");
            else
                admins = [@admins, x];
        }
        t = (("Admin" + listlen(admins)) == 1) ? "" | "s";
    } else if (all) {
        t = "All Admins";
        admins = $sys.admins();
    } else {
        t = "Connected Admins";
        for a in ($user_db.connected()) {
            if (a.has_ancestor($admin))
                admins = [@admins, a];
        }
    }
    if (!admins)
        return 0;
    return [admins, t];
.

method _who_short
    var user, tmp, who, namestr, total;
    
    (> .perms(sender(), 'this) <);
    who = [];
    total = listlen($user_db.connected());
    .tell((("Currently connected users (total: " + tostr(total)) + ((total == 1) ? " person" | " people")) + "):");
    for user in ($user_db.connected()) {
        namestr = (((((" " + (user.namef())) + " (") + ($time.elapsed(user.connected_at()))) + " ") + ($time.dhms(user.idle_seconds()))) + ")";
        who = [@who, namestr];
        if (tmp < (strlen(namestr) + 2))
            tmp = strlen(namestr) + 2;
    }
    .tell($list.columnize(who, (.linelen()) / (tmp + 1), " ", .linelen()));
.

method remember_cmd
    arg cmd, str;
    
    if (str == "here") {
        .remember_place(.location());
        .tell(("Remembering " + ((.location()).name())) + ".");
    } else {
        // Eventually you may want to remember people for some reason,
        // for now just say something vague and maybe they will think
        // it did something -B
        .tell("Ok.");
    }
.

method remember_place
    arg place;
    
    (> .perms(sender(), 'this) <);
    if (type(place) != 'dbref)
        throw(~type, "Place must be submitted as an object.");
    
    // because it is not initialized (and shoudn't be)
    if (!remembered_places)
        remembered_places = [];
    if (place in remembered_places)
        return;
    remembered_places = [@remembered_places, place];
.

method forget_place
    arg place;
    
    (> .perms(sender(), 'this) <);
    if (type(place) != 'dbref)
        throw(~type, "Place must be submitted as an object.");
    remembered_places = setremove(remembered_places, place);
.

method remembered_places
    (> .perms(sender(), 'this) <);
    return remembered_places || [];
.

method remembered_cmd
    arg cmd, str;
    var line;
    
    (> .perms(sender(), 'this) <);
    if (str != "places") {
        .tell("You can only currently remember \"places\".");
        return;
    }
    if (!(.remembered_places()))
        return .tell("You do not remember any place.");
    
    // now is a good a time as any...
    .validate_remembered_places();
    .tell("Remembered Places:");
    for line in (((.remembered_places()).map('name)).lcolumnize((.linelen()) - 2))
        .tell("  " + line);
.

method forget_cmd
    arg cmd, str;
    
    (> .perms(sender(), 'this) <);
    if (str == "here") {
        .forget_place(.location());
        .tell(("Forgetting " + ((.location()).name())) + ".");
    } else {
        // Eventually you may want to remember people for some reason,
        // for now just say something vague and maybe they will think
        // it did something -B
        .tell("Ok.");
    }
.

method idle_time
    arg [args];
    var idle;
    
    args = [@args, 'dhms][1];
    idle = .idle_seconds();
    if ((connected_at < 0) || (idle < 30))
        return "";
    switch (args) {
        case 'dhms:
            return $time.dhms(idle, 'long);
        case 'elapsed:
            return $time.elapsed(idle);
        case 'seconds:
            return idle;
    }
.

method connected_time
    arg [args];
    
    args = [@args, 'dhms][1];
    if (connected_at < 0)
        return "Last on: " + ctime(abs(connected_at));
    switch (args) {
        case 'dhms:
            return $time.dhms(time() - connected_at, 'long);
        case 'elapsed:
            return $time.elapsed(time() - connected_at);
        case 'seconds:
            return time() - connected_at;
    }
.

method whereis_cmd
    arg cmd, who;
    var user, u;
    
    who = (who && (who.explode_english_list())) || [];
    if (who && ((who[1]) == "is"))
        who = delete(who, 1);
    if (!who)
        $parse.tell_error("You must specify somebody.");
    for user in [1 .. listlen(who)] {
        u = (| $user_db.find(who[user]) |);
        if (!u) {
            .tell(("I don't know who \"" + (who[user])) + "\" is.");
            who = replace(who, user, 0);
        } else {
            who = replace(who, user, u);
        }
    }
    for user in (who) {
        if (user)
            .tell(((user.name()) + " is in ") + ((user.location()).name('def)));
    }
.

method validate_remembered_places
    var place;
    
    (> .perms(sender(), 'this) <);
    for place in (remembered_places) {
        if (!valid(place))
            remembered_places = setremove(remembered_places, place);
    }
.

method terminated_tell
    return (| (settings[$user])["terminated-tell"] |) || "text/plain";
.

method new_connection
    arg addr, port;
    var line;
    
    (> .perms(caller(), $connection) <);
    connections = connections + [sender()];
    if (listlen(connections) == 1)
        .login(sender());
    else
        .login_again(sender());
    line = ("CONNECT " + tostr(sender() in connections)) + " (";
    line = (line + (((.parents())[1]).dbref())) + ") : ";
    line = (((line + (.dbref())) + " <") + (sender().address())) + "> ";
    $sys.log(line);
.

method connection_going_away
    arg addr, port;
    var con, line;
    
    (> .perms(caller(), $connection) <);
    con = sender() in connections;
    connections = setremove(connections, sender());
    if (!connections)
        .logout(sender());
    else
        .logout_connection(sender());
    connections = setremove(connections, sender());
    line = ("DISCONNECT " + tostr(sender() in connections)) + " (";
    line = (line + (((.parents())[1]).dbref())) + ") : ";
    line = (((line + (.dbref())) + " <") + (sender().address())) + "> ";
    $sys.log(line);
.

method read
    arg [args];
    var text, output, head, tail;
    
    if (!(.connections()))
        return 'not_connected;
    if (((.connections())[1]).is_reading())
        return 'engaged;
    head = [@args, "Now receiving input, enter \".\" to finish or \"@abort\" to abort."][1];
    tail = [@args, "** Aborted **; Text thrown away.", "** Aborted **; Text thrown away."][2];
    if (head)
        .tell(head);
    output = ((.connections())[1]).start_reading('multiple);
    if ((output == 'aborted) && tail)
        .tell(tail);
    return output;
.

method read_line
    arg [args];
    var line, abort_msg, head;
    
    if (!(.connections()))
        return 'not_connected;
    if (((.connections())[1]).is_reading())
        return 'engaged;
    head = [@args, ""][1];
    abort_msg = [@args, "** Aborted **", "** Aborted **"][2];
    if (head)
        .tell(head);
    line = ((.connections())[1]).start_reading('one);
    if ((line == 'aborted) && abort_msg)
        .tell(abort_msg);
    return line[1];
.

method prompt
    arg prompt, [abort_msg];
    
    .non_terminated_tell(prompt);
    return (> .read_line("", @abort_msg) <);
.

method short_description
    arg actor, [exclude];
    var out, obj, contents, line, p, wearing;
    
    out = (> pass(actor, @exclude) <);
    contents = .contents();
    wearing = .wearing();
    p = (.gender()).pronoun('psc);
    line = p + " is ";
    out = [@out, (line + ((.activity()) || "awake")) + "."];
    if (!contents)
        out = [@out, p + " is holding nothing."];
    else
        out = [@out, (p + " is holding ") + (contents.map_to_english('namef))];
    if (!wearing)
        out = [@out, p + " is wearing nothing!"];
    else
        out = [@out, (p + " is wearing ") + (wearing.map_to_english('name))];
    return out;
.

method ways_cmd
    arg cmd;
    
    (> .perms(caller(), 'this) <);
    .tell((.location()).visible_exits_formatted(this(), 'long));
.

method describe_cmd
    arg cmd, str;
    var args, syn, what, desc;
    
    (> .perms(sender(), 'this) <);
    syn = cmd + " <what> as <description>";
    args = match_template("* as *", str);
    if (!args)
        $parse.tell_error("You may also use \"@prose\".", syn);
    what = .match_env_nice(args[1]);
    desc = args[3];
    catch any {
        what.set_prose('short, [desc]);
    } with handler {
        .tell((traceback()[1])[2]);
        return;
    }
    .tell(("Short prose description for " + (what.namef('ref))) + " set.");
.

method gender_cmd
    arg cmd, gender;
    var genders;
    
    (> .perms(sender(), 'this) <);
    genders = [$gender_female, $gender_male, $gender_neuter, $gender_plural];
    if (!gender) {
        .tell(("Syntax: `" + cmd) + " <gender>`");
        .tell("Available Genders: " + ((genders.map('name)).to_english()));
        .tell(("You are currently a gender of " + ((.gender()).name())) + ".");
    } else {
        gender = gender in (genders.map('name));
        if (!gender) {
            .tell("Gender must be one of: " + (genders.to_english()));
        } else {
            .set_gender(genders[gender]);
            .tell(("Gender set to " + ((.gender()).name())) + ".");
            .tell(("Pronouns: " + (((((.gender()).pronouns()).values()).sublist(1, 5)).to_english())) + ".");
        }
    }
.

method content_type
    return (| (settings_data[$user])["content-type"] |) || "text/plain";
.