-- This is a replacement automatizer Lua script for ToME version 2.3.x and -- 2.2.y (for y >= 6). It includes a pristine copy of the original -- auto.lua, thus providing all the functionality of the original, and adds -- extra code to provide additional functionality: -- -- - New clauses and , which are true if any -- object in your inventory resp. equipment match the enclosed clause. -- - A new parser, allowing automat.atm files to be written in a more -- human-readable and -writable language which I've dubbed ATP (short -- for 'autoparse', the original ATP parsing Perl script). -- - A new utility to test automatizer rules against items in your -- inventory, accessible as a power under the 'U' menu. -- -- As noted above, all original automatizer functionality is still present; -- your existing automat.atm file should continue to work just fine. -- -- For documentation of the ATP language and a sample ATP automatizer file, -- see the Autoparse project at -- . -- Change log: -- -- 2007-01-07: -- Added automatizer rule tester. -- -- 2007-01-01: -- Initial release. -- ===================== BEGIN ORIGINAL auto.lua ========================= -- This file is the core of the Automatizer -- Please do not touch unless you know what you are doing __rules = {} __rules_max = 0 rule_aux = {} -- Rule apply function, does .. nothing function auto_nothing(obj, item) return end function auto_inscribe(obj, item, note) if obj.note ~= 0 then return end msg_print("") obj.note = quark_add(note) return TRUE end -- Rule apply function, pickup object function auto_pickup(obj, item) if item >= 0 then return end if inven_carry_okay(obj) == FALSE then return end msg_print("") object_pickup(-item) return TRUE end -- Rule apply function, destroy item function auto_destroy(obj, item) -- be carefull to what we can destroy -- Unaware things won't be destroyed. if is_aware(obj) == FALSE then return end -- Inscribed things won't be destroyed! if obj.note ~= 0 then return end -- Keep Artifacts -- they cannot be destroyed anyway if is_artifact(obj) == TRUE then return end -- Cannot destroy CURSE_NO_DROP objects local f1, f2, f3, f4, f5, esp = object_flags(obj); if band(f4, TR4_CURSE_NO_DROP) ~= 0 and band(obj.ident, IDENT_CURSED) then return end msg_print("") -- Eliminate the item (from the pack) if item >= 0 then inven_item_increase(item, -obj.number) inven_item_describe(item) inven_item_optimize(item) -- Eliminate the item (from the floor) else floor_item_increase(0 - item, -obj.number) floor_item_describe(0 - item) floor_item_optimize(0 - item) end return TRUE end -- Report the status of an object function object_status(obj) local sense = { [SENSE_CURSED] = "bad", [SENSE_WORTHLESS] = "very bad", [SENSE_AVERAGE] = "average", [SENSE_GOOD_LIGHT] = "good", [SENSE_GOOD_HEAVY] = "good", [SENSE_EXCELLENT] = "very good", [SENSE_SPECIAL] = "special", [SENSE_TERRIBLE] = "terrible", } if is_known(obj) == FALSE then if sense[obj.sense] then return sense[obj.sense] else return "" end else if nil then -- test local osense = -1 local type = select_sense(obj, TRUE, TRUE) if type == 1 then osense = value_check_aux1(obj) elseif type == 2 then osense = value_check_aux1_magic(obj) end print("type : "..type) if sense[osense] then print("sense: "..sense[osense]) return sense[osense] else print("sense: ") return "" end else -- the real one local slot = wield_slot_ideal(obj, TRUE) -- Arts items if is_artifact(obj) == TRUE then if band(obj.ident, IDENT_CURSED) == 0 then return "special" else return "terrible" end -- Ego items elseif (obj.name2 > 0 or obj.name2b > 0) then if band(obj.ident, IDENT_CURSED) == 0 then return "very good" else return "very bad" end -- weapon elseif (slot == INVEN_WIELD) or (slot == INVEN_BOW) or (slot == INVEN_AMMO) or (slot == INVEN_TOOL) then if obj.to_h + obj.to_d < 0 then return "bad" elseif obj.to_h + obj.to_d > 0 then return "good" else return "average" end -- armor elseif (slot >= INVEN_BODY) and (slot <= INVEN_FEET) then if obj.to_a < 0 then return "bad" elseif obj.to_a > 0 then return "good" else return "average" end -- ring elseif slot == INVEN_RING then if (obj.to_d + obj.to_h < 0) or (obj.to_a < 0) or (obj.pval < 0) then return "bad" else return "average" end -- amulet elseif slot == INVEN_NECK then if (obj.pval < 0) then return "bad" else return "average" end -- chests elseif obj.tval == TV_CHEST then if obj.pval == 0 then return "empty" elseif obj.pval < 0 then return "disarmed" else return "average" end else return "average" end end end end -- Recursive function to generate a rule function tree function gen_rule_fct(r) -- It is a test rule (or, and, ...) if r.label == "and" or r.label == "or" then local i local fct_tbl = {} for i = 1, getn(r) do if r[i].label ~= "comment" then tinsert(fct_tbl, gen_rule_fct(r[i])) end end if r.label == "and" then return function(object) local fcts = %fct_tbl local i for i = 1, getn(fcts) do if not fcts[i](object) then return end end return TRUE end elseif r.label == "or" then return function(object) local fcts = %fct_tbl local i for i = 1, getn(fcts) do if fcts[i](object) then return TRUE end end end end -- It is a condition rule (name, type, level, ...) else if r.label == "not" then local f if not r[1] then f = function (object) return TRUE end else f = gen_rule_fct(r[1]) end return function(object) return not %f(object) end elseif r.label == "name" then return function(object) if strlower(object_desc(object, -1, 0)) == strlower(%r[1]) then return TRUE end end elseif r.label == "contain" then return function(object) if strfind(strlower(object_desc(object, -1, 0)), strlower(%r[1])) then return TRUE end end elseif r.label == "symbol" then return function(object) if strchar(get_kind(object).d_char) == %r[1] then return TRUE end end elseif r.label == "inscribed" then return function(object) if object.note ~= 0 and strfind(strlower(quark_str(object.note)), strlower(%r[1])) then return TRUE end end elseif r.label == "discount" then local d1 = r.args.min local d2 = r.args.max if tonumber(d1) == nil then d1 = getglobal(d1) else d1 = tonumber(d1) end if tonumber(d2) == nil then d2 = getglobal(d2) else d2 = tonumber(d2) end return function(object) if is_aware(object) == TRUE and object.discount >= %d1 and object.discount <= %d2 then return TRUE end end elseif r.label == "tval" then local tv = r[1] if tonumber(tv) == nil then tv = getglobal(tv) else tv = tonumber(tv) end return function(object) if object.tval == %tv then return TRUE end end elseif r.label == "sval" then assert(r.args.min and r.args.max, "sval rule lacks min or max") local sv1 = r.args.min local sv2 = r.args.max if tonumber(sv1) == nil then sv1 = getglobal(sv1) else sv1 = tonumber(sv1) end if tonumber(sv2) == nil then sv2 = getglobal(sv2) else sv2 = tonumber(sv2) end return function(object) if is_aware(object) == TRUE and object.sval >= %sv1 and object.sval <= %sv2 then return TRUE end end elseif r.label == "status" then return function(object) if object_status(object) == strlower(%r[1]) then return TRUE end end elseif r.label == "state" then if r[1] == "identified" then return function(object) if is_known(object) == TRUE then return TRUE end end else return function(object) if is_known(object) == FALSE then return TRUE end end end elseif r.label == "race" then return function(object) if strlower(get_race_name()) == strlower(%r[1]) then return TRUE end end elseif r.label == "subrace" then return function(object) if strlower(get_subrace_name()) == strlower(%r[1]) then return TRUE end end elseif r.label == "class" then return function(object) if strlower(get_class_name()) == strlower(%r[1]) then return TRUE end end elseif r.label == "level" then assert(r.args.min and r.args.max, "level rule lacks min or max") return function(object) if player.lev >= tonumber(%r.args.min) and player.lev <= tonumber(%r.args.max) then return TRUE end end elseif r.label == "skill" then assert(r.args.min and r.args.max, "skill rule lacks min or max") local s = find_skill_i(r[1]) assert(s ~= -1, "no skill "..r[1]) return function(object) if get_skill(%s) >= tonumber(%r.args.min) and get_skill(%s) <= tonumber(%r.args.max) then return TRUE end end elseif r.label == "ability" then local s = find_ability(r[1]) assert(s ~= -1, "no ability "..r[1]) return function(object) if has_ability(%s) == TRUE then return TRUE end end end end end function auto_inscribe_maker(inscription) return function(...) arg.n = arg.n + 1 arg[getn(arg)] = %inscription return call(auto_inscribe, arg) end end -- Generate a rule from a table function gen_full_rule(t) -- only honor rules for this module if not t.args.module then t.args.module = "ToME" end if not ((t.args.module == "all") or (t.args.module == game_module)) then return function() end end -- Check for which action to do local apply_fct = auto_nothing if t.args.type == "destroy" then apply_fct = auto_destroy elseif t.args.type == "pickup" then apply_fct = auto_pickup elseif t.args.type == "inscribe" then apply_fct = auto_inscribe_maker(t.args.inscription) end -- create the function tree local rf if t[1] then rf = gen_rule_fct(t[1]) else rf = function (object) end end -- create the final function return function(...) local rf = %rf if rf(arg[1]) then if call(%apply_fct, arg) == TRUE then return TRUE end end end end -- Create a function that checks for the rules(passed in xml form) function add_ruleset(s) local tbl = xml:collect(s) local i -- Add all rules for i = 1, getn(tbl) do local t = tbl[i] if t.label == "rule" then -- Create the function tree local fct = gen_full_rule(t) -- Create the test function __rules[__rules_max] = { ["table"] = t, ["fct"] = fct } __rules_max = __rules_max + 1 end end end -- Apply the current rules to an object -- call with at least (object, idx) function apply_rules(...) local i for i = 0, __rules_max - 1 do if call(__rules[i].fct, arg) then return TRUE end end return FALSE end -- Clear the current rules function clean_ruleset() __rules_max = 0 __rules = {} end ------ helper fonctions for the GUI auto_aux = {} auto_aux.stack = { n = 0 } auto_aux.idx = 1 auto_aux.rule = 1 function auto_aux:go_right() if auto_aux.rule[1] and type(auto_aux.rule[1]) == "table" then tinsert(auto_aux.stack, auto_aux.idx) tinsert(auto_aux.stack, auto_aux.rule) auto_aux.rule = auto_aux.rule[1] auto_aux.idx = 1 end end function auto_aux:go_left(sel) local n = getn(auto_aux.stack) if n > 0 then auto_aux.idx = auto_aux.stack[n - 1] auto_aux.rule = auto_aux.stack[n] tremove(auto_aux.stack) tremove(auto_aux.stack) end end function auto_aux:go_down() if getn(auto_aux.stack) > 1 then if auto_aux.stack[getn(auto_aux.stack)][auto_aux.idx + 1] then auto_aux.idx = auto_aux.idx + 1 auto_aux.rule = auto_aux.stack[getn(auto_aux.stack)][auto_aux.idx] end end end function auto_aux:go_up() if getn(auto_aux.stack) > 1 then if auto_aux.stack[getn(auto_aux.stack)][auto_aux.idx - 1] then auto_aux.idx = auto_aux.idx - 1 auto_aux.rule = auto_aux.stack[getn(auto_aux.stack)][auto_aux.idx] end end end function auto_aux:scroll_up() xml.write_off_y = xml.write_off_y - 1 end function auto_aux:scroll_down() xml.write_off_y = xml.write_off_y + 1 end function auto_aux:scroll_left() xml.write_off_x = xml.write_off_x + 1 end function auto_aux:scroll_right() xml.write_off_x = xml.write_off_x - 1 end function auto_aux:adjust_current(sel) if __rules_max == 0 then return end xml.write_off_y = 0 xml.write_off_x = 0 auto_aux.idx = 1 auto_aux.stack = { n = 0 } auto_aux.rule = __rules[sel].table end function auto_aux:move_up(sel) if sel > 0 then local u = __rules[sel - 1] local d = __rules[sel] __rules[sel - 1] = d __rules[sel] = u return sel - 1 end return sel end function auto_aux:move_down(sel) if sel < __rules_max - 1 then local u = __rules[sel] local d = __rules[sel + 1] __rules[sel + 1] = u __rules[sel] = d return sel + 1 end return sel end function auto_aux:new_rule(sel, nam, typ, arg) local r -- nam can also directly be the table itself if type(nam) == "table" then r = { ["table"] = nam, ["fct"] = function (object) end } elseif typ == "inscribe" then if arg == "" then arg = input_box("Inscription?", 79) end r = { ["table"] = { label = "rule", args = { name = nam, type = typ, inscription = arg, module = game_module }, }, ["fct"] = function (object) end } else r = { ["table"] = { label = "rule", args = { name = nam, type = typ, module = game_module }, }, ["fct"] = function (object) end } end tinsert(__rules, sel, r) __rules_max = __rules_max + 1 end function auto_aux:rename_rule(sel, nam) if sel >= 0 and sel < __rules_max then __rules[sel].table.args.name = nam end end function auto_aux:save_ruleset() xml.write = xml.write_file print_hook("clean_ruleset()\nadd_ruleset\n[[\n") local i for i = 0, __rules_max - 1 do xml:print_xml(__rules[i].table, '') end print_hook("]]\n") xml.write = xml.write_screen end function auto_aux:del_self(sel) if auto_aux.rule.label == "rule" then tremove(__rules, sel) __rules_max = __rules_max - 1 return sel - 1 else local idx = auto_aux.idx auto_aux:go_left(sel) tremove(auto_aux.rule, idx) return sel end end auto_aux.types_desc = { ["and"] = { "Check is true if all rules within it are true", xml:collect([[.........]]), function () return xml:collect("") end, }, ["or"] = { "Check is true if at least one rule within it is true", xml:collect([[.........]]), function () return xml:collect("") end, }, ["not"] = { "Invert the result of its child rule", xml:collect([[...]]), function () return xml:collect("") end, }, ["comment"] = { "Comments are meaningless", xml:collect([[Comment explaining something]]), function () local n = input_box("Comment?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["name"] = { "Check is true if object name matches name", xml:collect([[potion of healing]]), function () local n = input_box("Object name to match?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["contain"] = { "Check is true if object name contains word", xml:collect([[healing]]), function () local n = input_box("Word to find in object name?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["inscribed"] = { "Check is true if object inscription contains word", xml:collect([[=g]]), function () local n = input_box("Word to find in object inscription?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["discount"] = { "Check is true if object discount is between 2 values", xml:collect([[]]), function () local s = "" return xml:collect(s) end, }, ["symbol"] = { "Check is true if object symbol is ok", xml:collect([[!]]), function () local n = input_box("Symbol to match?", 1) if n == "" then return end return xml:collect(""..n.."") end, }, ["status"] = { "Check is true if object status is ok", xml:collect([[good]]), function () local n = msg_box("[t]errible, [v]ery bad, [b]ad, [a]verage, [G]ood, [V]ery good, [S]pecial?") local t = { ["t"] = "terrible", ["v"] = "very bad", ["b"] = "bad", ["a"] = "average", ["G"] = "good", ["V"] = "very good", ["S"] = "special", } if not t[strchar(n)] then return end return xml:collect(""..t[strchar(n)].."") end, }, ["state"] = { "Check is true if object is identified/unidentified", xml:collect([[identified]]), function () local n = msg_box("[i]dentified, [n]on identified?") local t = { ["i"] = "identified", ["n"] = "not identified", } if not t[strchar(n)] then return end return xml:collect(""..t[strchar(n)].."") end, }, ["tval"] = { "Check is true if object tval(from k_info.txt) is ok", xml:collect([[55]]), function () local n = input_box("Tval to match?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["sval"] = { { "Check is true if object sval(from k_info.txt) is between", "2 values", }, xml:collect([[]]), function () local s = "" return xml:collect(s) end, }, ["race"] = { "Check is true if player race is ok", xml:collect([[dunadan]]), function () local n = input_box("Player race to match?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["subrace"] = { "Check is true if player subrace is ok", xml:collect([[vampire]]), function () local n = input_box("Player subrace to match?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["class"] = { "Check is true if player class is ok", xml:collect([[sorceror]]), function () local n = input_box("Player class to match?", 79) if n == "" then return end return xml:collect(""..n.."") end, }, ["level"] = { "Check is true if player level is between 2 values", xml:collect([[]]), function () local s = "" return xml:collect(s) end, }, ["skill"] = { "Check is true if player skill level is between 2 values", xml:collect([[Divination]]), function () local s = "" n = input_box("Skill name?", 79) if n == "" then return end if find_skill_i(n) == -1 then return end s = s..n.."" return xml:collect(s) end, }, ["ability"] = { "Check is true if player has the ability", xml:collect([[Ammo creation]]), function() local n = input_box("Ability name?", 79) if n == "" then return end if find_ability(n) == -1 then return end return xml:collect(""..n.."") end, }, } function auto_aux:display_desc(sel) local d = auto_aux.types_desc[sel][1] if type(d) == "string" then c_prt(TERM_WHITE, d, 1, 17) else local k, e, i i = 0 for k, e in d do c_prt(TERM_WHITE, e, 1 + i, 17) i = i + 1 end end end function auto_aux:add_child(sel) -- and contain only one match if (auto_aux.rule.label == "rule" or auto_aux.rule.label == "not") and auto_aux.rule[1] then return end -- Only and can contain if auto_aux.rule.label ~= "rule" and auto_aux.rule.label ~= "and" and auto_aux.rule.label ~= "or" and auto_aux.rule.label ~= "not" then return end -- get it local r = auto_aux.types_desc[sel][3]() if not r then return end -- Ok add it tinsert(auto_aux.rule, r[1]) end function auto_aux.regen_ruleset() local i for i = 0, __rules_max - 1 do __rules[i].fct = gen_full_rule(__rules[i].table) end end -- Easily add new rules function easy_add_rule(typ, mode, do_status, obj) local detect_rule if mode == "tval" then detect_rule = ""..obj.tval.."" elseif mode == "tsval" then detect_rule = ""..obj.tval.."" elseif mode == "name" then detect_rule = ""..strlower(object_desc(obj, -1, 0)).."" end if do_status == TRUE then local status = object_status(obj) if status and not (status == "") then detect_rule = ""..detect_rule..""..status.."" end end local rule = ""..detect_rule.."" auto_aux:new_rule(0, xml:collect(rule)[1], '') auto_aux.regen_ruleset() msg_print("Rule added. Please go to the Automatizer screen (press = then T)") msg_print("to save the modified ruleset.") end -- ===================== END ORIGINAL auto.lua ========================= -- -- And now the extra stuff. -- -- Local data for the ATP parser. atp = { -- All the syntactic token types we recognize. toks = { -- A single punctuation character { "^%s*([%(%)%-=;,])", "_LITERAL" }, -- A double-quoted string. { "^%s*\"(.-)\"", "_STRING" }, -- A number. { "^%s*(%d+)", "_NUMBER" }, -- A word token. { "^%s*([%a_][%w_]*)", "_TOKEN" }, }, -- All the recognized clauses. clauses = { "name", "contain", "inscribed", "symbol", "state", "status", "tval", "sval", "race", "subrace", "tval", "race", "subrace", "class", "ability", "discount", "level", "skill", }, -- The keywords or token types that are recognized as values for -- each clause. valtypes = { name = { "_STRING" }, contain = { "_STRING" }, inscribed = { "_STRING" }, symbol = { "_STRING" }, state = { "identified", "unidentified" }, status = { "very bad", "bad", "average", "good", "very good", "special", "terrible", "empty", }, tval = { "_NUMBER", "TV_SKELETON", "TV_BOTTLE", "TV_BATERIE", "TV_SPIKE", "TV_MSTAFF", "TV_CHEST", "TV_PARCHMENT", "TV_PARCHEMENT", "TV_CORPSE", "TV_EGG", "TV_JUNK", "TV_TOOL", "TV_INSTRUMENT", "TV_BOOMERANG", "TV_SHOT", "TV_ARROW", "TV_BOLT", "TV_BOW", "TV_DIGGING", "TV_HAFTED", "TV_POLEARM", "TV_SWORD", "TV_AXE", "TV_BOOTS", "TV_GLOVES", "TV_HELM", "TV_CROWN", "TV_SHIELD", "TV_CLOAK", "TV_SOFT_ARMOR", "TV_HARD_ARMOR", "TV_DRAG_ARMOR", "TV_LITE", "TV_AMULET", "TV_RING", "TV_TRAPKIT", "TV_STAFF", "TV_WAND", "TV_ROD", "TV_ROD_MAIN", "TV_SCROLL", "TV_POTION", "TV_POTION2", "TV_FLASK", "TV_FOOD", "TV_HYPNOS", "TV_GOLD", "TV_RANDART", "TV_RUNE1", "TV_RUNE2", "TV_BOOK", "TV_SYMBIOTIC_BOOK", "TV_MUSIC_BOOM", "TV_DRUID_BOOK", "TV_DAEMON_BOOK", }, sval = { "_NUMBER", "SV_TOOL_CLIMB", "SV_PORTABLE_HOLE", "SV_MSTAFF", "SV_AMMO_LIGHT", "SV_AMMO_NORMAL", "SV_AMMO_HEAVY", "SV_FLUTE", "SV_BANJO", "SV_LUTE", "SV_MANDOLIN", "SV_DRUM", "SV_HARP", "SV_HORN", "SV_TRAPKIT_SLING", "SV_TRAPKIT_BOW", "SV_TRAPKIT_XBOW", "SV_TRAPKIT_POTION", "SV_TRAPKIT_SCROLL", "SV_TRAPKIT_DEVICE", "SV_BOOM_S_WOOD", "SV_BOOM_WOOD", "SV_BOOM_S_METAL", "SV_BOOM_METAL", "SV_SLING", "SV_SHORT_BOW", "SV_LONG_BOW", "SV_LIGHT_XBOW", "SV_HEAVY_XBOW", "SV_SHOVEL", "SV_GNOMISH_SHOVEL", "SV_DWARVEN_SHOVEL", "SV_PICK", "SV_ORCISH_PICK", "SV_DWARVEN_PICK", "SV_MATTOCK", "SV_CLUB", "SV_WHIP", "SV_QUARTERSTAFF", "SV_NUNCHAKU", "SV_MACE", "SV_BALL_AND_CHAIN", "SV_WAR_HAMMER", "SV_LUCERN_HAMMER", "SV_THREE_PIECE_ROD", "SV_MORNING_STAR", "SV_FLAIL", "SV_LEAD_FILLED_MACE", "SV_TWO_HANDED_FLAIL", "SV_GREAT_HAMMER", "SV_MACE_OF_DISRUPTION", "SV_GROND", "SV_HATCHET", "SV_CLEAVER", "SV_LIGHT_WAR_AXE", "SV_BEAKED_AXE", "SV_BROAD_AXE", "SV_BATTLE_AXE", "SV_GREAT_AXE", "SV_LOCHABER_AXE", "SV_SLAUGHTER_AXE", "SV_SPEAR", "SV_SICKLE", "SV_AWL_PIKE", "SV_TRIDENT", "SV_FAUCHARD", "SV_BROAD_SPEAR", "SV_PIKE", "SV_GLAIVE", "SV_HALBERD", "SV_GUISARME", "SV_SCYTHE", "SV_LANCE", "SV_TRIFURCATE_SPEAR", "SV_HEAVY_LANCE", "SV_SCYTHE_OF_SLICING", "SV_BROKEN_DAGGER", "SV_BROKEN_SWORD", "SV_DAGGER", "SV_MAIN_GAUCHE", "SV_RAPIER", "SV_SMALL_SWORD", "SV_BASILLARD", "SV_SHORT_SWORD", "SV_SABRE", "SV_CUTLASS", "SV_KHOPESH", "SV_TULWAR", "SV_BROAD_SWORD", "SV_LONG_SWORD", "SV_SCIMITAR", "SV_KATANA", "SV_BASTARD_SWORD", "SV_GREAT_SCIMITAR", "SV_CLAYMORE", "SV_ESPADON", "SV_TWO_HANDED_SWORD", "SV_FLAMBERGE", "SV_EXECUTIONERS_SWORD", "SV_ZWEIHANDER", "SV_BLADE_OF_CHAOS", "SV_SHADOW_BLADE", "SV_BLUESTEEL_BLADE", "SV_DARK_SWORD", "SV_SMALL_LEATHER_SHIELD", "SV_SMALL_METAL_SHIELD", "SV_LARGE_LEATHER_SHIELD", "SV_LARGE_METAL_SHIELD", "SV_DRAGON_SHIELD", "SV_SHIELD_OF_DEFLECTION", "SV_HARD_LEATHER_CAP", "SV_METAL_CAP", "SV_IRON_HELM", "SV_STEEL_HELM", "SV_DRAGON_HELM", "SV_IRON_CROWN", "SV_GOLDEN_CROWN", "SV_JEWELED_CROWN", "SV_MORGOTH", "SV_PAIR_OF_SOFT_LEATHER_BOOTS", "SV_PAIR_OF_HARD_LEATHER_BOOTS", "SV_PAIR_OF_METAL_SHOD_BOOTS", "SV_CLOAK", "SV_ELVEN_CLOAK", "SV_FUR_CLOAK", "SV_SHADOW_CLOAK", "SV_SET_OF_LEATHER_GLOVES", "SV_SET_OF_GAUNTLETS", "SV_SET_OF_CESTI", "SV_FILTHY_RAG", "SV_ROBE", "SV_PAPER_ARMOR", "SV_SOFT_LEATHER_ARMOR", "SV_SOFT_STUDDED_LEATHER", "SV_HARD_LEATHER_ARMOR", "SV_HARD_STUDDED_LEATHER", "SV_RHINO_HIDE_ARMOR", "SV_CORD_ARMOR", "SV_PADDED_ARMOR", "SV_LEATHER_SCALE_MAIL", "SV_LEATHER_JACK", "SV_STONE_AND_HIDE_ARMOR", "SV_THUNDERLORD_SUIT", "SV_RUSTY_CHAIN_MAIL", "SV_RING_MAIL", "SV_METAL_SCALE_MAIL", "SV_CHAIN_MAIL", "SV_DOUBLE_RING_MAIL", "SV_AUGMENTED_CHAIN_MAIL", "SV_DOUBLE_CHAIN_MAIL", "SV_BAR_CHAIN_MAIL", "SV_METAL_BRIGANDINE_ARMOUR", "SV_SPLINT_MAIL", "SV_PARTIAL_PLATE_ARMOUR", "SV_METAL_LAMELLAR_ARMOUR", "SV_FULL_PLATE_ARMOUR", "SV_RIBBED_PLATE_ARMOUR", "SV_MITHRIL_CHAIN_MAIL", "SV_MITHRIL_PLATE_MAIL", "SV_ADAMANTITE_PLATE_MAIL", "SV_DRAGON_BLACK", "SV_DRAGON_BLUE", "SV_DRAGON_WHITE", "SV_DRAGON_RED", "SV_DRAGON_GREEN", "SV_DRAGON_MULTIHUED", "SV_DRAGON_SHINING", "SV_DRAGON_LAW", "SV_DRAGON_BRONZE", "SV_DRAGON_GOLD", "SV_DRAGON_CHAOS", "SV_DRAGON_BALANCE", "SV_DRAGON_POWER", "SV_LITE_TORCH", "SV_LITE_LANTERN", "SV_LITE_TORCH_EVER", "SV_LITE_DWARVEN", "SV_LITE_FEANORIAN", "SV_LITE_GALADRIEL", "SV_LITE_ELENDIL", "SV_LITE_THRAIN", "SV_LITE_UNDEATH", "SV_LITE_PALANTIR", "SV_ANCHOR_SPACETIME", "SV_STONE_LORE", "SV_AMULET_DOOM", "SV_AMULET_TELEPORT", "SV_AMULET_ADORNMENT", "SV_AMULET_SLOW_DIGEST", "SV_AMULET_RESIST_ACID", "SV_AMULET_SEARCHING", "SV_AMULET_BRILLANCE", "SV_AMULET_CHARISMA", "SV_AMULET_THE_MAGI", "SV_AMULET_REFLECTION", "SV_AMULET_CARLAMMAS", "SV_AMULET_INGWE", "SV_AMULET_DWARVES", "SV_AMULET_NO_MAGIC", "SV_AMULET_NO_TELE", "SV_AMULET_RESISTANCE", "SV_AMULET_NOTHING", "SV_AMULET_SERPENT", "SV_AMULET_TORIS_MEJISTOS", "SV_AMULET_TRICKERY", "SV_AMULET_DEVOTION", "SV_AMULET_WEAPONMASTERY", "SV_AMULET_WISDOM", "SV_AMULET_INFRA", "SV_AMULET_SPELL", "SV_RING_WOE", "SV_RING_AGGRAVATION", "SV_RING_WEAKNESS", "SV_RING_STUPIDITY", "SV_RING_TELEPORTATION", "SV_RING_SPECIAL", "SV_RING_SLOW_DIGESTION", "SV_RING_FEATHER_FALL", "SV_RING_RESIST_FIRE", "SV_RING_RESIST_COLD", "SV_RING_SUSTAIN_STR", "SV_RING_SUSTAIN_INT", "SV_RING_SUSTAIN_WIS", "SV_RING_SUSTAIN_DEX", "SV_RING_SUSTAIN_CON", "SV_RING_SUSTAIN_CHR", "SV_RING_PROTECTION", "SV_RING_ACID", "SV_RING_FLAMES", "SV_RING_ICE", "SV_RING_RESIST_POIS", "SV_RING_FREE_ACTION", "SV_RING_SEE_INVIS", "SV_RING_SEARCHING", "SV_RING_STR", "SV_RING_INT", "SV_RING_DEX", "SV_RING_CON", "SV_RING_ACCURACY", "SV_RING_DAMAGE", "SV_RING_SLAYING", "SV_RING_SPEED", "SV_RING_BARAHIR", "SV_RING_TULKAS", "SV_RING_NARYA", "SV_RING_NENYA", "SV_RING_VILYA", "SV_RING_POWER", "SV_RING_RES_FEAR", "SV_RING_RES_LD", "SV_RING_RES_NETHER", "SV_RING_RES_NEXUS", "SV_RING_RES_SOUND", "SV_RING_RES_CONFUSION", "SV_RING_RES_SHARDS", "SV_RING_RES_DISENCHANT", "SV_RING_RES_CHAOS", "SV_RING_RES_BLINDNESS", "SV_RING_LORDLY", "SV_RING_ATTACKS", "SV_RING_NOTHING", "SV_RING_PRECONITION", "SV_RING_FLAR", "SV_RING_INVIS", "SV_RING_FLYING", "SV_RING_WRAITH", "SV_RING_ELEC", "SV_RING_CRIT", "SV_RING_SPELL", "SV_STAFF_SCHOOL", "SV_STAFF_NOTHING", "SV_WAND_SCHOOL", "SV_WAND_NOTHING", "SV_ROD_NOTHING", "SV_ROD_DETECT_DOOR", "SV_ROD_IDENTIFY", "SV_ROD_RECALL", "SV_ROD_ILLUMINATION", "SV_ROD_MAPPING", "SV_ROD_DETECTION", "SV_ROD_PROBING", "SV_ROD_CURING", "SV_ROD_HEALING", "SV_ROD_RESTORATION", "SV_ROD_SPEED", "SV_ROD_TELEPORT_AWAY", "SV_ROD_DISARMING", "SV_ROD_LITE", "SV_ROD_SLEEP_MONSTER", "SV_ROD_SLOW_MONSTER", "SV_ROD_DRAIN_LIFE", "SV_ROD_POLYMORPH", "SV_ROD_ACID_BOLT", "SV_ROD_ELEC_BOLT", "SV_ROD_FIRE_BOLT", "SV_ROD_COLD_BOLT", "SV_ROD_ACID_BALL", "SV_ROD_ELEC_BALL", "SV_ROD_FIRE_BALL", "SV_ROD_COLD_BALL", "SV_ROD_HAVOC", "SV_ROD_DETECT_TRAP", "SV_ROD_HOME", "SV_ROD_WOODEN", "SV_ROD_COPPER", "SV_ROD_IRON", "SV_ROD_ALUMINIUM", "SV_ROD_SILVER", "SV_ROD_GOLDEN", "SV_ROD_MITHRIL", "SV_ROD_ADMANTITE", "SV_SCROLL_DARKNESS", "SV_SCROLL_AGGRAVATE_MONSTER", "SV_SCROLL_CURSE_ARMOR", "SV_SCROLL_CURSE_WEAPON", "SV_SCROLL_SUMMON_MONSTER", "SV_SCROLL_SUMMON_UNDEAD", "SV_SCROLL_SUMMON_MINE", "SV_SCROLL_TRAP_CREATION", "SV_SCROLL_PHASE_DOOR", "SV_SCROLL_TELEPORT", "SV_SCROLL_TELEPORT_LEVEL", "SV_SCROLL_WORD_OF_RECALL", "SV_SCROLL_IDENTIFY", "SV_SCROLL_STAR_IDENTIFY", "SV_SCROLL_REMOVE_CURSE", "SV_SCROLL_STAR_REMOVE_CURSE", "SV_SCROLL_ENCHANT_ARMOR", "SV_SCROLL_ENCHANT_WEAPON_TO_HIT", "SV_SCROLL_ENCHANT_WEAPON_TO_DAM", "SV_SCROLL_ENCHANT_WEAPON_PVAL", "SV_SCROLL_STAR_ENCHANT_ARMOR", "SV_SCROLL_STAR_ENCHANT_WEAPON", "SV_SCROLL_RECHARGING", "SV_SCROLL_RESET_RECALL", "SV_SCROLL_LIGHT", "SV_SCROLL_MAPPING", "SV_SCROLL_DETECT_GOLD", "SV_SCROLL_DETECT_ITEM", "SV_SCROLL_DETECT_TRAP", "SV_SCROLL_DETECT_DOOR", "SV_SCROLL_DETECT_INVIS", "SV_SCROLL_DIVINATION", "SV_SCROLL_SATISFY_HUNGER", "SV_SCROLL_BLESSING", "SV_SCROLL_HOLY_CHANT", "SV_SCROLL_HOLY_PRAYER", "SV_SCROLL_MONSTER_CONFUSION", "SV_SCROLL_PROTECTION_FROM_EVIL", "SV_SCROLL_RUNE_OF_PROTECTION", "SV_SCROLL_TRAP_DOOR_DESTRUCTION", "SV_SCROLL_DEINCARNATION", "SV_SCROLL_STAR_DESTRUCTION", "SV_SCROLL_DISPEL_UNDEAD", "SV_SCROLL_MASS_RESURECTION", "SV_SCROLL_GENOCIDE", "SV_SCROLL_MASS_GENOCIDE", "SV_SCROLL_ACQUIREMENT", "SV_SCROLL_STAR_ACQUIREMENT", "SV_SCROLL_FIRE", "SV_SCROLL_ICE", "SV_SCROLL_CHAOS", "SV_SCROLL_RUMOR", "SV_SCROLL_ARTIFACT", "SV_SCROLL_NOTHING", "SV_SCROLL_SPELL", "SV_POTION_WATER", "SV_POTION_APPLE_JUICE", "SV_POTION_SLIME_MOLD", "SV_POTION_BLOOD", "SV_POTION_SLOWNESS", "SV_POTION_SALT_WATER", "SV_POTION_POISON", "SV_POTION_BLINDNESS", "SV_POTION_INVIS", "SV_POTION_CONFUSION", "SV_POTION_MUTATION", "SV_POTION_SLEEP", "SV_POTION_LEARNING", "SV_POTION_LOSE_MEMORIES", "SV_POTION_RUINATION", "SV_POTION_DEC_STR", "SV_POTION_DEC_INT", "SV_POTION_DEC_WIS", "SV_POTION_DEC_DEX", "SV_POTION_DEC_CON", "SV_POTION_DEC_CHR", "SV_POTION_DETONATIONS", "SV_POTION_DEATH", "SV_POTION_INFRAVISION", "SV_POTION_DETECT_INVIS", "SV_POTION_SLOW_POISON", "SV_POTION_CURE_POISON", "SV_POTION_BOLDNESS", "SV_POTION_SPEED", "SV_POTION_RESIST_HEAT", "SV_POTION_RESIST_COLD", "SV_POTION_HEROISM", "SV_POTION_BESERK_STRENGTH", "SV_POTION_CURE_LIGHT", "SV_POTION_CURE_SERIOUS", "SV_POTION_CURE_CRITICAL", "SV_POTION_HEALING", "SV_POTION_STAR_HEALING", "SV_POTION_LIFE", "SV_POTION_RESTORE_MANA", "SV_POTION_RESTORE_EXP", "SV_POTION_RES_STR", "SV_POTION_RES_INT", "SV_POTION_RES_WIS", "SV_POTION_RES_DEX", "SV_POTION_RES_CON", "SV_POTION_RES_CHR", "SV_POTION_INC_STR", "SV_POTION_INC_INT", "SV_POTION_INC_WIS", "SV_POTION_INC_DEX", "SV_POTION_INC_CON", "SV_POTION_INC_CHR", "SV_POTION_AUGMENTATION", "SV_POTION_ENLIGHTENMENT", "SV_POTION_STAR_ENLIGHTENMENT", "SV_POTION_SELF_KNOWLEDGE", "SV_POTION_EXPERIENCE", "SV_POTION_RESISTANCE", "SV_POTION_CURING", "SV_POTION_INVULNERABILITY", "SV_POTION_NEW_LIFE", "SV_POTION2_MIMIC", "SV_POTION2_MIMIC_ABOMINATION", "SV_POTION2_MIMIC_WOLF", "SV_POTION2_MIMIC_APE", "SV_POTION2_MIMIC_GOAT", "SV_POTION2_MIMIC_INSECT", "SV_POTION2_MIMIC_SPARROW", "SV_POTION2_MIMIC_STATUE", "SV_POTION2_MIMIC_VAMPIRE", "SV_POTION2_MIMIC_SPIDER", "SV_POTION2_MIMIC_MANA_BALL", "SV_POTION2_MIMIC_FIRE_CLOUD", "SV_POTION2_MIMIC_COLD_CLOUD", "SV_POTION2_MIMIC_CHAOS_CLOUD", "SV_POTION2_CURE_LIGHT_SANITY", "SV_POTION2_CURE_SERIOUS_SANITY", "SV_POTION2_CURE_CRITICAL_SANITY", "SV_POTION2_CURE_SANITY", "SV_POTION2_CURE_WATER", "SV_FOOD_POISON", "SV_FOOD_BLINDNESS", "SV_FOOD_PARANOIA", "SV_FOOD_CONFUSION", "SV_FOOD_HALLUCINATION", "SV_FOOD_PARALYSIS", "SV_FOOD_WEAKNESS", "SV_FOOD_SICKNESS", "SV_FOOD_STUPIDITY", "SV_FOOD_NAIVETY", "SV_FOOD_UNHEALTH", "SV_FOOD_DISEASE", "SV_FOOD_CURE_POISON", "SV_FOOD_CURE_BLINDNESS", "SV_FOOD_CURE_PARANOIA", "SV_FOOD_CURE_CONFUSION", "SV_FOOD_CURE_SERIOUS", "SV_FOOD_RESTORE_STR", "SV_FOOD_RESTORE_CON", "SV_FOOD_RESTORING", "SV_FOOD_BISCUIT", "SV_FOOD_JERKY", "SV_FOOD_RATION", "SV_FOOD_SLIME_MOLD", "SV_FOOD_WAYBREAD", "SV_FOOD_PINT_OF_ALE", "SV_FOOD_PINT_OF_WINE", "SV_FOOD_ATHELAS", "SV_FOOD_GREAT_HEALTH", "SV_FOOD_FORTUNE_COOKIE", "SV_BATERIE_POISON", "SV_BATERIE_EXPLOSION", "SV_BATERIE_TELEPORT", "SV_BATERIE_COLD", "SV_BATERIE_FIRE", "SV_BATERIE_ACID", "SV_BATERIE_LIFE", "SV_BATERIE_CONFUSION", "SV_BATERIE_LITE", "SV_BATERIE_CHAOS", "SV_BATERIE_TIME", "SV_BATERIE_MAGIC", "SV_BATERIE_XTRA_LIFE", "SV_BATERIE_DARKNESS", "SV_BATERIE_KNOWLEDGE", "SV_BATERIE_FORCE", "SV_BATERIE_LIGHTNING", "SV_BATERIE_MANA", "SV_CORPSE_CORPSE", "SV_CORPSE_SKELETON", "SV_CORPSE_HEAD", "SV_CORPSE_SKULL", "SV_CORPSE_MEAT", "SV_DEMONBLADE", "SV_DEMONSHIELD", "SV_DEMONHORN", }, race = { "Human", "Half-Elf", "Elf", "Hobbit", "Gnome", "Dwarf", "Orc", "Troll", "Dunadan", "High-Elf", "Half-Ogre", "Beorning", "Kobold", "Petty-Dwarf", "Dark-Elf", "Ent", "RohanKnight", "Thunderlord", "DeathMold", "Yeek", "Wood-Elf", "Maia", }, subrace = { "Classical", "Vampire", "Spectre", "Skeleton", "Zombie", "Barbarian", "Hermit", "Corrupted", "LostSoul", }, class = { "Warrior", "Swordmaster", "Axemaster", "Haftedmaster", "Polearmmaster", "Unbeliever", "Demonologist", "Mage", "Geomancer", "Elementalist", "Warper", "Sorceror", "Necromancer", "Runecrafter", "Thaumaturgist", "Alchemist", "Archer", "Ranger", "Rogue", "Assassin", "Loremaster", "Possessor", "Mimic", "Symbiant", "Summoner", "Monk", "Bard", "Priest(Eru)", "Priest(Manwe)", "Druid", "Dark-Priest", "Paladin", "Mindcrafter", }, ability = { "_STRING" }, discount = { "_NUMBER" }, level = { "_NUMBER" }, skill = { "_NUMBER" }, }, -- Extra syntax information for clauses that expect a range of -- values. |pfx|, if present, is a value type that must appear -- before the "=" of the clause; |ok1|, if present, indicates that -- a singleton value can be provided, which will be uesd for both -- min and max. syntax = { discount = { ok1=1 }, sval = { ok1=1 }, level = {}, skill = { pfx="_STRING" } }, -- And since there's nowhere better to put it, a few extra bits for -- the automatizer rule tester: -- Color attr to char conversion array. attr2char = { 'd', 'w', 's', 'o', 'r', 'g', 'b', 'u', 'D', 'W', 'v', 'y', 'R', 'G', 'B', 'U', }, -- Current rule test results (0/1 for test/failure) rule_test = 0, } -- -- Assorted hook hijacks for the automatizer rule tester: -- atp.write_file_with_color_hijack = function (color, s) local ch = atp.attr2char[color+1] if atp.rule_test_obj and atp.rule_test == 0 then -- Non-match rule; hijack the green bcol/ecol as red. if ch == 'G' then ch = 'R' elseif ch == 'g' then ch = 'r' end end if ch == 'w' or strfind(s, "^%s*$") then print_hook(s) else -- Have to be careful about newlines... local ss = gsub(s, "\n", "]\n[[[[[" .. ch) print_hook("[[[[[" .. ch .. ss .. "]") end end -- Test a rule or subrule on the curren test object. atp.perform_rule_test = function(t) if not atp.rule_test_obj then return end local f -- Build a function for the rule. if t.label == "rule" then f = gen_rule_fct(t[1]) else f = gen_rule_fct(t) end -- Feed the test object to it and remember the result. if f(atp.rule_test_obj) then atp.rule_test = 1 else atp.rule_test = 0 end end -- Hijack xml:print_xml to insert rule testing for the rule tester. atp.orig_print_xml = xml.print_xml xml.print_xml = function(self, t, tab) local parent_rule_test = atp.rule_test atp.perform_rule_test(t) atp.orig_print_xml(xml, t, tab) atp.rule_test = parent_rule_test end atp.choose_rule = function() local rule_names = {} local sel = 1 local begin = 1 local res = nil local i for i = 0, __rules_max - 1 do rule_names[i+1] = __rules[i].table.args.name end screen_save() while not nil do display_list(1, 0, 20, 20, "Select rule to test", rule_names, begin, sel, TERM_L_BLUE) local key = inkey() if key == ESCAPE then break elseif key == strbyte("8") then sel = sel - 1 if sel < 1 then sel = 1 end if sel < begin then begin = begin - 1 end elseif key == strbyte("2") then sel = sel + 1 if sel > getn(rule_names) then sel = getn(rule_names) end if sel >= begin + 19 then begin = begin + 1 end elseif key == strbyte("\r") then res = rule_names[sel] break; end end screen_load() return res end -- Make it accessible as a power on the 'U' menu. POWER_AUTO_TEST = add_power { ["name"] = "Test automatizer rule", ["desc"] = "You can test automatizer rules against specific items", ["desc_get"] = "", ["desc_lose"] = "", ["level"] = 0, ["cost"] = 0, ["stat"] = A_INT, ["fail"] = 0, ["power"] = function () local ret, item, i local name = "" local t = nil ret, item = get_item("Test which item?", "You have nothing to test", bor(USE_INVEN), function (obj) return TRUE end) if ret == FALSE then return end name = atp.choose_rule() if not name then return end for i = 0, __rules_max - 1 do if __rules[i].table.args.name == name then t = __rules[i].table break end end if not t then cmsg_print(TERM_RED, "No such rule '" .. name .. "'") return end atp.rule_test_obj = get_object(item) xml.write = atp.write_file_with_color_hijack make_temp_file() xml:display_xml(t, '') close_temp_file() screen_save() show_file(get_temp_name(), "Automatizer rule test", 0, 0) screen_load() end_temp_file() atp.rule_test_obj = nil xml.write = xml.write_screen end, } function add_auto_test_power() player.add_power(POWER_AUTO_TEST) end add_hook_script(HOOK_CALC_POWERS, "add_auto_test_power", "add_auto_test_power") -- -- Assorted hook hijacks for the new 'inventory'/'equipment' clauses: -- -- Hijack gen_rule_fct() to add handling for the new clauses. atp.orig_gen_rule_fct = gen_rule_fct gen_rule_fct = function(r) if r.label == "inventory" then local f if not r[1] then f = function(object) return end else f = gen_rule_fct(r[1]) end return function(object) local i = 0 while i < INVEN_WIELD do if %f(player.inventory(i)) then return TRUE end i = i + 1 end end elseif r.label == "equipment" then local f if not r[1] then f = function(object) return end else f = gen_rule_fct(r[1]) end return function(object) local i = INVEN_WIELD while i < INVEN_TOTAL do if %f(player.inventory(i)) then return TRUE end i = i + 1 end end else return atp.orig_gen_rule_fct(r) end end -- Add type descriptions. auto_aux.types_desc["inventory"] = { { "Check is true if something in player's inventory matches", "the contained rule", }, xml:collect([[...]]), function () return xml:collect("") end, } auto_aux.types_desc["equipment"] = { { "Check is true if something in player's equipment matches", "the contained rule", }, xml:collect([[...]]), function () return xml:collect("") end, } -- Hijack xml:english_xml() to add English output for the new clauses. -- [Also to insert rule testing for the rule tester, as above.] atp.orig_english_xml = xml.english_xml xml.english_xml = function(self, t, tab, not_flag) local i local nextlevel = tab .. " " local bcol, ecol = TERM_L_GREEN, TERM_GREEN local parent_rule_test = atp.rule_test atp.perform_rule_test(t) if not_flag then atp.rule_test = 1 - atp.rule_test end if t.label ~= "inventory" and t.label ~= "equipment" then atp.orig_english_xml(xml, t, tab, not_flag) atp.rule_test = parent_rule_test return end if not_flag then xml.write(TERM_WHITE, tab) xml.write(ecol, "Nothing in your ") xml.write(bcol, t.label) xml.write(ecol, " matches the following:") xml.write(TERM_WHITE, "\n") else xml.write(TERM_WHITE, tab) xml.write(ecol, "Something in your ") xml.write(bcol, t.label) xml.write(ecol, " matches the following:") xml.write(TERM_WHITE, "\n") end for i = 1, getn(t) do if type(t[i]) == "string" then -- xml.write(TERM_WHITE, t[i].."\n") else xml:english_xml(t[i], nextlevel, nil) end end atp.rule_test = parent_rule_test end -- -- And finally, the parsing code for the new ATP automatizer language. -- -- token() -- Extract a lexical token from the input string provided to -- add_atp_ruleset(). If a token has already been read previously and not -- yet consumed by a call to match() or maybe_match(), that token is -- returned instead. Sets atp.tok to the text of the token, and -- atp.toktype to the type of the token. Returns the cached token, or the -- next token read from input, or nil at end of file. function token() local i, j, tok -- If we've already matched a token, return it. if atp.tok then return atp.tok end -- Try all the token types we recognize. for i = 1, getn(atp.toks) do local t = atp.toks[i] j,_,tok = strfind(atp.txt, t[1]) if j then atp.tok = tok atp.toktype = t[2] atp.txt = gsub(atp.txt, t[1], "", 1) return atp.tok end end -- No match; assume end of string return nil end -- An internal function to format list tables nicely. function tabletostring(t) local val local ret = "{" for _, val in t do if idx ~= "n" then ret = ret .. " " if type(val) == "table" then ret = ret .. tabletostring(val) else ret = ret .. val end end end return ret .. " }" end -- An internal implementation of look() for easier handling of Lua -- varargs. function look_guts(arg) if arg.n == 1 and type(arg[1]) == "table" then return look_guts(arg[1]) end local i atp.tok = atp.tok or token() -- No args means match the current token. if getn(arg) == 0 then return not nil end for i = 1, getn(arg) do if not arg[i] and not atp.tok then return not nil end if atp.tok == arg[i] or atp.toktype == arg[i] then return not nil end end return nil end -- look() -- Returns a non-nil value if the next input token (as returned -- by token()) matches any of the tokens or token types passed as -- arguments. The tokens to match may also be passed as a single table -- argument. look(nil) returns true at end of file. function look(...) return look_guts(arg) end -- maybe_match() -- Consumes and returns the next token (as returned by -- token()) if it matches any of the provided tokens or token types (a la -- look()); otherwise, leaves the token unconsumed and returns nil. function maybe_match(...) if not look_guts(arg) then return nil end local tok = atp.tok atp.tok = nil return tok end -- match() -- Consumes and returns the next token (as returned by token()) -- if it matches any of the provided tokens or token types (a la look()); -- otherwise, throws an error. function match(...) if look_guts(arg) then local tok = atp.tok atp.tok = nil return tok end local found = atp.tok and "'" .. atp.tok .. "'" or "end of file" error("expected one of " .. tabletostring(arg) .. " instead of " .. found) return nil end -- clause() -- Matches a single ATP matching condition, possibly preceded -- by 'not', or a parenthesized list of such clauses. Returns an -- automatizer data structure representing the clause. function clause() -- Catch 'not' and parenthesized expressions first if maybe_match("(") then local ret = clauses("or") match(")") return ret end if maybe_match("not") then return { label="not", args={} ; clause() } end -- Catch 'inventory' and 'equipment' clauses. if look("inventory", "equipment") then return { label=match(), args={} ; clause() } end -- So now we're looking for a matching condition local cls = { label=match(atp.clauses), args={} } local valtypes = atp.valtypes[cls.label] local syntax = atp.syntax[cls.label] -- Some matching conditions, like 'skill', will have an extra token -- to match. local preval = syntax and syntax.pfx and match(syntax.pfx) match("=") if syntax then -- Match a numeric range like '1-50'. if preval then cls[1] = preval end cls.args.min = match(valtypes) cls.args.max = cls.args.min if not syntax.ok1 or look("-") then match("-") cls.args.max = match(valtypes) end -- For sval, optionally match a comma-separated list of -- svals or sval ranges. if cls.label == "sval" and look(",") then local mcls = { label="or", args={} } tinsert(mcls, cls) while maybe_match(",") do local lcls = { label=cls.label, args={} } lcls.args.min = match(valtypes) lcls.args.max = lcls.args.min if not syntax.ok1 or look("-") then match("-") lcls.args.max = match(valtypes) end tinsert(mcls, lcls) end cls = mcls end else -- Match a single token or one of the allowed types, or a -- comma-separated list of same. cls[1] = match(valtypes) if look(",") then local mcls = { label="or", args={} } tinsert(mcls, cls) while maybe_match(",") do local val = match(valtypes) tinsert(mcls, { label=cls.label, args={} ; val }) end cls = mcls end end return cls end -- clauses() -- Matches one or more ATP clauses separated by 'and', or one -- or more groups of 'and'-ed clauses separated by 'or', as per the -- provided 'join' parameter. Returns an automatizer data structure -- representing the combined clauses. function clauses(join) local cls = { label=join, args={} } local subcls = (join == "or" and clauses("and") or clause()) -- If the subclause is of the same 'and'/'or' type as the -- containing clause, it can be "flattened" in. if subcls.label == cls.label then cls = subcls else tinsert(cls, subcls) end while maybe_match(join) do subcls = (join == "or" and clauses("and") or clause()) -- As above. if subcls.label == cls.label then local i for i = 1, getn(subcls) do tinsert(cls, subcls[i]) end else tinsert(cls, subcls) end end return getn(cls) == 1 and cls[1] or cls end -- rule() -- Matches an ATP rule. returns an automatizer data structure -- representing the rule. function rule() local r = { label="rule", args={ module=atp.cur_module } } match("rule") r.args.name = match("_STRING") r.args.type = match("destroy", "pickup", "inscribe") r.args.inscription = (r.args.type == "inscribe" and match("_STRING")) match("if") r[1] = clauses("or") match(";") return r end -- add_atp_ruleset() -- Parses the provided ATP string and creates the -- internal Lua functions that that implemented the requested automatizer -- rules. function add_atp_ruleset(s) atp.txt = gsub(s, "%s*#.-\n", " ") while 1 do if look("rule") then local r = rule() local fct = gen_full_rule(r) __rules[__rules_max] = { ["table"] = r, ["fct"] = fct } __rules_max = __rules_max + 1 elseif look("module") then match("module") atp.cur_module = maybe_match("_STRING") match(";") elseif look(nil) then break else -- We know this will fail; we just do it to get the -- error message. match("rule", "module") end end end