/
ColdCore-3.0a8/
ColdCore-3.0a8/src/
new object $robot: $body, $has_reactions;

var $body body_parts = #[];
var $described prose = <$ctext_frob, [["A generic automated robot object."], #[['this, $robot]]]>;
var $has_commands remote = #[["@reactions", [["@reactions", "*", "@reactions <this>", 'reactions_cmd, #[[1, ['this, []]]]]]]];
var $has_name name = ['uniq, "Robot", "the Robot"];
var $located location = $lost_and_found;
var $located obvious = 1;
var $location contents = [];
var $robot active = 0;
var $robot active_ids = 0;
var $robot last_id = 0;
var $robot match_types = #[["regexp", ['regexp, "rex"]], ["pattern", ['match_pattern, "pat"]], ["template", ['match_template, "tmp"]]];
var $robot reactions = 0;
var $root created_on = 796268969;
var $root flags = ['methods, 'code, 'variables, 'core, 'fertile, 'command_cache];
var $root inited = 1;
var $root managed = [$robot];
var $root manager = $robot;
var $thing gender = $gender_neuter;

public method .activate_reaction() {
    arg id;
    var ra, key, e, inserted, chance, times;
    
    if ((!reactions) || (!dict_contains(reactions, id)))
        throw(~noreaction, ("No reaction with id '" + id) + "'.");
    if (!active)
        active = #[];
    [key, chance, times] = sublist(reactions[id], 3, 3);
    if (dict_contains(active, key)) {
        ra = active[key];
        if (ra.contains(id))
            return .update_active(key, id, [chance, times]);
        active = dict_del(active, key);
        ra = map e in (ra) to (e);
    } else {
        ra = [];
    }
    for e in [1 .. listlen(ra)] {
        if ((((ra[e])[2])[1]) < chance) {
            ra = insert(ra, e, [id, [chance, times]]);
            inserted++;
        }
    }
    if (!inserted)
        ra += [[id, [chance, times]]];
    active = dict_add(active, key, hash e in (ra) to (e));
    active_ids = dict_add(active_ids || #[], id, key);
};

public method .active() {
    return active;
};

public method .add_reaction() {
    arg @args;
    var r, id;
    
    // make sure it doesn't already exist..
    for r in (reactions || #[]) {
        if (((r[2])[2]) == (args[2])) {
            if ((r[2]) == args)
                return r[1];
        }
    }
    (> .check_reaction_args(@args) <);
    reactions = dict_add(reactions || #[], ++last_id, args);
};

private method .check_reaction_args() {
    arg method, template, type, chance, times, hook, min, max;
    
    (> .check_reaction_matchwith(method) <);
    (> .check_reaction_template(template) <);
    (> .check_reaction_type(type) <);
    (> .check_reaction_chance(chance) <);
    (> .check_reaction_times(times) <);
    (> .check_reaction_hook(hook) <);
    (> .check_reaction_hook_method(hook[1]) <);
    (> .check_reaction_hook_args(hook[2]) <);
    (> .check_reaction_min(min) <);
    (> .check_reaction_max(max) <);
};

private method .check_reaction_chance() {
    arg chance;
    
    if (type(chance) != 'integer)
        throw(~type, "Reaction chance is not a integer.");
};

private method .check_reaction_hook() {
    arg hook;
    
    if (type(hook) != 'list)
        throw(~type, "Reaction hook is not a list.");
    if (listlen(hook) != 2)
        throw(~type, "Reaction hook is not a two element list.");
};

private method .check_reaction_hook_args() {
    arg args;
    
    if (type(args) != 'list)
        throw(~type, "Reaction hook arguments is not a list.");
};

private method .check_reaction_hook_method() {
    arg method;
    
    if (type(method) != 'symbol)
        throw(~type, "Reaction hook method is not a symbol.");
};

private method .check_reaction_matchwith() {
    arg method;
    
    if (type(method) != 'symbol)
        throw(~type, "Match type is not a symbol.");
};

private method .check_reaction_max() {
    arg max;
    
    if (type(max) != 'integer)
        throw(~type, "Reaction maximum delay is not a integer.");
};

private method .check_reaction_min() {
    arg min;
    
    if (type(min) != 'integer)
        throw(~type, "Reaction minimum delay is not a integer.");
};

private method .check_reaction_template() {
    arg template;
    
    if (type(template) != 'string)
        throw(~type, "Match template is not a string.");
};

private method .check_reaction_times() {
    arg times;
    
    if (type(times) != 'integer)
        throw(~type, "Reaction times is not a integer.");
};

private method .check_reaction_type() {
    arg type;
    
    if (type(type) != 'symbol)
        throw(~type, "Reaction type is not a symbol.");
};

protected method .check_reactions() {
    arg type, str, sender;
    var rnum, t, id, chance, times, method, template, m, types;
    
    rnum = random(100);
    if (type == 'tell)
        types = ['tell, 'any];
    else
        types = [type, 'notell, 'any];
    for t in (types) {
        if (!dict_contains(active, t))
            continue;
        for id in (dict_keys(active[t])) {
            [chance, times] = (active[t])[id];
            if (rnum > chance)
                break;
            [method, template] = reactions[id];
            if ((!template) || (m = str.(method)(template))) {
                if (times == 1)
                    .remove_active(t, id);
                else if (times > 1)
                    .update_active(t, id, [chance, --times]);
                if ((.do_reaction(str, m, id, sender)) != 'continue)
                    return;
            }
        }
    }
};

public method .deactivate_reaction() {
    arg id;
    
    if (active_ids && dict_contains(active_ids, id)) {
        .remove_active(active_ids[id], id);
        active_ids = dict_del(active_ids, id);
        if (!active_ids)
            clear_var('active_ids);
    }
};

public method .del_reaction() {
    arg id;
    var key;
    
    (> .perms(sender()) <);
    if (!dict_contains(reactions, id))
        return;
    key = (reactions[id])[3];
    (| .remove_active(key, id) |);
    reactions = reactions.del(id);
};

protected method .do_reaction(): forked  {
    arg str, match, id, sender;
    var method, args, min, max, time, range;
    
    [[method, args], min, max] = sublist(reactions[id], 6);
    range = max - min;
    if (range < 2)
        time = 1;
    else
        time = random(range) + min;
    $scheduler.sleep(time);
    catch any
        return (> .(method)(str, match, sender, @args) <);
    with
        .tell_traceback(traceback());
    return 'stop;
};

public method .event_notify() {
    arg event, origin, @args;
    
    if (caller() != $event_handler)
        throw(~perm, caller() + " is not $event_handler.");
    if (!(.active()))
        return;
    if (event == 'social)
        .check_reactions(args[2], args[3], args[1]);
};

public method .match_type() {
    arg type;
    
    type = strsed(tostr(type), "match_", "");
    if (!dict_contains(match_types, type))
        throw(~type, "Invalid type matcher: " + type);
    return (| match_types[type] |);
};

public method .parse_line() {
    arg line;
    var parse;
    
    (> .perms(sender()) <);
    catch any {
        parse = $command_parser.parse(this(), line, $null_parser);
        (> .handle_parser_result(@parse) <);
    } with {
        if (error() == ~stop) {
            if ((traceback()[1])[2])
                .tell((traceback()[1])[2]);
        } else if ((.manager()) != this()) {
            (.manager()).tell_traceback(traceback(), line, 0, error());
        }
    }
};

protected method .react_command() {
    arg str, match, sender, cmd;
    
    .parse_line(cmd);
};

protected method .react_subcmd() {
    arg str, match, sender, cmd;
    var m;
    
    cmd = strsub(cmd, "%P", sender.name());
    for m in [1 .. listlen(match)]
        cmd = strsub(cmd, "%" + m, match[m]);
    .parse_line(cmd);
};

public method .reactions() {
    return reactions;
};

public method .reactions_cmd() {
    arg cmdstr, cmd, this;
    var id, m, tmpl, type, chance, times, method, args, max, min, out, t, a;
    
    // [id, [match, template, type, chance, times, [method, args], max, min]]
    out = [];
    for id in (dict_keys(reactions || #[])) {
        [m, tmpl, type, chance, times, [method, args], min, max] = reactions[id];
        t = $robot.match_type(m);
        a = dict_contains(active_ids, id);
        out += [strfmt("%l%3r %3r %4r %6l %8c %22l %l %l", a ? "*" : " ", id, chance, (times == (-1)) ? "inf" : times, type, (min == max) ? min : ((min + "~") + max), method, t[2], tmpl ? (("\"" + tmpl) + "\"") : "anything")];
    }
    if (out)
        return (["-- Defined Reactions:", "  ID %CH    # TYPE    DELAY   HOOK                   MT  TEMPLATE"] + out) + ["--"];
    return "-- No Reactions Defined --";
};

protected method .remove_active() {
    arg key, id;
    
    active = dict_add(active, key, dict_del(active[key], id));
    if (!(active[key]))
        active = dict_del(active, key);
    if (!active)
        clear_var('active);
};

public method .startup() {
    .hook_events('startup);
};

public method .tell() {
    arg what, @who;
    var line;
    
    if (!(.active()))
        return;
    switch (type(what)) {
        case 'list:
            for line in (what)
                .tell(line, @who);
        case 'string:
            // drop through, this is what we want
        default:
            return;
    }
    if (who && (sender().is($place)))
        who = who[1];
    else
        who = sender();
    if (who == this())
        return;
    .check_reactions('tell, what, who);
};

public method .tell_traceback() {
    arg @args;
    
};

protected method .update_active() {
    arg key, id, value;
    
    active = dict_add(active, key, dict_add(active[key], id, value));
};

public method .update_reaction() {
    arg id, part, value;
    var r, chance, times, active;
    
    (> .perms(sender()) <);
    if (!(reactions.contains(id)))
        throw(~noreaction, ("No reaction with id \"" + id) + "\".");
    r = reactions[id];
    if ((active = dict_contains(active_ids, id)))
        .deactivate_reaction(id);
    switch (part) {
        case 'matchwith:
            (> .check_reaction_matchwith(value) <);
            r = replace(r, 1, value);
        case 'template:
            (> .check_reaction_template(value) <);
            r = replace(r, 2, value);
        case 'type:
            (> .check_reaction_type(value) <);
            r = replace(r, 3, value);
        case 'chance:
            (> .check_reaction_chance(value) <);
            r = replace(r, 4, value);
        case 'times:
            (> .check_reaction_times(value) <);
            r = replace(r, 5, value);
        case 'method, 'hook_method:
            (> .check_reaction_hook_method(value) <);
            r = replace(r, 6, replace(r[6], 1, value));
        case 'args, 'hook_args:
            (> .check_reaction_hook_args(value) <);
            r = replace(r, 6, replace(r[6], 2, value));
        case 'min, 'min_delay:
            (> .check_reaction_min(value) <);
            r = replace(r, 7, value);
        case 'max, 'max_delay:
            (> .check_reaction_max(value) <);
            r = replace(r, 8, value);
        default:
            throw(~invpart, ("Invalid part '" + part) + ".");
    }
    reactions = reactions.add(id, r);
    if (active)
        .activate_reaction(id);
};