XidusCore-v0.42/
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()));
	}
};