generic player (#6)

(an instance of Root Class made by The_Mayor)

     You see a player who should type '@describe me as ...'.


generic player owns


VERB SOURCE CODE:

init_for_core:
if (caller_perms().wizard)
    pass();
    this.home = (this in {$no_one, $hacker}) ? $nothing | $player_start;
    if (a = $list_utils:assoc(this, {{$prog, {$prog_help, $builtin_function_help, 
$verb_help, $core_help}}, {$wiz, $wiz_help}}))
        this.help = a[2];
    else
        this.help = 0;
    endif
    if (this != $player)
        for p in ({"last_connect_place", "all_connect_places"})
            clear_property(this, p);
        endfor
    endif
endif
.


confunc:
if (((valid(cp = caller_perms()) && (caller != this)) && (!$perm_utils:controls(cp, 
this))) && (caller != #0))
    return E_PERM;
endif
nm = this:length_all_msgs() - this:length_date_le(this:get_current_message()[2]);
if (nm)
    this:notify(tostr("You have new mail (", nm, " message", (nm == 1) ? "" | "s", 
").", this:mail_option("expert") ? "" | "  Type 'help mail' for info on reading it."));
endif
this:mail_catch_up();
this:check_mail_lists();
this:("@last-connection")();
$news:check();
.


disfunc:
if (((valid(cp = caller_perms()) && (caller != this)) && (!$perm_utils:controls(cp, 
this))) && (caller != #0))
    return E_PERM;
endif
this:expunge_rmm();
this:erase_paranoid_data();
return;
.


initialize:
if ((caller == this) || $perm_utils:controls(caller_perms(), this))
    this.help = 0;
    return pass(@args);
else
    return E_PERM;
endif
.


recycle:
set_task_perms(caller_perms());
if ((!is_player(this)) || caller_perms().wizard)
    pass(@args);
    return;
endif
"...start off with a wizard shout...";
for p in (connected_players())
    if (p.wizard)
        p:tell($string_utils:pronoun_sub("%N (%#) is currently trying to recycle 
%t (%[#t])"));
    endif
endfor
"...Okay here's the fun part.";
"...Doing kill_task(task_id()) doesn't work because the server can";
"...figure out that it's okay to go ahead and recycle once the task finishes.";
"...Evidently, suspend() confuses the server sufficiently that it forgets to";
"...do the recycle once the task finishes or dies.  Now of course, we don't";
"...want suspended tasks hanging around indefinitely, so we fork something";
"...off to kill it.  This seems to work...";
t = task_id();
fork (1)
    kill_task(t);
endfork
"...let him think he succeeded (should we do this ?)...no.";
"...boot_player(this)";
"...emergency life support...";
suspend(1073741823);
"...code not reached --- the patient lives...";
"...keep this around for posterity...";
if (is_player(this))
    for a in (this.aliases)
        $player_db:delete(a);
    endfor
    $player_db:delete(this.name);
endif
pass(@args);
.


acceptable:
return !is_player(args[1]);
.


my_huh:
"Extra parsing of player commands.  Called by $command_utils:do_huh.";
"This version of my_huh just handles features.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    "Standard permissions check.";
    return E_PERM;
endif
"verb - obvious                 pass - would be args";
"plist - list of prepspecs that this command matches";
"dlist and ilist - likewise for dobjspecs, iobjspecs";
verb = args[1];
if ($code_utils:tonum(verb))
    return;
endif
pass = args[2];
plist = {"any", prepstr ? $code_utils:full_prep(prepstr) | "none"};
dlist = dobjstr ? {"any"} | {"none", "any"};
ilist = iobjstr ? {"any"} | {"none", "any"};
for fobj in (this.features)
    if (!$recycler:valid(fobj))
        this:remove_feature(fobj);
    elseif (valid(loc = $object_utils:has_callable_verb(fobj, verb)[1]))
        vargs = verb_args(loc, verb);
        if ((vargs[2] in plist) && ((vargs[1] in dlist) && (vargs[3] in ilist)))
            "(got rid of notify_huh - should write a @which command)";
            "if (this.notify_huh)";
            "player:notify(tostr(\"Using \", what.name, \" (\", what, \")\"));";
            "endif";
            set_task_perms(this);
            fobj:(verb)(@pass);
            "Problem with verbs of the same name. If we use which=vrb in the loop 
instead, we have a problem with verbs that use the variable verb.";
            return 1;
        endif
    endif
    if ($command_utils:running_out_of_time())
        player:tell("You have too many features.  Parsing your command runs out of 
ticks while checking ", fobj.name, " (", fobj, ").");
        return 1;
    endif
endfor
.


last_huh:
":last_huh(verb,args)  final attempt to parse a command...";
set_task_perms(caller_perms());
verb = args[1];
args = args[2];
if ((verb[1] == "@") && (prepstr == "is"))
    "... set or show _msg property ...";
    set_task_perms(player);
    $last_huh:(verb)(@args);
elseif (verb in {"give", "hand", "get", "take", "drop", "throw"})
    $last_huh:(verb)(@args);
else
    return 0;
endif
return 1;
.


my_match_object:
":my_match_object(string [,location])";
return $string_utils:match_object(@{@args, this.location}[1..2], this);
.


tell_contents:
c = args[1];
if (c)
    longear = {};
    gear = {};
    width = player:linelen();
    half = width / 2;
    player:tell("Carrying:");
    for thing in (c)
        cx = tostr(" ", thing:title());
        if (length(cx) > half)
            longear = {@longear, cx};
        else
            gear = {@gear, cx};
        endif
    endfor
    player:tell_lines($string_utils:columnize(gear, 2, width));
    player:tell_lines(longear);
endif
.


titlec:
return $object_utils:has_property(this, "namec") ? this.namec | this:title();
.


notify:
line = args[1];
if (this.pagelen)
    if (!(this in connected_players()))
        "...drop it on the floor...";
        return 0;
    elseif ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
        return E_PERM;
    endif
    "...need wizard perms if this and this.owner are different, since...";
    "...only this can notify() and only this.owner can read .linebuffer...";
    if ((player == this) && (this.linetask[2] != task_id()))
        "...player has started a new task...";
        "....linetask[2] is the taskid of the most recent player task...";
        if (this.linetask[2] != this.linetask[1])
            this.linesleft = this.pagelen - 2;
        endif
        this.linetask[2] = task_id();
    endif
    "... digest the current line...";
    if (this.linelen > 0)
        lbuf = {@this.linebuffer, @this:linesplit(line, this.linelen)};
    else
        lbuf = {@this.linebuffer, line};
    endif
    "... print out what we can...";
    if (this.linesleft)
        howmany = min(this.linesleft, length(lbuf));
        for l in (lbuf[1..howmany])
            notify(this, l);
        endfor
        this.linesleft = this.linesleft - howmany;
        lbuf[1..howmany] = {};
    endif
    if (lbuf)
        "...see if we need to say ***More***";
        if (this.linetask[1] != this.linetask[2])
            "....linetask[1] is the taskid of the most recent player task";
            "...   for which ***More*** was printed...";
            this.linetask[1] = this.linetask[2];
            fork (0)
                notify(this, strsub(this.more_msg, "%n", tostr(length(this.linebuffer))));
            endfork
        endif
        llen = length(lbuf);
        if (llen > 500)
            "...way too much saved text, flush some of it...";
            lbuf[1..llen - 100] = {"*** buffer overflow, lines flushed ***"};
        endif
    endif
    this.linebuffer = lbuf;
else
    if (caller != this)
        set_task_perms(caller_perms());
    endif
    if (this.linelen > 0)
        for l in (this:linesplit(line, this.linelen))
            notify(this, l);
        endfor
    else
        notify(this, line);
    endif
endif
.


notify_lines:
if ($perm_utils:controls(caller_perms(), this) || (caller == this))
    set_task_perms(caller_perms());
    for line in ((typeof(lines = args[1]) != LIST) ? {lines} | lines)
        this:notify(tostr(line));
    endfor
else
    return E_PERM;
endif
.


linesplit:
":linesplit(line,len) => list of substrings of line";
"used by :notify to split up long lines if .linelen>0";
line = args[1];
len = args[2];
cline = {};
while (length(line) > len)
    cutoff = rindex(line[1..len], " ");
    if (nospace = cutoff < ((4 * len) / 5))
        cutoff = len + 1;
        nospace = line[cutoff] != " ";
    endif
    cline = {@cline, line[1..cutoff - 1]};
    line = (nospace ? " " | "") + line[cutoff..length(line)];
endwhile
return {@cline, line};
.


linelen:
return abs(this.linelen);
.


@more:
if (player != this)
    "... somebody's being sneaky...";
    "... Can't do set_task_perms(player) since we need to be `this'...";
    "... to notify and `this.owner' to change +c properties...";
    return;
elseif (!(lbuf = this.linebuffer))
    this.linesleft = this.pagelen - 2;
    notify(this, "*** No more ***");
elseif (index("flush", dobjstr || "x") == 1)
    this.linesleft = this.pagelen - 2;
    notify(this, tostr("*** Flushed ***  ", length(lbuf), " lines"));
    this.linebuffer = {};
elseif ((index("rest", dobjstr || "x") == 1) || (!this.pagelen))
    this.linesleft = this.pagelen - 2;
    for l in (lbuf)
        notify(this, l);
    endfor
    this.linebuffer = {};
else
    howmany = min(this.pagelen - 2, llen = length(lbuf = this.linebuffer));
    for l in (lbuf[1..howmany])
        notify(this, l);
    endfor
    this.linesleft = (this.pagelen - 2) - howmany;
    this.linebuffer = lbuf[howmany + 1..llen];
    if (howmany < llen)
        notify(this, strsub(this.more_msg, "%n", tostr(llen - howmany)));
        this.linetask[1] = task_id();
    endif
endif
this.linetask[2] = task_id();
.


@wrap:
if (player != this)
    "... someone is being sneaky...";
    "... Can't do set_task_perms(player) since we need to be `this'...";
    "... to notify and `this.owner' to change +c properties...";
    return;
endif
linelen = player.linelen;
if (!(prepstr in {"on", "off"}))
    player:notify("Usage:  @wrap on|off");
    player:notify(tostr("Word wrap is currently ", (linelen > 0) ? "on" | "off", 
"."));
    return;
endif
player.linelen = abs(linelen) * ((prepstr == "on") ? 1 | -1);
player:notify(tostr("Word wrap is now ", prepstr, "."));
.


@linelen*gth:
if (callers() ? (caller != this) && (!$perm_utils:controls(caller_perms(), this)) 
| (player != this))
    "... somebody is being sneaky ...";
    return;
endif
curlen = player.linelen;
wrap = curlen > 0;
wrapstr = wrap ? "on" | "off";
if (!dobjstr)
    player:notify(tostr("Usage:  ", verb, " "));
    player:notify(tostr("Current line length is ", abs(curlen), ".  Word wrapping 
is ", wrapstr, "."));
    return;
endif
newlen = tonum(dobjstr);
if (newlen < 0)
    player:notify("Line length can't be a negative number.");
    return;
elseif (newlen < 10)
    player:notify("You don't want your linelength that small.  Setting it to 10.");
    newlen = 10;
endif
this:set_linelength(newlen);
player:notify(tostr("Line length is now ", abs(player.linelen), ".  Word wrapping 
is ", wrapstr, "."));
if (!wrap)
    player:notify("To enable word wrapping, type `@wrap on'.");
endif
.


@pagelen*gth:
"@pagelength number  -- sets page buffering to that many lines (or 0 to turn off 
page buffering)";
if (player != this)
    "... somebody is being sneaky ...";
    "... Can't do set_task_perms(player) since we need to be `this'...";
    "... to notify and `this.owner' to change +c properties...";
    return;
elseif (!dobjstr)
    notify(player, tostr("Usage:  ", verb, " "));
    notify(player, tostr("Current page length is ", player.pagelen, "."));
    return;
elseif (0 > (newlen = tonum(dobjstr)))
    notify(player, "Page length can't be a negative number.");
    return;
elseif (newlen == 0)
    player.pagelen = 0;
    notify(player, "Page buffering off.");
elseif (newlen < 5)
    player.pagelen = 5;
    notify(player, "Too small.  Setting it to 5.");
else
    notify(player, tostr("Page length is now ", player.pagelen = newlen, "."));
endif
if (this.linebuffer)
    notify(this, strsub(this.more_msg, "%n", tostr(length(this.linebuffer))));
    player.linetask = {task_id(), task_id()};
    player.linesleft = 0;
else
    player.linetask = {0, task_id()};
    player.linesleft = player.pagelen - 2;
endif
.


tell:
if (this.gaglist || this.paranoid)
    "Check the above first, default case, to save ticks.  Paranoid gaggers are cost 
an extra three or so ticks by this, probably a net savings.";
    if (this:gag_p())
        return;
    endif
    if (this.paranoid == 1)
        $paranoid_db:add_data(this, {{@callers(), {player, "", player}}, 
args});
    elseif (this.paranoid == 2)
        z = this:whodunnit({@callers(), {player, "", player}}, {this, $no_one}, {})[3];
        args = {"(", z.name, " ", z, ") ", @args};
    endif
endif
pass(@args);
.


gag_p:
if (player in this.gaglist)
    return 1;
elseif (gag = this.gaglist)
    for x in (callers())
        if ((x[1] in gag) || (x[4] in gag))
            return 1;
        endif
    endfor
endif
return 0;
"--- old definition --";
if (player in this.gaglist)
    return 1;
elseif (this.gaglist)
    for x in (callers())
        if (valid(x[1]))
            if (x[1] in this.gaglist)
                return 1;
            endif
        endif
    endfor
endif
return 0;
.


set_gaglist:
":set_gaglist(@newlist) => this.gaglist = newlist";
if (!((caller == this) || $perm_utils:controls(caller_perms(), this)))
    return E_PERM;
else
    return this.gaglist = args;
endif
.


@gag*!:
set_task_perms(player);
if (player != this)
    player:notify("Permission denied.");
    return;
endif
if (!args)
    player:notify(tostr("Usage:  ", verb, "  [...]"));
    return;
endif
victims = $string_utils:match_player_or_object(@args);
changed = 0;
for p in (victims)
    if (p in player.gaglist)
        player:notify(tostr("You are already gagging ", p.name, "."));
    elseif (p == player)
        player:notify("Gagging yourself is a bad idea.");
    elseif (children(p) && (verb != "@gag!"))
        player:tell("If you really want to gag all descendents of ", $string_utils:nn(p), 
", use `@gag! ", p, "' instead.");
    else
        changed = 1;
        player:set_gaglist(@setadd(this.gaglist, p));
    endif
endfor
if (changed)
    this:("@listgag")("but don't fork");
endif
.


@listgag @gaglist:
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (!this.gaglist)
    player:notify(tostr("You are ", callers() ? "no longer gagging anything." | "not 
gagging anything right now."));
else
    player:notify(tostr("You are ", callers() ? "now" | "currently", " gagging ", 
$string_utils:english_list($list_utils:map_arg(2, $string_utils, "pronoun_sub", "%n 
(%#)", this.gaglist)), "."));
endif
gl = {};
if (args)
    player:notify("Searching for players who may be gagging you...");
    for p in (players())
        if ((typeof(p.gaglist) == LIST) && (this in p.gaglist))
            gl = {@gl, p};
        endif
        $command_utils:suspend_if_needed(0);
    endfor
    if (gl || (!callers()))
        player:notify(tostr($string_utils:english_list($list_utils:map_arg(2, $string_utils, 
"pronoun_sub", "%n (%#)", gl), "No one"), " appear", (length(gl) <= 1) ? "s" | "", 
" to be gagging you."));
    endif
endif
.


@ungag:
if ((player != this) || ((caller != this) && (!$perm_utils:controls(caller_perms(), 
this))))
    player:notify("Permission denied.");
elseif (dobjstr == "")
    player:notify(tostr("Usage:  ", verb, "   or  ", verb, " everyone"));
elseif (dobjstr == "everyone")
    this.gaglist = {};
    player:notify("You are no longer gagging anyone or anything.");
else
    if (valid(dobj))
        match = dobj;
    elseif ((match = toobj(dobjstr)) > #0)
    else
        match = $string_utils:match(dobjstr, this.gaglist, "name", this.gaglist, 
"aliases");
    endif
    if (match == $failed_match)
        player:notify(tostr("You don't seem to be gagging anything named ", dobjstr, 
"."));
    elseif (match == $ambiguous_match)
        player:notify(tostr("I don't know which \"", dobjstr, "\" you mean."));
    else
        this.gaglist = setremove(this.gaglist, match);
        player:notify(tostr(valid(match) ? match.name | match, " removed from gag 
list."));
    endif
    this:("@listgag")("but don't fork");
endif
.


whodunnit:
record = args[1];
trust = args[2];
mistrust = args[3];
s = {this, "???", this};
for w in (record)
    if (((s[3].wizard || (s[3] in trust)) && (!(s[3] in mistrust))) || (s[1] == this))
        s = w;
    else
        return s;
    endif
endfor
return s;
.


@ch*eck-full:
responsible = $paranoid_db:get_data(this);
if (length(verb) <= 6)
    "@check, not @check-full";
    n = 5;
    trust = {this, $no_one};
    "... trust no one, my friend.... no one....  --Herod";
    mistrust = {};
    for k in (args)
        if (z = $code_utils:tonum(k))
            n = z;
        elseif (k[1] == "!")
            mistrust = listappend(mistrust, $string_utils:match_player(k[2..length(k)]));
        else
            trust = listappend(trust, $string_utils:match_player(k));
        endif
    endfor
    msg_width = player:linelen() - 60;
    for q in ((n > (y = length(responsible))) ? responsible | responsible[(y - n) 
+ 1..y])
        msg = tostr(@q[2]);
        if (length(msg) > msg_width)
            msg = msg[1..msg_width];
        endif
        s = this:whodunnit(q[1], trust, mistrust);
        text = valid(s[1]) ? s[1].name | "** NONE **";
        this:notify(tostr($string_utils:left(tostr((length(text) > 13) ? text[1..13] 
| text, " (", s[1], ")"), 20), $string_utils:left(s[2], 15), $string_utils:left(tostr((length(s[3].name) 
> 13) ? s[3].name[1..13] | s[3].name, " (", s[3], ")"), 20), msg));
    endfor
    this:notify("*** finished ***");
else
    "@check-full, from @traceback by APHiD";
    matches = {};
    if (length(match = argstr) == 0)
        player:notify(tostr("Usage: ", verb, "  --or-- ", verb, " "));
        return;
    endif
    if (!responsible)
        player:notify("No text has been saved by the monitor.  (See `help @paranoid').");
    else
        if (typeof(x = $code_utils:tonum(argstr)) == ERR)
            for line in (responsible)
                if (index(tostr(@line[length(line)]), argstr))
                    matches = {@matches, line};
                endif
            endfor
        else
            matches = responsible[((l = length(responsible)) - x) + 1..l];
        endif
        if (matches)
            linelen = player:linelen();
            string_utils = $string_utils;
            list_utils = $list_utils;
            for match in (matches)
                $command_utils:suspend_if_needed(3);
                text = tostr(@match[length(match)]);
                player:notify("Traceback for:");
                player:notify(text);
                verbwidth = 0;
                for verbitem in (list_utils:slice(mm = listdelete(mm = match[1], 
length(mm)), 2))
                    verbwidth = max(verbwidth, length(verbitem));
                endfor
                numwidth = ((linelen - verbwidth) / 4) - 1;
                widths = {numwidth, verbwidth, numwidth, numwidth, numwidth};
                top = l = between = "";
                for x in [1..5]
                    top = (top + between) + string_utils:left({"This", "Verb", "Programmer", 
"VerbLocation", "Player"}[x], -widths[x]);
                    l = (l + between) + string_utils:space(widths[x], "-");
                    between = " ";
                endfor
                player:notify(top);
                player:notify(l);
                for line in (listdelete(mm = match[1], length(mm)))
                    output = {};
                    for bit in [1..5]
                        output = {@output, string_utils:left((typeof(word = line[bit]) 
== STR) ? word | tostr(word, "(", valid(word) ? list_utils:shortest({word.name, @word.aliases}) 
| ((word == $nothing) ? "invalid" | ((word == $ambiguous_match) ? "ambiguous match" 
| "Error")), ")"), -widths[bit]), " "};
                    endfor
                    player:notify(string_utils:trimr(tostr(@output)));
                endfor
                player:notify(l);
            endfor
            player:notify("**** finished ****");
        else
            player:notify(tostr("No matches for \"", argstr, "\" found."));
        endif
    endif
endif
.


@paranoid:
if ((args == {}) || ((typ = args[1]) == ""))
    $paranoid_db:set_kept_lines(this, 10);
    this.paranoid = 1;
    this:notify("Anti-spoofer on and keeping 10 lines.");
elseif (index("immediate", typ))
    $paranoid_db:set_kept_lines(this, 0);
    this.paranoid = 2;
    this:notify("Anti-spoofer now in immediate mode.");
elseif (index("off", typ) || (typ == "0"))
    this.paranoid = 0;
    $paranoid_db:set_kept_lines(this, 0);
    this:notify("Anti-spoofer off.");
elseif ((tostr(y = tonum(typ)) != typ) || (y < 0))
    this:notify(tostr("Usage: ", verb, "      to turn on your anti-spoofer."));
    this:notify(tostr("       ", verb, " off                    to turn it off."));
    this:notify(tostr("       ", verb, " immediate              to use immediate 
mode."));
else
    this.paranoid = 1;
    $paranoid_db:set_kept_lines(this, y = min(y, 30));
    this:notify(tostr("Anti-spoofer on and keeping ", y, " lines."));
endif
.


@sw*eep:
buggers = 1;
found_listener = 0;
here = this.location;
for thing in (setremove(here.contents, this))
    tellwhere = $object_utils:has_verb(thing, "tell");
    notifywhere = $object_utils:has_verb(thing, "notify");
    if (thing in connected_players())
        this:notify(tostr(thing.name, " (", thing, ") is listening."));
        found_listener = 1;
    elseif ($object_utils:has_callable_verb(thing, "sweep_msg") && (typeof(msg = 
thing:sweep_msg()) == STR))
        this:notify(tostr(thing.name, " (", thing, ") ", msg, "."));
        found_listener = 1;
    elseif (tellwhere && (((owner = verb_info(tellwhere[1], "tell")[1]) != this) 
&& (!owner.wizard)))
        this:notify(tostr(thing.name, " (", thing, ") has been taught to listen by 
", owner.name, " (", owner, ")"));
        found_listener = 1;
    elseif (notifywhere && (((owner = verb_info(notifywhere[1], "notify")[1]) != 
this) && (!owner.wizard)))
        this:notify(tostr(thing.name, " (", thing, ") has been taught to listen by 
", owner.name, " (", owner, ")"));
        found_listener = 1;
    endif
endfor
buggers = {};
for v in ({"announce", "announce_all", "announce_all_but", "say", "emote", "huh", 
"here_huh", "huh2", "whisper"})
    vwhere = $object_utils:has_verb(here, v);
    if (vwhere && (((owner = verb_info(vwhere[1], v)[1]) != this) && (!owner.wizard)))
        buggers = setadd(buggers, owner);
    endif
endfor
if (buggers != {})
    if ($object_utils:has_verb(here, "sweep_msg") && (typeof(msg = here:sweep_msg()) 
== STR))
        this:notify(tostr(here.name, " (", here, ") ", msg, "."));
    else
        this:notify(tostr(here.name, " (", here, ") may have been bugged by ", $string_utils:english_list($list_utils:map_prop(buggers, 
"name")), "."));
    endif
elseif (!found_listener)
    this:notify("Communications look secure.");
endif
.


wh*isper:
this:tell(player.name, " whispers, \"", dobjstr, "\"");
player:tell("You whisper, \"", dobjstr, "\" to ", this.name, ".");
.


page:
nargs = length(args);
if (nargs < 1)
    player:notify(tostr("Usage: ", verb, "  [with ]"));
    return;
endif
who = $string_utils:match_player(args[1]);
if ($command_utils:player_match_result(who, args[1])[1])
    return;
elseif (who in this.gaglist)
    player:tell("You have ", who:title(), " @gagged.  If you paged ", who.po, ", 
", who.ps, " wouldn't be able to answer you.");
    return;
endif
"for pronoun_sub's benefit...";
dobj = who;
iobj = player;
header = player:page_origin_msg();
text = "";
if (nargs > 1)
    if ((args[2] == "with") && (nargs > 2))
        msg_start = 3;
    else
        msg_start = 2;
    endif
    msg = $string_utils:from_list(args[msg_start..nargs], " ");
    text = tostr($string_utils:pronoun_sub(($string_utils:index_delimited(header, 
player.name) ? "%S" | "%N") + " %, \""), msg, "\"");
endif
result = text ? who:receive_page(header, text) | who:receive_page(header);
if (result == 2)
    "not connected";
    player:tell((typeof(msg = who:page_absent_msg()) == STR) ? msg | $string_utils:pronoun_sub("%n 
is not currently logged in.", who));
else
    player:tell(who:page_echo_msg());
endif
.


receive_page:
"called by $player:page.  Two args, the page header and the text, all pre-processed 
by the page command.  Could be extended to provide haven abilities, multiline pages, 
etc.  Indeed, at the moment it just does :tell_lines, so we already do have multiline 
pages, if someone wants to take advantage of it.";
"Return codes:";
"  1:  page was received";
"  2:  player is not connected";
"  0:  page refused";
"If a specialization wants to refuse a page, it should return 0 to say it was refused. 
 If it uses pass(@args) it should propagate back up the return value.  It is possible 
that this code should interact with gagging and return 0 if the page was gagged.";
if (this:is_listening())
    this:tell_lines(args);
    return 1;
else
    return 2;
endif
.


page_origin_msg page_echo_msg page_absent_msg:
"set_task_perms(this.owner)";
return (msg = this.(verb)) ? $string_utils:pronoun_sub(this.(verb), this) | "";
.


i inv*entory:
if (c = player:contents())
    this:tell_contents(c);
else
    player:tell("You are empty-handed.");
endif
.


look_self:
player:tell(this:titlec());
pass();
if (!(this in connected_players()))
    player:tell($gender_utils:pronoun_sub("%{:He} %{!is} sleeping.", this));
elseif ((idle = idle_seconds(this)) < 60)
    player:tell($gender_utils:pronoun_sub("%{:He} %{!is} awake and %{!looks} alert.", 
this));
else
    time = $string_utils:from_seconds(idle);
    player:tell($gender_utils:pronoun_sub("%{:He} %{!is} awake, but %{!has} been 
staring off into space for ", this), time, ".");
endif
if (c = this:contents())
    this:tell_contents(c);
endif
.


home:
start = this.location;
if (start == this.home)
    player:tell("You're already home!");
    return;
elseif (typeof(this.home) != OBJ)
    player:tell("You've got a weird home, pal.  I've reset it to the default one.");
    this.home = $player_start;
elseif (!valid(this.home))
    player:tell("Oh no!  Your home's been recycled.  Time to look around for a new 
one.");
    this.home = $player_start;
else
    player:tell("You click your heels three times.");
endif
this:moveto(this.home);
if (!valid(start))
elseif (start == this.location)
    start:announce(player.name, " learns that you can never go home...");
else
    start:announce(player.name, " goes home.");
endif
if (this.location == this.home)
    this.location:announce(player.name, " comes home.");
elseif (this.location == start)
    player:tell("Either home doesn't want you, or you don't really want to go.");
else
    player:tell("Wait a minute!  This isn't your home...");
    if (valid(this.location))
        this.location:announce(player.name, " arrives, looking quite bewildered.");
    endif
endif
.


@sethome:
set_task_perms(this);
here = this.location;
if (!$object_utils:has_callable_verb(here, "accept_for_abode"))
    player:notify("This is a pretty odd place.  You should make your home in an actual 
room.");
elseif (here:accept_for_abode(this))
    this.home = here;
    player:notify(tostr(here.name, " is your new home."));
else
    player:notify(tostr("This place doesn't want to be your home.  Contact ", here.owner.name, 
" to be added to the residents list of this place, or choose another place as your 
home."));
endif
.


g*et take:
player:tell("This is not a pick-up joint!");
this:tell(player.name, " tried to pick you up.");
.


@move:
"'@move  to ' - Teleport an object. Example: '@move trash to #11' 
to move trash to the closet.";
set_task_perms((caller == this) ? this | $no_one);
dobj = this:my_match_object(dobjstr);
iobj = this:my_match_object(iobjstr);
if ($command_utils:object_match_failed(dobj, dobjstr) || ((iobj != $nothing) && $command_utils:object_match_failed(iobj, 
iobjstr)))
    return;
endif
if ((!$perm_utils:controls(this, dobj)) && (this != dobj))
    player:tell("You may only @move your own things.");
    return;
endif
old_loc = dobj.location;
if (old_loc == iobj)
    player:tell(dobj.name, " is already ", valid(iobj) ? "in " + iobj.name | "nowhere", 
".");
    return;
endif
dobj:moveto(iobj);
if (dobj.location == iobj)
    player:tell("Moved.");
    if (is_player(dobj))
        if (valid(old_loc))
            old_loc:announce_all(dobj.name, " disappears suddenly for parts unknown.");
            if (dobj != player)
                dobj:tell("You have been moved by ", player.name, ".");
            endif
        endif
        if (valid(dobj.location))
            dobj.location:announce(dobj.name, " materializes out of thin air.");
        endif
    endif
elseif (dobj.location == old_loc)
    if ($object_utils:contains(dobj, iobj))
        player:tell(iobj.name, " is inside of ", dobj.name, "!");
    else
        player:tell($string_utils:pronoun_sub("Either %d doesn't want to go, or %i 
doesn't want to accept %[dpo]."));
    endif
elseif (dobj == player)
    player:tell("You have been deflected from your original destination.");
else
    player:tell($string_utils:pronoun_sub("%D has been deflected from %[dpp] original 
destination."));
endif
.



@eject @eject! @eject!!:
set_task_perms(player);
if (iobjstr == "here")
    iobj = player.location;
elseif (iobjstr == "me")
    iobj = player;
elseif ($command_utils:object_match_failed(iobj, iobjstr))
    return;
endif
if (!$perm_utils:controls(player, iobj))
    player:notify(tostr("You are not the owner of ", iobj.name, "."));
    return;
endif
if (dobjstr == "me")
    dobj = player;
elseif (($failed_match == (dobj = $string_utils:literal_object(dobjstr))) && $command_utils:object_match_failed(dobj 
= iobj:match(dobjstr), dobjstr))
    return;
endif
if (dobj.location != iobj)
    player:notify(tostr(dobj.name, "(", dobj, ") is not in ", iobj.name, "(", iobj, 
")."));
    return;
endif
if (dobj.wizard)
    player:notify(tostr("Sorry, you can't ", verb, " a wizard."));
    dobj:tell(player.name, " tried to ", verb, " you.");
    return;
endif
iobj:((verb == "@eject") ? "eject" | "eject_basic")(dobj);
player:notify($object_utils:has_callable_verb(iobj, "ejection_msg") ? iobj:ejection_msg() 
| $room:ejection_msg());
if (verb != "@eject!!")
    dobj:tell($object_utils:has_callable_verb(iobj, "victim_ejection_msg") ? iobj:victim_ejection_msg() 
| $room:victim_ejection_msg());
endif
iobj:announce_all_but({player, dobj}, $object_utils:has_callable_verb(iobj, "oejection_msg") 
? iobj:oejection_msg() | $room:oejection_msg());
.


where*is @where*is:
if (!args)
    them = connected_players();
else
    who = $command_utils:player_match_result($string_utils:match_player(args), args);
    if (length(who) <= 1)
        if (!who[1])
            player:notify("Where is who?");
        endif
        return;
    elseif (who[1])
        player:notify("");
    endif
    them = listdelete(who, 1);
endif
lmax = rmax = 0;
for p in (them)
    player:notify($string_utils:pronoun_sub("%N (%#) is in %l (%[#l]).", p));
endfor
.


@who:
if (caller != player)
    return E_PERM;
endif
plyrs = args ? listdelete($command_utils:player_match_result($string_utils:match_player(args), 
args), 1) | connected_players();
if (!plyrs)
    return;
endif
$code_utils:show_who_listing(plyrs);
.


@wizards:
"@wizards [all]";
if (caller != player)
    return E_PERM;
endif
if (args)
    $code_utils:show_who_listing($wiz_utils:all_wizards());
else
    $code_utils:show_who_listing($wiz_utils:connected_wizards()) || player:notify("No 
wizards currently logged in.");
endif
.


?* help info*rmation @help:
set_task_perms(callers() ? caller_perms() | player);
"...this code explicitly relies on being !d in several places...";
if ((index(verb, "?") != 1) || (length(verb) <= 1))
    what = $string_utils:trimr(argstr);
elseif (argstr)
    what = tostr(verb[2..length(verb)], " ", $string_utils:trimr(argstr));
else
    what = verb[2..length(verb)];
endif
"...find a db that claims to know about `what'...";
dblist = $code_utils:help_db_list();
result = $code_utils:help_db_search(what, dblist);
if (!result)
    "... note: all of the last-resort stuff...";
    "... is now located on $help:find_topics/get_topic...";
    "$wiz_utils:missed_help(what, result)";
    player:notify(tostr("Sorry, but no help is available on `", what, "'."));
elseif (result[1] == $ambiguous_match)
    "$wiz_utils:missed_help(what, result)";
    player:notify_lines(tostr("Sorry, but the topic-name `", what, "' is ambiguous. 
 I don't know which of the following topics you mean:"));
    for x in ($string_utils:columnize($help:sort_topics(result[2]), 3, 60))
        player:notify(tostr("   ", x));
    endfor
else
    help = result[1];
    topic = result[2];
    if (topic != what)
        player:notify(tostr("Showing help on `", topic, "':"));
        player:notify("----");
    endif
    dblist = dblist[1 + (help in dblist)..length(dblist)];
    if (1 == (text = help:get_topic(topic, dblist)))
        "...get_topic took matters into its own hands...";
    elseif (text)
        "...these can get long...";
        for line in ((typeof(text) == LIST) ? text | {text})
            if (typeof(line) != STR)
                player:notify("Odd results from help -- complain to a wizard.");
            else
                player:notify(line);
            endif
            $command_utils:suspend_if_needed(0);
        endfor
    else
        player:notify(tostr("Help DB ", help, " thinks it knows about `", what, "' 
but something's messed up."));
        player:notify(tostr("Tell ", help.owner.wizard ? "" | tostr(help.owner.name, 
" (", help.owner, ") or "), "a wizard."));
    endif
endif
.


news:
$news:read();
.


mail_forward mail_notify:
if (typeof(mf = this.(verb)) == STR)
    return $string_utils:pronoun_sub(mf, @args);
else
    return mf;
endif
.


receive_message:
":receive_message(msg,from)";
if ((!$perm_utils:controls(caller_perms(), this)) && (caller != this))
    return E_PERM;
endif
if (this:mail_option("netmail"))
    msg = args[1];
    message = {"Forwarded: " + msg[4], "Original-date: " + ctime(msg[1]), "Original-From: 
" + msg[2], "Original-To: " + msg[3], ((("Reply-To: " + args[2].name) + "@") + $network.moo_name) 
+ ".moo.mud.org"};
    for x in (msg[5..length(msg)])
        message = {@message, @$generic_editor:fill_string(x, this:linelen())};
    endfor
    if (this:send_self_netmail(message, @listdelete(args, 1)) == 0)
        return 0;
    endif
endif
set_task_perms(this.owner);
new = this:new_message_num();
ncur = (new <= 1) ? 0 | min(this:current_message(this), new);
this:set_current_message(this, ncur);
new = max(new, ncur + 1);
this.messages = {@this.messages, {new, args[1]}};
"... new-mail notification is now done directly by $mail_agent:raw_send";
"... see :notify_mail...";
return new;
.


display_message:
":display_message(preamble,msg) --- prints msg to player.";
vb = ((this._mail_task == task_id()) || (caller == $mail_editor)) ? "notify_lines" 
| "tell_lines";
preamble = args[1];
player:(vb)({@(typeof(preamble) == LIST) ? preamble | {preamble}, @args[2], "--------------------------"});
.


parse_message_seq from_msg_seq %from_msg_seq to_msg_seq %to_msg_seq subject_msg_seq body_msg_seq kept_msg_seq unkept_msg_seq display_seq_headers display_seq_full messages_in_seq list_rmm new_message_num length_num_le length_date_le length_date_gt length_all_msgs exists_num_eq msg_seq_to_msg_num_list msg_seq_to_msg_num_string rm_message_seq undo_rmm expunge_rmm renumber keep_message_seq:
"parse_message_seq(strings,cur)         => msg_seq";
"messages_in_seq(msg_seq);              => text of messages in msg_seq";
"display_seq_headers(msg_seq[,current]) :displays summary lines of those msgs";
"rmm_message_seq(msg_seq)               => string giving msg numbers removed";
"undo_rmm()    => msg_seq of restored messages";
"expunge_rmm() => number of messages expunged";
"list_rmm()    => number of messages awaiting expunge";
"renumber(cur) => {number of messages in folder, new_cur}";
"";
"See the corresponding routines on $mail_agent.";
if ((caller == $mail_agent) || $perm_utils:controls(caller_perms(), this))
    set_task_perms(this.owner);
    return $mail_agent:(verb)(@args);
else
    return E_PERM;
endif
.


msg_summary_line:
return $mail_agent:msg_summary_line(@args);
.


msg_text:
return $mail_agent:to_text(@args);
.


notify_mail:
":notify_mail(from,recipients[,msgnums])";
" used by $mail_agent:raw_send to notify this player about mail being sent";
" from  to .   if given gives the message number(s) assigned 
(in the event that the corresponding recipient actually kept the mail)";
if (!$object_utils:connected(this))
    return;
elseif (!((caller in {this, $mail_agent}) || $perm_utils:controls(caller_perms(), 
this)))
    return E_PERM;
else
    from = args[1];
    from_name = $mail_agent:name(from);
    recipients = args[2];
    msgnums = {@args, {}}[3];
    "... msgnums may be shorter than recipients or may have some slots filled";
    "... with 0's if msg numbers are not available for some recipients.";
    if ((t = this in recipients) && ((length(msgnums) >= t) && msgnums[t]))
        "... you are getting the mail and moreover your :receive_message kept it.";
        namelist = $string_utils:english_list($list_utils:map_arg($mail_agent, "name", 
setremove(recipients, this)), "");
        this:notify(tostr("You have new mail (", msgnums[t], ") from ", from_name, 
namelist ? " which was also sent to " + namelist | "", "."));
        if (!this:mail_option("expert"))
            this:notify(tostr("Type `help mail' for info on reading it."));
        endif
    else
        "... vanilla notification; somebody got sent mail and you're finding out.";
        namelist = $string_utils:english_list({@t ? {"You"} | {}, @$list_utils:map_arg($mail_agent, 
"name", setremove(recipients, this))}, "");
        this:notify(tostr(namelist, (length(recipients) == 1) ? " has" | " have", 
" just been sent new mail by ", from_name, "."));
    endif
endif
.


__fix:
"runs the old->new format conversion on every message in this.messages.";
" => 1 if successful";
" => 0 if anything toward happened during a suspension";
"      (e.g., new message received, someone deleted stuff) ";
"      in which case this.messages is left as if this routine were never run.";
if (!$perm_utils:controls(caller_perms(), this))
    return E_PERM;
endif
msgs = {};
i = 1;
for m in (oldmsgs = this.messages)
    msgs = {@msgs, {m[1], $mail_agent:__convert_new(@m[2])}};
    if ($command_utils:running_out_of_time())
        player:notify(tostr("...", i, " ", this));
        suspend(0);
        if (oldmsgs != this.messages)
            return 0;
        endif
    endif
    i = i + 1;
endfor
this.messages = msgs;
return 1;
.


current_message:
":current_message([recipient])";
" => current message number for the given recipient (defaults to this).";
" => 0 if we have no record of that recipient.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    $error:raise(E_PERM);
elseif ((!args) || (args[1] == this))
    return this.current_message[1];
elseif (a = $list_utils:assoc(args[1], this.current_message))
    return a[2];
else
    return 0;
endif
.


get_current_message:
":get_current_message([recipient])";
" => {msg_num, last_read_date} for the given recipient.";
" => 0 if we have no record of that recipient.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    $error:raise(E_PERM);
elseif ((!args) || (args[1] == this))
    return this.current_message[1..2];
elseif (a = $list_utils:assoc(args[1], this.current_message))
    return a[2..3];
else
    return 0;
endif
.


set_current_message:
":set_current_message(recipient[,number[,date]])";
"Returns the new {number,last-read-date} pair for recipient.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    return $error:raise(E_PERM);
endif
recip = args[1];
number = {@args, E_NONE}[2];
date = {@args, 0, 0}[3];
cm = this.current_message;
if (recip == this)
    this.current_message[2] = max(date, cm[2]);
    if (number != E_NONE)
        this.current_message[1] = number;
    endif
    return this.current_message[1..2];
elseif (i = $list_utils:iassoc(recip, cm))
    return (this.current_message[i] = {recip, (number == E_NONE) ? cm[i][2] | number, 
max(date, cm[i][3])})[2..3];
else
    entry = {recip, (number != E_NONE) && number, date};
    this.current_message = {@cm, entry};
    return entry[2..3];
endif
.


make_current_message:
":make_current_message(recipient)";
"starts a new current_message record for recipient";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    return $error:raise(E_PERM);
elseif (((recip = args[1]) != this) && (!$list_utils:assoc(recip, cm = this.current_message)))
    this.current_message = listappend(cm, {recip, 0, 0});
endif
.


kill_current_message:
":kill_current_message(recipient)";
"entirely forgets current message for this recipient...";
"Returns true iff successful.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    return $error:raise(E_PERM);
else
    return ((recip = args[1]) != this) && ((i = $list_utils:iassoc(recip, cm = this.current_message)) 
&& (this.current_message = listdelete(cm, i)));
endif
.


current_folder:
":current_folder() => default folder to use, always an object, usually `this'";
set_task_perms(caller_perms());
return ((!this:mail_option("sticky")) || this.current_folder) && this;
.


set_current_folder:
set_task_perms(caller_perms());
return this.current_folder = args[1];
.


parse_folder_spec:
":parse_folder_spec(verb,args,expected_preposition[,allow_trailing_args_p])";
" => {folder, msg_seq_args, trailing_args}";
set_task_perms(caller_perms());
folder = this:current_folder();
if (!prepstr)
    return {folder, args[2], {}};
endif
verb = args[1];
prep = args[3];
extra = {@args, 0}[4];
args = args[2];
p = prepstr in args;
if (prepstr != prep)
    "...unexpected preposition...";
    if (extra && (!index(prepstr, " ")))
        return {folder, args[1..p - 1], args[p..length(args)]};
    else
        player:tell("Usage:  ", verb, " [] [", prep, " ]");
    endif
elseif (!((p < length(args)) && (fname = args[p + 1])))
    "...preposition but no iobj...";
    player:tell(verb, " ", $string_utils:from_list(args, " "), " WHAT?");
elseif ($mail_agent:match_failed(folder = $mail_agent:match_recipient(fname), fname))
    "...bogus mail folder...";
else
    return {folder, args[1..p - 1], args[p + 2..length(args)]};
endif
return 0;
.


parse_mailread_cmd:
":parse_mailread_cmd(verb,args,default,prep[,trailer])";
"  handles anything of the form  `VERB message_seq [PREP folder ...]'";
"    default is the default msg-seq to use if none given";
"    prep is the expected prepstr (assumes prepstr is set), usually `on'";
"    trailer, if present and true, indicates trailing args are permitted.";
"  returns {recipient object, message_seq, current_msg,\"...\"} or 0";
set_task_perms(caller_perms());
if (!(pfs = this:parse_folder_spec(@listdelete(args, 3))))
    return 0;
endif
verb = args[1];
default = args[3];
extra = {@args, 0}[5];
folder = pfs[1];
cur = this:get_current_message(folder) || {0};
if (typeof(pms = folder:parse_message_seq(pfs[2], @cur)) == LIST)
    rest = {@listdelete(pms, 1), @pfs[3]};
    if ((!extra) && rest)
        "...everything should have been gobbled by :parse_message_seq...";
        player:tell("I don't understand `", rest[1], "'");
        return 0;
    elseif (pms[1])
        "...we have a nonempty message sequence...";
        return {folder, pms[1], cur, rest};
    elseif (used = (length(pfs[2]) + 1) - length(pms))
        "...:parse_message_seq used some words, but didn't get anything out of it";
        pms = ("%f % no `" + $string_utils:from_list(pfs[2][1..used], " ")) 
+ "' messages.";
    elseif (typeof(pms = folder:parse_message_seq(default, @cur)) == LIST)
        "...:parse_message_seq used nothing, try the default; wow it worked";
        return {folder, pms[1], cur, rest};
    endif
elseif (typeof(pms) == ERR)
    player:tell($mail_agent:name(folder), " is not readable by you.");
    if (!$object_utils:isa(folder, $mail_recipient))
        player:tell("Use * to indicate a non-player mail recipient.");
    endif
    return 0;
endif
if (folder == this)
    subst = {{"%f's", "Your"}, {"%f", "You"}, {"%", "have"}};
elseif (is_player(folder))
    subst = {{"%f", folder.name}, {"%", $gender_utils:get_conj("has", folder)}};
else
    subst = {{"%f", $mail_agent:name(folder)}, {"%", "has"}};
endif
player:tell($string_utils:substitute(pms, {@subst, {"%%", "%"}}));
return 0;
.


@mail:
"@mail                 --- as in help @mail";
"@mail  on  --- shows mail on mailing list or player.";
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (p = this:parse_mailread_cmd("@mail", args, this:mail_option("@mail") || $mail_agent.("player_default_@mail"), 
"on"))
    this:set_current_folder(folder = p[1]);
    msg_seq = p[2];
    seq_size = $seq_utils:size(msg_seq);
    if ((lim = player:mail_option("manymsgs")) && ((lim <= seq_size) && (!$command_utils:yes_or_no(tostr("You 
are about to see ", seq_size, " message headers.  Continue?")))))
        player:notify(tostr("Aborted.  @mailoption manymsgs=", lim));
        return;
    endif
    if (1 != seq_size)
        player:notify(tostr(seq_size, " messages", (folder == this) ? "" | (" on 
" + $mail_agent:name(folder)), ":"));
    endif
    folder:display_seq_headers(msg_seq, @p[3]);
endif
.


@read @peek:
"@read ...                  -- as in help @read";
"@read ... on *  -- reads messages on recipient.";
"@peek ...                       -- like @read, but don't set current message";
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (p = this:parse_mailread_cmd("@read", args, "", "on"))
    this:set_current_folder(folder = p[1]);
    msg_seq = p[2];
    if ((lim = player:mail_option("manymsgs")) && ((lim <= (seq_size = $seq_utils:size(msg_seq))) 
&& (!$command_utils:yes_or_no(tostr("You are about to see ", seq_size, " messages. 
 Continue?")))))
        player:notify(tostr("Aborted.  @mailoption manymsgs=", lim));
        return;
    endif
    this._mail_task = task_id();
    if (cur = folder:display_seq_full(msg_seq, tostr("Message %d", (folder == this) 
? "" | (" on " + $mail_agent:name(folder)), ":")))
        if (verb != "@peek")
            this:set_current_message(folder, @cur);
        endif
    endif
endif
.


@next @prev:
set_task_perms(player);
if (dobjstr)
    player:notify(tostr("Usage:  ", verb, " [on ]"));
else
    this:("@read")(verb[2..5], @args);
endif
.


@rmm*ail:
"@rmm  [from ].   Use @unrmm if you screw up.";
" Beware, though.  @unrmm can only undo the most recent @rmm.";
set_task_perms(player);
if (!(p = this:parse_mailread_cmd("@rmm", args, "cur", "from")))
    "...parse failed, we've already complained...";
elseif ((!prepstr) && ((p[1] != this) && (!$command_utils:yes_or_no(("@rmmail from 
" + $mail_agent:name(p[1])) + ".  Continue?"))))
    "...wasn't the folder player was expecting...";
    player:notify("@rmmail aborted.");
else
    this:set_current_folder(folder = p[1]);
    e = folder:rm_message_seq(p[2]);
    if (typeof(e) == ERR)
        player:notify(tostr($mail_agent:name(folder), ":  ", e));
    else
        count = ((n = $seq_utils:size(p[2])) == 1) ? "." | tostr(" (", n, " messages).");
        fname = (folder == this) ? "" | (" from " + $mail_agent:name(folder));
        player:notify(tostr("Deleted ", e, fname, count));
    endif
endif
.


@renumber:
set_task_perms(player);
if (!dobjstr)
    folder = this:current_folder();
elseif ($mail_agent:match_failed(folder = $mail_agent:match_recipient(dobjstr), dobjstr))
    return;
endif
cur = this:current_message(folder);
fname = $mail_agent:name(folder);
if (typeof(h = folder:renumber(cur)) == ERR)
    player:notify(tostr(h));
else
    if (!h[1])
        player:notify(tostr("No messages on ", fname, "."));
    else
        player:notify(tostr("Messages on ", fname, " renumbered 1-", h[1], "."));
        this:set_current_folder(folder);
        if (h[2] && this:set_current_message(folder, h[2]))
            player:notify(tostr("Current message is now ", h[2], "."));
        endif
    endif
endif
.


@unrmm*ail:
"@unrmm [on ]  -- undoes the previous @rmm on that recipient.";
set_task_perms(player);
if (!(p = this:parse_folder_spec("@unrmm", args, "on")))
    return;
endif
dobjstr = $string_utils:from_list(p[2], " ");
keep = 0;
if ((!dobjstr) || (keep = index("keep", dobjstr) == 1))
    do = "undo_rmm";
elseif (index("expunge", dobjstr) == 1)
    do = "expunge_rmm";
elseif (index("list", dobjstr) == 1)
    do = "list_rmm";
else
    player:notify(tostr("Usage:  ", verb, " [expunge|list] [on ]"));
    return;
endif
this:set_current_folder(folder = p[1]);
if (msg_seq = folder:(do)(@keep ? {keep} | {}))
    if (do == "undo_rmm")
        player:notify(tostr($seq_utils:size(msg_seq), " messages restored to ", $mail_agent:name(folder), 
"."));
        folder:display_seq_headers(msg_seq, 0);
    else
        player:notify(tostr(msg_seq, " zombie message", (msg_seq == 1) ? " " | "s 
", (do == "expunge_rmm") ? "expunged from " | "on ", $mail_agent:name(folder), "."));
    endif
elseif (typeof(msg_seq) == ERR)
    player:notify(tostr($mail_agent:name(folder), ":  ", msg_seq));
else
    player:notify(tostr("No messages to ", (do == "expunge_rmm") ? "expunge from 
" | "restore to ", $mail_agent:name(folder)));
endif
.


@send:
if (args && (args[1] == "to"))
    args = listdelete(args, 1);
endif
subject = {};
for a in (args)
    if (((i = index(a, "=")) > 3) && (index("subject", a[1..i - 1]) == 1))
        args = setremove(args, a);
        a[1..i] = "";
        subject = {a};
    endif
endfor
$mail_editor:invoke(args, verb, @subject);
.


@answer @repl*y:
"@answer  [on *] [...]";
set_task_perms(who = valid(caller_perms()) ? caller_perms() | player);
if (p = this:parse_mailread_cmd(verb, args, "", "on", 1))
    if ($seq_utils:size(p[2]) != 1)
        player:notify("You can only answer *one* message at a time.");
    elseif (LIST != typeof(flags_replytos = $mail_editor:check_answer_flags(@p[4])))
        player:notify_lines({tostr("Usage:  ", verb, " [message-# [on ]] 
[flags...]"), "where flags include any of:", "  all        reply to everyone", " 
 sender     reply to sender only", "  include    include the original message in 
your reply", "  noinclude  don't include the original in your reply"});
    else
        this:set_current_folder(p[1]);
        $mail_editor:invoke(2, verb, p[1]:messages_in_seq(p[2])[1][2], @flags_replytos);
    endif
endif
.


@forward:
"@forward  [on *] to  [...]";
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (!(p = this:parse_mailread_cmd(verb, args, "", "on", 1)))
    "...lose...";
    return;
elseif ($seq_utils:size(sequence = p[2]) != 1)
    player:notify("You can only forward *one* message at a time.");
    return;
elseif ((length(p[4]) < 2) || (p[4][1] != "to"))
    player:notify(tostr("Usage:  ", verb, " [] [on ] to ..."));
    return;
endif
recips = {};
for rs in (listdelete(p[4], 1))
    if ($mail_agent:match_failed(r = $mail_agent:match_recipient(rs), rs))
        return;
    endif
    recips = {@recips, r};
endfor
this:set_current_folder(folder = p[1]);
m = folder:messages_in_seq(sequence)[1];
msgnum = m[1];
msgtxt = m[2];
from = msgtxt[2];
if (msgtxt[4] != " ")
    subject = tostr("[", from, ":  ", msgtxt[4], "]");
elseif ((h = "" in msgtxt) && (h < length(msgtxt)))
    subject = tostr("[", from, ":  `", msgtxt[h + 1][1..min(20, length(msgtxt[h + 
1]))], "']");
else
    subject = tostr("[", from, "]");
endif
result = $mail_agent:send_message(player, recips, subject, $mail_agent:to_text(@msgtxt));
if (!result)
    player:notify(tostr(result));
elseif (result[1])
    player:notify(tostr("Message ", msgnum, @(folder == this) ? {} | {" on ", $mail_agent:name(folder)}, 
" @forwarded to ", $mail_agent:name_list(@listdelete(result, 1)), "."));
else
    player:notify("Message not sent.");
endif
.


@gripe:
$mail_editor:invoke($gripe_recipients, "@gripe", "@gripe: " + argstr);
.


@typo @bug @suggest*ion @idea @comment:
subject = tostr($string_utils:capitalize(verb[2..length(verb)]), ":  ", (loc = this.location).name, 
"(", loc, ")");
if (this != player)
    return E_PERM;
elseif (argstr)
    result = $mail_agent:send_message(this, {loc.owner}, subject, argstr);
    if (result && result[1])
        player:notify(tostr("Your ", verb, " sent to ", $mail_agent:name_list(@listdelete(result, 
1)), ".  Input is appreciated, as always."));
    else
        player:notify(tostr("Huh?  This room's owner (", loc.owner, ") is invalid? 
 Tell a wizard..."));
    endif
    return;
elseif (!($object_utils:isa(loc, $room) && loc.free_entry))
    player:notify_lines({tostr("You need to make it a one-liner, i.e., `", verb, 
" something or other'."), "This room may not let you back in if you go to the Mail 
Room."});
elseif ($object_utils:isa(loc, $generic_editor))
    player:notify_lines({tostr("You need to make it a one-liner, i.e., `", verb, 
" something or other'."), "Sending you to the Mail Room from an editor is usually 
a bad idea."});
else
    $mail_editor:invoke({tostr(loc.owner)}, verb, subject);
endif
if (verb == "@bug")
    player:notify("For a @bug report, be sure to mention exactly what it was you 
typed to trigger the error...");
endif
.


@skip:
"@skip [*...]";
"  sets your last-read time for the given lists to now, indicating your";
"  disinterest in any new messages that might have appeared recently.";
set_task_perms(player);
current_folder = this:current_folder();
for a in (args || {0})
    if (a ? $mail_agent:match_failed(folder = $mail_agent:match_recipient(a), a) 
| (folder = this:current_folder()))
        "...bogus folder name, done...  No, try anyway.";
        if (this:kill_current_message(this:my_match_object(a)))
            player:notify("Invalid folder, but found it subscribed anyway.  Removed.");
        endif
    else
        lseq = folder:length_all_msgs();
        unread = (n = this:get_current_message(folder)) ? folder:length_date_gt(n[2]) 
| lseq;
        this:set_current_message(folder, lseq && folder:messages_in_seq({lseq, lseq 
+ 1})[1][1], time());
        player:notify(tostr(unread ? tostr("Ignoring ", unread) | "No", " unread 
message", (unread != 1) ? "s" | "", " on ", $mail_agent:name(folder)));
        if (current_folder == folder)
            this:set_current_folder(this);
        endif
    endif
endfor
.


@subscribe @unsubscribed:
"@subscribe *";
"  causes you to be notified when new mail arrives on this list";
"@subscribe";
"   just lists available mailing lists.";
set_task_perms(player);
if (!dobjstr)
    ml = $list_utils:slice(player.current_message[3..length(player.current_message)]);
    for c in ({@$mail_agent.contents, @this.mail_lists})
        $command_utils:suspend_if_needed(0);
        if ((c:is_usable_by(player) || c:is_readable_by(player)) && ((verb != "@unsubscribed") 
|| (!(c in ml))))
            c:look_self();
        endif
    endfor
    player:notify(tostr("-------- end of ", verb, " -------"));
    return;
elseif ($mail_agent:match_failed(folder = $mail_agent:match_recipient(dobjstr), dobjstr))
    return;
elseif (folder == this)
    player:notify("You don't need to @subscribe to yourself");
    return;
elseif ($object_utils:isa(folder, $mail_recipient) ? !folder:is_readable_by(this) 
| (!$perm_utils:controls(this, folder)))
    player:notify("That mailing list is not readable by you.");
    return;
endif
this:make_current_message(folder);
notification = iobjstr && (iobjstr[1] == "n");
if ($object_utils:isa(folder, $mail_recipient) && notification)
    folder:add_notify(this);
endif
len = folder:length_all_msgs();
player:notify(tostr($mail_agent:name(folder), " has ", len, " message", (len == 1) 
? "" | "s", ".", notification ? "  You will be notified immediately when new messages 
are posted." | "  Notification of new messages will be printed when you connect."));
this:set_current_folder(folder);
.


mail_catch_up:
set_task_perms((caller == this) ? this.owner | caller_perms());
new_cm = head = {};
for n in (this.current_message)
    $command_utils:suspend_if_needed(0);
    if (typeof(n) != LIST)
        head = {@head, n};
    elseif ($object_utils:isa(folder = n[1], $mail_recipient) && folder:is_readable_by(this))
        "...set current msg to be the last one you could possibly have read.";
        if (n[3] < folder.last_msg_date)
            i = folder:length_date_le(n[3]);
            n[2] = i && folder:messages_in_seq(i)[1];
        endif
        new_cm = listappend(new_cm, n, $list_utils:iassoc_sorted(n[3], new_cm, 3));
    endif
endfor
this.current_message = {@head, @$list_utils:reverse(new_cm)};
this:set_current_folder(this);
.


@rn check_mail_lists @subscribed:
set_task_perms((caller == this) ? this.owner | caller_perms());
which = {};
cm = this.current_message;
cm[1..2] = (verb == "@rn") ? {{this, @cm[1..2]}} | {};
all = verb == "@subscribed";
for n in (cm)
    rcpt = n[1];
    if (n == $news)
        "... $news is handled separately ...";
    elseif ($mail_agent:is_recipient(rcpt))
        if ((nmsgs = n[1]:length_date_gt(n[3])) || all)
            which = {@which, {n[1], nmsgs}};
        endif
    else
        player:notify(tostr("Bogus recipient ", rcpt, " removed from .current_message."));
        this.current_message = setremove(this.current_message, n);
    endif
    $command_utils:suspend_if_needed(0);
endfor
if (which)
    player:notify((verb == "@subscribed") ? "You are subscribed to the following 
lists:" | "There is new activity on the following lists:");
    for w in (which)
        name = (w[1] == this) ? " me" | $mail_agent:name(w[1]);
        player:notify(tostr($string_utils:left("    " + name, 40), " ", w[2], " new 
message", (w[2] == 1) ? "" | "s"));
    endfor
elseif (verb == "@rn")
    player:notify("No new activity on any of your lists.");
elseif (verb == "@subscribed")
    player:notify("You aren't subscribed to any mailing lists.");
endif
return which;
.


mail_option:
":mail_option(name)";
"Returns the value of the specified mail option";
if ((caller in {this, $mail_editor, $mail_agent}) || $perm_utils:controls(caller_perms(), 
this))
    return $mail_options:get(this.mail_options, args[1]);
else
    return E_PERM;
endif
.


display_option:
":display_option(name) => returns the value of the specified @display option";
if ((caller == this) || $perm_utils:controls(caller_perms(), this))
    return $display_options:get(this.display_options, args[1]);
else
    return E_PERM;
endif
.


edit_option:
":edit_option(name) => returns the value of the specified edit option";
if ((caller == this) || ($object_utils:isa(caller, $generic_editor) || $perm_utils:controls(caller_perms(), 
this)))
    return $edit_options:get(this.edit_options, args[1]);
else
    return E_PERM;
endif
.


set_mail_option set_edit_option set_display_option:
":set_edit_option(oname,value)";
":set_display_option(oname,value)";
":set_mail_option(oname,value)";
"Changes the value of the named option.";
"Returns a string error if something goes wrong.";
if (!((caller == this) || $perm_utils:controls(caller_perms(), this)))
    return tostr(E_PERM);
endif
"...this is kludgy, but it saves me from writing the same verb 3 times.";
"...there's got to be a better way to do this...";
verb[1..4] = "";
foo_options = verb + "s";
"...";
if (typeof(s = #0.(foo_options):set(this.(foo_options), @args)) == STR)
    return s;
elseif (s == this.(foo_options))
    return 0;
else
    this.(foo_options) = s;
    return 1;
endif
.


@mailo*ptions @mail-o*ptions @edito*ptions @edit-o*ptions @displayo*ptions @display-o*ptions:
"@-option 

set_name:
"set_name(newname) attempts to change this.name to newname";
"  => E_PERM   if you don't own this";
"  => E_INVARG if the name is already taken or prohibited for some reason";
"  => E_NACC   if the player database is not taking new names right now.";
"  => E_ARGS   if the name is too long (controlled by $login.max_player_name)";
if (!($perm_utils:controls(caller_perms(), this) || (this == caller)))
    return E_PERM;
elseif (!is_player(this))
    "we don't worry about the names of player classes.";
    set_task_perms(caller_perms());
    return pass(@args);
elseif ($player_db.frozen)
    return E_NACC;
elseif (length(name = args[1]) > $login.max_player_name)
    return E_ARGS;
elseif (!($player_db:available(name) in {this, 1}))
    return E_INVARG;
else
    old = this.name;
    this.name = name;
    if ((name != old) && (!(old in this.aliases)))
        $player_db:delete(old);
    endif
    $player_db:insert(name, this);
    return 1;
endif
.


set_aliases:
"set_aliases(alias_list)";
"For changing player aliases, we check to make sure that none of the aliases match 
existing player names/aliases.  Aliases containing spaces are not entered in the 
$player_db and so are not subject to this restriction ($string_utils:match_player 
will not match on them, however, so they only match if used in the immediate room, 
e.g., with match_object() or somesuch).";
"Also we make sure that the .name is included in the .alias list.  In any situation 
where .name and .aliases are both being changed, do the name change first.";
"  => E_PERM   if you don't own this";
"  => E_NACC   if the player database is not taking new aliases right now.";
"  => E_TYPE   if alias_list is not a list";
"  => E_INVARG if any element of alias_list is not a string";
if (!($perm_utils:controls(caller_perms(), this) || (this == caller)))
    return E_PERM;
elseif (!is_player(this))
    "we don't worry about the names of player classes.";
    return pass(@args);
elseif ($player_db.frozen)
    return E_NACC;
elseif (typeof(aliases = args[1]) != LIST)
    return E_TYPE;
elseif ((length(aliases = setadd(aliases, this.name)) > 30) && (length(aliases) >= 
length(this.aliases)))
    return E_INVARG;
else
    for a in (aliases)
        if (typeof(a) != STR)
            return E_INVARG;
        endif
        if ((!(index(a, " ") || index(a, "	"))) && (!($player_db:available(a) in 
{this, 1})))
            aliases = setremove(aliases, a);
        endif
    endfor
    old = this.aliases;
    this.aliases = aliases;
    for a in (old)
        if (!(a in aliases))
            $player_db:delete2(a, this);
        endif
    endfor
    for a in (aliases)
        if (!(index(a, " ") || index(a, "	")))
            $player_db:insert(a, this);
        endif
    endfor
    return 1;
endif
.


@rename:
if ((player != caller) || (player != this))
    return;
endif
set_task_perms(player);
spec = $code_utils:parse_verbref(dobjstr);
if (spec)
    object = this:my_match_object(spec[1]);
    if (!$command_utils:object_match_failed(object, spec[1]))
        vname = spec[2];
        info = verb_info(object, vname);
        if (info == E_VERBNF)
            player:notify("That object does not define that verb.");
        elseif (typeof(info) == ERR)
            player:notify(tostr(info));
        else
            result = set_verb_info(object, vname, listset(info, iobjstr, 3));
            if (typeof(result) == ERR)
                player:notify(tostr(result));
            else
                player:notify("Verb name changed.");
            endif
        endif
    endif
else
    object = this:my_match_object(dobjstr);
    if (!$command_utils:object_match_failed(object, dobjstr))
        old_name = object.name;
        old_aliases = object.aliases;
        if (e = $building_utils:set_names(object, iobjstr))
            if (strcmp(object.name, old_name) == 0)
                name_message = tostr("Name of ", object, " (", old_name, ") is unchanged");
            else
                name_message = tostr("Name of ", object, " changed to \"", object.name, 
"\"");
            endif
            aliases = $string_utils:from_value(object.aliases, 1);
            if (object.aliases == old_aliases)
                alias_message = tostr(".  Aliases are unchanged (", aliases, ").");
            else
                alias_message = tostr(", with aliases ", aliases, ".");
            endif
            player:notify(name_message + alias_message);
        elseif (e == E_INVARG)
            player:notify("That particular name change not allowed (see help @rename).");
            if (object == player)
                player:notify($player_db:why_bad_name(player, iobjstr));
            endif
        elseif (e == E_NACC)
            player:notify("Oops.  You can't update that name right now; try again 
in a few minutes.");
        elseif (e == E_ARGS)
            player:notify(tostr("Sorry, name too long.  Maximum number of characters 
in a name:  ", $login.max_player_name));
        else
            player:notify(tostr(e));
        endif
    endif
endif
.


@addalias @add-alias:
"Syntax: @addalias [,...,] to ";
"        @addalias [,...,] to :";
"";
"The first form is used to add aliases to an object's list of aliases.  You can separate 
multiple aliases with commas.  The aliases will be checked against the object's current 
aliases and all aliases not already in the object's list of aliases will be added.";
"";
"Example:";
"Muchkin wants to add new aliases to Rover the Wonder Dog:";
"  @addalias Dog,Wonder Dog to Rover";
"Since Rover the Wonder Dog already has the alias \"Dog\" but does not have the alias 
\"Wonder Dog\", Munchkin sees:";
"  Rover the Wonder Dog(#4237) already has the alias Dog.";
"  Alias Wonder Dog added to Rover the Wonder Dog(#4237).";
"";
"If the object is a player, spaces will also be assumed to be separations between 
aliases and each alias will be checked against the Player Name Database to make sure 
no one else is using it. Any already used aliases will be identified.";
"";
"Example:";
"Munchkin wants to add his nicknames to his own list of aliases:";
"  @addalias Foobar Davey to me";
"@Addalias recognizes that Munchkin is trying to add an alias to a valid player and 
checks the aliases against the Player Name Database.  Unfortunately, DaveTheMan is 
already using the alias \"Davey\" so Munchkin sees:";
"  DaveTheMan(#5432) is already using the alias Davey";
"  Alias Foobar added to Munchkin(#1523).";
"";
"The second form of the @addalias command is for use by programmers, to add aliases 
to a verb they own.  All commas and spaces are assumed to be separations between 
aliases.";
if (player != this)
    return;
endif
set_task_perms(player);
spec = $code_utils:parse_verbref(iobjstr);
if (spec)
    object = player:my_match_object(spec[1]);
    if (!$command_utils:object_match_failed(object, spec[1]))
        vname = spec[2];
        info = verb_info(object, vname);
        if (info == E_VERBNF)
            player:notify("That object does not define that verb.");
        elseif (typeof(info) == ERR)
            player:notify(tostr(info));
        else
            old_aliases = $string_utils:explode(info[3]);
            used = {};
            for alias in (new_aliases = $list_utils:remove_duplicates($string_utils:explode(strsub(dobjstr, 
",", " "))))
                if (alias in old_aliases)
                    used = {@used, alias};
                    new_aliases = setremove(new_aliases, alias);
                endif
            endfor
            if (used)
                player:notify(tostr(object.name, "(", object, "):", vname, " already 
has the alias", (length(used) > 1) ? "es" | "", " ", $string_utils:english_list(used), 
"."));
            endif
            if (new_aliases)
                info = listset(info, aliases = $string_utils:from_list({@old_aliases, 
@new_aliases}, " "), 3);
                result = set_verb_info(object, vname, info);
                if (typeof(result) == ERR)
                    player:notify(tostr(result));
                else
                    player:notify(tostr("Alias", (length(new_aliases) > 1) ? "es" 
| "", " ", $string_utils:english_list(new_aliases), " added to verb ", object.name, 
"(", object, "):", vname));
                    player:notify(tostr("Verbname is now ", object.name, "(", object, 
"):\"", aliases, "\""));
                endif
            endif
        endif
    endif
else
    object = player:my_match_object(iobjstr);
    if (!$command_utils:object_match_failed(object, iobjstr))
        old_aliases = object.aliases;
        used = {};
        for alias in (new_aliases = $list_utils:remove_duplicates($list_utils:map_arg($string_utils, 
"trim", $string_utils:explode(is_player(object) ? strsub(dobjstr, " ", ",") | dobjstr, 
","))))
            if (alias in old_aliases)
                used = {@used, alias};
                new_aliases = setremove(new_aliases, alias);
            elseif (is_player(object) && valid(someone = $player_db:find_exact(alias)))
                player:notify(tostr(someone.name, "(", someone, ") is already using 
the alias ", alias, "."));
                new_aliases = setremove(new_aliases, alias);
            endif
        endfor
        if (used)
            player:notify(tostr(object.name, "(", object, ") already has the alias", 
(length(used) > 1) ? "es" | "", " ", $string_utils:english_list(used), "."));
        endif
        if (new_aliases)
            if ((e = object:set_aliases(aliases = {@old_aliases, @new_aliases})) 
&& (object.aliases == {@old_aliases, @new_aliases}))
                player:notify(tostr("Alias", (length(new_aliases) > 1) ? "es" | "", 
" ", $string_utils:english_list(new_aliases), " added to ", object.name, "(", object, 
")."));
                player:notify(tostr("Aliases for ", object.name, "(", object, ") 
are now ", $string_utils:from_value(aliases, 1)));
            elseif (e || (e == E_INVARG))
                player:notify("That particular name change not allowed (see help 
@rename or help @addalias).");
            elseif (e == E_NACC)
                player:notify("Oops.  You can't update that object's aliases right 
now; try again in a few minutes.");
            else
                player:notify(tostr(e));
            endif
        endif
    endif
endif
.



@rmalias:
"Syntax: @rmalias [,...,] from ";
"        @rmalias [,...,] from :";
"";
"The first form is used to remove aliases from an object.  If the object is a valid 
player, space and commas will be assumed to be separations between unwanted aliases. 
 Otherwise, only commas will be assumed to be separations.";
"[5/10/93 Nosredna: flushed above is_player feature";
"Note that @rmalias will not affect the object's name, only its aliases.";
"";
"The second form is for use by programmers, to remove aliases from a verb they own. 
 All spaces and commas are assumed to be separations between unwanted aliases.";
if (player != this)
    return;
endif
set_task_perms(player);
spec = $code_utils:parse_verbref(iobjstr);
if (spec)
    object = player:my_match_object(spec[1]);
    if (!$command_utils:object_match_failed(object, spec[1]))
        vname = spec[2];
        info = verb_info(object, vname);
        if (info == E_VERBNF)
            player:notify("That object does not define that verb.");
        elseif (typeof(info) == ERR)
            player:notify(tostr(info));
        else
            old_aliases = $string_utils:explode(info[3]);
            not_used = {};
            for alias in (bad_aliases = $list_utils:remove_duplicates($string_utils:explode(strsub(dobjstr, 
",", " "))))
                if (!(alias in old_aliases))
                    not_used = {@not_used, alias};
                    bad_aliases = setremove(bad_aliases, alias);
                else
                    old_aliases = setremove(old_aliases, alias);
                endif
            endfor
            if (not_used)
                player:notify(tostr(object.name, "(", object, "):", vname, " does 
not have the alias", (length(not_used) > 1) ? "es" | "", " ", $string_utils:english_list(not_used), 
"."));
            endif
            if (bad_aliases && old_aliases)
                info = listset(info, aliases = $string_utils:from_list(old_aliases, 
" "), 3);
                result = set_verb_info(object, vname, info);
                if (typeof(result) == ERR)
                    player:notify(tostr(result));
                else
                    player:notify(tostr("Alias", (length(bad_aliases) > 1) ? "es" 
| "", " ", $string_utils:english_list(bad_aliases), " removed from verb ", object.name, 
"(", object, "):", vname));
                    player:notify(tostr("Verbname is now ", object.name, "(", object, 
"):\"", aliases, "\""));
                endif
            elseif (!old_aliases)
                player:notify("You have to leave a verb with at least one alias.");
            endif
        endif
    endif
else
    object = player:my_match_object(iobjstr);
    if (!$command_utils:object_match_failed(object, iobjstr))
        old_aliases = object.aliases;
        not_used = {};
        for alias in (bad_aliases = $list_utils:remove_duplicates($list_utils:map_arg($string_utils, 
"trim", $string_utils:explode(dobjstr, ","))))
            "removed is_player(object) ? strsub(dobjstr, \" \", \",\") | --Nosredna";
            if (!(alias in old_aliases))
                not_used = {@not_used, alias};
                bad_aliases = setremove(bad_aliases, alias);
            else
                old_aliases = setremove(old_aliases, alias);
            endif
        endfor
        if (not_used)
            player:notify(tostr(object.name, "(", object, ") does not have the alias", 
(length(not_used) > 1) ? "es" | "", " ", $string_utils:english_list(not_used), "."));
        endif
        if (bad_aliases)
            if (e = object:set_aliases(old_aliases))
                player:notify(tostr("Alias", (length(bad_aliases) > 1) ? "es" | "", 
" ", $string_utils:english_list(bad_aliases), " removed from ", object.name, "(", 
object, ")."));
                player:notify(tostr("Aliases for ", object.name, "(", object, ") 
are now ", $string_utils:from_value(old_aliases, 1)));
            elseif (e == E_INVARG)
                player:notify("That particular name change not allowed (see help 
@rename or help @rmalias).");
            elseif (e == E_NACC)
                player:notify("Oops.  You can't update that object's aliases right 
now; try again in a few minutes.");
            else
                player:notify(tostr(e));
            endif
        endif
    endif
endif
.



@desc*ribe:
set_task_perms(player);
dobj = player:my_match_object(dobjstr);
if ($command_utils:object_match_failed(dobj, dobjstr))
    "...lose...";
elseif (e = dobj:set_description(iobjstr))
    player:notify("Description set.");
else
    player:notify(tostr(e));
endif
.


@mess*ages:
set_task_perms(player);
if (dobjstr == "")
    player:notify(tostr("Usage:  ", verb, " "));
    return;
endif
dobj = player:my_match_object(dobjstr);
if ($command_utils:object_match_failed(dobj, dobjstr))
    return;
endif
found_one = 0;
props = $object_utils:all_properties(dobj);
if (typeof(props) == ERR)
    player:notify("You can't read the messages on that.");
    return;
endif
for pname in (props)
    len = length(pname);
    if ((len > 4) && (pname[len - 3..len] == "_msg"))
        found_one = 1;
        msg = dobj.(pname);
        if (msg == E_PERM)
            value = "isn't readable by you.";
        elseif (!msg)
            value = "isn't set.";
        elseif (typeof(msg) != STR)
            value = "is corrupted! **";
        else
            value = "is " + $string_utils:print(msg);
        endif
        player:notify(tostr("@", pname[1..len - 4], " ", dobjstr, " ", value));
    endif
endfor
if (!found_one)
    player:notify("That object doesn't have any messages to set.");
endif
.



@notedit:
$note_editor:invoke(dobjstr, verb);
.


@password:
if (typeof(player.password) != STR)
    if (length(args) != 1)
        return player:notify(tostr("Usage:  ", verb, " "));
    else
        new_password = args[1];
    endif
elseif (length(args) != 2)
    player:notify(tostr("Usage:  ", verb, "  "));
    return;
elseif (player.password != crypt(tostr(args[1]), player.password))
    player:notify("That's not your old password.");
    return;
else
    new_password = args[2];
endif
player.password = crypt(tostr(new_password));
player:notify("New password set.");
.


@last-c*onnection:
"@last-c           reports when and from where you last connected.";
"@last-c all       adds the 10 most recent places you connected from.";
"@last-c confunc   is like `@last-c' but is silent on first login.";
opts = {"all", "confunc"};
i = 0;
if (caller != this)
    return E_PERM;
elseif (args && ((length(args) > 1) || (!(i = $string_utils:find_prefix(args[1], 
opts)))))
    this:notify(tostr("Usage:  ", verb, " [all]"));
    return;
endif
opt_all = i && (opts[i] == "all");
opt_confunc = i && (opts[i] == "confunc");
if (!(prev = this.previous_connection))
    this:notify("Something was broken when you logged in; tell a wizard.");
elseif (prev[1] == 0)
    opt_confunc || this:notify("Your previous connection was before we started keeping 
track.");
elseif (prev[1] > time())
    this:notify("This is your first time connected.");
else
    this:notify(tostr("Last connected ", ctime(prev[1]), " from ", prev[2]));
    if (opt_all)
        this:notify("Previous connections have been from the following sites:");
        for l in (this.all_connect_places)
            this:notify("   " + l);
        endfor
    endif
endif
.


set_gender:
"set_gender(newgender) attempts to change this.gender to newgender";
"  => E_PERM   if you don't own this or aren't its parent";
"  => Other return values as from $gender_utils:set.";
if (!($perm_utils:controls(caller_perms(), this) || (this == caller)))
    return E_PERM;
else
    result = $gender_utils:set(this, args[1]);
    this.gender = (typeof(result) == STR) ? result | args[1];
    return result;
endif
.


@gender:
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (!args)
    player:notify(tostr("Your gender is currently ", this.gender, "."));
    player:notify($string_utils:pronoun_sub("Your pronouns:  %s,%o,%p,%q,%r,%S,%O,%P,%Q,%R"));
    player:notify(tostr("Available genders:  ", $string_utils:english_list($gender_utils.genders, 
"", " or ")));
else
    result = this:set_gender(args[1]);
    quote = (result == E_NONE) ? "\"" | "";
    player:notify(tostr("Gender set to ", quote, this.gender, quote, "."));
    if (typeof(result) != ERR)
        player:notify($string_utils:pronoun_sub("Your pronouns:  %s,%o,%p,%q,%r,%S,%O,%P,%Q,%R"));
    elseif (result != E_NONE)
        player:notify(tostr("Couldn't set pronouns:  ", result));
    else
        player:notify("Pronouns unchanged.");
    endif
endif
.


set_brief:
"set_brief(value)";
"set_brief(value, anything)";
"If  is given, add value to the current value; otherwise, just set the 
value.";
if (!($perm_utils:controls(caller_perms(), this) || (this == caller)))
    return E_PERM;
else
    if (length(args) == 1)
        this.brief = args[1];
    else
        this.brief = this.brief + args[1];
    endif
endif
.


@mode:
"@mode ";
"Current modes are brief and verbose.";
"General verb for setting player `modes'.";
"Modes are coded right here in the verb.";
if (caller != this)
    player:tell("You can't set someone else's modes.");
    return E_PERM;
endif
modes = {"brief", "verbose"};
mode = modes[$string_utils:find_prefix(dobjstr, modes)];
if (!mode)
    player:tell("Unknown mode \"", dobjstr, "\".  Known modes:");
    for mode in (modes)
        player:tell("  ", mode);
    endfor
    return 0;
elseif (mode == "brief")
    this:set_brief(1);
elseif (mode == "verbose")
    this:set_brief(0);
endif
player:tell($string_utils:capitalize(mode), " mode set.");
return 1;
.


@exam*ine:
"This verb should probably go away once 'examine' is in place.";
if (dobjstr == "")
    player:notify(tostr("Usage:  ", verb, " "));
    return;
endif
what = $string_utils:match_object(dobjstr, player.location);
if ($command_utils:object_match_failed(what, dobjstr))
    return;
endif
player:notify(tostr(what.name, " (", what, ") is owned by ", valid(what.owner) ? 
what.owner.name | "a recycled player", " (", what.owner, ")."));
player:notify(tostr("Aliases:  ", $string_utils:english_list(what.aliases)));
desc = what:description();
if (desc)
    player:notify_lines(desc);
else
    player:notify("(No description set.)");
endif
if ($perm_utils:controls(player, what))
    player:notify(tostr("Key:  ", $lock_utils:unparse_key(what.key)));
endif
contents = what.contents;
if (contents)
    player:notify("Contents:");
    for item in (contents)
        player:notify(tostr("  ", item.name, " (", item, ")"));
    endfor
endif
"Use dobjstr, not shortest alias.";
name = dobjstr;
"name = what.name;";
"if (typeof(what.aliases) == LIST && what.aliases != {})";
"for alias in (what.aliases)";
"if (length(alias) <= length(name))";
"name = alias;";
"endif";
"endfor";
"endif";
vrbs = {};
commands_ok = what in {player, player.location};
dull_classes = {$root_class, $room, $player, $prog};
what = what;
printed_working_msg = 0;
while (what != $nothing)
    if ($command_utils:running_out_of_time())
        if (!printed_working_msg)
            player:notify("Working on list of obvious verbs...");
            printed_working_msg = 1;
        endif
        suspend(0);
    endif
    if (!(what in dull_classes))
        for i in [0..length(verbs(what)) - 1]
            if ($command_utils:running_out_of_time())
                if (!printed_working_msg)
                    player:notify("Working on list of obvious verbs...");
                    printed_working_msg = 1;
                endif
                suspend(0);
            endif
            info = verb_info(what, tostr(i));
            syntax = verb_args(what, tostr(i));
            if ((index(info[2], "r") && ((syntax[2..3] != {"none", "this"}) && (commands_ok 
|| ("this" in syntax)))) && verb_code(what, tostr(i)))
                dobj = syntax[1];
                prep = syntax[2];
                iobj = syntax[3];
                if (syntax == {"any", "any", "any"})
                    prep = "none";
                endif
                if (prep != "none")
                    for x in ($string_utils:explode(prep, "/"))
                        if (length(x) <= length(prep))
                            prep = x;
                        endif
                    endfor
                endif
                "This is the correct way to handle verbs ending in *";
                vname = info[3];
                while (i = index(vname, "* "))
                    vname = tostr(vname[1..i - 1], "", vname[i + 1..length(vname)]);
                endwhile
                if (vname[i = length(vname)] == "*")
                    vname = vname[1..i - 1] + "";
                endif
                vname = strsub(vname, " ", "/");
                rest = "";
                if (prep != "none")
                    rest = " " + ((prep == "any") ? "" | prep);
                    if (iobj != "none")
                        rest = tostr(rest, " ", (iobj == "this") ? name | "");
                    endif
                endif
                if (dobj != "none")
                    rest = tostr(" ", (dobj == "this") ? name | "", rest);
                endif
                vrbs = setadd(vrbs, ("  " + vname) + rest);
            endif
        endfor
    endif
    what = parent(what);
endwhile
if (vrbs)
    player:notify("Obvious Verbs:");
    player:notify_lines(vrbs);
    printed_working_msg && player:notify("(End of list.)");
elseif (printed_working_msg)
    player:notify("No obvious verbs found.");
endif
.



exam*ine:
set_task_perms(player);
if (!dobjstr)
    player:notify(tostr("Usage:  ", verb, " "));
    return E_INVARG;
endif
what = player.location:match_object(dobjstr);
if ($command_utils:object_match_failed(what, dobjstr))
    return;
endif
what:do_examine(player);
.



add_feature:
"Add a feature to this player's features list.  Caller must be this or have suitable 
permissions (this or wizardly).";
"If this is a nonprogrammer, then ask feature if it is feature_ok (that is, if it 
has a verb :feature_ok which returns a true value, or a property .feature_ok which 
is true).";
"After adding feature, call feature:feature_add(this).";
"Returns true if successful, E_INVARG if not a valid object, and E_PERM if !feature_ok 
or if caller doesn't have permission.";
if ((caller == this) || $perm_utils:controls(caller_perms(), this))
    feature = args[1];
    if ((typeof(feature) != OBJ) || (!valid(feature)))
        return E_INVARG;
        "Not a valid object.";
    endif
    if ($code_utils:verb_or_property(feature, "feature_ok", this))
        "The object is willing to be a feature.";
        if (typeof(this.features) == LIST)
            "If list, we can simply setadd the feature.";
            this.features = setadd(this.features, feature);
        else
            "If not, we erase the old value and create a new list.";
            this.features = {feature};
        endif
        "Tell the feature it's just been added.";
        feature:feature_add(this);
        return 1;
        "We're done.";
    else
        return E_PERM;
        "Feature isn't feature_ok.";
    endif
else
    return E_PERM;
    "Caller doesn't have permission.";
endif
.


remove_feature:
"Remove a feature from this player's features list.  Caller must be this, or have 
permissions of this, a wizard, or feature.owner.";
"Returns true if successful, E_PERM if caller didn't have permission.";
feature = args[1];
if (((caller == this) || $perm_utils:controls(caller_perms(), this)) || (caller_perms() 
== feature.owner))
    if (typeof(this.features) == LIST)
        "If this is a list, we can just setremove...";
        this.features = setremove(this.features, feature);
        "Otherwise, we leave it alone.";
    endif
    "Let the feature know it's been removed.";
    feature:feature_remove(this);
    return 1;
    "We're done.";
else
    return E_PERM;
    "Caller didn't have permission.";
endif
.


@add-feature @addfeature:
"Usage:  @add-feature ";
"Add an object to your features list.";
set_task_perms(player);
if (dobjstr)
    if (!$command_utils:object_match_failed(dobj, dobjstr))
        if (dobj in player.features)
            player:tell(dobjstr, " is already one of your features.");
        elseif (player:add_feature(dobj))
            player:tell(dobj, " (", dobj.name, ") added as a feature.");
        else
            player:tell("You can't seem to add ", dobj, " (", dobj.name, ") to your 
features list.");
        endif
    endif
else
    player:tell("Usage:  @add-feature ");
endif
.



@remove-feature @rmfeature:
"Usage:  @remove-feature ";
"Remove an object from your .features list.";
set_task_perms(player);
if (dobjstr)
    features = player.features;
    if (!valid(dobj))
        dobj = $string_utils:match(dobjstr, features, "name", features, "aliases");
    endif
    if (!$command_utils:object_match_failed(dobj, dobjstr))
        if (dobj in features)
            player:remove_feature(dobj);
            player:tell(dobj, " (", dobj.name, ") removed from your features list.");
        else
            player:tell(dobjstr, " is not one of your features.");
        endif
    endif
else
    player:tell("Usage:  @remove-feature ");
endif
.



@features:
"Usage:  @features [] for ";
"List the feature objects matching  used by .";
if (!iobjstr)
    player:tell("Usage: @features [] for ");
    return;
elseif ($command_utils:player_match_failed(whose = $string_utils:match_player(iobjstr), 
iobjstr))
    return;
endif
features = {};
for feature in (whose.features)
    if (!valid(feature))
        this:remove_feature(feature);
    elseif ((!dobjstr) || ((dobjstr in feature.aliases) || ((pref = $string_utils:find_prefix(dobjstr, 
feature.aliases)) || (pref == $ambiguous_match))))
        features = listappend(features, feature);
    endif
endfor
if (features)
    len = max(length("Feature"), length(tostr(max_object()))) + 1;
    player:tell($string_utils:left("Feature", len), "Name");
    player:tell($string_utils:left("-------", len), "----");
    for feature in (features)
        player:tell($string_utils:left(tostr(feature), len), feature.name);
    endfor
    player:tell($string_utils:left("-------", len), "----");
    cstr = ((tostr(length(features)) + " feature") + ((length(features) > 1) ? "s" 
| "")) + " found";
    if (whose != this)
        cstr = ((((cstr + " on ") + whose.name) + " (") + tostr(whose)) + ")";
    endif
    if (dobjstr)
        cstr = ((cstr + " matching \"") + dobjstr) + "\"";
    endif
    cstr = cstr + ".";
    player:tell(cstr);
elseif (dobjstr)
    player:tell("No features found on ", whose.name, " (", whose, ") matching \"", 
dobjstr, "\".");
else
    player:tell("No features found on ", whose.name, " (", whose, ").");
endif
.


@features:
"Usage:  @features []";
"List the feature objects matching  used by player.";
iobjstr = player.name;
iobj = player;
this:("@features")();
.


@memory:
stats = memory_usage();
if (!stats)
    player:notify("Sorry, but no memory-usage statistics are available for this server.");
    return;
endif
su = $string_utils;
player:notify("Block Size   # In Use    # Free    Bytes In Use   Bytes Free");
player:notify("----------   --------   --------   ------------   ----------");
nused = nfree = bytesused = bytesfree = 0;
kilo = 1024;
meg = kilo * kilo;
for x in (stats)
    if (x[2..3] != {0, 0})
        bsize = x[1];
        if ((bsize % meg) == 0)
            bsize = tostr(bsize / meg, " M");
        elseif ((bsize % kilo) == 0)
            bsize = tostr(bsize / kilo, " K");
        endif
        bused = x[1] * x[2];
        bfree = x[1] * x[3];
        player:notify(tostr(su:left(bsize, 10), "   ", su:right(su:group_number(x[2]), 
8), "   ", su:right(su:group_number(x[3]), 8), "   ", su:right(su:group_number(bused), 
12), "   ", su:right(su:group_number(bfree), 10)));
        nused = nused + x[2];
        nfree = nfree + x[3];
        bytesused = bytesused + bused;
        bytesfree = bytesfree + bfree;
    endif
endfor
player:notify("");
player:notify(tostr(su:left("Totals:", 10), "   ", su:right(su:group_number(nused), 
8), "   ", su:right(su:group_number(nfree), 8), "   ", su:right(su:group_number(bytesused), 
12), "   ", su:right(su:group_number(bytesfree), 10)));
player:notify("");
player:notify(tostr("Total Memory Size: ", su:group_number(bytesused + bytesfree), 
" bytes."));
.


@version:
player:notify(tostr("The MOO is currently running version ", server_version(), " 
of the LambdaMOO server code."));
.


@uptime:
player:notify(tostr("The server has been up for ", $time_utils:english_time(time() 
- $last_restart_time), "."));
.


@quit:
boot_player(player);
"-- argh, let the player decide; #3:disfunc() takes care of this --Rog";
"player:moveto(player.home)";
.


examine_commands_ok:
return this == args[1];
.


is_listening:
"return true if player is active. This verb is !d";
return typeof(idle_seconds(this)) != ERR;
.


moveto:
if (args[1] == #-1)
    return E_INVARG;
    this:notify("You are now in #-1, The Void.  Type `home' to get back.");
endif
set_task_perms(caller_perms());
pass(@args);
.


announce*_all_but:
return this.location:(verb)(@args);
"temporarily let player:announce be noisy to player";
if (verb == "announce_all_but")
    if (this in args[1])
        return;
    endif
    args = args[2..length(args)];
endif
this:tell("(from within you) ", @args);
.


linewrap:
"Return a true value if this needs linewrapping.";
"default is true if .linelen > 0";
return this.linelen > 0;
.


@set-note-string @set-note-text:
"Usage:  @set-note-{string | text} {#xx | #xx.pname}";
"        ...lines of text...";
"        .";
"";
"For use by clients' local editors, to save new text for a note or object property. 
 See $note_editor:local_editing_info() for details.";
set_task_perms(player);
text = $command_utils:read_lines();
if ((verb == "@set-note-string") && (length(text) <= 1))
    text = text ? text[1] | "";
endif
if (spec = $code_utils:parse_propref(argstr))
    o = toobj(spec[1]);
    p = spec[2];
    if ($object_utils:has_verb(o, vb = "set_" + p) && (typeof(e = o:(vb)(text)) != 
ERR))
        player:tell("Set ", p, " property of ", o.name, " (", o, ") via :", vb, ".");
    elseif (text != (e = o.(p) = text))
        player:tell("Error:  ", e);
    else
        player:tell("Set ", p, " property of ", o.name, " (", o, ").");
    endif
elseif (typeof(note = $code_utils:toobj(argstr)) == OBJ)
    e = note:set_text(text);
    if (typeof(e) == ERR)
        player:tell("Error:  ", e);
    else
        player:tell("Set text of ", note.name, " (", note, ").");
    endif
else
    player:tell("Error:  Malformed argument to ", verb, ": ", argstr);
endif
.


verb_sub:
"Copied from Generic Player Class With Additional Features of Dubious Utility (#7069):verb_sub 
by ur-Rog (#6349) Tue Nov 10 15:03:38 1992 PST";
text = args[1];
if (a = $list_utils:assoc(text, this.verb_subs))
    return a[2];
else
    return $gender_utils:get_conj(text, this);
endif
.


ownership_quota:
if ($perm_utils:controls(caller_perms(), this))
    return this.(verb);
else
    return E_PERM;
endif
.


tell_lines:
lines = args[1];
if (typeof(lines) != LIST)
    lines = {lines};
endif
if (this.gaglist || this.paranoid)
    "Check the above first, default case, to save ticks.  Paranoid gaggers are cost 
an extra three or so ticks by this, probably a net savings.";
    if (this:gag_p())
        return;
    endif
    if (this.paranoid == 2)
        z = this:whodunnit({@callers(), {player, "", player}}, {this, $no_one}, {})[3];
        lines = {((("[start text by " + z.name) + " (") + tostr(z)) + ")]", @lines, 
((("[end text by " + z.name) + " (") + tostr(z)) + ")]"};
    elseif (this.paranoid == 1)
        $paranoid_db:add_data(this, {{@callers(), {player, "", player}}, 
lines});
    endif
endif
"don't gather stats for now: $list_utils:check_nonstring_tell_lines(lines)";
this:notify_lines(lines);
.


@lastlog:
"Copied from generic room (#3):@lastlog by Haakon (#2) Wed Dec 30 13:30:02 1992 PST";
if (dobjstr != "")
    dobj = $string_utils:match_player(dobjstr);
    if (!valid(dobj))
        player:tell("Who?");
        return;
    endif
    folks = {dobj};
else
    folks = players();
endif
day = week = month = ever = never = {};
a_day = (24 * 60) * 60;
a_week = 7 * a_day;
a_month = 30 * a_day;
now = time();
for x in (folks)
    when = x.last_connect_time;
    how_long = now - when;
    if ((when == 0) || (when > now))
        never = {@never, x};
    elseif (how_long < a_day)
        day = {@day, x};
    elseif (how_long < a_week)
        week = {@week, x};
    elseif (how_long < a_month)
        month = {@month, x};
    else
        ever = {@ever, x};
    endif
endfor
for entry in ({{day, "the last day"}, {week, "the last week"}, {month, "the last 
30 days"}, {ever, "recorded history"}})
    if (entry[1])
        player:tell("Players who have connected within ", entry[2], ":");
        for x in (entry[1])
            player:tell("  ", x.name, " last connected ", ctime(x.last_connect_time), 
".");
        endfor
    endif
endfor
if (never)
    player:tell("Players who have never connected:");
    player:tell("  ", $string_utils:english_list($list_utils:map_prop(never, "name")));
endif
.


set_linelength:
"Set linelength.  Linelength must be an integer >= 10.";
"If wrap is currently off (i.e. linelength is less than 0), maintains sign.  That 
is, this function *takes* an absolute value, and coerces the sign to be appropriate.";
"If you want to override the dwimming of wrap, pass in a second argument.";
"returns E_PERM if not allowed, E_INVARG if linelength is too low, otherwise the 
linelength.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    return E_PERM;
elseif (abs(len = args[1]) < 10)
    return E_INVARG;
elseif (length(args) > 1)
    this.linelen = len;
else
    "DWIM here.";
    this.linelen = (this.linelen > 0) ? len | (-len);
    return len;
endif
.


set_pagelength:
"Set pagelength. Must be an integer >= 5, or 0 to turn pagelength off.";
"Returns E_PERM if you shouldn't be doing this, E_INVARG if it's too low, otherwise, 
what it got set to.";
if ((caller != this) && (!$perm_utils:controls(caller_perms(), this)))
    return E_PERM;
elseif (((len = args[1]) < 5) && (len != 0))
    return E_INVARG;
else
    return this.pagelen = len;
endif
.


set_home:
"set_home(newhome) attempts to change this.home to newhome";
"E_TYPE   if newhome doesn't have a callable :accept_for_abode verb.";
"E_INVARG if newhome won't accept you as a resident.";
"E_PERM   if you don't own this and aren't its parent.";
"1        if it works.";
newhome = args[1];
if ((caller == this) || $perm_utils:controls(caller_perms(), this))
    if ($object_utils:has_callable_verb(newhome, "accept_for_abode"))
        if (newhome:accept_for_abode(this))
            return (typeof(e = this.home = args[1]) != ERR) || e;
        else
            return E_INVARG;
        endif
    else
        return E_TYPE;
    endif
else
    return E_PERM;
endif
.


@unsub*scribe:
"@unsubscribe [* ...]";
"entirely removes the record of your current message for the named folders,";
"indicating your disinterest in anything that might appear there in the future.";
set_task_perms(player);
unsubscribed = {};
current_folder = this:current_folder();
for a in (args || {0})
    if (a ? $mail_agent:match_failed(folder = $mail_agent:match_recipient(a), a) 
| (folder = current_folder))
        "...bogus folder name, done...  No, try anyway.";
        if (this:kill_current_message(this:my_match_object(a)))
            player:notify("Invalid folder, but found it subscribed anyway.  Removed.");
        endif
    elseif (folder == this)
        player:notify(tostr("You can't ", verb, " yourself."));
    else
        if (!this:kill_current_message(folder))
            player:notify(tostr("You weren't subscribed to ", $mail_agent:name(folder)));
            if ($object_utils:isa(folder, $mail_recipient))
                result = folder:delete_notify(this);
                if ((typeof(result) == LIST) && (result[1] == this))
                    player:notify("Removed you from the mail notifications list.");
                endif
            endif
        else
            unsubscribed = {@unsubscribed, folder};
            if ($object_utils:isa(folder, $mail_recipient))
                folder:delete_notify(this);
            endif
        endif
    endif
endfor
if (unsubscribed)
    player:notify(tostr("Forgetting about ", $string_utils:english_list($list_utils:map_arg($mail_agent, 
"name", unsubscribed))));
    if (current_folder in unsubscribed)
        this:set_current_folder(this);
    endif
endif
.


@registerme:
"@registerme as  -- enter a new email address for player";
"   will change the database entry, assign a new password, and mail the new password 
to the player at the given email address.";
if (player != this)
    return player:notify(tostr(E_PERM));
endif
who = this;
if ($object_utils:isa(this, $guest))
    who:notify("Sorry, guests should use the '@request' command to request a character.");
    return;
endif
connection = $string_utils:connection_hostname(connection_name(who));
if (!argstr)
    if (who.email_address)
        player:tell("You are currently registered as:  ", who.email_address);
    else
        player:tell("You are not currently registered.");
    endif
    player:tell("Use @registerme as 
to change this."); return; elseif (((prepstr != "as") || (!iobjstr)) || dobjstr) player:tell("Usage: @registerme as
"); return; endif email = iobjstr; if (email == this.email_address) who:notify("That is your current address. Not changed."); return; elseif (reason = $wiz_utils:check_reregistration(this, email, connection)) if (reason[1] == "-") if (!$command_utils:yes_or_no(reason[2..length(reason)] + ". Automatic registration not allowed. Ask to be registered at this address anyway?")) who:notify("Okay."); return; endif else return who:notify(tostr(reason, " Please try again.")); endif endif if ($network.active && (!reason)) if (!$command_utils:yes_or_no(tostr("If you continue, your password will be changed, the new password mailed to `", email, "'. Do you want to continue?"))) return who:notify("Registration terminated."); endif password = $wiz_utils:random_password(5); old = who.email_address || "[ unregistered ]"; who:notify(tostr("Registering you, and changing your password and mailing new one to ", email, ".")); result = $network:sendmail(email, tostr("Your ", $network.MOO_Name, " character, ", who.name), "Reply-to: " + $login.registration_address, @$generic_editor:fill_string(tostr("Your ", $network.MOO_name, " character, ", $string_utils:nn(who), " has been registered to this email address (", email, "), and a new password assigned. The new password is `", password, "'. Please keep your password secure. You can change your password with the @password command."), 75)); if (result != 0) who:notify(tostr("Mail sending did not work: ", reason, ". Reregistration terminated.")); return; endif who:notify(tostr("Mail with your new password forwarded. If you do not get it, send regular email to ", $login.registration_address, " with your character name.")); $mail_agent:send_message($new_player_log, $new_player_log, "reg " + $string_utils:nn(this), {email, tostr("formerly ", old)}); $registration_db:add(this, email, "Reregistered at " + ctime()); this.email_address = email; who.password = crypt(password); who.last_password_time = time(); else who:notify("No automatic reregistration: your request will be forwarded."); if (typeof(curreg = $registration_db:find(email)) == LIST) additional_info = {"Current registration information for this email address:", @$registration_db:describe_registration(curreg)}; else additional_info = {}; endif $mail_agent:send_message(this, $registration_db.registrar, "Registration request", {((("Reregistration request from " + $string_utils:nn(who)) + " connected via ") + connection) + ":", "", (("@register " + who.name) + " ") + email, ("@new-password " + who.name) + " is ", "", "Reason this request was forwarded:", reason, @additional_info}); endif .

ctime:
":ctime([NUM time]) => STR as the function.";
"May be hacked by players and player-classes to reflect differences in time-zone.";
return ctime(@args);
.


send_self_netmail:
":send_self_netmail(msg [ ,from ])";
"return 0 if successful, otherwise error.";
if (!$perm_utils:controls(caller_perms(), this))
    return E_PERM;
elseif (error = $network:invalid_email_address(this.email_address))
    return "Invalid email address: " + error;
else
    msg = args[1];
    if (length(args) > 1)
        from = args[2];
        this:notify(tostr("Receiving mail from ", from:title(), " (", from, ") and 
forwarding it to your .email_address."));
    endif
    oplayer = player;
    player = this;
    error = $network:sendmail(this.email_address, @msg);
    if (error && (length(args) > 1))
        this:notify(tostr("Mail sending failed: ", error));
    endif
    player = oplayer;
    return error;
endif
.


@netforw*ard:
"@netforward ...                  -- as in help on @netforward";
"@netforward ... on *  -- netforwards messages on recipient.";
"This command forwards mail-messages to your registered email-address.";
if (player != this)
    return player:tell(E_PERM);
endif
if (reason = $network:email_will_fail(email = player.email_address))
    return player:notify(tostr("Cannot forward mail to your email address: ", reason));
endif
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (p = player:parse_mailread_cmd("@read", args, "", "on"))
    player:set_current_folder(folder = p[1]);
    msg_seq = p[2];
    folderstr = (folder == player) ? "" | tostr(" from *", folder.name, "(", folder, 
")");
    if (!$command_utils:yes_or_no(tostr("You are about to forward ", seq_size = $seq_utils:size(msg_seq), 
" message(s)", folderstr, " to your registered email-address, ", email, ".  Continue?")))
        player:notify(tostr("@Netforward cancelled."));
        return;
    endif
    player._mail_task = task_id();
    netmail = {};
    linelen = player:linelen();
    maxmsg = minmsg = 0;
    for msg in (folder:messages_in_seq(msg_seq))
        minmsg = minmsg ? min(msg[1], minmsg) | msg[1];
        maxmsg = maxmsg ? max(msg[1], maxmsg) | msg[1];
        lines = {tostr("Message ", msg[1], folderstr, ":"), tostr("Date:     ", ctime(msg[2][1])), 
"From:     " + msg[2][2], "To:       " + msg[2][3], @(length(subj = msg[2][4]) > 
1) ? {"Subject:  " + subj} | {}};
        for line in (msg[2][5..length(msg[2])])
            lines = {@lines, @$generic_editor:fill_string(line, linelen)};
            $command_utils:suspend_if_needed(0);
        endfor
        netmail = {@netmail, @lines, "", "--------------------------", "", ""};
    endfor
    reason = player:send_self_netmail({header = tostr($network.MOO_name, " Message(s) 
", minmsg, @(minmsg != maxmsg) ? {" - ", maxmsg} | {}, folderstr), @netmail});
    player:notify((reason == 0) ? tostr("@netforward of ", header, " completed.") 
| tostr("@netforward failed: ", reason, "."));
endif
.


@@sendmail:
"Syntax: @@sendmail";
"This is intended for use with client editors.  You probably don't want to try using 
this command manually.";
"Reads a formatted mail message, extracts recipients, subject line and/or reply-to 
header and sends message without going to the mailroom.  Example:";
"";
"@@send";
"To: Rog (#4292)";
"Subject: random";
"";
"first line";
"second line";
".";
"";
"Currently, header lines must have the same format as in an actual message.";
set_task_perms(player);
if (args)
    player:notify(tostr("The ", verb, " command takes no arguments."));
    $command_utils:read_lines();
    return;
elseif (this != player)
    player:notify(tostr("You can't use ", this.pp, " ", verb, " verb."));
    $command_utils:read_lines();
    return;
endif
msg = $command_utils:read_lines();
end_head = ("" in msg) || (length(msg) + 1);
from = this;
subject = "";
replyto = "";
rcpts = {};
body = msg[end_head + 1..length(msg)];
for i in [1..end_head - 1]
line = msg[i];
if (index(line, "Subject:") == 1)
    subject = $string_utils:trim(line[9..length(line)]);
elseif (index(line, "To:") == 1)
    if (!(rcpts = $mail_agent:parse_address_field(line)))
        player:notify("No recipients found in To: line");
        return;
    endif
elseif (index(line, "Reply-to:") == 1)
    if ((!(replyto = $mail_agent:parse_address_field(line))) && $string_utils:trim(line[10..length(line)]))
        player:notify("No address found in Reply-to: line");
        return;
    endif
elseif (index(line, "From:") == 1)
    "... :send_message() bombs if designated sender != player ...";
    if (!(from = $mail_agent:parse_address_field(line)))
        player:notify("No sender found in From: line");
        return;
    elseif (length(from) > 1)
        player:notify("Multiple senders?");
        return;
    endif
    from = from[1];
elseif (i = index(line, ":"))
    player:notify(tostr("Unknown header \"", line[1..i], "\""));
    return;
else
    player:notify("Blank line must separate headers from body.");
    return;
endif
endfor
if (!rcpts)
player:notify("No To: line found.");
elseif (!(subject || body))
player:notify("Blank message not sent.");
else
player:notify("Sending...");
result = $mail_agent:send_message(from, rcpts, replyto ? subject | {subject, replyto}, 
body);
if (e = result && result[1])
    if (length(result) == 1)
        player:notify("Mail actually went to no one.");
    else
        player:notify(tostr("Mail actually went to ", $mail_agent:name_list(@listdelete(result, 
1)), "."));
    endif
else
    player:notify(tostr((typeof(e) == ERR) ? e | ("Bogus recipients:  " + $string_utils:from_list(result[2]))));
    player:notify("Mail not sent.");
endif
endif
.


@keep-m*ail @keepm*ail:
"@keep-mail [|none] [on ]";
"marks the indicated messages as `kept'.";
set_task_perms(valid(caller_perms()) ? caller_perms() | player);
if (args[1] == "none")
    args[1..1] = {};
    if (!(pfs = this:parse_folder_spec(verb, args, "on", 0)))
        return;
    elseif (pfs[2])
        player:notify(tostr(verb, "  or `none', but not both."));
        return;
    endif
    this:set_current_folder(folder = pfs[1]);
    if (e = folder:keep_message_seq({}))
        player:notify(tostr("Messages on ", $mail_agent:name(folder), " are no longer 
marked as kept."));
    else
        player:notify(tostr(e));
    endif
    return;
elseif (p = this:parse_mailread_cmd(verb, args, "", "on"))
    if ((folder = p[1]) != this)
        "... maybe I'll take this clause out some day...";
        player:notify(tostr(verb, " can only be used on your own mail collection."));
        return;
    endif
    this:set_current_folder(folder);
    if (e = folder:keep_message_seq(msg_seq = p[2]))
        player:notify(tostr("Message", match(e, "[.,]") ? "s " | " ", e, " now marked 
as kept."));
    elseif (typeof(e) == ERR)
        player:notify(tostr(e));
    else
        player:notify(tostr(((seq_size = $seq_utils:size(msg_seq)) == 1) ? "That 
message is" | "Those messages are", " already marked as kept."));
    endif
endif
.


my_match_recipient:
":my_match_recipient(string) => matches string against player's private mailing lists.";
if (!(string = args[1]))
    return $nothing;
elseif (string[1] == "*")
    string = string[2..length(string)];
endif
return $string_utils:match(string, this.mail_lists, "aliases");
.


expire_old_messages:
set_task_perms(caller_perms());
if (!$perm_utils:controls(caller_perms(), this))
    return E_PERM;
elseif (0 >= (period = this:mail_option("expire") || $mail_agent.player_expire_time))
    "...no expiration allowed here...";
    return E_NACC;
elseif (!(curmsg = this:get_current_message(this)))
    "No messages!  Don't even try.";
    return 0;
elseif (rmseq = $seq_utils:remove(this:unkept_msg_seq(), 1 + this:length_date_le(min(time() 
- period, curmsg[2] - 86400))))
    "... the 86400 is pure fudge...";
    this:rm_message_seq(rmseq);
    return this:expunge_rmm();
else
    return 0;
endif
.


@age:
if ((dobjstr == "") || (dobj == player))
    dobj = player;
else
    dobj = $string_utils:match_player(dobjstr);
    if (!valid(dobj))
        $command_utils:player_match_failed(dobj, dobjstr);
        return;
    endif
endif
time = dobj.first_connect_time;
if (time == $maxint)
    player:notify(tostr(dobj.name, " has never connected."));
elseif (time == 0)
    player:notify(tostr(dobj.name, " first connected before initial connections were 
being recorded."));
else
    player:notify(tostr(dobj.name, " first connected on ", ctime(time)));
    duration = time() - time;
    if (duration < 86400)
        notice = $string_utils:from_seconds(duration);
    else
        notice = $time_utils:english_time((duration / 86400) * 86400);
    endif
    player:notify(tostr($string_utils:pronoun_sub("%S % ", dobj), notice, " old."));
endif
.


@edit:
"Calls the verb editor on verbs, the note editor on properties, and on anything else 
assumes it's an object for which you want to edit the .description.";
if (!args)
    ((player in $note_editor.active) ? $note_editor | $verb_editor):invoke(dobjstr, 
verb);
elseif ($code_utils:parse_verbref(args[1]))
    if (player.programmer)
        $verb_editor:invoke(argstr, verb);
    else
        player:notify("You need to be a programmer to do this.");
        player:notify("If you want to become a programmer, talk to a wizard.");
        return;
    endif
else
    $note_editor:invoke(dobjstr, verb);
endif
.


erase_paranoid_data:
if (!($perm_utils:controls(caller_perms(), this) || (this == caller)))
    return E_PERM;
else
    $paranoid_db:erase_data(this);
endif
.



PROPERTY DATA:
      features
      previous_connection
      mail_lists
      email_address
      last_disconnect_time
      help
      more_msg
      linetask
      linesleft
      linebuffer
      pagelen
      _mail_task
      owned_objects
      linelen
      current_folder
      all_connect_places
      last_connect_place
      dict
      messages_going
      brief
      responsible
      lines
      page_absent_msg
      pq
      pqc
      page_origin_msg
      page_echo_msg
      mail_notify
      mail_forward
      edit_options
      mail_options
      current_message
      messages
      last_connect_time
      ownership_quota
      gender
      prc
      ppc
      poc
      psc
      pr
      pp
      po
      ps
      home
      password
      gaglist
      paranoid
      display_options
      verb_subs
      first_connect_time
      messages_kept
      message_keep_date
      size_quota

CHILDREN:
Everyman Basic Local PC Nymph Three Meep Chrysalis Nightfall Lachesis