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