/
CDC-1.1/
parent $body
parent $interaction
parent $input
parent $command_aliases
parent $bad_commands
parent $user_data
parent $has_settings
parent $settings_ui
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], ["@aliases *", 'aliases_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], ["@who-s?hort|@whos?hort", 'who_short_cmd], ["news", 'news_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], ["@who-a?dmins|@whoa?dmins *", 'who_admins_cmd], ["@add-command-a?lias|@aca?lias *", 'add_command_alias_cmd], ["@who-c?ount|@whoc?ount", 'who_count_cmd], ["@who-p?rogrammers|@whop?rogrammers *", 'who_progs_cmd], ["@login-watch *", 'watch_logins_cmd], ["@com?mands *", 'commands_cmd], ["@age *", 'age_cmd], ["@spawn|@create *", 'spawn_cmd], ["@prose|@describe *", 'prose_cmd], ["@status", 'status_cmd], ["@rehash?-commands", 'rehash_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 0
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 $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 $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 $has_settings setting_templates #[["terminated-tell", 'boolean], ["Content-type", 'string]]

method init_user
    .perms(caller(), $root);
    password = "*";
    connected_at = 0;
    last_command_at = 0;
    connections = [];
    creation_time = time();
    parsers = [$command_parser, $verb_parser];
    filters = [];
    action = "";
    prompt = "";
    context = #[];
    .set_quota($sys.user_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($room))
    //  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;
    
    .perms(caller(), $old_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:
                .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());
    }
    return 0;
.

method connection_logged_in
    arg addr, port;
    var line;
    
    if (caller() != $old_connection)
        throw(~perm, "Caller is not $old_connection.");
    connections = connections + [sender()];
    if (listlen(connections) == 1)
        .login(sender());
    else
        .login_again(sender());
    line = ("CONNECT " + tostr(sender() in connections)) + ": ";
    line = (((line + (.dbref())) + " <") + (sender().addr())) + "> ";
    $sys.log(line);
.

