Generic user
This object determines the behavior of user.
Informational public methods:
doing() Get doing string for who list
connected_at() Return connection time
last_command_at() Return time of last input
check_password(str) True if str crypts to password
match_environment(s) Return nearby object matching s
local_to_environment(obj) True if obj is nearby
Public methods to perform actions:
tell(s) Write string to user
invalidate_verb_cache() Remove verb cache
Owner methods:
set_name(s) Set name
set_doing(s) Set doing string
set_password(str) Set password to crypt(str)
parse_command(str) Execute input line str
Protected methods:
did_move(obj, place) Notification of completed move
Private methods:
validate_verb_cache() Build verb cache if necessary
System object methods:
login() Perform login actions
Server methods:
disconnect() Indicates disconnection
parse(s) Indicates input line
Commands:
who_cmd("@who") Display a who listing
doing_cmd("@doing", s) Set doing message
quit_cmd("@quit") Log out
help_on_cmd("help", topic, "on", obj)
Get help on a topic on an object
help_cmd("help", str) Get help
parent person
parent commands
object user
var user password 0
var user verb_cache 0
var user doing 0
var user connected_at 0
var user last_command_at 0
var user editor 0
var user editor_method 0
var user connections 0
method init
arg ancestors;
(> pass(ancestors) <);
if (definer() in ancestors) {
password = "*";
verb_cache = 0;
doing = "";
connected_at = 0;
last_command_at = 0;
editor = 0;
editor_method = 0;
connections = [];
$sys.register_new_user_name();
}
.
method set_name
arg s;
var old_name;
old_name = .name();
if (s == old_name)
return;
if (" " in s)
throw(~space, "Space in new name.");
if ((| $sys.find_user(s) |))
throw(~duplicate, "Duplicate user name.");
(> pass(s) <);
$sys.user_changed_name(old_name);
.
eval
var err;
.initialize();
.set_name("JoeAverage");
.add_command("@who", 'template, 'who_cmd);
.add_command("@doing *", 'template, 'doing_cmd);
.add_command("@quit", 'template, 'quit_cmd);
.add_command("i?nventory", 'template, 'inventory_cmd);
.add_command("p?age * with *", 'template, 'page_cmd);
.add_command("sample_edit *", 'template, 'sample_edit_cmd);
.
method doing
return doing;
.
method set_doing
arg s;
if (!.is_owned_by(sender()))
throw(~perm, "Sender not an owner.");
if (type(s) != 'string)
throw(~type, "Argument not a string.");
doing = s;
.
method connected_at
return connected_at;
.
method last_command_at
return last_command_at;
.
method verb_changed_inside
.invalidate_verb_cache();
.
method tell
arg what;
var line, conn;
if (type(what) == 'list) {
for line in (what)
.tell(line);
} else {
for conn in (connections)
conn.tell(what);
}
.
method set_password
arg str;
if (!.is_owned_by(sender()))
throw(~perm, "Sender not an owner.");
password = crypt(str);
.
method check_password
arg str;
return crypt(str, substr(password, 1, 2)) == password;
.
method validate_verb_cache
var loc, objects, all_objects, obj, anc, templates;
if (sender() != this() || caller() != definer())
throw(~perm, "Sender not this.");
if (verb_cache != 0)
return;
// Get a list of objects to look for verbs on.
loc = .location();
objects = [this()] + [loc] + .contents();
objects = objects + setremove(loc.contents(), this());
// Now expand that list to include ancestors.
all_objects = [];
for obj in (objects) {
all_objects = setadd(all_objects, obj);
all_objects = union(all_objects, obj.ancestors());
}
// Now, for each object in that list, add all its templates to the verb
// cache.
verb_cache = [];
for obj in (all_objects) {
templates = (| obj.verb_templates() |);
if (templates)
verb_cache = union(verb_cache, templates);
}
.
method invalidate_verb_cache
verb_cache = 0;
.
method did_move
arg [args];
var loc;
(> pass(@args) <);
.invalidate_verb_cache();
loc = .location();
(| loc.look_cmd("look") |);
.
method parse
arg s;
if (!caller().is_agent('connection))
throw(~perm, "Sender is not an agent of connection protocol.");
// Don't let regular users see stack traces.
catch any {
last_command_at = time();
while (s && s[1] == " ")
s = substr(s, 2);
if (!s)
return;
return .parse_command(s);
} with handler {
.tell("Internal error processing command: " + s);
}
.
method parse_command
arg str;
var cmd, loc, result, i, j, template, word, fields, obj, verb_info;
if (!.is_owned_by(sender()))
throw(~perm, "Sender not an owner.");
// Are we editing?
if (editor) {
if (substr(str, 1, 1) == "<") {
str = substr(str, 2);
if (!str)
return;
} else {
result = editor.handle_editor_command(str);
switch (result[1]) {
case 'not_done:
editor = result[2];
case 'abort:
editor = 0;
case 'done:
.(editor_method)(result[2]);
}
return;
}
}
// Check commands on this.
cmd = .match_command(str);
if (cmd)
return .(cmd[1])(@cmd[2]);
// Check commands on location.
loc = .location();
cmd = loc.match_command(str);
if (cmd) {
loc.(cmd[1])(@cmd[2]);
return;
}
// Resort to verb cache.
.validate_verb_cache();
catch ~objnf, ~verbnf {
for template in (union(verb_cache, $sys.remote_verb_templates())) {
fields = match_template(strsub(template, "%this", "*"), str);
if (type(fields) != 'list)
continue;
j = 1;
for word in (explode(template)) {
if (word == "%this") {
obj = sender().match_environment(fields[j]);
verb_info = obj.verb_info(template);
if (verb_info[2] != 'remote && !.local_to_environment(obj))
.tell("You cannot do that from here.");
else
obj.(verb_info[1])(@fields);
return;
} else if (word == "*=*") {
j = j + 2;
} else {
j = j + 1;
}
}
}
} with handler {
switch (error()) {
case ~objnf:
.tell("I don't see \"" + error_arg() + "\" here.");
case ~verbnf:
.tell("You can't do that to that object.");
}
return;
}
// Try exit names.
for obj in (loc.exits()) {
if (str == obj.name()) {
obj.go_through();
return;
}
}
// No luck.
.tell("I don't understand that.");
.
method connection_logged_in
arg addr, port;
if (!caller().is_agent('connection))
throw(~perm, "Caller is not an agent of connection protocol.");
connections = connections + [sender()];
if (listlen(connections) == 1)
.login(sender());
else
.login_again(sender());
.
method connection_gone
arg addr, port;
if (!caller().is_agent('connection))
throw(~perm, "Caller is not an agent of connection protocol.");
connections = setremove(connections, sender());
if (!connections)
.logout(sender());
connections = setremove(connections, sender());
.
method login
arg connection;
var loc;
if (sender() != this() || definer() != caller())
throw(~perm, "Invalid access to private method.");
$sys.user_logged_in();
.tell("* * * Connected * * *");
connected_at = time();
last_command_at = time();
loc = .location();
(| loc.did_connect() |);
(| loc.look_cmd("look") |);
.
method login_again
arg connection;
if (sender() != this() || definer() != caller())
throw(~perm, "Invalid access to private method.");
last_command_at = time();
connection.tell("* * * Already connected * * *");
.
method logout
arg connection;
if (sender() != this() || definer() != caller())
throw(~perm, "Invalid access to private method.");
$sys.user_logged_out();
.invalidate_verb_cache();
(| loc.did_disconnect() |);
.
method match_environment
arg s;
var loc, objects, obj;
loc = .location();
// Handle special cases.
if (s == "me")
return this();
if (s == "here")
return loc;
if (s && s[1] == "$") {
obj = todbref(substr(s, 2));
if (!valid(obj))
throw(~objnf, "No such object " + s, s);
return obj;
}
objects = [this()] + [loc] + .contents();
objects = objects + setremove(loc.contents(), this());
objects = objects + loc.exits();
// Look first for exact matches.
for obj in (objects) {
if (obj.name() == s)
return obj;
}
// Now look for partial matches.
for obj in (objects) {
if (match_begin(obj.name(), s))
return obj;
}
throw(~objnf, "No object " + s + " in environment.", s);
.
method local_to_environment
arg obj;
var loc;
if (obj == this() || obj in .contents())
return 1;
loc = .location();
if (obj == loc || obj in loc.contents() || obj in loc.exits())
return 1;
return 0;
.
method who_cmd
arg dummy;
var user, seconds, namestr, constr, idlestr, n, doing;
if (sender() != this())
throw(~perm, "Sender not this.");
.tell("User Name On For Idle " + $sys.doing_poll());
for user in ($sys.connected_users()) {
namestr = pad(user.name(), 14) + " ";
// Put together the string for connect time.
seconds = time() - user.connected_at();
if (seconds > 86400) {
constr = tostr(seconds / 86400);
constr = constr + "d";
} else {
constr = "";
}
constr = pad(constr, -3);
seconds = seconds % 86400;
constr = constr + " " + pad(tostr(seconds / 3600), -2, "0");
constr = constr + ":" + pad(tostr((seconds % 3600) / 60), -2, "0");
// Put together the string for idle time.
seconds = time() - user.last_command_at();
if (seconds > 86400)
idlestr = tostr(seconds / 86400) + "d";
else if (seconds > 3600)
idlestr = tostr(seconds / 3600) + "h";
else if (seconds > 60)
idlestr = tostr(seconds / 60) + "m";
else
idlestr = tostr(seconds) + "s";
idlestr = pad(idlestr, -3) + " ";
// Get doing and truncate if neccessary.
doing = user.doing();
if (strlen(doing) > 46)
doing = substr(doing, 1, 46);
// Display the resulting line.
.tell(namestr + constr + " " + idlestr + doing);
}
n = listlen($sys.connected_users());
if (n == 1)
.tell("One user logged in.");
else
.tell(tostr(n) + " users logged in.");
.
method doing_cmd
arg dummy, s;
if (sender() != this())
throw(~perm, "Sender not this.");
.set_doing(s);
.tell("Set.");
.
method quit_cmd
arg dummy;
if (sender() != this())
throw(~perm, "Sender not this.");
return 'disconnect;
.
method inventory_cmd
arg dummy;
var i;
if (sender() != this())
throw(~perm, "Sender not this.");
.tell("Carrying:");
for i in (.contents())
.tell(" " + i.name());
.
method page_cmd
arg dummy1, recipient, dummy2, message;
var user;
if (sender() != this())
throw(~perm, "Sender not this.");
catch ~usernf {
user = $sys.find_user(recipient);
if (!(user in $sys.connected_users())) {
.tell(user.name() + " is not connected.");
return;
}
user.tell(.name() + " pages: " + message);
.tell("You page \"" + message + "\" to " + user.name() + ".");
} with handler {
.tell(recipient + " is not the name of a user.");
}
.
method sample_edit_cmd
arg dummy, str;
editor = $editor_class.new([str]);
editor_method = 'sample_edit_done;
.
method sample_edit_done
arg text;
.tell("Sample edit finished:");
.tell(text);
.