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