method connection_logged_out
    arg addr, port;
    var con, line;
    
    if (caller() != $old_connection)
        throw(~perm, "Caller is not $old_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 + (.dbref())) + " <") + (sender().addr())) + "> ";
    $sys.log(line);
.

method login
    arg connection;
    var loc;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    .tell(("* * * Login successful (" + (connection.addr())) + ") * * *");
    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(((("* * * " + ($integer.n_to_nth(connection in (.connections())))) + " Login successful (") + (connection.addr())) + ") * * *");
.

method logout
    arg connection;
    
    if ((sender() != this()) || (definer() != caller()))
        throw(~perm, "Invalid access to private method.");
    (| (.location()).did_disconnect() |);
    
    // 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, [args];
    var who, x, person, where, meths, header;
    
    // just the basic who listing
    .perms(sender(), 'parser);
    who = [];
    if (!(args[1])) {
        who = $user_db.connected();
    
        // looks like they want who in a room.
    } else if (((args[1])[1]) == "@") {
        where = $room.match_descendants(substr(args[1], 2));
        if (!where)
            $parse.tell_error(("I do not know where \"" + substr(args[1], 2)) + "\" is.");
        for x in (where.contents()) {
            if (x.has_ancestor($user))
                who = [@who, x];
        }
        if (!who)
            $parse.tell_error(("Nobody is in " + (where.namef())) + ".");
        meths = [['namef, 'titled, " (", 'activity, ")"], ['time_poll]];
        header = ["Name", "Times (idle)"];
        .tell($code.generate_listing(who, "Users in " + (where.namef()), meths, header, [1, 1]));
        return;
    } else {
        //
        // user, or list of users
        for x in ($string.explode_english_list(@args)) {
            catch any {
                person = $user_db.find(x);
            } with handler {
                switch (error()) {
                    case ~ambig:
                        .tell(((("The name \"" + x) + "\" can match any of: ") + ($list.to_english($list.map((traceback()[1])[3], 'namef)))) + ".");
                    default:
                        .tell(("I don't know who \"" + x) + "\" is.");
                }
                continue;
            }
            who = [@who, person];
        }
        if (!who)
            return;
    }
    .tell($code.generate_listing(who));
.

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 who_short_cmd
    arg com;
    var user, tmp, who, namestr, total;
    
    if (sender() != this())
        throw(~perm, "Sender not this.");
    who = [];
    total = listlen($user_db.connected());
    .tell((("Currently connected users (total of " + 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 who_admins_cmd
    arg com, [who];
    var admins, admin, title;
    
    who = who[1];
    
    // everybody, connected or not
    if (who == "all") {
        admins = $sys.admins();
        title = "All Admins";
    
        // ok, just the connected ones then
    } else if (who == "") {
        title = "All Connected Admins";
        admins = [];
        for admin in ($sys.admins()) {
            if (admin.connected())
                admins = [@admins, admin];
        }
    
        // hrm, possibly somebody specific
    } else {
        admins = [.match_object_nice(who, $admin, 'children)];
    }
    .tell($code.generate_listing(admins, title));
.

method who_progs_cmd
    arg com, [args];
    var progs, all_progs, x, title;
    
    args = args[1];
    if (args) {
        if (match_begin(args, "all")) {
            progs = $programmer.descendants();
            title = "All Programmers";
        } else {
            title = "Programmer";
            progs = [.find_object_nice(args, 'environment, 'user, 'grasp)];
        }
    } else {
        progs = [];
        title = "Connected Programmers";
        for x in ($programmer.descendants()) {
            if (x.connected())
                progs = [@progs, x];
        }
    }
    .tell($code.generate_listing(progs, title));
.

method who_count_cmd
    arg com;
    var len, rz, pz;
    
    .perms(sender(), 'parser);
    len = listlen($user_db.connected());
    if (len == 1) {
        rz = "is";
        pz = "person";
    } else {
        rz = "are";
        pz = "people";
    }
    .tell(((((("There " + rz) + " currently ") + tostr(len)) + " ") + pz) + " connected.");
    .tell("Lag is <lag meter will eventually go here>");
.

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 time_poll
    arg [args];
    var idle;
    
    if (!(.connected()))
        return "Last On: " + substr(ctime(last_command_at), 1, 10);
    if (!args)
        idle = ((.idle_seconds()) > 10) ? $time.dhms(.idle_seconds(), 'long) | "";
    else
        idle = ((.idle_seconds()) > 10) ? $time.dhms(.idle_seconds()) | "";
    return ($time.elapsed(.connected_at())) + (idle ? " " + idle | "");
.

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 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
    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>'";
    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]) == "-prop") || (((line[x]) == "-p") || ((line[x]) == "-proper"))) {
            article = 'prop;
            line = delete(line, x);
            break;
        }
        if (((line[x]) == "-uniq") || (((line[x]) == "-u") || ((line[x]) == "-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('ref)), col);
            size = $integer.to_english(obj.size());
            line = (line + pad(size, -((strlen(size) > 10) ? strlen(size) | 10))) + " ";
            loc = (obj.has_ancestor($located)) ? ("[" + ((obj.location()).namef())) + "]" | "";
            .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 varous
    // different things, such as new mail, news, etc.
    if ($news.new())
        .tell("There is new News (use `news' to read it).");
    if (.unread())
        .tell("You have new mail (use `@Mm?ail on me' to list it).");
    .mmail_lists_cmd("", "subscribed");
.

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;
    
    .perms(sender(), 'this);
    if (type(what) == 'list) {
        for line in (what)
            ._tell(line);
    } else {
        for conn in (connections)
            conn.tell(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(), 'parser);
    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("Old password does not match up.", 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 ((.namef()) == new_name)
        return;
    if ($user_db.valid_name(new_name))
        old_name = .namef();
    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;
    
    if (sender() != this())
        throw(~perm, "Sender not this.");
    syn = ("`" + com) + " <parent> [named|called] <new object name>'";
    args = explode(args);
    builder = !($user in (.parents()));
    if ((!args) || (((listlen(args) < 1) && builder) || (listlen(args) < 2)))
        $parse.tell_error("Not enough arguments specified.", syn);
    args = [@args, ""];
    parent = args[1];
    parent = .match_env_nice(parent, syn);
    
    // users shouldn't be creating non VR objs.
    if ((!(parent.has_ancestor($thing))) && (!builder))
        $parse.tell_error("Parent is not a descendant of a Generic Thing ($thing).");
    if (match_begin("named", args[2]) || match_begin("called", args[2]))
        args = delete(args, 2);
    name = $list.to_string(sublist(args, 2));
    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;
    
    .perms(sender());
    syn = ("`" + cmd) + " <object> [long|short]`";
    args = explode(args);
    obj = [@args, ""][1];
    which = [@args, "short", "short"][2];
    if (!(which in ["short", "long"]))
        $parse.tell_error("Must either be \"short\" or \"long\" for the prose type.", syn);
    which = tosym(which);
    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) {
        .prompt("Enter line: ", '_prose_done, obj, which);
    } else {
        .tell("Enter text for prose (end with a period or abort with `@abort`):");
        .read('_prose_done, obj, which);
    }
.

method status_cmd
    arg com, [args];
    var line;
    
    (> .perms(sender(), 'this) <);
    .tell(((($motd.server_name()) + ", ") + ($motd.server_title())) + ".");
    .tell("Startup time:     " + ($time.date($sys.startup_time())));
    .tell(("                  (" + ($time.to_english($sys.uptime()))) + ")");
    .tell("Last backup time: " + ($time.date($sys.last_backup())));
    .tell("Driver:           ColdMUD " + ($sys.version('driver)));
    .tell("Core:             the Cold Dark Core " + ($sys.version('core)));
.

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(("* * * " + ($integer.n_to_nth(listlen(.connections()) + 1))) + " Logout sucessful * * *");
.

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();
.