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