object $root; var $root obj_desc = "Root of the object hierarchy"; var $root method_descs = #[['log, "log a string via dblog"], ['set_objname, "set a (new) $objname"], ['data, "return dictionary of dictionaries of objvars"], ['destroy, "destroy an object"], ['has_ancestor, "check if this() has a given obj in parent tree"], ['list_method, "list decompiled method"], ['find_method, "find a method in ancestor tree"], ['parents, "return list of parents"], ['children, "return list of children"], ['methods, "return list of methods"], ['get_var, "return contents of a variable"], ['add_var, "add an object variable"], ['add_method, "add/set method text"], ['del_method, "delete a method"], ['has_name, "returns 0, overridden by real objects"], ['method_descs, "return method_descs object variable"], ['variables, "return list of objvars defined on obj"], ['get_var_on, "returns a variable defined by this() present on an object"], ['set_var_on, "sets a variable defined by this() on an object"], ['del_var, "remove an objvar from an object"], ['obj_desc, "return description of an object"], ['set_text_method, "set a text buffer related to a method"], ['get_text_method, "get a text buffer related to a method"], ['set_parents, "set parents list of an object"], ['method_flags, "return flags for a method"], ['set_method_flags, "set flags for a method"], ['flags, "returns full list of flags in var"], ['hasflag, "checks flags var for a flag"], ['setflag, "sets a flag in flags var"], ['remflag, "removes a flag in flags var"], ['objnum, "return #dbref for object"], ['is, "checks if this() is a child of arg"], ['unsetflag, "unsets a flag in flags var"], ['set_method_descs, "sets method_descs var"]]; var $root method_args = #[['log, #[['type, "symbol, log level: boot, info, debug, notice, system, net, error"], ['str, "string to log"]]], ['set_objname, #[['name, "string, new object name"]]]]; var $root text_methods = #[]; var $root flags = ['safe]; var $root owner = 0; public method .destroy() { if (.hasflag('safe)) throw(~perm, "Object is marked safe, cannot be destroyed."); (| .uninitialize(.ancestors()) |); .log('db, ((tostr(sender()) + " destroyed ") + tostr(this())) + "."); return (> destroy() <); }; public method .log() { arg type, str; var logstr; // ['boot, 'info, 'debug, 'notice, 'system, 'net, 'error] are types used logstr = (((tostr(caller()) + ": ") + tostr(type)) + ": ") + ((type(str) == 'string) ? str : toliteral(str)); dblog(logstr); $debug_port.debug_log(logstr); return str; }; public method .has_ancestor() { arg obj; return has_ancestor(obj); }; public method .list_method() { arg meth; return (> list_method(meth) <); }; public method .find_method() { arg meth; return (> find_method(meth) <); }; public method .parents() { return (> parents() <); }; public method .children() { return (> children() <); }; public method .set_objname() { arg name; return (> set_objname(name) <); }; public method .data() { return (> data() <); }; public method .methods() { return (> methods() <); }; public method .get_var() { arg v; return (> .get_var_on(this(), v) <); }; public method .add_var() { arg name; return (> add_var(name) <); }; public method .add_method() { arg code, name; return (> add_method(code, name) <); }; public method .del_method() { arg name; var descs; descs = (| dict_del(.method_descs(), name) |); if (type(descs) == 'dictionary) .set_method_descs(descs); return (> del_method(name) <); }; public method .has_name() { // no names for non-real objects return 0; }; public method .method_descs() { return method_descs || #[]; }; public method .variables() { return (> variables() <); }; public method .get_var_on() { arg obj, v; var meth, i, val; // cheats encapsulation, don't use this terribly often // call definer.get_var_on(object, var) to get definer.var on object if (!(obj.has_ancestor(this()))) throw(~varnf, "object is not a child of definer."); // generate random method name i = random(10000); meth = tosym((((("__get_var_on_for_" + strsub(tostr(caller()), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); while ((| obj.find_method(meth) |)) { i++; meth = tosym((((("__get_var_on_for_" + strsub(tostr(caller()), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); } // add and evaluate the method to get the variable value add_method([("return " + tostr(v)) + ";"], meth); val = (| obj.(meth)() |); del_method(meth); if (type(val) == 'error) throw(~varnf, "Variable not found."); return val; }; public method .set_var_on() { arg obj, v, val; var meth, i; // cheats encapsulation, don't use this terribly often // call definer.set_var_on(object, var, val) to set definer.var on object if (!(obj.has_ancestor(this()))) throw(~varnf, "object is not a child of this."); // generate random method name i = random(10000); meth = tosym((((("__set_var_on_for_" + strsub(tostr(caller()), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); while ((| obj.find_method(meth) |)) { i++; meth = tosym((((("__set_var_on_for_" + strsub(tostr(caller()), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); } // add and evaluate add_method([((tostr(v) + " = ") + toliteral(val)) + ";"], meth); (| obj.(meth)() |); del_method(meth); }; public method .del_var() { arg val; return (> del_var(val) <); }; public method .obj_desc() { return obj_desc || ""; }; public method .set_text_method() { arg block, meth; var newlist; // each method can have a block of text associated with it // $root.text_methods is a dictionary full of them // usually used to track copies of methods containing syntax errors, // which are saved for later re-editing. if (type(text_methods) != 'dictionary) text_methods = #[]; if (!block) { if (dict_contains(text_methods, meth)) text_methods = dict_del(text_methods, meth); } else { text_methods = dict_add(text_methods, meth, block); } }; public method .get_text_method() { arg meth; var block; block = (| text_methods[meth] |); if (!block) return []; return block; }; public method .set_parents() { arg newparents; var old, new, i, add, del; // based on ColdCore's code old = .ancestors(); new = []; for i in (newparents) new += i.ancestors(); add = []; del = []; for i in (new + old) { if (i in old) { if (!(i in new)) del += [i]; } else { add += [i]; } } // okay, let's do this (| .uninitialize(del) |); chparents(pars); (| .initialize(add) |); }; public method .method_flags() { arg meth; return (> method_flags(meth) <); }; public method .set_method_flags() { arg meth, flags; return (> set_method_flags(meth, flags) <); }; public method .hasflag() { arg flag; if ((| flag in flags |)) return 1; return 0; }; public method .setflag() { arg flag; if (type(flag) != 'symbol) throw(~type, "Flags must be symbols."); flags = (flags ? flags : []).setadd(flag); }; public method .set_method_descs() { arg descs; method_descs = descs || #[]; }; public method .flags() { return flags; }; public method .objnum() { return (> objnum() <); }; public method .unsetflag() { arg flag; if (type(flag) != 'symbol) throw(~type, "Flags must be symbols."); flags = setremove(flags ? flags : [], flag); }; public method .is() { arg obj, @extra; // is this a child of obj? // ugly kludge for data types so "abc".is($string) is true if (extra) return (> has_ancestor(extra[1]) <); return (> has_ancestor(obj) <); }; public method .method_info() { arg meth; return (> method_info(meth) <); }; public method .owner() { return owner; }; public method .chown() { arg new; if (!(new.is($user))) throw(~perm, (new.ref()) + " is not a $user."); if (owner) { .log('db, (((("Owner of " + (.ref())) + " changed from ") + (owner.name())) + " to ") + (new.name())); owner.unown(this()); } else { .log('db, (("Owner of " + (.ref())) + " set to ") + (new.name())); } owner = new; owner.own(this()); }; public method .ref() { return tostr(this()); }; public method .own() { arg obj; owned = setadd(owned ? owned : [], obj); }; public method .unown() { arg obj; owned = setremove(owned, obj); }; public method .init_root() { obj_desc = ""; method_descs = #[]; method_args = #[]; text_methods = #[]; flags = []; owner = 0; }; public method .objlist() { return 0; }; public method .tostr() { return tostr(this()); }; public method .spawn() { arg name; var new; // spawn new child of this() with name if (type(name) != 'symbol) name = (> tosym(name) <); if ((| lookup(name) |)) throw(~perm, "An object already exists with that name."); new = create([this()]); new.set_objname(name); new.initialize((new.ancestors()).reverse()); return new; }; public method .initialize() { arg parents; var a, meth, definer; // takes a list of parents to initialize for this() // a newly-created object is initalized for all its parents // an object chparented to some new parents is only initalized for those // // idea taken from ColdCore -- looks like the best way to do it // a parent object X can have an init_X function on it which is // called as the child when a child first gets X as a parent // uninit_X is also called when the child loses that parent // ._init_uninit(parents, "init"); }; public method ._init_uninit() { arg objects, what; var obj, meth, definer; if (!(what in ["init", "uninit"])) throw(~args, "Can only init or uninit an object."); if ((!objects) || (type(objects) != 'list)) return; for obj in (objects) { meth = (| tosym((what + "_") + (tostr(obj).substr(2))) |); definer = (| find_method(meth) |); if (!definer) { // no error, just not found continue; } if (definer != obj) { // problem, init_func not where it should be .log('error, (((((what.capitalize()) + " method for ") + obj) + " found on ") + definer) + " instead."); continue; } catch any { // run it as ourselves, so that parent can init us .(meth)(); } with { .log('error, (((("Problem during " + what) + " for ") + obj) + ": ") + toliteral(traceback())); } // next parent to init/uninit } }; public method .uninitialize() { arg parents; // call uninit_X for each parent X that we're losing ._init_uninit(parents, "uninit"); }; public method .ancestors() { return (> ancestors() <); }; object $sys: $root; var $root obj_desc = "System object -- driver methods and other important stuff."; var $sys bindings = #[['shutdown, $sys], ['destroy, $root], ['chparents, $root]]; var $sys startup_objects = [$listener, $debug_port, $user, $anonymous]; var $sys shutdown_ready = 0; var $root method_descs = #[['startup, "driver method called at startup"], ['heartbeat, "driver method called every <heartbeat> secs"], ['pre_shutdown, "used to inform objects of impending shutdown"], ['shutdown, "actually perform the shutdown"], ['next_objnum, "?"], ['status, "?"], ['version, "?"]]; var $root text_methods = #[]; var $root flags = ['safe]; driver method .startup() { arg args; var i, f; // called at the very beginning, every boot shutdown_ready = 0; // setup for new character to be admin if ('freshinstall in (.variables())) { .log('boot, "Detected fresh install, deleting non-core objects..."); $admin_if.set_var_on($admin_if, 'admin_flags, ['creation]); $user.setflag('freshinstall); // clean things up for i in (filter f in (map i in ([$place, $thing, $user]) to (i.children()).flatten()) where (!(f.hasflag('safe)))) i.destroy(); del_var('freshinstall); } // take a port argument. Note that you have to run // 'bin/genesis --4242' to get a port number to here // yes, there should be error-checking here catch any { if (args && (i = (| toint((args[1]).substr(2)) |))) $listener.set_var_on($listener, 'port, i); } with { .log('boot, "Problem with arguments: " + toliteral((traceback()[1])[2])); } // bind native functions for f in (bindings) { catch any bind_function(f[1], f[2]); with .log('boot, ((("Binding unsuccessful: " + (f[1])) + " to ") + (f[2])) + "."); } // start me up .log('boot, "-------------------- Booting XidusCore: " + ctime()); .log('boot, "Startup called by driver."); set_heartbeat(2); catch any { for i in (startup_objects) { .log('boot, ("calling " + tostr(i)) + ".startup()"); i.startup(); } } with { // caught from $listener.startup(), always fatal if (error() == ~bind) { .log('fatal, strfmt("%60{-=}c", "Fatal")); .log('fatal, "Whoa there... couldn't bind the network port."); .log('fatal, ("I was trying for port " + ($listener.get_var('port))) + ", but was unable to bind to it."); .log('fatal, "Try starting as 'bin/genesis --portnum' to change ports."); .log('fatal, " (for example, 'bin/genesis --4343')"); .log('fatal, strfmt("%60{-=}c", "Fatal")); shutdown(); return; } else { .log('notice, "Error in startup, traceback follows: --->"); .log('notice, toliteral(traceback())); .log('notice, "<--- Traceback ends."); } } .log('boot, "Startup complete."); if ($user.hasflag('freshinstall)) .log('boot, "If you're seeing this spam on the console, then make sure there's a 'logs' subdirectory, and restart the server."); }; driver method .heartbeat() { //noop for now }; public method .pre_shutdown() { // eventually other stuff here, like disconnecting users shutdown_ready = 1; }; public method .shutdown() { .log('system, "Shutdown called..."); if (!shutdown_ready) .pre_shutdown(); shutdown(); }; public method .next_objnum(): native; public method .status(): native; public method .version(): native; new object $real: $root; var $root obj_desc = "Parent of all 'real' objects."; var $real name = "Unnamed Object"; var $root method_descs = #[['has_name, "return 1 for real objs"], ['name, "return name object variable"], ['set_name, "set name object variable"]]; var $root flags = ['safe]; var $real desc = 0; var $root text_methods = #[]; var $real owner = 0; public method .has_name() { return 1; }; public method .name() { return name; }; public method .set_name() { arg val; name = val; }; public method .desc() { var i; // returns description, or chases parentage up (as long as they're // children of $real) to get a description if (this() == definer()) { .log('error, "$real.desc called on $real"); return "You see nothing."; } if (!desc) return (filter i in (.parents()) where (i.is($real))[1]).desc(); return desc; }; public method .listens() { return 0; }; public method .owner() { return owner; }; public method .set_owner() { arg newowner; // blerg, eat perms if (!(newowner.is($user))) throw(~args, "Owner must be a $user."); owner = newowner; }; public method .init_real() { name = "Unnamed Object"; desc = "An undescribed something."; // need to init owner somehow }; public method .set_desc() { arg new; desc = new; }; public method .visible() { return 1; }; new object $located: $real; var $root flags = ['safe]; var $root obj_desc = "Parent of objects having location."; var $located location = 0; var $root text_methods = #[]; var $located last_loc = 0; public method .move_to() { arg dest, manner; // manner is 'exit or 'teleport so far if (!(dest.is($container))) throw(~perm, "Can't move into that!"); if (valid(location) && (location.is($place))) location.objleft(this(), manner); last_loc = valid(location) ? location : 0; location = dest; location.objentered(this(), manner); }; public method .loc() { // hack, shouldn't have invalid location ever return location || $the_void; }; public method .move_through() { arg exit; var loc, dest, pair, i; // exit is an $exit_frob loc = .loc(); dest = exit.dest(); .tell(("You " + (exit.text_go())) + "."); loc.otell(this(), (((.name()) + " ") + (exit.text_went())) + "."); .move_to(dest, 'exit); // find paired exit, if any, to get arrival message pair = exit.pair(); if (pair && ((pair = filter i in (dest.exits()) where (i.matches(pair))).length())) { pair = pair[1]; dest.otell(this(), (((.name()) + " ") + (pair.text_arrives())) + "."); } else { dest.otell(this(), (.name()) + " arrives."); } // have a look around .parse("look"); }; public method .last_loc() { // last_loc tracks the last place you were // the word 'last' can be used in some places to refer to this place if (!last_loc) throw(~nomatch, "No last location set."); return last_loc; }; public method .init_located() { .move_to($the_void, 'creation); last_loc = $the_void; }; public method .uninit_located() { if (valid(location) && (location.is($place))) location.objleft(this(), 'destroyed); }; new object $container: $real; var $root flags = ['safe]; var $root obj_desc = "Parent of objects with contents"; var $container contents = 0; var $root text_methods = #[]; public method .contents() { return contents || []; }; public method .objleft() { arg obj, manner; if (obj in (.contents())) contents = setremove(.contents(), obj); else .log('debug, (obj + " leaving wasn't in contents in objleft for ") + this()); }; public method .objentered() { arg obj, manner; if (!(obj in (.contents()))) contents = setadd(.contents(), obj); else .log('debug, (obj + " was already in contents list in objentered for ") + this()); }; public method .announce() { arg str; var i; // inform contents for i in (contents) i.tell(str); }; public method .init_container() { contents = []; }; public method .uninit_container() { var i; for i in (.contents()) { if (i.listens()) i.tell("The room you're standing in shimmers strangely, and you find yourself in a new place."); (| i.move_to($the_void, 'teleport) |); } }; new object $person: $located, $container; var $root obj_desc = "Parent of $user and $npc, real people."; var $root method_descs = #[]; var $root flags = ['safe]; var $root text_methods = #[]; var $person perms = 0; public method .can() { arg what, @target; // this entire mess is short-circuited until further notice... // essentially no security at this time // always say yes: return 1; // NOTREACHED if (target) target = target[1]; if (what in ['build, 'tinker, 'architect, 'explore, 'code, 'admin]) { if ((!target) && (what in ['architect, 'explore])) return (what in (perms || [])) || (.can('code)); else if (target && (what == 'build)) return (.can('architect)) || ((.can('build)) && (.owns(target))); else if (target && (what == 'tinker)) return (.can('code)) || ((.can('tinker)) && (.owns(target))); else if (target && (what == 'explore)) return (.can('explore)) || ((.can('tinker)) && (.owns(target))); else return what in (perms || []); } if ((what in ['add_exit, 'create_room, 'create_thing, 'change_desc, 'modify_exit, 'view]) && (.can('build, target))) return 1; if ((what in ['lookup]) && (.can('explore))) return 1; if ((what in ['view, 'view_var, 'view_code]) && (.can('explore, target))) return 1; if ((what in ['modify_var, 'modify_code]) && (.can('tinker, target))) return 1; if ((what in ['shutdown, 'player_admin]) && (.can('admin))) return 1; return 0; }; public method .match_object() { arg str; var obj; // find an object in the environment if ((str == "me") || ((str.lowercase()) == "i")) return this(); if (str == "here") return .loc(); // return a dbref for this object switch (str[1]) { case "$": if ((.can('lookup, "$")) && (obj = (| lookup(tosym(substr(str, 2))) |))) return obj; else throw(~nomatch, "Object not found."); case "#": // is_number required because fromliteral ignores junk after objnum if ((!(.can('lookup, "#"))) || (!((str.substr(2)).is_number()))) throw(~nomatch, "Object not found."); if (type((| (obj = fromliteral(str)) |)) != 'objnum) throw(~nomatch, "Object not found."); return obj; case "*": if ((.can('lookup, "*")) && (obj = (| .match_user(substr(str, 2)) |))) return obj; throw(~nomatch, "User not found."); default: // actual environment checking if ((obj = (| .match_env(str) |))) return obj; throw(~nomatch, "Object not found."); } }; public method .match_env() { arg str; var con, people, objs, i, x; // people, objects, in location con = (.loc()).contents(); people = (objs = []); for i in [1 .. con.length()] { if ((con[i]).is($person)) people += [con[i]]; else objs += [con[i]]; } // need better matching eventually if ((i = str in map x in (people) to (x.name()))) return people[i]; if ((i = str in map x in (objs) to (x.name()))) return objs[i]; throw(~nomatch, "Object not found."); }; public method .match_user() { arg str; var obj, i; // match a user name if ((str[1]) == "*") str = str.substr(2); if (str in ["me", "i", "I"]) return this(); for i in ($user.children()) { if (str == (i.name())) return i; } throw(~nomatch, "User not found."); }; public method .match_location() { arg str; var obj; // match a place if (str == "here") return .loc(); obj = (| user.match_object(str) |); if (obj) { if (obj.is($place)) return obj; else throw(~nomatch, "Object is not a location."); } throw(~nomatch, "Unknown location reference."); }; public method .match_exit() { arg dir; var exit; // match an exit in location exit = filter exit in ((.loc()).exits()) where (exit.matches(dir)); switch (exit.length()) { case 0: return 0; case 1: return exit[1]; default: throw(~ambig, exit); } }; public method .add_perm() { arg what; var new, obj; // not terribly usable right now new = (perms || []).setadd(what); if ((new.length()) == ((perms || []).length())) return; perms = new; // if there's an appropriate parser, make sure it's added switch (what) { case 'build: obj = [$builder_if, $explorer_if]; case 'tinker: obj = [$explorer_if, $coder_if]; case 'architect: obj = [$builder_if, $explorer_if]; case 'explore: obj = [$explorer_if]; case 'code: obj = [$builder_if, $explorer_if, $coder_if]; case 'admin: obj = [$admin_if]; default: obj = []; } if (obj) { new = (.parsers()) + obj; if ((new.length()) != ((.parsers()).length())) .set_parsers(new); } }; public method .init_person() { perms = []; }; new object $libs: $root; var $root obj_desc = "Parent of library objects."; var $root flags = ['safe]; new object $env: $libs; var $root obj_desc = "currently unused"; var $root text_methods = #[]; var $root flags = ['safe]; new object $network: $libs; var $root method_descs = #[['hostname, "native binding"], ['ip, "native binding"]]; var $root obj_desc = "Repository for network-related functions."; var $root flags = ['safe]; var $root text_methods = #[]; public method .hostname(): native; public method .ip(): native; public method .connection() { return (| connection() |); }; public method .reassign_connection() { arg obj, @ip; // transfer a connection from this to a new object ip = (| ip[1] |); ip ?= (| .ip() |); ip ?= "unknown"; if (!valid(obj)) throw(~nomatch, "Object not valid."); (| .unassigned_connection() |); (> reassign_connection(obj) <); (| obj.assigned_connection(ip, caller()) |); }; new object $listener: $network; var $listener port = 4343; var $root method_descs = #[['startup, "begin listening on assigned port"], ['connect, "driver method, handle new connection by passing to a new anonymous object"], ['parse, "driver method, report error if called"]]; var $root obj_desc = "Listens on a port for network connections."; var $root flags = ['safe]; var $root text_methods = #[]; public method .startup() { .log('boot, "startup called"); (> bind_port(port) <); .log('boot, "port bound successfully."); }; driver method .connect() { arg remote, local, socket; var conn; .log('net, "connect from " + tostr(remote)); catch any { // $anonymous children handle all login sessions conn = $anonymous.makenew(); .reassign_connection(conn, remote); } with { .log('error, "Problem with hand-off: " + toliteral(traceback())); } }; driver method .parse() { arg buf; // all incoming data should go to a new child of $anonymous .log('notice, "listener.parse got data, shouldn't happen."); cwrite(str_to_buf("Something's broken!\n")); }; new object $http: $network; var $root flags = ['safe]; var $root obj_desc = "Future http interface, maybe."; public method .decode(): native; public method .encode(): native; new object $networked: $network; var $root flags = ['safe]; var $networked ip = 0; var $networked buffer = `[]; var $networked connected = 0; var $root text_methods = #[]; var $root obj_desc = "Parent of objects handling connections"; public method .tell() { arg str; var i; // lowest-level network-write function, called by everybody if (type(str) == 'string) { cwrite(str_to_buf(str + "\n")); } else if (type(str) == 'list) { for i in (str) { if (type(i) == 'string) { cwrite(str_to_buf(i + "\n")); } else { .log('error, "unsupported data type: " + toliteral(str)); throw(~unsupp, "Unsupported data type in connected.tell, " + type(i)); } } } else { .log('error, "unsupported data type: " + toliteral(str)); throw(~unsupp, "Unsupported data type in connected.tell"); } }; public method .connected() { return connected; }; driver method .disconnect() { // called by driver on netdeath/connection closed .log('net, ("Disconnect from " + (.ip())) + "."); connected = 0; catch ~methodnf { // inform whoever's handling the connection .connection_closed(); } with { // no error } }; public method .parse() { arg buf; var l, i; // low-level network-read function, called by driver if (type(buf) == 'string) return .parse_line(buf); l = buf_to_strings((buffer ? buffer : `[]) + buf); buffer = l[listlen(l)]; l = delete(l, listlen(l)); for i in (l) .parse_line(i); }; public method .close() { // asked to close the connection close_connection(); }; public method .ip() { return ip || 0; }; public method .set_ip() { arg val; ip = val; }; public method .connect() { // not sure this ever gets called .log('net, ("Connection from " + (.ip())) + "."); connected = 1; catch ~methodnf { .connection_opened(); } with { // no error } }; public method .assigned_connection() { arg ip, obj; // we're handling a new connection .set_ip(ip); .log('net, (((tostr(this()) + " assigned conn from ") + (.ip())) + " by ") + tostr(obj)); connected = 1; catch ~methodnf { // inform whoever's listening .connection_opened(); } with { //noerror } }; public method .unassigned_connection() { // not ours anymore .log('net, (tostr(this()) + " unassigned connection to ") + (.ip())); // doesn't call connection_closed, which is somewhat inconsistent // connection_closed is reserved for actual closure/netdeath, though connected = 0; }; new object $parser: $libs; var $root flags = ['safe]; var $root obj_desc = "Parser library"; new object $parses: $parser; var $root flags = ['safe]; var $parses parsers = 0; var $parses preparsers = 0; var $root text_methods = #[]; var $parses snarfer = 0; var $parses last_args = 0; var $root obj_desc = "Parent of objects that parse, handles parse_line, snarfing, etc."; public method .set_parsers() { arg list; var i; // set the list of parsers after sorting by priority if (type(list) != 'list) throw(~args, "Argument must be a list of $interfaces."); if ((filter i in (list) where (!(i.is($interfaces))).length()) != 0) throw(~args, "Argument must be a list of $interfaces."); parsers = (list.sort(map i in (list) to (i.priority()))).compress(); }; public method .set_preparsers() { arg list; preparsers = (type(list) == 'list) ? list : []; }; public method .parsers() { return parsers || []; }; public method .parse_line() { arg str; var i, result; // called by $networked.parse with one line at a time // this is it folks, the start of the parser // snarfers grab all input for a while, check those first if (snarfer != 0) { if (str == "!stopsnarf") { snarfer = 0; .tell("Snarf aborted."); return; } if ((snarfer[1]).(snarfer[2])(this(), str) == 'done) snarfer = 0; // no other processing on this input, it's been snarfed return; } catch any { // fold the string through the preparsers, lets each one // modify it in turn for i in (.preparsers()) str = i.parse(this(), str); // run it through the parsers, stopping if one of them handles it for i in (.parsers()) { if ((result = i.parse(this(), str)) == 'handled) break; } // nobody wanted it, print the 'Doh!' message if (result != 'handled) .doh(str); } with { .log('error, "Caught... " + toliteral(traceback())); // shouldn't really wind up here if (error() == ~perm) .tell("Permission denied."); else .tell("Traceback from execution: " + toliteral(traceback())); } }; public method .preparsers() { return preparsers || []; }; public method .last_args() { // last args are used by $cmd_ifs to allow $_ in place of the last // arguments used return last_args || []; }; public method .set_last_args() { arg args; // last args are used by $cmd_ifs to allow $_ in place of the last // arguments used last_args = args || []; }; public method .start_snarfing() { arg obj, method, data; // allow a snarfer to begin taking all data from a connection // until it's had enough if (!(.connected())) { .log('debug, "Attempt to start snarfing for non-connected " + tostr(this())); return 0; } // we're going to assume that snarfers can keep themselves straight... // snarfing should only be the result of an interactive command, and // interactive commands can't be used while snarfing. We'll see... if (snarfer) return; // sets data snarfer = [obj, method, data]; }; public method .snarfer_data() { return snarfer[3]; }; public method .set_snarfer_data() { arg val; // used by snarfer routines to keep track of their data snarfer = [snarfer[1], snarfer[2], val]; }; public method .stop_snarfing() { // doesn't handle things gracefully yet snarfer = 0; }; new object $npc: $person, $parses; var $root obj_desc = "Object that can use person and environment commands."; var $parses parsers = [$null_if, $person_if]; var $npc owner = 0; var $root flags = ['safe]; var $root text_methods = #[]; public method .tell() { arg str; // process text }; public method .doh() { arg str; // handle command not found }; public method .listens() { return 1; }; new object $anonymous: $networked, $parses; var $root obj_desc = "Parent of non-logged-in connections."; var $parses preparsers = []; var $parses parsers = [$null_if, $login_if]; var $anonymous next_idx = 0; var $root method_descs = #[['makenew, "spawn a new anonymous child"], ['start, "begin handling a new connection with given IP"], ['connection_closed, "called on socket disconnect"], ['doh, "called to inform user of unknown command."], ['user_connect, "called by login_if on connect command"], ['user_create, "called by login_if on create command"]]; var $root flags = ['safe]; var $root text_methods = #[]; public method .makenew() { var str, new; // cook up a new child of $anonymous to handle a connection str = (tostr(objname()) + "_") + tostr(next_idx++); new = .spawn(str); return new; }; public method .connection_opened() { // called after being assigned a new connection .log('net, ("Login connection from " + (.ip())) + "."); // yes, there should be a banner here .tell(("Welcome, " + (.ip())) + "."); .tell("Type create <user> <pass> to create a new user,"); .tell("Or connect <user> <pass> to connect to an existing one."); }; public method .doh() { arg str; .tell("Command not recognized, type 'help' for help."); }; public method .user_connect() { arg uname, passw; var i; // find the given user, report back or transfer their connection // to a user object. Maybe I should rename $anonymous to $receptionist... for i in ($user.children()) { if (uname == (i.name())) { if (i.match_pass(passw)) { // we're in catch any { // my work here is done .reassign_connection(i); .destroy(); return; } with { if (error() == ~perm) { // hack .tell("You appear to already be connected. Multiple connections are not yet supported."); .log('net, "Login: attempted second connection for " + (i.name())); } else { .tell("Could not connect you to user for unknown reason."); .log('error, "Traceback from connect: " + toliteral(traceback())); } return; } // notreached } else { // wrong pass .tell("Either that user does not exist, or the password you gave was incorrect."); .log('net, (("Login: Bad password for " + (i.name())) + " from ") + (.ip())); return; } } } // no such user .tell("Either that user does not exist, or the password you gave was incorrect."); .log('net, (("Login: Non-existent user " + uname) + " from ") + (.ip())); }; public method .user_create() { arg user, passw; var i, u; // create command from login screen catch any { if ((!($user.hasflag('freshinstall))) && (!('creation in ($admin_if.admin_flags())))) { .tell("Character creation is disabled. Please contact the administrator for more information."); return; } for i in ($user.children()) { if (user == (i.name())) { .tell("Sorry, that name is already taken."); return; } } // name not in use u = $user.makenew(user, passw); .log('net, (("Creating new user " + (u.name())) + " for ") + (.ip())); .tell(("Creating user " + (u.name())) + "..."); .reassign_connection(u); .destroy(); } with { .tell("Could not create your user for unknown reason."); .log('error, "Traceback from create: " + toliteral(traceback())); return; } }; public method .connection_closed() { if (this() == definer()) throw(~flamingduck, "Augh! connection_closed called on $anonymous"); .log('net, (.ip()) + " disconnected at the login screen."); .destroy(); }; public method .init_anonymous() { .set_preparsers($anonymous.preparsers()); .set_parsers($anonymous.parsers()); }; public method .startup() { var i; // there should be a nicer way to do this... for i in (.children()) { if (match_begin(tostr(i), "$anonymous_")) i.destroy(); } next_idx = 0; }; new object $user: $networked, $parses, $person; var $root obj_desc = "Parent of logged in users."; var $user name = "User Parent"; var $user password = ""; var $user doh_msg = "Doh! Command not found."; var $user undo_blocks = []; var $root method_descs = #[['doh, "inform user of unknown command"], ['makenew, "make a new user child"], ['set_passw, "Set password, crypt happens here"], ['match_pass, "check a password against stored"], ['welcome, "welcome banner, login stuff"], ['connection_closed, "called on socket disconnect"], ['add_undo, "Add a block of text to the undo buffer."], ['undo_blocks, "return current undo buffer"]]; var $root text_methods = #[]; var $root flags = ['safe, 'freshinstall]; var $parses parsers = [$null_if, $person_if, $user_if, $exits_if]; var $parses preparsers = [$alias_if]; var $real desc = ""; var $parses last_args = []; public method .doh() { arg str; // users get to customize their 'Doh!' message .tell(doh_msg); }; public method .makenew() { arg user, passw; var u; // assumes name has already been checked u = .spawn("user_" + lowercase(user)); u.set_name(user); u.set_passw(passw); // new install? Make them an admin, and flag for the welcoming committee if (.hasflag('freshinstall)) { u.set_parsers((.parsers()) + [$builder_if, $explorer_if, $coder_if, $admin_if]); u.setflag('newbie); .unsetflag('freshinstall); } return u; }; public method .set_passw() { arg val; // should check pass security here password = crypt(val); }; public method .visible() { return .connected(); }; public method .match_pass() { arg passw; return match_crypted(password, passw); }; public method .listens() { // do we want to hear things that go on? return 1; }; public method .welcome() { .tell("Welcome! Type 'help' for help."); // new admin? Here comes the welcoming committee... if (.hasflag('newbie)) { $user.set_parsers([$null_if, $person_if, $user_if, $exits_if]); .parse("disable creation"); .tell(["", "Welcome to XidusCore!", "", "System: I've just disabled the creation of new players, just in case you don't want any other company. To turn player creation on again at any time, type 'enable creation'."]); .unsetflag('newbie); } }; public method .connection_closed() { .log('net, (("Connection closed to " + (.name())) + " from ") + (.ip())); $user.announce('all, ("System: " + (.name())) + " has disconnected."); }; public method .add_undo() { arg text; // used when a method is deleted, just in case // need some kind of interface for this eventually // for now, just push a list of strings onto the top of the pile. undo_blocks = ([text] + (undo_blocks ? undo_blocks : [])).sublist(1, 20); }; public method .undo_blocks() { return undo_blocks || []; }; public method .nuke_user() { // do any uninit... any last words? // should leave location here... .destroy(); }; public method .connection_opened() { .log('net, ((.ip()) + " connected to user ") + (.name())); $user.announce('all, ("System: " + (.name())) + " has connected."); .welcome(); }; public method .init_user() { // $user name = "Unknown"; password = "XX"; doh_msg = "Doh! Command not found."; undo_blocks = []; // $parses .set_parsers($user.parsers()); .set_preparsers($user.preparsers()); // $real .set_desc("You see a user who's too lazy to describe themselves."); }; public method .announce() { arg who, str; var i, x, meth; // general-purpose connected-user announcement if (who == 'all) { for i in (filter x in ($user.children()) where (x.connected())) (| i.tell(str) |); } else { throw(~syntax, "Unknown specifier, arg 1."); } }; public method .tostr() { arg user; // for formatting porpoises return user.name(); }; public method .startup() { var i; for i in (.children()) { // breaks encapsulation, but oh well $networked.set_var_on(i, 'connected, 0); } }; new object $obj_lib: $libs; var $root obj_desc = "Functions related to displaying object info."; var $root method_descs = #[['print_tree, "send an ascii-art object hierarchy via tell to user, optional starting place"], ['_print_tree, "helper recursive"]]; var $root flags = ['safe]; var $root text_methods = #[]; public method .print_tree() { arg user, @obj; var start; start = ((| obj[1] |) && valid(obj[1])) ? (obj[1]) : $root; // recursion is our friend ._print_tree(user, start, 0, []); }; private method ._print_tree() { arg user, obj, depth, seen; var ind, str, i; // depth-first tree, don't go into the children of an object // appearing multiple times more than once, mark with a * instead. ind = ""; for i in [1 .. depth] ind += "-"; str = ind + tostr(obj); if ((tostr(obj)[1]) != "#") { if (obj in seen) user.tell(strfmt("%20s %50e", str + "*", strsub(ind, "-", " ") + (obj.obj_desc()))); else user.tell(strfmt("%20s %50e", str, strsub(ind, "-", " ") + (obj.obj_desc()))); } if (!(obj in seen)) { seen += [obj]; for i in (obj.children()) seen = ._print_tree(user, i, depth + 1, seen); } return seen; }; new object $data: $libs; var $root obj_desc = "Parent of libs dealing with data types."; var $root flags = ['safe]; new object $buffer: $data; var $root obj_desc = "Buffer-related methods."; var $root method_descs = #[['chop, "Remove any trailing CR or LF chars from a buffer"], ['length, "Native, return length of a buffer."]]; var $root method_args = #[['chop, #[['buf, "buffer to chop"]]]]; var $root flags = ['safe]; public method .length(): native; public method .to_string(): native; public method .to_strings(): native; public method .from_string(): native; public method .from_strings(): native; public method .replace(): native; public method .subrange(): native; public method .to_veil_pkts(): native; public method .from_veil_pkts(): native; public method .chop() { arg buf; while (((buf[buflen(buf)]) == 13) || ((buf[buflen(buf)]) == 10)) buf = subbuf(buf, 1, buflen(buf) - 1); return buf; }; new object $string: $data; var $root obj_desc = "String-related methods."; var $root flags = ['safe]; var $root text_methods = #[]; var $string numbers = "0123456789"; public method .quote() { arg str; return ("\"" + str) + "\""; }; public method .unquote() { arg str, @delims; var left, right, r; // remove quotes from left and right of a string left = delims ? (delims[1]) : "\""; right = (listlen(delims) == 2) ? (delims[2]) : left; r = str; if (str && ((str[1]) == left)) r = substr(str, 2); if ((r[r.length()]) == right) return substr(r, 1, (r.length()) - 1); return r; }; public method .length(): native; public method .subrange(): native; public method .explode(): native; public method .pad(): native; public method .match_begin(): native; public method .match_template(): native; public method .match_pattern(): native; public method .match_regexp(): native; public method .regexp(): native; public method .sed(): native; public method .replace(): native; public method .crypt(): native; public method .uppercase(): native; public method .lowercase(): native; public method .capitalize(): native; public method .compare(): native; public method .format(): native; public method .trim(): native; public method .split(): native; public method .word(): native; public method .dbquote_explode(): native; public method .html_escape(): native; public method .first() { arg str; if (((str.explode()).length()) == 0) return ""; return ((str.explode()).subrange(1, 1)).join(); }; public method .rest() { arg str; return ((str.explode()).subrange(2)).join(); }; public method .substr() { arg str, i1, @i2; i2 = (| i2[1] |); i2 ?= (strlen(str) - i1) + 1; return substr(str, i1, i2); }; public method .is_number() { arg str; var i; for i in [1 .. str.length()] { if (stridx(numbers, str[i]) == 0) return 0; } return 1; }; public method .last() { arg str; return (str.explode()).last(); }; public method .compassdir_long() { arg dir; return #[["n", "north"], ["ne", "northeast"], ["e", "east"], ["se", "southeast"], ["s", "south"], ["sw", "southwest"], ["w", "west"], ["nw", "northwest"]][dir]; }; public method .is_compassdir() { arg dir; return (.is_compassdir_long(dir)) || (.is_compassdir_short(dir)); }; public method .compassdir_opposite() { arg dir; return #[["north", "south"], ["northeast", "southwest"], ["east", "west"], ["southeast", "northwest"], ["south", "north"], ["southwest", "northeast"], ["west", "east"], ["northwest", "southeast"]][dir]; }; public method .is_compassdir_short() { arg dir; return dir in ["n", "ne", "e", "se", "s", "sw", "w", "nw"]; }; public method .is_compassdir_long() { arg dir; return dir in ["north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest"]; }; public method .compassdir_short() { arg dir; return #[["north", "n"], ["northeast", "ne"], ["east", "e"], ["southeast", "se"], ["south", "s"], ["southwest", "sw"], ["west", "w"], ["northwest", "nw"]][dir]; }; new object $dictionary: $data; var $root flags = ['safe]; var $root obj_desc = "Dictionary-related methods"; public method .add(): native; public method .del(): native; public method .keys(): native; public method .contains(): native; public method .values(): native; public method .union(): native; new object $list: $data; var $root flags = ['safe]; var $root text_methods = #[]; var $root obj_desc = "List-related methods"; public method .setadd(): native; public method .union(): native; public method .sort(): native; public method .delete(): native; public method .replace(): native; public method .join(): native; public method .length(): native; public method .setremove(): native; public method .insert(): native; public method .subrange(): native; public method .toenglish() { arg list; var i, l, str; l = list.length(); i = 1; str = ""; if (l == 1) return tostr(list[1]); while (i < l) { str += tostr(list[i]) + ", "; i++; } str += "and " + tostr(list[l]); return str; }; public method .compress() { arg list; var x; // shamelessly yoinked from ColdCore return hash x in (list) to ([x, 1]).keys(); }; public method .english() { arg list, @options; var empty, and, sep; // shamelessly yoinked from ColdCore [(empty ?= "nothing"), (and ?= " and "), (sep ?= ", ")] = options; switch (list.length()) { case 0: return empty; case 1: return tostr(list[1]); } return (join(list.delete(list.length()), sep) + and) + tostr(list[list.length()]); }; public method .flatten() { arg list; var toret, elem; // shamelessly yoinked from ColdCore // [[[x], x], x] => [x, x, x] toret = []; for elem in (list) { if (type(elem) == 'list) toret += .flatten(elem); else toret += [elem]; } return toret; }; public method .last() { arg list; return list[list.length()]; }; public method .reverse() { arg list; var i, len; // .reverse(list) // -> list with its elements reversed len = (list.length()) + 1; return map i in [1 .. len - 1] to (list[len - i]); }; public method .sublist() { arg list, start, @end; end = (| end[1] |); if (end) return sublist(list, start, end); return sublist(list, start); }; new object $integer: $data; var $root flags = ['safe]; var $root obj_desc = "Integer-related methods"; public method .and(): native; public method .or(): native; public method .xor(): native; public method .shleft(): native; public method .shright(): native; public method .not(): native; new object $time: $libs; var $root flags = ['safe]; var $root obj_desc = "Time-related methods"; public method .format(): native; new object $math: $libs; var $root flags = ['safe]; var $root obj_desc = "Math library"; public method .minor(): native; public method .major(): native; public method .add(): native; public method .sub(): native; public method .dot(): native; public method .distance(): native; public method .cross(): native; public method .scale(): native; public method .is_lower(): native; public method .transpose(): native; new object $frob: $libs; var $root flags = ['safe]; new object $exit_frob: $frob; var $root text_methods = #[['text_arrived_through, ["arg this, user;", "", "if ('flags in this.keys() && 'compass in this['flags])", " return user.name() + \" arrives from the \" + .name(this) + \".\";", "", "return user.name() + \" arrives", ""]]]; var $root flags = ['safe]; public method .dest() { arg this; // return the destination of this exit // note how 'this' exit_frob is always passed as an argument return (| this['dest] |) || #-1; }; public method .name() { arg this; return this['name]; }; public method .aliases() { arg this; var a; a = []; if ((| this['aliases] |)) a += this['aliases]; a += [this['name]]; return a; }; public method .matches() { arg this, what; // does 'what' match us? Try compassdir transformations. if (what in (.aliases(this))) return 1; if (what.is_compassdir()) { if (what.is_compassdir_short()) what = what.compassdir_long(); else what = what.compassdir_short(); } if (what in (.aliases(this))) return 1; return 0; // eventually need to handle more complex matchings }; public method .pair() { arg this; var n; // return the name of the exit we're paired with on 'the other side', // or in our destination room if ('pair in (this.keys())) return this['pair]; if (('flags in (this.keys())) && ('compass in (this['flags]))) { n = this['name]; if (!(n.is_compassdir())) throw(~hissyfit, "'compass flag on non-compass direction"); if (n.is_compassdir_short()) n = n.compassdir_long(); return n.compassdir_opposite(); } return ""; }; public method .text_go() { arg this; // return text like "move through the door" if (.compass(this)) return "move " + (this['name]); if ('go in (this.keys())) return this['go]; return "move in an unknown direction"; }; public method .text_went() { arg this; // return text like "went through the door" if (.compass(this)) return "moved " + (this['name]); if ('went in (this.keys())) return this['went]; return "moved in an unknown direction"; }; public method .compass() { arg this; // is this a compass-like direction? return ('flags in (this.keys())) && ('compass in (this['flags])); }; public method .text_arrives() { arg this; // return text like 'entered through the door' // (this is for 'the other way' through the exit if (.compass(this)) return "arrives from the " + (this['name]); if ('arrives in (this.keys())) return this['arrives]; return "arrives"; }; new object $thing: $located, $container; var $root obj_desc = "Parent of things, nonsentient but movable items"; var $root flags = ['safe]; var $real desc = "You see an indescribable, or at least undescribed, thing."; var $root text_methods = #[]; public method .init_thing() { .set_desc("An undescribed thing."); }; new object $place: $container; var $root obj_desc = "Parent of places, immobile containers of objects."; var $root flags = ['safe]; var $root text_methods = #[['exitdest, ["arg exit;", "var exits, i;", "", "exits = .exits('all);", "", "if (!(i in exits.flatten)) {", " throw(~nomatch);", ""]], ['check_exit, ["arg exit;", "", "// called to check integrity of exit", "", "if (!valid(exit.dest())) {", ""]], ['exits, ["[]"]]]; var $place exits = 0; var $real desc = "You see an extremely empty room."; public method .announce() { arg str; var i; for i in (.contents()) (| i.tell(str) |); }; public method .obj_entered() { arg obj; .set_contents(setadd(.contents(), obj)); .announce((obj.name()) + " has entered."); }; public method .obj_left() { arg obj; .set_contents(setremove(.contents(), obj)); .announce((obj.name()) + " has left."); }; public method .exits() { // frobs are meant to be passed around, darnit! return exits || []; }; public method .otell() { arg user, str; var person, i; // tell everyone except user in this location for person in (filter i in (.contents()) where ((i.listens()) && (i != user))) person.tell(str); }; public method .add_exit() { arg user, dir, dest; var exit; // add an exit to this room if ((exit = filter exit in (.exits()) where (exit.matches(dir))).length()) throw(~args, ((("The exit named " + ((exit[1]).name())) + " already matches ") + str) + "."); if (!(dest.is($place))) throw(~args, "Destination is not a place."); if (!(user.can('add_exit, this()))) throw(~perm, "Permission denied: You aren't allowed to build here."); if (dir in ["north", "northeast", "east", "southeast", "south", "southwest", "west", "northwest"]) exit = (<$exit_frob, #[['flags, ['compass]], ['name, dir], ['dest, dest]]>); else exit = (<$exit_frob, #[['name, dir], ['dest, dest]]>); exits = (.exits()) + [exit]; return exit; }; public method .check_exits() { var i, exit, bad, newexits; // called to check integrity of exits when someone has reason to doubt // possibilities: bad exits list, exit with destination that no longer // exists... i = 1; newexits = .exits(); while (i <= (newexits.length())) { exit = newexits[i]; if (!(| frob_class(exit) == $exit_frob |)) { .log('error, ((this() + ".check_exits found non-$exit_frob exit ") + toliteral(exit)) + ", deleting."); newexits = newexits.delete(i); continue; } if (!valid((| exit.dest() |))) { .log('error, ((this() + ".check_exits found exit-frob with bad dest ") + toliteral(exit)) + ", deleting."); newexits = newexits.delete(i); continue; } i++; } exits = newexits; }; public method .del_exit() { arg name; var matches, i; matches = filter i in (.exits()) where (i.matches(name)); if (!matches) return; if ((matches.length()) > 1) throw(~ambig, "Could match more than one exit."); exits = filter i in (.exits()) where (!(i.matches(name))); }; public method .uninit_place() { var i, j, k; // can't do anything about unpaired exits yet :/ for i in (.exits()) { if ((j = i.pair())) { k = i.dest(); k.del_exit(j); } } }; new object $the_void: $place; var $real desc = "Blerg, eat lack of description."; var $real name = "The Void"; var $root obj_desc = "The Void."; var $container contents = []; var $place exits = []; var $real owner = 0; var $root flags = ['safe]; new object $interfaces: $root; var $root obj_desc = "Parent of interfaces, which define sets of commands."; var $root text_methods = #[]; var $interfaces priority = 0; var $root flags = ['safe]; public method .priority() { // set per-parser, used to sort them in $parses.set_parsers return priority; }; new object $cmd_ifs: $interfaces; var $cmd_ifs cmds = []; var $cmd_ifs cmds_info = #[]; var $root method_descs = #[['cmds, "return object variable cmds"], ['parse, "parse a line of input according to the commands for this interface."], ['args, "utility, assert argcount, throws ~usage"], ['argrange, "utility, assert argcount, throws ~usage"], ['set_cmds, "set object variable cmds"], ['cmds_info, "return object variable cmds_info containing extended info about commands"], ['add_cmd, "add a string to object variable cmds"], ['parse_arg, "parse a single arg template item"], ['parse_args, "parse an argument template"]]; var $root text_methods = #[]; var $root flags = ['safe]; var $root obj_desc = "'Normal' commands with argument parsing, etc."; public method .cmds() { return cmds || []; }; public method .parse() { arg user, str; var i, cmds, args, cmd, method, info, arglist; // parser guts, called per-interface with a command args = str; arglist = args.explode(); cmd = arglist[1]; cmds = .cmds(); info = .cmds_info(); for i in (cmds) { if (i == cmd) { // match method = tosym((cmd.lowercase()) + "_cmd"); catch any { // old, still needed, tho? // --------------------------------------- // if ((info = (| info[cmd] |))) { // if ('noquoted in info['flags]) // args = explode(str); // if ('string in info['flags]) // args = [str]; // } // ---------------------------------------- // lastarg replacement if ((listlen(arglist) == 2) && ((arglist[2]) == "$_")) args = cmd + (user.last_args()); else if (!(((arglist.length()) > 2) && (((arglist[1]) == "edit") && ((arglist[2]) == "write")))) user.set_last_args((arglist.subrange(2)).join()); // unless the method wants to pass the command on for some // reason, we'll say we handled it if (.(method)(user, args) != 'pass) return 'handled; } with { switch (error()) { case ~methodnf: // apparently from trying to call <command>_cmd user.tell("Sorry, this command appears to be broken."); .log('error, (("interface found broken command " + (i.quote())) + ", no method found: ") + toliteral(traceback())); return 'handled; case ~template: // from more parser guts, coder error user.tell("Sorry, this command appears to be broken."); .log('error, (((("Bad template for " + cmd) + " on ") + tostr(this())) + ": ") + ((traceback()[1])[2])); return 'handled; case ~usage: // syntax, just print message user.tell((traceback()[1])[2]); return 'handled; case ~nomatch: // nomatch/notfound, just print message user.tell((traceback()[1])[2]); return 'handled; default: rethrow(error()); } } } } return; }; public method .set_cmds() { arg val; cmds = val; }; public method .add_cmd() { arg val; cmds = setadd(cmds ? cmds : [], val); }; public method .cmds_info() { return cmds_info; }; public method .argtemplate() { return 0; }; public method .parse_arg() { arg user, templ, str, templnext; var what, first, rest, obj, obj2, i, word, strrest, c; // this is about as deep in parser guts as you can get // attempts to parse a single word, templ, of a template // has remaining text of the user command, 'str', and next // template item, templnext. // doesn't handle optional template items here // returns list [str(remaining), status, error str(if any)] // throws ~template if programmer's template is bad // // start munging around on the template item templ = templ.trim(); str = str.trim(); // all space? if (!templ) return [str, 'space, ""]; if ((templ[1]) != "<") { // constant match, or literal i = 1; catch ~range { while ((templ[i]) == (str[i])) i++; } with { if (i > strlen(templ)) { // that's all we needed return [substr(str, i), templ]; } if (i > strlen(str)) { // nope, ran off the end of the user input return [str, ~nomatch, ("Required word " + templ) + " not found."]; } } // nope, no match return [str, ~nomatch, ("Required word " + templ) + " not found."]; } // look at first char of templnext to determine delimiter for match // if next up is something strange like '.', use that c = (| templnext[1] |); // is it a normal match? Use space instead if ((!c) || stridx("<[abcdefghijklmnopqrstuvwxyz", c)) c = " "; // find chunk of user input we should be matching i = 1; while ((i <= strlen(str)) && ((str[i]) != c)) i++; word = substr(str, 1, (i > strlen(str)) ? strlen(str) : (i - 1)).trim(); strrest = (| substr(str, i) |); strrest ?= ""; what = templ.unquote("<", ">"); first = what.first(); rest = what.rest(); // did we fall off the end of user input? if (!word) return [strrest, ~end]; // match template item switch (first) { case "user": return [strrest, (| user.match_user(word) |)]; case "object": obj = (| user.match_object(word) |); if (!obj) return [str, ~nomatch, ("Object " + word) + " not found."]; if (!rest) return [strrest, obj]; first = rest.first(); rest = rest.rest(); // additional options, passed as, eg, <object child of $interfaces> switch (first) { case "child": first = (rest.explode())[1]; if (first == "of") first = (rest.explode())[2]; // can't match_object as the user for the template arg... // need to use some sort of uber-user here if (!(obj2 = (| $person.match_object(first) |))) throw(~template, "'child of' target not found: " + first); if (obj.has_ancestor(obj2)) return [strrest, obj]; return [str, ~nomatch, ("Object must be a child of " + tostr(obj2)) + "."]; default: throw(~template, ((("Unknown options " + first) + " ") + rest) + " in <user> template arg."); } case "location": what = (| user.match_location(word) |); if (!what) return [str, ~nomatch, ("Location " + word) + " not found."]; return [strrest, what]; case "symbol": if ((word[1]) == "$") word = word.substr(2); what = (| tosym(word) |); if (what) return [strrest, what]; return [str, ~nomatch, "Not a valid symbol: " + word]; case "string": // assumed to be one word. :/ return [strrest, word]; case "list": // assumed to end of line. :/ if ((str[1]) != "[") word = ("[" + str) + "]"; what = (| fromliteral(word) |); if (type(what) != 'list) return [str, ~nomatch, "Not a list: " + str]; return ["", what]; case "number": what = (| toint(word) |); if (type(what) == 'error) return [str, ~nomatch, "Not a number: " + word]; return [strrest, what]; case "text": // assumed to be to end of line return ["", str]; default: throw(~template, ((("Unknown template arg " + first) + " ") + rest) + "."); } }; public method .parse_args() { arg user, args, template, @flags; var i, j, item, items, what, res, rest, opt, pos, err, str, items, out; // welcome to parser guts central... this calls parse_arg a lot, but // does all the logic-type stuff, and is the 'visible' function // for commands. returns a list of args if all went well, else // throws ~nomatch, ~usage, or ~template flags = (| flags[1] |); flags ?= []; str = args; if (type(args) == 'list) str = args.join(); res = []; // first up here is to parse the template into words items = []; pos = 1; rest = template; catch any { while (rest) { opt = 0; if ((rest[1]) == "[") { // optional arg opt = 1; rest = (| substr(rest, 2) |); if (!rest) throw(~template, ("Empty option brackets [] in template at " + tostr(pos)) + "."); pos++; if ((rest[1]) == "]") throw(~template, ("Empty option brackets [] in template at " + tostr(pos)) + "."); } if ((rest[1]) == "<") { pos++; i = 2; item = "<"; if (stridx(rest, ":") && (stridx(rest, ":") < stridx(rest, ">"))) { j = stridx(rest, ":"); rest = substr(rest, j); pos += j - 1; } while ((i <= strlen(rest)) && (!stridx("<>[]", rest[i]))) { item += rest[i]; i++; pos++; } item += ">"; err = ""; if (!item) err = "Empty template brackets <>"; switch ((| rest[i] |)) { case "<": err = "Nested template brackets not allowed"; case "[": err = "Option bracket not allowed"; case "]": err = "Option bracket not allowed"; case ~range: err = "Unterminated template bracket."; } if ((!err) && (opt && ((| rest[i + 1] |) != "]"))) err = "Unterminated option bracket"; if (err) throw(~template, (err + " in template at ") + tostr(pos)); rest = (| substr(rest, (i + 1) + opt) |); } else { // literal i = 1; item = ""; while ((i <= strlen(rest)) && (!stridx("<>[]", rest[i]))) { item += rest[i]; i++; pos++; } err = ""; switch ((| rest[i] |)) { case ">": err = "Mismatched template bracket"; case "]": if (!opt) err = "Mismatched option bracket"; } if (opt && ((rest[i]) != "]")) err = "Unterminated option bracket"; if (err) throw(~template, ((err + " in template at ") + tostr(pos)) + "."); rest = (| substr(rest, i + opt) |); } // store item for processing items += [[item, opt]]; // and continue looping until done with template } // okay, now actually process the items for i in [1 .. items.length()] { item = (items[i])[1]; opt = (items[i])[2]; what = .parse_arg(user, item, str, (| (items[i + 1])[1] |) ? (items[i + 1]) : ""); if ((what.length()) == 2) what = what + [""]; [str, what, err] = what; if (type(what) == 'error) { if (!opt) { // not optional and not found == syntax error if (!err) { // flay the template into readability template = strsed(template, "(<[a-z\$\ _]+):[a-z\$\ _]+>", "%1>", "g"); throw(~usage, "Syntax: " + template); } // use the error if given, should be more specific throw(~usage, err); } else if (('places in flags) && (((item[1]) == "<") || ('lits in flags))) { // if it was optional, and we want placeholders // and the arg was variable or we want literals if (what == ~nomatch) { // if it was a nomatch on a variable arg, though, // then it's probably a bona fide error unless caller // specifically wants optnomatch returns // nomatch on opt literal with 'lits == ~optnf if (((item[1]) == "<") && (!('optnomatch in flags))) throw(~usage, err); else if ((item[1]) == "<") res += [~optnomatch]; else res += [~optnf]; } else { res += [~optnf]; } } } else if (what != 'space) { // spaces are just ignored // woohoo, we got something. If it's a var arg or we want // lits, then put it in the results list if (((item[1]) == "<") || ('lits in flags)) res += [what]; } // loop to next item in template list } // all done with that, is there user input left over? if (str) { // that'll be a syntax error, sir template = strsed(template, "(<[a-z\$\ _]+):[a-z\$\ _]+>", "%1>", "g"); throw(~usage, "Syntax: " + template); } // if we had literals on, we wound up with the command name in there // take it out if we don't want it if (('lits in flags) && (!('cmdname in flags))) res = res.subrange(2); // and there's your results return res; } with { // tsk tsk, syntax error in the template -- coder error if (error() == ~template) throw(~template, (traceback()[1])[2]); throw(~template, (((((((((("Unknown error occurred: " + toliteral(error())) + " debug info: rest: ") + rest) + " item: ") + item) + " pos: ") + tostr(pos)) + " i: ") + tostr(i)) + " traceback: ") + toliteral(traceback())); } // and that's the parser... nothin' to it! }; public method .del_cmd() { arg cmd; cmds = setremove(cmds, cmd); }; new object $login_if: $cmd_ifs; var $root obj_desc = "Login screen commands."; var $cmd_ifs cmds = ["connect", "create", "help", "quit"]; var $root method_descs = #[['connect_cmd, "connection to a user"], ['create_cmd, "creation of a user"], ['help_cmd, "show help information at login screen"], ['quit_cmd, "disconnect user"]]; var $root flags = ['safe]; var $root text_methods = #[]; var $interfaces priority = 0; public method .connect_cmd() { arg user, args; var uname, passw; // handoff to $anonymous [uname, passw] = (> .parse_args(user, args, "connect <username:string> <password:string>") <); user.user_connect(uname, passw); }; public method .create_cmd() { arg user, args; var uname, passw; // handoff to $anonymous [uname, passw] = (> .parse_args(user, args, "create <username:string> <password:string>") <); user.user_create(uname, passw); }; public method .help_cmd() { arg user, args; user.tell(["Commands available are:", "connect <name> <password>", "create <name> <password>", "quit"]); }; public method .quit_cmd() { arg user, args; user.tell("Byebye!"); user.close(); }; new object $person_if: $cmd_ifs; var $root obj_desc = "Person commands, includes npcs"; var $root flags = ['safe]; var $cmd_ifs cmds = ["look", "teleport", "say", "emote"]; var $root text_methods = #[]; var $interfaces priority = 1; var $cmd_ifs cmds_info = #[["say", #[['flags, ['string]]]], ["emote", #[['flags, ['string]]]]]; public method .look_cmd() { arg user, args; var what, i, s, c; // have a look around [what] = (> .parse_args(user, args, "look [at] [<something:object>]", ['places]) <); catch any { if (what) { user.tell(("You look at " + (what.name())) + "..."); user.tell(what.desc()); } else { user.tell("You look around..."); user.tell((user.loc()).name()); user.tell((user.loc()).desc()); c = filter i in ((user.loc()).contents()) where ((i != user) && (i.visible())); switch (c.length()) { case 0: user.tell("Noone else is here."); case 1: user.tell(((c[1]).name()) + " is here."); default: user.tell(("Also here are: " + (map i in (c) to (i.name()).english())) + "."); } user.tell("Exits:"); for i in ((user.loc()).exits()) { if (valid(i.dest())) user.tell(((i.name()) + " leads to ") + ((i.dest()).name())); else (| (user.loc()).check_exits() |); } } } with { user.tell("You see only darkness."); .log('debug, "Look failed: " + toliteral(traceback())); } }; public method .teleport_cmd() { arg user, args; var obj, dest; [obj, dest] = (> .parse_args(user, args, "teleport <object> [to] [<location:object>]", ['places]) <); if (!dest) { dest = obj; obj = user; } // gah, need permissions checking someday if ((obj.loc()) == dest) { user.tell(((obj == user) ? "You're" : ((obj.name()) + " is")) + " already there."); return; } // does announces and the actual move obj.move_to(dest, 'teleport); if (obj.listens()) obj.parse_line("look"); }; public method .say_cmd() { arg user, args; var what; [what] = (> .parse_args(user, args, "say <what:text>") <); what = what.quote(); (user.loc()).otell(user, ((user.name()) + " says, ") + what); user.tell("You say, " + what); }; public method .emote_cmd() { arg user, args; var what; [what] = (> .parse_args(user, args, "emote <what:text>") <); // pet peeve if (!((what[what.length()]) in [".", "!", "?", "\"", "'"])) what = what + "."; (user.loc()).announce(((user.name()) + " ") + what); }; new object $user_if: $cmd_ifs; var $root obj_desc = "Logged-in user commands."; var $cmd_ifs cmds = ["who", "quit", "paste", "passwd", "think", "help"]; var $cmd_ifs cmds_info = #[]; var $root method_descs = #[['who_cmd, "display connected users"], ['quit_cmd, "disconnect user"], ['paste_cmd, "paste a block of text for others to see"], ['paste_snarfer, "snarfer for paste command"]]; var $root text_methods = #[]; var $root flags = ['safe]; var $interfaces priority = 2; public method .who_cmd() { arg user, args; var i, l; // format list of connected users l = filter i in ($user.children()) where (i.connected()); i = l.length(); user.tell(((((("There " + ((i == 1) ? "is" : "are")) + " ") + tostr(l.length())) + " user") + ((i == 1) ? "" : "s")) + " connected:"); user.tell(strfmt("%15s %30e %30e", "Name", "Location", "Host")); for i in (l) user.tell(strfmt("%15s %30e %30e", i.name(), (i.loc()) ? ((i.loc()).name()) : "Unknown", i.ip())); }; public method .quit_cmd() { arg user, args; user.tell("Byebye!"); user.close(); }; public method .paste_cmd() { arg user, args; user.tell("Enter text, end with a \".\" on a line by itself."); user.start_snarfing(this(), 'paste_snarfer, []); }; public method .paste_snarfer() { arg user, str; var paste_block; paste_block = user.snarfer_data(); if (str != ".") { paste_block += ["| " + str]; user.set_snarfer_data(paste_block); return 'notdone; } else { (user.loc()).announce((user.name()) + " spams the world with the following:"); (user.loc()).announce(paste_block); (user.loc()).announce((user.name()) + " ceases to spam the world."); return 'done; } }; public method .passwd_cmd() { arg user, args; user.tell(["This command will allow you to change your password.", "Type your current password:"]); user.start_snarfing(this(), 'passwd_snarfer, ['current, ""]); }; public method .passwd_snarfer() { arg user, str; var state, passw; [state, passw] = user.snarfer_data(); switch (state) { case 'current: if (!(user.match_pass(str))) { user.tell("Permission denied."); return 'done; } user.tell("Now enter a new password:"); state = 'new1; user.set_snarfer_data([state, passw]); return 'notdone; case 'new1: user.tell("Enter the new password again:"); state = 'new2; passw = str; user.set_snarfer_data([state, passw]); return 'notdone; case 'new2: if (str != passw) { user.tell("Passwords don't match. Password unchanged."); return 'done; } if (!(user.set_passw(passw))) { user.tell("Password change failed."); return 'done; } user.tell("Password changed."); return 'done; } }; public method .think_cmd() { arg user, args; var text; [text] = (> .parse_args(user, args, "think <text>") <); user.tell(text); }; public method .help_cmd() { arg user, args; user.tell(["The help system is in the works, but here's a categorized list of commands to get you started. You can type any of these commands to get info on how to use them.", "", "Ordinary commands: look, teleport, say, emote, who, quit, paste, passwd, think.", "", "Building commands: dig, open, describe, name.", "", "Exploring commands: methods, vars, kids, parents, tree, show, list, ex, info.", "", "Coding commands: editvar, edit, addmethod, delmethod, eval, addvar, delvar, addcmd, delcmd, create, parent, setflag, copymeth, movemeth, objdesc, descmeth, walktree.", "", "Admin commands: shutdown, nuke, allow, can, enable, disable."]); }; new object $explorer_if: $cmd_ifs; var $root obj_desc = "Coding commands with no write access."; var $cmd_ifs cmds = ["methods", "vars", "kids", "parents", "tree", "show", "list", "ex", "info"]; var $root method_descs = #[['methods_cmd, "list methods on an object"], ['vars_cmd, "list variables on an object."], ['kids_cmd, "list children of an object"], ['parents_cmd, "list parenst of an object"], ['tree_cmd, "show object tree with optional starting object"], ['show_cmd, "show variable value"], ['list_cmd, "dump method text"], ['ex_cmd, "examine an object"], ['info_cmd, "display info about a method"]]; var $root text_methods = #[]; var $root flags = ['safe]; var $interfaces priority = 4; public method .methods_cmd() { arg user, args; var obj, i, descs; [obj] = (> .parse_args(user, args, "methods <object>") <); descs = obj.method_descs(); user.tell((("Methods on " + tostr(obj)) + ((obj.has_name()) ? ((" (" + (obj.name())) + ")") : "")) + ":"); for i in ((| obj.methods() |)) user.tell(.method_info_line(obj, i)); }; public method .vars_cmd() { arg user, args; // yeah, I know, I'll write it someday user.tell("Vars command."); }; public method .kids_cmd() { arg user, args; var obj, l, i; [obj] = (> .parse_args(user, args, "kids <object>") <); l = obj.children(); if (!listlen(l)) { user.tell(tostr(obj) + " has no children."); return; } user.tell(((tostr(obj) + " has ") + tostr(listlen(l))) + " children:"); for i in (l) user.tell(strfmt(" %20s %50e", tostr(i) + ":", (| i.obj_desc() |) || "no info")); }; public method .parents_cmd() { arg user, args; var obj, l, i; [obj] = (> .parse_args(user, args, "parents <object>") <); l = obj.parents(); if (!listlen(l)) { user.tell(tostr(obj) + " has no children."); return; } user.tell(((tostr(obj) + " has ") + tostr(listlen(l))) + " parents:"); for i in (l) user.tell(strfmt(" %20s %50e", tostr(i) + ":", (| i.obj_desc() |) || "no info")); }; public method .tree_cmd() { arg user, args; var obj; // print ascii-art hierarchy from a given spot, or from root [obj] = (> .parse_args(user, args, "tree [<root:object>]", ['places]) <); obj ?= $root; user.tell(strfmt("%70{-=}c", (" Object tree from " + obj) + " ")); $obj_lib.print_tree(user, obj); }; public method .show_cmd() { arg user, args; var str, def, v, obj, text; [def, v, obj] = (> .parse_args(user, args, "show <definer:object>.<variable:symbol> [on] [<object>]", ['places]) <); obj ?= def; if (!(obj.has_ancestor(def))) throw(~usage, ((tostr(obj) + " is not a child of ") + tostr(def)) + "!"); if ((text = (| def.get_var_on(obj, v) |)) == ~varnf) throw(~usage, (("Variable " + tostr(v)) + " not found on ") + tostr(obj)); text = toliteral(text); user.tell((((((("On " + tostr(obj)) + ", var ") + tostr(def)) + ".") + tostr(v)) + " = ") + text); }; public method .list_cmd() { arg user, args; var str, obj, meth; [obj, meth] = (> .parse_args(user, args, "list <object>.<method:symbol>", ['nolit]) <); if (!(meth in (obj.methods()))) throw(~nomatch, "Method not found on object."); user.tell(([((("Listing for " + tostr(obj)) + ".") + tostr(meth)) + "():", strfmt("%75{-}c", "begin")] + (obj.list_method(meth))) + [strfmt("%75{-}c", "end")]); }; public method .ex_cmd() { arg user, args; var obj, l, i; [obj] = (> .parse_args(user, args, "ex <object>") <); user.tell([((("Information on " + tostr(obj)) + ", child of ") + (| tostr((obj.parents())[1]) |)) + ":", obj.obj_desc(), " Object variables:"]); for l in (obj.data()) { if ((l[1]) == obj) user.tell((" variables defined on " + tostr(obj)) + ":"); else user.tell((" variables from " + tostr(l[1])) + ":"); for i in (l[2]) user.tell(strfmt(" %10s %60e", (i[1]) + ":", toliteral(i[2]))); } user.tell((" Methods defined on " + tostr(obj)) + ":"); l = obj.method_descs(); for i in (obj.methods()) user.tell(" " + (.method_info_line(obj, i))); }; public method .info_cmd() { arg user, args; var obj, meth, desc, text, i; [obj, meth] = (> .parse_args(user, args, "info <object>.<method:symbol>", ['nolit]) <); if (!(| obj.find_method(meth) |)) throw(~nomatch, (("Method " + tostr(meth)) + " not found on ") + tostr(obj)); text = obj.list_method(meth); desc = (| (obj.method_descs())[meth] |) || "no info"; user.tell([((("Info for " + tostr(obj)) + ".") + tostr(meth)) + "():", " Desc: " + desc]); for i in (text) { if ((explode(i)[1]) == "arg") { user.tell(" Args: " + join(sublist(explode(i), 2))); break; } } }; public method .method_info_line() { arg obj, meth; var desc; desc = ""; if ('native in (obj.method_flags(meth))) desc += "[native] "; if ('nooverride in (obj.method_flags(meth))) desc += "[nooverride] "; desc += (| (obj.method_descs())[meth] |) || "no info"; return strfmt("%30e %45e", ((tostr(meth) + "(") + ((obj.method_info(meth))[1])) + "):", desc); }; new object $coder_if: $cmd_ifs; var $cmd_ifs cmds = ["edit", "editvar", "addvar", "addmethod", "delmethod", "eval", "addcmd", "delcmd", "create", "addparent", "addnative", "setflag", "copymeth", "parent", "movemeth", "delvar", "objdesc", "descmeth", "walktree"]; var $coder_if interfaces = 0; var $root method_descs = #[['editvar_cmd, "edit the contents of a variable via MCP"], ['editwritevar_snarfer, "snarfer for reading text back"], ['edit_cmd, "edit a method's code via MCP"], ['editwrite_snarfer, "snarfer for reading code back"], ['discard_snarfer, "null snarfer, discards until '.' encountered."], ['addmethod_cmd, "add an empty method to the given object"], ['eval_cmd, "evaluate code as from a method on self"], ['delmethod_cmd, "remove a method"], ['addvar_cmd, "add a variable"], ['addcmd_cmd, "Add a command to an interface."], ['delmeth_cmd, "delete a method, save text to undo buffer"], ['delcmd_cmd, "delete a command from an interface"], ['create_cmd, "create a new object, optionally parenting it"], ['setparent_cmd, "set parents list of an object to a single parent"], ['setflag_cmd, "set an object flag"]]; var $cmd_ifs cmds_info = #[["eval", #[['flags, ['noquoted]]]]]; var $root obj_desc = "Coding commands requiring write access."; var $root text_methods = #[]; var $root flags = ['safe]; var $interfaces priority = 5; public method .editvar_cmd() { arg user, args; var str, def, v, obj, text, desc, dummy, write; [write, def, dummy, v, dummy, obj] = (> .parse_args(user, args, "editvar [write] <object>.<variable:symbol> [on] [<object>]", ['places, 'lits]) <); obj ?= def; if (write) { if (!(obj.has_ancestor(def))) throw(~usage, ((tostr(obj) + " is not a child of ") + tostr(def)) + "!"); if ((| def.get_var(v) |) == ~varnf) throw(~usage, ((("Variable " + tostr(v)) + " not found on ") + tostr(def)) + "."); user.start_snarfing(this(), 'editwritevar_snarfer, [def, v, obj, []]); user.tell("Enter new value, end with a dot on a line by itself."); } else { if (!(obj.has_ancestor(def))) throw(~usage, ((tostr(obj) + " is not a child of ") + tostr(def)) + "!"); if (!(v in (def.variables()))) throw(~usage, ((("Variable " + tostr(v)) + " not found on ") + tostr(def)) + "."); text = toliteral(def.get_var_on(obj, v)); desc = ((tostr(def) + ".") + tostr(v)) + ((def != obj) ? (" on " + tostr(obj)) : ""); // magic line user.tell(([(("#$# edit name: " + desc) + " upload: editvar write ") + desc] + [text]) + ["."]); } }; public method .editwritevar_snarfer() { arg user, str; var def, v, obj, block, desc; [def, v, obj, block] = user.snarfer_data(); if (str != ".") { block += [str]; user.set_snarfer_data([def, v, obj, block]); return 'notdone; } desc = ((tostr(def) + ".") + tostr(v)) + ((def != obj) ? (" on " + tostr(obj)) : ""); user.tell(("Writing " + desc) + "..."); catch any { def.set_var_on(obj, v, fromliteral(join(block))); user.tell("Successful."); } with { user.tell("Unsuccessful."); } return 'done; }; public method .edit_cmd() { arg user, args; var obj, meth, text, desc, dummy, write; [write, obj, dummy, meth] = (> .parse_args(user, args, "edit [write] <object>.<method:symbol>", ['places, 'lits]) <); if (write) { user.start_snarfing(this(), 'editwrite_snarfer, [obj, meth, []]); user.tell("Enter method text, end with a dot on a line by itself."); } else { // edit a method if (!(meth in (obj.methods()))) throw(~nomatch, ((("Method " + tostr(meth)) + " not found on ") + tostr(obj)) + "."); if ((text = obj.get_text_method(meth)) == []) text = obj.list_method(meth); desc = (tostr(obj) + ".") + tostr(meth); // the magic line for mcp edit macros, the text, and a single dot user.tell(([(("#$# edit name: " + desc) + " upload: edit write ") + desc] + text) + ["."]); } }; public method .editwrite_snarfer() { arg user, str; var obj, meth, block, err, desc; [obj, meth, block] = user.snarfer_data(); if (str != ".") { if (str != "***reedit") block += [str]; user.set_snarfer_data([obj, meth, block]); return 'notdone; } else { user.tell(((("Writing " + tostr(obj)) + ".") + tostr(meth)) + "()..."); err = obj.add_method(block, meth); //errors if (listlen(err) > 0) { catch any { desc = ((tostr(obj) + ".") + tostr(meth)) + "()"; user.tell(["***Parse errors:"] + err); user.tell("Method text was NOT changed. Editing the method again will open the copy with parse errors."); // store method text for reediting obj.set_text_method(block, meth); return 'done; } with { .log('error, "editwrite snarfer: " + toliteral(traceback())); } } else { user.tell("Successful."); obj.set_text_method([], meth); return 'done; } } }; public method .discard_snarfer() { arg user, str; if (str == ".") return 'done; return 'notdone; }; public method .addmethod_cmd() { arg user, args; var str, obj, meth, i; str = (| .parse_args(user, args, "addmethod <method:symbol> [to] <object>") |); if (str) { [meth, obj] = str; } else { str = (> .parse_args(user, args, "addmethod <object>.<method:symbol>") <); [obj, meth] = str; } if (meth in (obj.methods())) throw(~nomatch, "A method already exists with that name."); catch any { obj.add_method(["return 0;"], meth); user.tell(((("Method " + tostr(meth)) + " added to ") + tostr(obj)) + "."); } with { user.tell("There was a problem adding the method."); .log('debug, "addmethod traceback: " + toliteral(traceback())); } }; public method .eval_cmd() { arg user, args; var obj, meth, code, i, err; [obj, code] = (> .parse_args(user, args, "eval [as] [<victim:object>] <code:text>", ['places, 'optnomatch]) <); obj ?= user; if (((code[strlen(code)]) != ";") && (((explode(code)[1]) != "return") && ((explode(code)[1]) != "var"))) code = "return " + code; if ((code[strlen(code)]) != ";") code += ";"; user.tell("<= " + code); i = random(10000); meth = tosym((((("__eval_for_" + strsub(tostr(user), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); while ((| obj.find_method(meth) |)) { i++; meth = tosym((((("__eval_for_" + strsub(tostr(user), "$", "")) + "_at_") + tostr(time())) + "_") + tostr(i)); } err = obj.add_method([code], meth); if (listlen(err) > 0) { user.tell("Parse errors: " + join(err, ", ")); return; } catch any user.tell("=> " + toliteral(obj.(meth)())); with user.tell("Traceback: " + toliteral(traceback())); obj.del_method(meth); }; public method .delmethod_cmd() { arg user, args; var str, obj, meth, text; [obj, meth] = (> .parse_args(user, args, "delmethod <object>.<method:symbol>", ['nolit]) <); if (!(meth in (obj.methods()))) throw(~nomatch, ((("Method " + tostr(meth)) + " not found on object ") + tostr(obj)) + "."); text = obj.list_method(meth); str = ((tostr(obj) + ".") + tostr(meth)) + "()"; user.add_undo(text + ["// undo del_method " + str]); obj.del_method(meth); user.tell(("Deleted method " + str) + "."); }; public method .addvar_cmd() { arg user, args; var obj, v, l; l = (| .parse_args(user, args, "addvar <variable:symbol> [to] <object>", ['nolit]) |); if (!l) { l = (> .parse_args(user, args, "addvar <object>.<variable:symbol>", ['nolit]) <); [obj, v] = l; } else { [v, obj] = l; } if (v in (obj.variables())) throw(~nomatch, ((tostr(obj) + " already has a variable named ") + tostr(v)) + "."); obj.add_var(v); user.tell(((("Added variable " + tostr(v)) + " to object ") + tostr(obj)) + "."); }; public method .addcmd_cmd() { arg user, args; var obj, cmd; [cmd, obj] = (> .parse_args(user, args, "addcmd <name:string> [to] <cmd_if:object child of $cmd_ifs>") <); if ((| tosym(cmd) |) == ~symbol) throw(~nomatch, "Non-alpha chars in command name."); if (cmd in (obj.cmds())) throw(~nomatch, "Command already exists."); catch any { obj.add_cmd(cmd); if (!(tosym(cmd + "_cmd") in (obj.methods()))) obj.add_method(["arg user, args;", "", "return 0;"], tosym(cmd + "_cmd")); user.tell("Successful."); user.set_last_args([(((obj.tostr()) + ".") + cmd) + "_cmd"]); } with { user.tell("There was a problem adding the command."); .log('error, toliteral(traceback())); } }; public method .delcmd_cmd() { arg user, args; var obj, cmd, meth; [cmd, obj] = (> .parse_args(user, args, "delcmd <name:string> [from] <cmd_if:object child of $cmd_ifs>", ['nolit]) <); if ((| tosym(cmd) |) == ~symbol) throw(~nomatch, "Non-alpha chars in command name."); if (!(cmd in (obj.cmds()))) throw(~nomatch, "Command not found."); catch any { meth = ((tostr(obj) + ".") + tostr(cmd)) + "_cmd"; obj.del_cmd(cmd); if (type((| .delmethod_cmd(user, ["delmethod"] + [meth]) |)) == 'error) user.tell("Command deleted, no method found."); else user.tell("Successful."); } with { user.tell("Unsuccessful."); .log('error, toliteral(traceback())); } }; public method .create_cmd() { arg user, args; var name, obj, par; [name, @par] = (> .parse_args(user, args, "create <name:symbol> [child of] [<parent:object>]") <); par = (| par[1] |); par ?= $root; if ((| lookup(name) |)) throw(~nomatch, ("The name " + name) + " is already taken."); catch any { obj = par.spawn(name); user.tell((("Object " + tostr(obj)) + " created, parented to ") + tostr(par)); } with { user.tell("There was a problem creating the object."); .log('error, "In $coder_if.create_cmd: " + toliteral(traceback())); } }; public method .parent_cmd() { arg user, args; var obj, str, par; [obj, par] = (> .parse_args(user, args, "parent <object> [to] <newparents:list>") <); obj.set_parents(par); user.tell(((("Parents of " + tostr(obj)) + " changed to ") + (par.toenglish())) + "."); }; public method .setflag_cmd() { arg user, args; var flag, str, obj; [flag, obj] = (> .parse_args(user, args, "setflag <flag:symbol> [on] <object>") <); if (!(| obj.setflag(flag) |)) user.tell("Flag set failed."); else user.tell("Flag set."); }; public method .copymeth_cmd() { arg user, args; var objf, methf, objt, metht; [objf, methf, objt, metht] = (> .parse_args(user, args, "copymeth <from:object>.<method:symbol> [to] <to:object>[.][<newmethod:symbol>]", ['places]) <); if (!metht) metht = methf; if ((objf == objt) && (methf == metht)) throw(~nomatch, "Source and destination are the same."); if (!(methf in (objf.methods()))) throw(~nomatch, (("Method " + tostr(methf)) + " not found on ") + tostr(objf)); if (metht in (objt.methods())) throw(~nomatch, (tostr(objt) + " already has a method named ") + tostr(metht)); if ((| objt.add_method(objf.list_method(methf), metht) |) != []) user.tell("There was some kind of error in copying the method."); else user.tell(((((((("Method " + tostr(objf)) + ".") + tostr(methf)) + "() copied to ") + tostr(objt)) + ".") + tostr(metht)) + "()"); }; public method .movemeth_cmd() { arg user, args; var objf, methf, objt, metht; [objf, methf, objt, metht] = (> .parse_args(user, args, "movemeth <from:object>.<method:symbol> [to] <to:object>.<newmethod:symbol>") <); if (!metht) metht = methf; if ((objf == objt) && (methf == metht)) throw(~nomatch, "Source and destination are the same method."); if (!(methf in (objf.methods()))) throw(~nomatch, (("Method " + tostr(methf)) + " not found on ") + tostr(objf)); if (metht in (objt.methods())) throw(~nomatch, (tostr(objt) + " already has a method named ") + tostr(metht)); if ((| objt.add_method(objf.list_method(methf), metht) |) != []) { user.tell("There was some kind of error in copying the method."); } else { objf.del_method(methf); user.tell(((((((("Method " + tostr(objf)) + ".") + tostr(methf)) + "() moved to ") + tostr(objt)) + ".") + tostr(metht)) + "()"); } }; public method .delvar_cmd() { arg user, args; var obj, v, a; a = (| .parse_args(user, args, "delvar <variable:symbol> [from] <object>") |); if (a) { [v, obj] = a; } else { a = (> .parse_args(user, args, "delvar <object>.<variable:symbol>") <); [obj, v] = a; } if (!(v in (obj.variables()))) throw(~nomatch, ((tostr(obj) + " has no variable named ") + tostr(v)) + "."); obj.del_var(v); user.tell(((("Variable " + tostr(v)) + " deleted from object ") + tostr(obj)) + "."); }; public method .objdesc_cmd() { arg user, args; var obj, text; [obj, text] = (> .parse_args(user, args, "objdesc <object> [as] <text>") <); $root.set_var_on(obj, 'obj_desc, text); user.tell("Object description changed."); }; public method .descmeth_cmd() { arg user, args; var obj, meth, text; [obj, meth, text] = (> .parse_args(user, args, "descmeth <object>.<method:symbol> [as] <text>") <); if (!(meth in (obj.methods()))) throw(~nomatch, ((("No method " + tostr(meth)) + " on object ") + tostr(obj)) + "."); obj.set_method_descs(dict_add(obj.method_descs(), meth, text)); user.tell(((("Method description set for " + tostr(obj)) + ".") + tostr(meth)) + "()"); }; public method .walktree_cmd() { arg user, args; var as, obj, code, err; [as, obj, code] = (> .parse_args(user, args, "walktree [as] [<object>] [from] [<object>] [do] <code:text>", ['places, 'optnomatch]) <); as ?= user; obj ?= $root; if ((| (err = as.add_method([code], 'walktree_tmp)) |) != []) { if (err) user.tell(toliteral(err)); else user.tell("There was a problem adding the method."); return; } user.tell("Starting..."); (| ._walktree(as, obj, []) |); (| as.del_method('walktree_tmp) |); user.tell("Done."); }; public method ._walktree() { arg as, obj, seen; var i; if (obj in seen) return seen; (| as.walktree_tmp(obj) |); seen += [obj]; for i in (obj.children()) seen = ._walktree(as, i, seen); return seen; }; new object $admin_if: $cmd_ifs; var $cmd_ifs cmds = ["shutdown", "nuke", "allow", "can", "disable", "enable"]; var $root obj_desc = "All-powerful commands"; var $root text_methods = #[]; var $root flags = ['safe]; var $interfaces priority = 6; var $admin_if admin_flags = ['creation]; var $admin_if admin_flag_descs = #[['creation, "allow player creation"], ['debug_port, "enable the debug_port (insecure)"]]; var $admin_if admin_flag_names = ['creation, 'debug_port]; public method .shutdown_cmd() { arg user, args; .log('admin, (user.name()) + " requests a server shutdown."); user.tell("Shutting down now."); $user.announce('all, "System: Server shutdown."); $sys.shutdown(); }; public method .nuke_cmd() { arg user, args; var obj; [obj] = (> .parse_args(user, args, "nuke <user>") <); // confirm user.tell(("Are you -sure- you want to permanently destroy user " + (obj.name())) + "? [yes/NO]"); user.start_snarfing(this(), 'nuke_confirm, obj); }; public method .nuke_confirm() { arg user, str; var obj; obj = user.snarfer_data(); if (lowercase(str) != "yes") { user.tell("Canceling."); return 'done; } obj.tell(("You have the honor of being nuked by the illustrious " + (user.name())) + "."); obj.nuke_user(); user.tell("User nuked."); return 'done; }; public method .allow_cmd() { arg user, args; var who, what; // not terribly usable [who, what] = (> .parse_args(user, args, "allow <user> [to] <what:string>") <); what = (| tosym(what) |); if (what in ['build, 'tinker, 'architect, 'explore, 'code, 'admin]) { who.add_perm(what); user.tell(((("Okay, " + (who.name())) + " can now ") + tostr(what)) + "."); } else { user.tell("What do you want to allow them to do? (build, explore, code, admin)"); } }; public method .can_cmd() { arg user, args; var who, what, targ, i; // not terribly usable [who, what, targ] = (> .parse_args(user, args, "can <who:user> [do] <what:symbol> [on] [<target:object>]", ['places]) <); if (targ) i = who.can(what, targ); else i = who.can(what); if (i) user.tell(((("Yes, " + (who.name())) + " can ") + tostr(what)) + "."); else user.tell(((("No, " + (who.name())) + " cannot ") + tostr(what)) + "."); }; public method .disable_cmd() { arg user, args; var what, i; admin_flags ?= []; [what] = (> .parse_args(user, args, "disable [<what:symbol>]", ['places]) <); if (!what) { user.tell("Changeable flags:"); user.tell(strfmt(" %15e %45e %10e", "Flag", "Desc", "State")); for i in (admin_flag_names) user.tell(strfmt(" %15e %45e %10e", tostr(i) + ":", (| admin_flag_descs[i] |), (i in admin_flags) ? "enabled" : "disabled")); return; } if (!(what in admin_flag_names)) throw(~nomatch, "Flag not found."); if (!(what in admin_flags)) { user.tell("Flag already disabled."); } else { admin_flags = admin_flags.setremove(what); user.tell("Flag disabled."); } }; public method .enable_cmd() { arg user, args; var what, i; admin_flags ?= []; [what] = (> .parse_args(user, args, "enable [<what:symbol>]", ['places]) <); if (!what) { user.tell("Changeable flags:"); user.tell(strfmt(" %15e %45e %10e", "Flag", "Desc", "State")); for i in (admin_flag_names) user.tell(strfmt(" %15e %45e %10e", tostr(i) + ":", (| admin_flag_descs[i] |), (i in admin_flags) ? "enabled" : "disabled")); return; } if (!(what in admin_flag_names)) throw(~nomatch, "Flag not found."); if (what in admin_flags) { user.tell("Flag already enabled."); } else { admin_flags = admin_flags.setadd(what); user.tell("Flag enabled."); } if (('creation in admin_flags) && ($admin_if in ($user.parsers()))) user.tell("WARNING: New users will have full administrative priveleges. Type 'disable creation' or change $parses.parsers on $user to change this. (see help editvar)"); }; public method .admin_flags() { return admin_flags; }; new object $builder_if: $cmd_ifs; var $cmd_ifs cmds = ["dig", "open", "describe", "name"]; var $root text_methods = #[]; var $root obj_desc = "Building commands"; var $interfaces priority = 3; var $root flags = ['safe]; public method .dig_cmd() { arg user, args; var dir, name, room, exit; [dir, name] = (> .parse_args(user, args, "dig <direction:string> [<name:text>]", ['places]) <); if (!name) name = "Unnamed Room"; catch any { if (user.match_exit(dir)) throw(~nomatch, ("Your location already has an exit named " + dir) + "."); // blergiful, need to restrict create() room = create([$place]); room.set_owner(user); room.set_name(name); exit = (user.loc()).add_exit(user, dir, room); if (dir.is_compassdir()) room.add_exit(user, dir.compassdir_opposite(), user.loc()); user.move_through(exit); } with { user.tell("There was a problem digging the room."); .log('error, toliteral(traceback())); } }; public method .open_cmd() { arg user, args; var dir, dest; if ((args.last()) == "last") { // special case [dir] = (> .parse_args(user, args, "open <direction:string> [to] last") <); dest = user.last_loc(); } else { [dir, dest] = (> .parse_args(user, args, "open <direction:string> [to] <destination:object>") <); } if (user.match_exit(dir)) throw(~nomatch, ("Your location already has an exit matching " + dir) + "."); if ((!valid(dest)) || (!(dest.is($place)))) throw(~nomatch, "Destination specified is not a $place."); (user.loc()).add_exit(user, dir, dest); user.tell(((("Exit named " + dir) + " leading to ") + (dest.name())) + " added successfully."); }; public method .describe_cmd() { arg user, args; var obj, text; [obj, text] = (> .parse_args(user, args, "describe <object> [as] <text>") <); if (!(user.can('change_desc, obj))) throw(~perm, "You aren't allowed to change that description."); obj.set_desc(text); user.tell("Description set."); }; public method .name_cmd() { arg user, args; var obj, name; [obj, name] = (> .parse_args(user, args, "name <real object:object child of $real> <new name:text>") <); if (!(user.can('build, obj))) throw(~perm, "You aren't allowed to change that object's name."); obj.set_name(name); user.tell("Name changed."); }; new object $preparsers: $interfaces; var $root obj_desc = "Preparsers, which manipulate the user's input"; var $root flags = ['safe]; new object $alias_if: $preparsers; var $alias_if aliases = #[["l", "look"], ["tel", "teleport"], ["@tel", "teleport"], ["desc", "describe"]]; var $root text_methods = #[]; var $root obj_desc = "Translates aliases into actual commands"; var $root flags = ['safe]; public method .parse() { arg user, str; var word; // ugly hack for single-char aliases if (!str) return ""; if (((str[1]) == "'") || ((str[1]) == "\"")) return "say " + (str.substr(2)); if (((str[1]) == ":") || ((str[1]) == ";")) { if ((| str[2] |) == ":") { if ((str.substr((str.length()) - 1)) == "::") str = (str.substr(3, (str.length()) - 4)) + "."; else str = str.substr(3); } else { str = str.substr(2); } return "emote " + str; } word = str.first(); if ((word in (aliases.keys())) == 0) return str; return ((aliases[word]) + " ") + (str.rest()); }; new object $null_if: $interfaces; var $root obj_desc = "Handles empty commands."; var $root method_descs = #[['parse, "Responsible for discarding empty commands."]]; var $root flags = ['safe]; var $interfaces priority = -1; public method .parse() { arg user, str; var i; for i in [1 .. str.length()] { if ((str[i]) != " ") return; } // an empty command gets slurped here return 'handled; }; new object $exits_if: $interfaces; var $root text_methods = #[]; var $interfaces priority = 7; var $root flags = ['safe]; public method .parse() { arg user, str; var exit, i; exit = (| user.match_exit(str) |); if (exit == ~ambig) { // messy, I know, but it works exit = (traceback[1])[2]; user.tell((((str.quote()) + " could be ") + (map i in (exit) to (i.name()).english("nothing?", " or "))) + "."); return 'handled; } else if (!exit) { if (str.is_compassdir()) { if (str.is_compassdir_short()) str = str.compassdir_long(); user.tell(("You can't go " + str) + " from here."); return 'handled; } return; } user.move_through(exit); return 'handled; }; new object $last_chance: $interfaces; var $root text_methods = #[]; var $root flags = ['safe]; public method .parse() { arg user, str; if (!(user.can('code))) return; // coders defaults if ((| user.match_object(str) |)) { user.parse("ex " + str); return 'handled; } if ((| $cmd_ifs.parse_args(user, str, "<object>.<symbol>") |)) { user.parse("list " + str); return 'handled; } return; }; new object $debug_port: $root; var $root obj_desc = "Secondary listener for parsing simple debug commands."; var $debug_port port = 4243; var $debug_port log_lines = ["$listener: boot: port bound successfully.", "$sys: boot: calling $debug_port.startup()", "$debug_port: debug: The debug port is disabled in the current configuration.", "$sys: boot: calling $user.startup()", "$sys: boot: calling $anonymous.startup()", "$sys: boot: Startup complete.", "$sys: boot: Detected fresh install, deleting non-core objects...", "$root: db: $sys destroyed $user_spoon.", "$sys: boot: -------------------- Booting XidusCore: Tue Jul 4 17:41:53 2000", "$sys: boot: Startup called by driver.", "$sys: boot: calling $listener.startup()", "$listener: boot: startup called", "$listener: boot: port bound successfully.", "$sys: boot: calling $debug_port.startup()", "$debug_port: debug: The debug port is disabled in the current configuration.", "$sys: boot: calling $user.startup()", "$sys: boot: calling $anonymous.startup()", "$sys: boot: Startup complete.", "$sys: boot: If you're seeing this spam on the console, then make sure there's a 'logs' subdirectory, and restart the server."]; var $debug_port log_max = 20; var $root flags = ['safe]; var $root text_methods = #[]; driver method .connect() { .welcome(); }; public method .welcome() { var i; .tell("Connected."); .tell(("Last " + listlen(log_lines)) + " lines:"); for i in (log_lines) .tell(i); }; public method .match_method() { arg user, str; var obj, meth; if (substr(str, strlen(str) - 1) == "()") str = substr(str, 1, strlen(str) - 2); if (!stridx(str, ".")) throw(~nomatch, "Bad method reference. Try '$object.method'."); [obj, meth] = match_pattern(str, "*.*"); if (!(obj = (| user.match_object(user, obj) |))) throw(~nomatch, "Object not found."); if (!(meth = (| tosym(meth) |))) throw(~nomatch, "Bad method name. Try '$object.method'."); if (!(| obj.find_method(meth) |)) throw(~nomatch, "Method not present on object."); return [obj, meth]; }; public method .tell() { arg str; var i; if (type(str) == 'string) { cwrite(str_to_buf(str + "\n")); } else if (type(str) == 'list) { for i in (str) { if (type(i) == 'string) cwrite(str_to_buf(i + "\n")); else throw(~unsupp, "Unsupported data type in $debug_port.tell"); } } else { throw(~unsupp, "Unsupported data type in $debug_port.tell"); } }; driver method .disconnect() { // }; public method .startup() { if (!('debug_port in ($admin_if.admin_flags()))) .log('debug, "The debug port is disabled in the current configuration."); else if (!(| bind_port(port) |)) (| .log('debug, ("Debug port unavailable, was hoping to get port " + port) + ".") |); else .log('debug, ("Debug port running on port " + port) + "."); }; driver method .parse() { arg buf; var str, args, cmd, obj, objn, tmp, i, meth; // ugly parser, should be able to withstand a nuclear attack. ;) // general idea is that it'll keep working even if I break things // severely in the rest of the core, especially the parser // it's saved me a few times, so I'm happy with it. :) // // oh, and debug_port also lets you watch the server log, although // there are easier ways to do that // catch any { str = buf_to_str(buf.chop()); args = explode(str); cmd = (| args[1] |); obj = (| fromliteral(args[2]) |); objn = (| tostr(obj) |); args = sublist(args, 2); switch (cmd) { case "tree": $obj_lib.print_tree($debug_port, obj); case "dump": [obj, meth] = .match_method($user_xidus, args[1]); .tell(obj.list_method(meth)); case "data": .tell((("Data for " + objn) + ":") + toliteral(obj.data())); case "kids": .tell((("Kids of " + objn) + ": ") + (obj.children())); case "parents": .tell((("Parents of " + objn) + ": ") + (obj.parents())); case "add_var": tmp = tosym(args[2]); obj.add_var(tmp); case "evalas": tmp = join(sublist(args, 2)); .tell((("Adding method to " + objn) + ": ") + (obj.add_method([tmp], 'tmp_eval))); .tell("Output: " + (obj.tmp_eval())); obj.del_method('tmp_eval); case "eval": tmp = join(args); obj = $debug_port; objn = tostr(obj); .tell("Code: " + tmp); .tell((("Adding method to " + objn) + ": ") + (obj.add_method([tmp], 'tmp_eval))); .tell("Output: " + (obj.tmp_eval())); obj.del_method('tmp_eval); case "tail": .tell(("Last " + listlen(log_lines)) + " lines:"); for i in (log_lines) .tell(i); case "shutdown": $sys.shutdown(); case "quit": .tell("Byebye!"); close_connection(); default: .tell(("Command " + tostr(str)) + " not found."); } } with { .tell("Traceback: " + toliteral(traceback())); } }; public method .debug_log() { arg str; log_lines = log_lines + [str]; if (listlen(log_lines) == log_max) log_lines = sublist(log_lines, 2); .tell(str); }; eval { // flag the fresh install for $sys, it'll take care of the rest catch any { $sys.add_var('freshinstall); } with { dblog(""); dblog("Traceback was: " + toliteral(traceback())); } };