From 6add91e17080e06cae938a31c53c94e59c7f0bfb Mon Sep 17 00:00:00 2001 From: Bardur Arantsson Date: Tue, 19 Jun 2012 18:32:22 +0200 Subject: Lua: Move automatizer to C --- CMakeLists.txt | 13 + README.txt | 13 + lib/core/auto.lua | 859 ---------- lib/core/init.lua | 3 - lib/core/xml.lua | 375 ----- lib/mods/theme/core/auto.lua | 859 ---------- lib/mods/theme/core/init.lua | 3 - lib/mods/theme/core/xml.lua | 375 ----- src/dungeon.c | 6 +- src/externs.h | 5 +- src/init2.c | 4 +- src/loadsave.c | 2 +- src/modules.c | 26 +- src/squeltch.c | 3550 ++++++++++++++++++++++++++++++++++++++---- 14 files changed, 3338 insertions(+), 2755 deletions(-) delete mode 100644 lib/core/auto.lua delete mode 100644 lib/core/xml.lua delete mode 100644 lib/mods/theme/core/auto.lua delete mode 100644 lib/mods/theme/core/xml.lua diff --git a/CMakeLists.txt b/CMakeLists.txt index 8714ca8a..da5486b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ CMAKE_MINIMUM_REQUIRED (VERSION 2.6) # We want a readable feature summary. INCLUDE(FeatureSummary) +# pkg-config support +INCLUDE(FindPkgConfig) + # Default flags. IF(CMAKE_COMPILER_IS_GNUCC) # Let's set sensible options. @@ -16,6 +19,16 @@ ENDIF() # Add definitions. ADD_DEFINITIONS(-DUSE_PRECISE_CMOVIE) +# +# JSON support +# +PKG_CHECK_MODULES(JANSSON REQUIRED jansson) +IF(JANSSON_FOUND) + ADD_DEFINITIONS(${JANSSON_CFLAGS}) + INCLUDE_DIRECTORIES(${JANSSON_INCLUDE_DIRS}) + SET(LIBS ${LIBS} ${JANSSON_LIBRARIES}) +ENDIF() + # # X11 support (OPTIONAL) # diff --git a/README.txt b/README.txt index b422701e..a4d243ef 100644 --- a/README.txt +++ b/README.txt @@ -4,6 +4,15 @@ Using the CMake build system There are basically two options for how to run ToME once built. +Prerequisites +============= + +You will need to have the following libraries installed +on your system somewhere where CMake can find them: + + - jansson + See http://www.digip.org/jansson/ + Option #1 : Run ToME from the build directory ============================================= @@ -46,6 +55,10 @@ probably missing the build-essential +package. You'll also need to install the + + libjansson-dev + package. Each frontend requires the additional packages listed below: diff --git a/lib/core/auto.lua b/lib/core/auto.lua deleted file mode 100644 index b758db52..00000000 --- a/lib/core/auto.lua +++ /dev/null @@ -1,859 +0,0 @@ --- 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 == "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 - 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, - }, - ["inventory"] = - { - { - "Check is true if something in player's inventory matches", - "the contained rule", - }, - xml:collect([[...]]), - function () - return xml:collect("") - end, - }, - ["equipment"] = - { - { - "Check is true if something in player's equipment matches", - "the contained rule", - }, - xml:collect([[...]]), - function () - return xml:collect("") - 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 - if (auto_aux.rule.label == "rule" or auto_aux.rule.label == "equipment") and auto_aux.rule[1] then return end - if (auto_aux.rule.label == "rule" or auto_aux.rule.label == "inventory") 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" and auto_aux.rule.label ~= "equipment" and auto_aux.rule.label ~= "inventory" 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 diff --git a/lib/core/init.lua b/lib/core/init.lua index 51dfc1d2..44ef9a7a 100644 --- a/lib/core/init.lua +++ b/lib/core/init.lua @@ -3,9 +3,6 @@ -- Load the system functions -- --- Very thin xml parser(49 lines ;) -tome_dofile_anywhere(ANGBAND_DIR_CORE, "xml.lua") - -- various vital helper code tome_dofile_anywhere(ANGBAND_DIR_CORE, "util.lua") tome_dofile_anywhere(ANGBAND_DIR_CORE, "player.lua") diff --git a/lib/core/xml.lua b/lib/core/xml.lua deleted file mode 100644 index 14f0511f..00000000 --- a/lib/core/xml.lua +++ /dev/null @@ -1,375 +0,0 @@ --- The xml module -xml = {} - -function xml:parseargs (s) - local arg = {} - gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) - %arg[w] = a - end) - return arg -end - --- s is a xml stream, returns a table -function xml:collect (s) - local stack = {n=0} - local top = {n=0} - tinsert(stack, top) - local ni,c,label,args, empty - local i, j = 1, 1 - while 1 do - ni,j,c,label,args, empty = strfind(s, "<(%/?)(%w+)(.-)(%/?)>", j) - if not ni then break end - local text = strsub(s, i, ni-1) - if not strfind(text, "^%s*$") then - tinsert(top, text) - end - if empty == "/" then -- empty element tag - tinsert(top, {n=0, label=label, args=xml:parseargs(args), empty=1}) - elseif c == "" then -- start tag - top = {n=0, label=label, args=xml:parseargs(args)} - tinsert(stack, top) -- new level - else -- end tag - local toclose = tremove(stack) -- remove top - top = stack[stack.n] - if stack.n < 1 then - error("nothing to close with "..label) - end - if toclose.label ~= label then - error("trying to close "..toclose.label.." with "..label) - end - tinsert(top, toclose) - end - i = j+1 - end - local text = strsub(s, i) - if not strfind(text, "^%s*$") then - tinsert(stack[stack.n], text) - end - if stack.n > 1 then - error("unclosed "..stack[stack.n].label) - end - return stack[1] -end - --- Viewport coordinates -xml.write_out_y = 0 -xml.write_out_x = 0 -xml.write_out_h = 24 -xml.write_out_w = 80 - --- Offsets -xml.write_off_y = 0 -xml.write_off_x = 0 - --- Current position -xml.write_y = 0 -xml.write_x = 0 - -xml.write_screen = function(color, s) - local i - for i = 1, strlen(s) do - local c = strsub(s, i, i + 1) - if c ~= "\n" then - if xml.write_y - xml.write_off_y >= 0 and xml.write_y - xml.write_off_y < xml.write_out_h and xml.write_x - xml.write_off_x >= 0 and xml.write_x - xml.write_off_x < xml.write_out_w then - Term_putch(xml.write_x - xml.write_off_x + xml.write_out_x, xml.write_y - xml.write_off_y + xml.write_out_y, color, strbyte(c)) - end - xml.write_x = xml.write_x + 1 - else - xml.write_x = 0 - xml.write_y = xml.write_y + 1 - end - end -end - -xml.write_file = function (color, s) - print_hook(s) -end - -xml.write = xml.write_screen - -xml.rule2string = { - ['name'] = {"Its ", "name", " is"}, - ['contain'] = {"Its ", "name", " contains"}, - ['symbol'] = {"Its ", "symbol", " is"}, - ['inscribed'] = {"Its ", "inscription", " contains"}, - ['state'] = {"Its ", "state", " is"}, - ['status'] = {"Its ", "status", " is"}, - ['tval'] = {"Its ", "tval", " is"}, - ['race'] = {"Your ", "race", " is"}, - ['subrace'] = {"Your ", "subrace", " is"}, - ['class'] = {"Your ", "class", " is"}, - ['foo1'] = {"The result of ", "test 1 ", "is"}, - ['foo2'] = {"The result of ", "test 2 ", "is"}, - ['foo3'] = {"The result of ", "test 3 ", "is"}, -} - -xml.display_english = 1 -function xml:display_xml(t, tab) - if xml.display_english then - xml:english_xml(t, tab) - else - xml:print_xml(t, tab) - end -end - -function xml:english_xml(t, tab, not_flag) - local i, k, e - local pre, post, recurse - local children_not_flag - local nextlevel - local bcol, ecol = TERM_L_GREEN, TERM_GREEN - - if xml.write_active and t == auto_aux.rule then bcol, ecol = TERM_VIOLET, TERM_VIOLET end - - nextlevel = tab .. " " - - recurse = 1 - - if t.label == "rule" then - if t.args.type == "inscribe" then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "A rule named \"") - xml.write(TERM_WHITE, tostring(t.args.name)) - xml.write(ecol, "\" to ") - xml.write(bcol, "inscribe") - xml.write(ecol, " an item with \"") - xml.write(TERM_WHITE, t.args.inscription) - xml.write(ecol, "\" when") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "A rule named \"") - xml.write(TERM_WHITE, tostring(t.args.name)) - xml.write(ecol, "\" to ") - xml.write(bcol, t.args.type) - xml.write(ecol, " when") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "and" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "At least one of the following is false:") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "All of the following are true:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "or" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "All of the following are false:") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "At least one of the following are true:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "not" then - if bcol == TERM_VIOLET or getn(t) == 0 then - xml.write(ecol, "(a negating rule)") - xml.write(TERM_WHITE, "\n") - else - nextlevel = tab - end - children_not_flag = not nil - elseif t.label == "inventory" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Nothing in your ") - xml.write(bcol, "inventory") - 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, "inventory") - xml.write(ecol, " matches the following:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "equipment" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Nothing in your ") - xml.write(bcol, "equipment") - 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, "equipment") - xml.write(ecol, " matches the following:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "comment" then - xml.write(TERM_WHITE, tab) - xml.write(TERM_WHITE, "(" .. t[1] .. ")") - xml.write(TERM_WHITE, "\n") - elseif t.label == "skill" then - local s = t[1] - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your skill in ") - xml.write(bcol, s) - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your skill in ") - xml.write(bcol, s) - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "ability" then - local s = t[1] - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "You do not have the ") - xml.write(bcol, s) - xml.write(ecol, " ability") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "You have the ") - xml.write(bcol, s) - xml.write(ecol, " ability") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "level" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your ") - xml.write(bcol, "level") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your ") - xml.write(bcol, "level") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "sval" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "sval") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "sval") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "discount" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "discount") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "discount") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - else - if xml.rule2string[t.label] then - local rule = xml.rule2string[t.label] - a, b, c = rule[1], rule[2], rule[3] - if not_flag then c = c .. " not" end - xml.write(TERM_WHITE, tab) - xml.write(ecol, a) - xml.write(bcol, b) - xml.write(ecol, c) - xml.write(ecol, " \"") - xml.write(TERM_WHITE, t[1]) - xml.write(ecol, "\"") - xml.write(TERM_WHITE, "\n") - else - if not_flag then - xml.write(bcol, "Not:\n") - tab = tab .. " " - xml:print_xml(t, tab) - return - end - end - 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, children_not_flag) - end - end -end - -function xml:print_xml(t, tab) - local i, k, e - local inside = nil - local bcol, ecol = TERM_L_GREEN, TERM_GREEN - - if xml.write_active and t == auto_aux.rule then bcol, ecol = TERM_VIOLET, TERM_VIOLET end - - xml.write(bcol, tab.."<"..t.label) - for k, e in t.args do - xml.write(TERM_L_BLUE, " "..k) - xml.write(TERM_WHITE, "=\"") - xml.write(TERM_YELLOW, e) - xml.write(TERM_WHITE, "\"") - end - xml.write(bcol, ">") - - for i = 1, getn(t) do - if type(t[i]) == "string" then - xml.write(TERM_WHITE, t[i]) - else - if not inside then xml.write(TERM_WHITE, "\n") end - inside = not nil - xml:print_xml(t[i], tab.." ") - end - end - - if not inside then - xml.write(ecol, "\n") - else - xml.write(ecol, tab.."\n") - end -end - --- t is a table representing xml, outputs the xml code via xml.write() -function xml:output(t) - local i - for i = 1, getn(t) do - xml:print_xml(t[i], "") - end -end diff --git a/lib/mods/theme/core/auto.lua b/lib/mods/theme/core/auto.lua deleted file mode 100644 index b758db52..00000000 --- a/lib/mods/theme/core/auto.lua +++ /dev/null @@ -1,859 +0,0 @@ --- 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 == "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 - 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, - }, - ["inventory"] = - { - { - "Check is true if something in player's inventory matches", - "the contained rule", - }, - xml:collect([[...]]), - function () - return xml:collect("") - end, - }, - ["equipment"] = - { - { - "Check is true if something in player's equipment matches", - "the contained rule", - }, - xml:collect([[...]]), - function () - return xml:collect("") - 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 - if (auto_aux.rule.label == "rule" or auto_aux.rule.label == "equipment") and auto_aux.rule[1] then return end - if (auto_aux.rule.label == "rule" or auto_aux.rule.label == "inventory") 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" and auto_aux.rule.label ~= "equipment" and auto_aux.rule.label ~= "inventory" 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 diff --git a/lib/mods/theme/core/init.lua b/lib/mods/theme/core/init.lua index 51dfc1d2..44ef9a7a 100644 --- a/lib/mods/theme/core/init.lua +++ b/lib/mods/theme/core/init.lua @@ -3,9 +3,6 @@ -- Load the system functions -- --- Very thin xml parser(49 lines ;) -tome_dofile_anywhere(ANGBAND_DIR_CORE, "xml.lua") - -- various vital helper code tome_dofile_anywhere(ANGBAND_DIR_CORE, "util.lua") tome_dofile_anywhere(ANGBAND_DIR_CORE, "player.lua") diff --git a/lib/mods/theme/core/xml.lua b/lib/mods/theme/core/xml.lua deleted file mode 100644 index 14f0511f..00000000 --- a/lib/mods/theme/core/xml.lua +++ /dev/null @@ -1,375 +0,0 @@ --- The xml module -xml = {} - -function xml:parseargs (s) - local arg = {} - gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) - %arg[w] = a - end) - return arg -end - --- s is a xml stream, returns a table -function xml:collect (s) - local stack = {n=0} - local top = {n=0} - tinsert(stack, top) - local ni,c,label,args, empty - local i, j = 1, 1 - while 1 do - ni,j,c,label,args, empty = strfind(s, "<(%/?)(%w+)(.-)(%/?)>", j) - if not ni then break end - local text = strsub(s, i, ni-1) - if not strfind(text, "^%s*$") then - tinsert(top, text) - end - if empty == "/" then -- empty element tag - tinsert(top, {n=0, label=label, args=xml:parseargs(args), empty=1}) - elseif c == "" then -- start tag - top = {n=0, label=label, args=xml:parseargs(args)} - tinsert(stack, top) -- new level - else -- end tag - local toclose = tremove(stack) -- remove top - top = stack[stack.n] - if stack.n < 1 then - error("nothing to close with "..label) - end - if toclose.label ~= label then - error("trying to close "..toclose.label.." with "..label) - end - tinsert(top, toclose) - end - i = j+1 - end - local text = strsub(s, i) - if not strfind(text, "^%s*$") then - tinsert(stack[stack.n], text) - end - if stack.n > 1 then - error("unclosed "..stack[stack.n].label) - end - return stack[1] -end - --- Viewport coordinates -xml.write_out_y = 0 -xml.write_out_x = 0 -xml.write_out_h = 24 -xml.write_out_w = 80 - --- Offsets -xml.write_off_y = 0 -xml.write_off_x = 0 - --- Current position -xml.write_y = 0 -xml.write_x = 0 - -xml.write_screen = function(color, s) - local i - for i = 1, strlen(s) do - local c = strsub(s, i, i + 1) - if c ~= "\n" then - if xml.write_y - xml.write_off_y >= 0 and xml.write_y - xml.write_off_y < xml.write_out_h and xml.write_x - xml.write_off_x >= 0 and xml.write_x - xml.write_off_x < xml.write_out_w then - Term_putch(xml.write_x - xml.write_off_x + xml.write_out_x, xml.write_y - xml.write_off_y + xml.write_out_y, color, strbyte(c)) - end - xml.write_x = xml.write_x + 1 - else - xml.write_x = 0 - xml.write_y = xml.write_y + 1 - end - end -end - -xml.write_file = function (color, s) - print_hook(s) -end - -xml.write = xml.write_screen - -xml.rule2string = { - ['name'] = {"Its ", "name", " is"}, - ['contain'] = {"Its ", "name", " contains"}, - ['symbol'] = {"Its ", "symbol", " is"}, - ['inscribed'] = {"Its ", "inscription", " contains"}, - ['state'] = {"Its ", "state", " is"}, - ['status'] = {"Its ", "status", " is"}, - ['tval'] = {"Its ", "tval", " is"}, - ['race'] = {"Your ", "race", " is"}, - ['subrace'] = {"Your ", "subrace", " is"}, - ['class'] = {"Your ", "class", " is"}, - ['foo1'] = {"The result of ", "test 1 ", "is"}, - ['foo2'] = {"The result of ", "test 2 ", "is"}, - ['foo3'] = {"The result of ", "test 3 ", "is"}, -} - -xml.display_english = 1 -function xml:display_xml(t, tab) - if xml.display_english then - xml:english_xml(t, tab) - else - xml:print_xml(t, tab) - end -end - -function xml:english_xml(t, tab, not_flag) - local i, k, e - local pre, post, recurse - local children_not_flag - local nextlevel - local bcol, ecol = TERM_L_GREEN, TERM_GREEN - - if xml.write_active and t == auto_aux.rule then bcol, ecol = TERM_VIOLET, TERM_VIOLET end - - nextlevel = tab .. " " - - recurse = 1 - - if t.label == "rule" then - if t.args.type == "inscribe" then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "A rule named \"") - xml.write(TERM_WHITE, tostring(t.args.name)) - xml.write(ecol, "\" to ") - xml.write(bcol, "inscribe") - xml.write(ecol, " an item with \"") - xml.write(TERM_WHITE, t.args.inscription) - xml.write(ecol, "\" when") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "A rule named \"") - xml.write(TERM_WHITE, tostring(t.args.name)) - xml.write(ecol, "\" to ") - xml.write(bcol, t.args.type) - xml.write(ecol, " when") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "and" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "At least one of the following is false:") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "All of the following are true:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "or" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "All of the following are false:") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "At least one of the following are true:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "not" then - if bcol == TERM_VIOLET or getn(t) == 0 then - xml.write(ecol, "(a negating rule)") - xml.write(TERM_WHITE, "\n") - else - nextlevel = tab - end - children_not_flag = not nil - elseif t.label == "inventory" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Nothing in your ") - xml.write(bcol, "inventory") - 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, "inventory") - xml.write(ecol, " matches the following:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "equipment" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Nothing in your ") - xml.write(bcol, "equipment") - 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, "equipment") - xml.write(ecol, " matches the following:") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "comment" then - xml.write(TERM_WHITE, tab) - xml.write(TERM_WHITE, "(" .. t[1] .. ")") - xml.write(TERM_WHITE, "\n") - elseif t.label == "skill" then - local s = t[1] - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your skill in ") - xml.write(bcol, s) - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your skill in ") - xml.write(bcol, s) - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "ability" then - local s = t[1] - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "You do not have the ") - xml.write(bcol, s) - xml.write(ecol, " ability") - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "You have the ") - xml.write(bcol, s) - xml.write(ecol, " ability") - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "level" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your ") - xml.write(bcol, "level") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Your ") - xml.write(bcol, "level") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "sval" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "sval") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "sval") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - elseif t.label == "discount" then - if not_flag then - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "discount") - xml.write(ecol, " is not from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - else - xml.write(TERM_WHITE, tab) - xml.write(ecol, "Its ") - xml.write(bcol, "discount") - xml.write(ecol, " is from ") - xml.write(TERM_WHITE, tostring(t.args.min)) - xml.write(ecol, " to ") - xml.write(TERM_WHITE, tostring(t.args.max)) - xml.write(TERM_WHITE, "\n") - end - else - if xml.rule2string[t.label] then - local rule = xml.rule2string[t.label] - a, b, c = rule[1], rule[2], rule[3] - if not_flag then c = c .. " not" end - xml.write(TERM_WHITE, tab) - xml.write(ecol, a) - xml.write(bcol, b) - xml.write(ecol, c) - xml.write(ecol, " \"") - xml.write(TERM_WHITE, t[1]) - xml.write(ecol, "\"") - xml.write(TERM_WHITE, "\n") - else - if not_flag then - xml.write(bcol, "Not:\n") - tab = tab .. " " - xml:print_xml(t, tab) - return - end - end - 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, children_not_flag) - end - end -end - -function xml:print_xml(t, tab) - local i, k, e - local inside = nil - local bcol, ecol = TERM_L_GREEN, TERM_GREEN - - if xml.write_active and t == auto_aux.rule then bcol, ecol = TERM_VIOLET, TERM_VIOLET end - - xml.write(bcol, tab.."<"..t.label) - for k, e in t.args do - xml.write(TERM_L_BLUE, " "..k) - xml.write(TERM_WHITE, "=\"") - xml.write(TERM_YELLOW, e) - xml.write(TERM_WHITE, "\"") - end - xml.write(bcol, ">") - - for i = 1, getn(t) do - if type(t[i]) == "string" then - xml.write(TERM_WHITE, t[i]) - else - if not inside then xml.write(TERM_WHITE, "\n") end - inside = not nil - xml:print_xml(t[i], tab.." ") - end - end - - if not inside then - xml.write(ecol, "\n") - else - xml.write(ecol, tab.."\n") - end -end - --- t is a table representing xml, outputs the xml code via xml.write() -function xml:output(t) - local i - for i = 1, getn(t) do - xml:print_xml(t[i], "") - end -end diff --git a/src/dungeon.c b/src/dungeon.c index af46976d..ce259344 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -5420,7 +5420,11 @@ static void load_all_pref_files(void) process_pref_file(buf); /* Process player specific automatizer sets */ - tome_dofile_anywhere(ANGBAND_DIR_USER, format("%s.atm", player_name), FALSE); + /* TODO: Disabled temporarily because it causes duplicate + * rules on save and subsequent game load. */ + /* sprintf(buf2, "%s.atm", player_name); */ + /* path_build(buf, sizeof(buf), ANGBAND_DIR_USER, buf2); */ + /* automatizer_init(buf); */ } /* diff --git a/src/externs.h b/src/externs.h index f2c38f08..71d7b393 100644 --- a/src/externs.h +++ b/src/externs.h @@ -994,7 +994,7 @@ extern u32b fake_text_size; extern bool_ gen_joke_monsters(void *data, void *in, void *out); /* loadsave.c */ -extern bool_ file_exist(char *buf); +extern bool_ file_exist(cptr buf); extern s16b rd_variable(void); extern void wr_variable(s16b *var); extern void wr_scripts(void); @@ -2275,7 +2275,7 @@ extern void squeltch_grid(void); extern void do_cmd_automatizer(void); extern void automatizer_add_rule(object_type *o_ptr, bool_ destroy); extern bool_ automatizer_create; - +extern void automatizer_init(cptr file_name); /* @@ -2363,6 +2363,7 @@ extern bool_ module_savefile_loadable(cptr savefile_mod); extern void tome_intro(); extern void theme_intro(); extern void init_hooks_module(); +extern int find_module(cptr name); /* lua_bind.c */ diff --git a/src/init2.c b/src/init2.c index 0ce8c1a0..2f897d9b 100644 --- a/src/init2.c +++ b/src/init2.c @@ -2779,8 +2779,8 @@ void init_angband(void) process_pref_file(buf); /* Initialise the automatizer */ - tome_dofile_anywhere(ANGBAND_DIR_CORE, "auto.lua", TRUE); - tome_dofile_anywhere(ANGBAND_DIR_USER, "automat.atm", FALSE); + path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "automat.atm"); + automatizer_init(buf); /* Done */ note("[Initialisation complete]"); diff --git a/src/loadsave.c b/src/loadsave.c index 45f493a6..08cb53b3 100644 --- a/src/loadsave.c +++ b/src/loadsave.c @@ -854,7 +854,7 @@ bool_ save_player(void) return (result); } -bool_ file_exist(char *buf) +bool_ file_exist(cptr buf) { int fd; bool_ result; diff --git a/src/modules.c b/src/modules.c index c4bea6eb..9376cc3d 100644 --- a/src/modules.c +++ b/src/modules.c @@ -181,6 +181,22 @@ bool_ module_savefile_loadable(cptr savefile_mod) /* Did the player force a module on command line */ cptr force_module = NULL; +/* Find module index by name. Returns -1 if matching module not found */ +int find_module(cptr name) +{ + int i = 0; + + for (i=0; i -extern lua_State *L; +#define RULES_MAX 4096 +#define STACK_MAX 1024 + +typedef enum { BAD, VERY_BAD, AVERAGE, + GOOD, VERY_GOOD, SPECIAL, + TERRIBLE, NONE, CHEST_EMPTY, + CHEST_DISARMED } status_type; + +struct status_map_type { + status_type status; + cptr status_s; +}; + +status_type object_status(object_type *o_ptr) +{ + if (!object_known_p(o_ptr)) + { + switch (o_ptr->sense) + { + case SENSE_CURSED: return BAD; + case SENSE_WORTHLESS: return VERY_BAD; + case SENSE_AVERAGE: return AVERAGE; + case SENSE_GOOD_LIGHT: return GOOD; + case SENSE_GOOD_HEAVY: return GOOD; + case SENSE_EXCELLENT: return VERY_GOOD; + case SENSE_SPECIAL: return SPECIAL; + case SENSE_TERRIBLE: return TERRIBLE; + default: return NONE; + } + } + else + { + s16b slot = wield_slot_ideal(o_ptr, TRUE); + + if (artifact_p(o_ptr)) + { + if (!(o_ptr->ident & IDENT_CURSED)) + { + return SPECIAL; + } + else + { + return TERRIBLE; + } + } + else if ((o_ptr->name2 > 0) || + (o_ptr->name2b > 0)) + { + if (!(o_ptr->ident & IDENT_CURSED)) + { + return VERY_GOOD; + } + else + { + return VERY_BAD; + } + } + else if ((slot == INVEN_WIELD) || + (slot == INVEN_BOW) || + (slot == INVEN_AMMO) || + (slot == INVEN_TOOL)) + { + if (o_ptr->to_h + o_ptr->to_d < 0) + { + return BAD; + } + else if (o_ptr->to_h + o_ptr->to_d > 0) + { + return GOOD; + } + else + { + return AVERAGE; + } + } + else if ((slot >= INVEN_BODY) && + (slot <= INVEN_FEET)) + { + if (o_ptr->to_a < 0) + { + return BAD; + } + else if (o_ptr->to_a > 0) + { + return GOOD; + } + else + { + return AVERAGE; + } + } + else if (slot == INVEN_RING) + { + if ((o_ptr->to_d + o_ptr->to_h < 0) || + (o_ptr->to_a < 0) || + (o_ptr->pval < 0)) + { + return BAD; + } + else + { + return AVERAGE; + } + } + else if (slot == INVEN_NECK) + { + if (o_ptr->pval < 0) + { + return BAD; + } + else + { + return AVERAGE; + } + } + else if (o_ptr->tval == TV_CHEST) + { + if (o_ptr->pval == 0) + { + return CHEST_EMPTY; + } + else if (o_ptr->pval < 0) + { + return CHEST_DISARMED; + } + else + { + return AVERAGE; + } + } + else + { + return AVERAGE; + } + } +} + +#define STATUS_MAP_SIZE 10 +struct status_map_type status_map[STATUS_MAP_SIZE] = { + { BAD, "bad" }, + { VERY_BAD, "very bad" }, + { AVERAGE, "average" }, + { GOOD, "good" }, + { VERY_GOOD, "very good" }, + { SPECIAL, "special" }, + { TERRIBLE, "terrible" }, + { NONE, "none" }, + { CHEST_EMPTY, "(empty chest)" }, + { CHEST_DISARMED, "(disarmed chest)" }, +}; + +static cptr status_to_string(status_type status) +{ + int i; + + for (i = 0; i < STATUS_MAP_SIZE; i++) + { + if (status_map[i].status == status) + { + return status_map[i].status_s; + } + } + + assert(FALSE); + return NULL; +} + +static bool_ status_from_string(cptr s, status_type *status) +{ + int i; + + for (i = 0; i < STATUS_MAP_SIZE; i++) + { + if (streq(status_map[i].status_s, s)) + { + *status = status_map[i].status; + return TRUE; + } + } + + return FALSE; +} + +/* Type of automatizer actions */ +typedef enum { AUTO_DESTROY, + AUTO_PICKUP, + AUTO_INSCRIBE } action_type; + +/* Convert action to/from string */ +struct action_map_type { + action_type action; + cptr action_s; +}; + +#define ACTION_MAP_SIZE 3 +struct action_map_type action_map[ACTION_MAP_SIZE] = { + { AUTO_DESTROY, "destroy" }, + { AUTO_PICKUP, "pickup" }, + { AUTO_INSCRIBE, "inscribe" } +}; + +static cptr action_to_string(action_type action) +{ + int i = 0; + + for (i = 0; i < ACTION_MAP_SIZE; i++) + { + if (action == action_map[i].action) + { + return action_map[i].action_s; + } + } + + assert(FALSE); + return NULL; +} + +static bool_ action_from_string(cptr s, action_type *action) +{ + int i = 0; + + for (i = 0; i < ACTION_MAP_SIZE; i++) + { + if (streq(action_map[i].action_s, s)) + { + *action = action_map[i].action; + return TRUE; + } + } + + return FALSE; +} + +/* Identification state */ +typedef enum { IDENTIFIED, NOT_IDENTIFIED } identification_state; + +#define S_IDENTIFIED "identified" +#define S_NOT_IDENTIFIED "not identified" + +cptr identification_state_to_string(identification_state i) +{ + switch (i) + { + case IDENTIFIED: return S_IDENTIFIED; + case NOT_IDENTIFIED: return S_NOT_IDENTIFIED; + } + + assert(FALSE); + return NULL; +} + +bool_ identification_state_from_string(cptr s, identification_state *state) +{ + if (streq(s, S_IDENTIFIED)) + { + *state = IDENTIFIED; + return TRUE; + } + else if (streq(s, S_NOT_IDENTIFIED)) + { + *state = NOT_IDENTIFIED; + return TRUE; + } + else + { + return FALSE; + } +} + +/* Match type */ +typedef enum { M_AND , M_OR , M_NOT , M_NAME , M_CONTAIN , + M_INSCRIBED, M_DISCOUNT, M_SYMBOL , M_STATE , M_STATUS , + M_TVAL , M_SVAL , M_RACE , M_SUBRACE , M_CLASS , + M_LEVEL , M_SKILL , M_ABILITY, M_INVENTORY, M_EQUIPMENT } + match_type; + +struct match_type_map { + match_type i; + cptr s; +}; + +#define MATCH_TYPE_MAP_SIZE 20 +struct match_type_map match_type_map[MATCH_TYPE_MAP_SIZE] = { + { M_AND, "and" }, + { M_OR, "or" }, + { M_NOT, "not" }, + { M_NAME, "name" }, + { M_CONTAIN, "contain" }, + { M_INSCRIBED, "inscribed" }, + { M_DISCOUNT, "discount" }, + { M_SYMBOL, "symbol" }, + { M_STATE, "state" }, + { M_STATUS, "status" }, + { M_TVAL, "tval" }, + { M_SVAL, "sval" }, + { M_RACE, "race" }, + { M_SUBRACE, "subrace" }, + { M_CLASS, "class" }, + { M_LEVEL, "level" }, + { M_SKILL, "skill" }, + { M_ABILITY, "ability" }, + { M_INVENTORY, "inventory" }, + { M_EQUIPMENT, "equipment" }, +}; + +cptr match_type_to_string(match_type m) +{ + int i; + + for (i = 0; i < MATCH_TYPE_MAP_SIZE; i++) + { + if (match_type_map[i].i == m) + { + return match_type_map[i].s; + } + } + + assert(FALSE); + return NULL; +} + +bool_ match_type_from_string(cptr s, match_type *match) +{ + int i; + + for (i = 0; i < MATCH_TYPE_MAP_SIZE; i++) + { + if (streq(match_type_map[i].s, s)) + { + *match = match_type_map[i].i; + return TRUE; + } + } + + return FALSE; +} + +/* Forward declarations */ +typedef struct condition_type condition_type; +struct condition_type; + +/* List of conditions */ +typedef struct condition_list condition_list; +struct condition_list { + condition_type *condition; + condition_list *next; +}; + +int compare_condition_list(condition_list *a, condition_list *b) +{ + assert(FALSE); +} + +SGLIB_DEFINE_LIST_PROTOTYPES(condition_list, compare_condition_list, next); +SGLIB_DEFINE_LIST_FUNCTIONS(condition_list, compare_condition_list, next); + +/* Condition instance */ +struct condition_type +{ + /* What do we want to match? */ + match_type match; + /* Sub-conditions for logical connectives; if applicable */ + struct { + condition_list *c; + } conditions; + /* Sub-condition for cases where there is only a single subcondition */ + condition_type *subcondition; + /* Tval to match if applicable. */ + byte tval; + /* Sval range if applicable. */ + struct { + byte min; + byte max; + } sval_range; + /* Discount range. */ + struct { + int min; + int max; + } discount; + /* Level range */ + struct { + int min; + int max; + } level_range; + /* Skill range */ + struct { + s16b min; + s16b max; + s16b skill_idx; + } skill_range; + /* Identification state to match if applicable */ + identification_state identification_state; + /* Status to match if applicable */ + status_type status; + /* Name to match */ + char *name; + /* Symbol to match if applicable */ + char symbol; + /* Inscription to find */ + char *inscription; + /* Subrace to match if applicable */ + char *subrace; + /* Race to match if applicable */ + char *race; + /* Class to match if applicable */ + char *klass; + /* Ability to match if applicable */ + s16b ability; + /* Comment */ + char *comment; +}; + +static condition_type *condition_new(match_type match) +{ + condition_type *cp = malloc(sizeof(condition_type)); + memset(cp, 0, sizeof(condition_type)); + cp->match = match; + return cp; +} + +static condition_type *condition_new_tval(byte tval) +{ + condition_type *cp = condition_new(M_TVAL); + cp->tval = tval; + return cp; +} + +static condition_type *condition_new_sval(byte min, byte max) +{ + condition_type *cp = condition_new(M_SVAL); + cp->sval_range.min = min; + cp->sval_range.max = max; + return cp; +} + +static condition_type *condition_new_and() +{ + condition_type *cp = condition_new(M_AND); + return cp; +} + +static condition_type *condition_new_or() +{ + condition_type *cp = condition_new(M_OR); + return cp; +} + +static condition_type *condition_new_not() +{ + condition_type *cp = condition_new(M_NOT); + return cp; +} + +static condition_type *condition_new_name(cptr name) +{ + condition_type *cp = condition_new(M_NAME); + cp->name = strdup(name); + return cp; +} + +static condition_type *condition_new_contain(cptr name) +{ + condition_type *cp = condition_new(M_CONTAIN); + cp->name = strdup(name); + return cp; +} + +static condition_type *condition_new_inscribed(cptr name) +{ + condition_type *cp = condition_new(M_INSCRIBED); + cp->inscription = strdup(name); + return cp; +} + +static condition_type *condition_new_status(status_type status) +{ + condition_type *cp = condition_new(M_STATUS); + cp->status = status; + return cp; +} + +static condition_type *condition_new_state(identification_state state) +{ + condition_type *cp = condition_new(M_STATE); + cp->identification_state = state; + return cp; +} + +static condition_type *condition_new_discount(int min, int max) +{ + condition_type *cp = condition_new(M_DISCOUNT); + cp->discount.min = min; + cp->discount.max = max; + return cp; +} + +static condition_type *condition_new_symbol(char c) +{ + condition_type *cp = condition_new(M_SYMBOL); + cp->symbol = c; + return cp; +} + +static condition_type *condition_new_race(cptr race) +{ + condition_type *cp = condition_new(M_RACE); + cp->race = strdup(race); + return cp; +} + +static condition_type *condition_new_subrace(cptr subrace) +{ + condition_type *cp = condition_new(M_SUBRACE); + cp->subrace = strdup(subrace); + return cp; +} + +static condition_type *condition_new_class(cptr klass) +{ + condition_type *cp = condition_new(M_CLASS); + cp->klass = strdup(klass); + return cp; +} + +static condition_type *condition_new_level(int min, int max) +{ + condition_type *cp = condition_new(M_LEVEL); + cp->level_range.min = min; + cp->level_range.max = max; + return cp; +} + +static condition_type *condition_new_skill(s16b min, s16b max, s16b skill_idx) +{ + condition_type *cp = condition_new(M_SKILL); + cp->skill_range.min = min; + cp->skill_range.max = max; + cp->skill_range.skill_idx = skill_idx; + return cp; +} + +static condition_type *condition_new_ability(s16b ability_idx) +{ + condition_type *cp = condition_new(M_ABILITY); + cp->ability = ability_idx; + return cp; +} + +static condition_type *condition_new_inventory() +{ + condition_type *cp = condition_new(M_INVENTORY); + return cp; +} + +static condition_type *condition_new_equipment() +{ + condition_type *cp = condition_new(M_EQUIPMENT); + return cp; +} + +static void condition_and_add(condition_type *and_c, condition_type *c) +{ + assert(and_c != NULL); + assert(c != NULL); + assert((and_c->match == M_AND) || (and_c->match == M_OR)); + + condition_list *cl = malloc(sizeof(condition_list)); + cl->condition = c; + cl->next = NULL; + + sglib_condition_list_add(&and_c->conditions.c, cl); +} + +static void condition_or_add(condition_type *or_c, condition_type *c) +{ + condition_and_add(or_c, c); +} + +static void condition_destroy(condition_type **cp) +{ + condition_type *c = NULL; + assert(cp != NULL); + assert(*cp != NULL); + + c = *cp; + + /* Free sub-conditions if any */ + { + condition_list *current = NULL; + condition_list *next = NULL; + + for (current = c->conditions.c; + current != NULL; + current = next) + { + condition_destroy(¤t->condition); + next = current->next; + free(current); + } + } + + /* Free sub-condition if any */ + if (c->subcondition) + { + condition_destroy(&c->subcondition); + } + + /* Free name if any */ + if (c->name) + { + free(c->name); + c->name = NULL; + } + + /* Free inscription if any */ + if (c->inscription) + { + free(c->inscription); + c->inscription = NULL; + } + + /* Free subrace if any */ + if (c->subrace) + { + free(c->subrace); + c->subrace = NULL; + } + + /* Free race if any */ + if (c->race) + { + free(c->race); + c->race = NULL; + } + + /* Free class if any */ + if (c->klass) + { + free(c->klass); + c->klass = NULL; + } + + /* Free comment if any */ + if (c->comment) + { + free(c->comment); + c->comment = NULL; + } + + /* Free the condition itself */ + free(*cp); + *cp = NULL; +} + +static bool_ condition_eval(condition_type *c, object_type *o_ptr) +{ + bool_ is_and = (c->match == M_AND); + bool_ is_or = (c->match == M_OR); + + switch (c->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + for (child = sglib_condition_list_it_init(&it, c->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + if (is_and && (!condition_eval(child->condition, o_ptr))) + { + return FALSE; + } + + if (is_or && condition_eval(child->condition, o_ptr)) + { + return TRUE; + } + } + + if (is_and) + { + return TRUE; + } + else + { + return FALSE; + } + } + + case M_NOT: + { + if (c->subcondition == NULL) + { + return TRUE; + } + + return !condition_eval(c->subcondition, o_ptr); + } + + case M_INVENTORY: + { + int i; + + if (c->subcondition == NULL) + { + return FALSE; + } + + for (i = 0; i < INVEN_WIELD; i++) + { + if (condition_eval(c->subcondition, &p_ptr->inventory[i])) + { + return TRUE; + } + } + + return FALSE; + } + + case M_EQUIPMENT: + { + int i; + + if (c->subcondition == NULL) + { + return FALSE; + } + + for (i = INVEN_WIELD; i < INVEN_TOTAL; i++) + { + if (condition_eval(c->subcondition, &p_ptr->inventory[i])) + { + return TRUE; + } + } + + return FALSE; + } + + case M_NAME: + { + char buf1[128]; + char buf2[128]; + + object_desc(buf1, o_ptr, -1, 0); + strlower(buf1); + + sprintf(buf2, "%s", c->name); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_CONTAIN: + { + char buf1[128]; + char buf2[128]; + + object_desc(buf1, o_ptr, -1, 0); + strlower(buf1); + + sprintf(buf2, "%s", c->name); + strlower(buf2); + + return (strstr(buf1, buf2) != NULL); + } + + case M_SYMBOL: + { + object_kind *k_ptr = &k_info[o_ptr->k_idx]; + + return k_ptr->d_char == c->symbol; + } + + case M_INSCRIBED: + { + char buf1[128]; + char buf2[128]; + + if (o_ptr->note == 0) + { + return FALSE; + } + + sprintf(buf1, "%s", quark_str(o_ptr->note)); + strlower(buf1); + + sprintf(buf2, "%s", c->inscription); + strlower(buf2); + + return (strstr(buf1, buf2) != NULL); + } + + case M_DISCOUNT: + { + return (object_aware_p(o_ptr) && + (o_ptr->discount >= c->discount.min) && + (o_ptr->discount <= c->discount.max)); + } + + case M_TVAL: + { + return (o_ptr->tval == c->tval); + } + + case M_SVAL: + { + return (object_aware_p(o_ptr) && + (o_ptr->sval >= c->sval_range.min) && + (o_ptr->sval <= c->sval_range.max)); + } + + case M_STATUS: + { + return c->status == object_status(o_ptr); + } + + case M_STATE: + { + switch (c->identification_state) + { + case IDENTIFIED: + return object_known_p(o_ptr); + case NOT_IDENTIFIED: + return !object_known_p(o_ptr); + default: + assert(FALSE); + } + } + + case M_RACE: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", rp_ptr->title + rp_name); + strlower(buf1); + + sprintf(buf2, "%s", c->race); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_SUBRACE: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", rmp_ptr->title + rmp_name); + strlower(buf1); + + sprintf(buf2, "%s", c->subrace); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_CLASS: + { + char buf1[128]; + char buf2[128]; + + sprintf(buf1, "%s", spp_ptr->title + c_name); + strlower(buf1); + + sprintf(buf2, "%s", c->race); + strlower(buf2); + + return streq(buf1, buf2); + } + + case M_LEVEL: + { + return ((p_ptr->lev >= c->level_range.min) && + (p_ptr->lev <= c->level_range.max)); + } + + case M_SKILL: + { + s16b sk = get_skill(c->skill_range.skill_idx); + return ((sk >= c->skill_range.min) && + (sk <= c->skill_range.max)); + } + + case M_ABILITY: + { + return has_ability(c->ability); + } + + } + + /* Don't match by default */ + return FALSE; +} + +static json_t *condition_to_json(condition_type *c) +{ + json_t *json = NULL; + + if (c == NULL) + { + return json_null(); + } + + json = json_object(); + json_object_set_new(json, "type", + json_string(match_type_to_string(c->match))); + + switch (c->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + json_t *conditions_json = json_array(); + + for (child = sglib_condition_list_it_init(&it, c->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + json_array_append_new(conditions_json, + condition_to_json(child->condition)); + } + + json_object_set_new(json, "conditions", conditions_json); + break; + } + + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + json_object_set_new(json, "condition", + condition_to_json(c->subcondition)); + break; + } + + case M_NAME: + { + json_object_set_new(json, "name", + json_string(c->name)); + break; + } + + case M_CONTAIN: + { + json_object_set_new(json, "contain", + json_string(c->name)); + break; + } + + case M_SYMBOL: + { + json_object_set_new(json, "symbol", + json_string(format("%c", c->symbol))); + break; + } + + case M_INSCRIBED: + { + json_object_set_new(json, "inscription", + json_string(c->inscription)); + break; + } + + case M_DISCOUNT: + { + json_object_set_new(json, "min", + json_integer(c->discount.min)); + json_object_set_new(json, "max", + json_integer(c->discount.max)); + break; + } + + case M_TVAL: + { + json_object_set_new(json, "tval", + json_integer(c->tval)); + break; + } + + case M_SVAL: + { + json_object_set_new(json, "min", + json_integer(c->sval_range.min)); + json_object_set_new(json, "max", + json_integer(c->sval_range.max)); + break; + } + + case M_STATUS: + { + json_object_set_new(json, "status", + json_string(status_to_string(c->status))); + break; + } + + case M_STATE: + { + json_object_set_new(json, "state", + json_string(identification_state_to_string(c->identification_state))); + break; + } + + case M_RACE: + { + json_object_set_new(json, "race", + json_string(c->race)); + break; + } + + case M_SUBRACE: + { + json_object_set_new(json, "subrace", + json_string(c->subrace)); + break; + } + + case M_CLASS: + { + json_object_set_new(json, "class", + json_string(c->klass)); + break; + } + + case M_LEVEL: + { + json_object_set_new(json, "min", + json_integer(c->level_range.min)); + json_object_set_new(json, "max", + json_integer(c->level_range.max)); + break; + } + + case M_SKILL: + { + json_object_set_new(json, "name", + json_string(s_info[c->skill_range.skill_idx].name + s_name)); + json_object_set_new(json, "min", + json_integer(c->skill_range.min)); + json_object_set_new(json, "max", + json_integer(c->skill_range.max)); + break; + } + + case M_ABILITY: + { + json_object_set_new(json, "ability", + json_string(ab_info[c->ability].name + ab_name)); + break; + } + + } + + return json; +} /* - * The functions here use direct lua stack manipulation for calls instead of - * exec_lua(format()) because string manipulations are too slow for such - * functions + * Cursor to maintain position in condition tree */ +static condition_type *cursor_stack[STACK_MAX]; +static int cursor_count = 0; + +static void cursor_push(condition_type *condition) +{ + assert(cursor_count < STACK_MAX); + + cursor_stack[cursor_count] = condition; + cursor_count++; +} + +static condition_type *cursor_pop() +{ + condition_type *c = NULL; + + assert(cursor_count > 0); + + c = cursor_stack[cursor_count-1]; + cursor_stack[cursor_count] = NULL; + cursor_count--; + return c; +} + +static condition_type *cursor_top() +{ + assert(cursor_count > 0); + + return cursor_stack[cursor_count - 1]; +} + +static void cursor_clear() +{ + while (cursor_count > 0) + { + cursor_pop(); + } +} + +/* Rule */ +typedef struct arule_type arule_type; +struct arule_type { + /* Rule name */ + char *name; + /* Which action do we take? */ + action_type action; /* Which action to take */ + /* Which module does this rule apply to? */ + int module_idx; + /* Inscription to use for inscription rules. */ + char *inscription; + /* Condition. */ + condition_type *condition; /* Condition for rule match */ +}; + +/* Initialize a rule */ +static arule_type *rule_new(cptr name, action_type action, int module_idx, condition_type *condition, cptr inscription) +{ + arule_type *rp = malloc(sizeof(arule_type)); + rp->name = strdup(name); + rp->action = action; + rp->module_idx = module_idx; + rp->condition = condition; + rp->inscription = (inscription == NULL) ? NULL : strdup(inscription); + return rp; +} + +static void rule_set_name(arule_type *rule, cptr new_name) +{ + if (rule->name) + { + free(rule->name); + } + + rule->name = strdup(new_name); +} + +static void rule_destroy(arule_type **rp) +{ + if ((*rp)->name) + { + free((*rp)->name); + } + + if ((*rp)->inscription) + { + free((*rp)->inscription); + } + + if ((*rp)->condition) + { + condition_destroy(&(*rp)->condition); + } + + free(*rp); + *rp = NULL; +} + +/* Current list of rules. */ +static arule_type *rules[RULES_MAX]; +static int rules_count = 0; /* Number of rules currently in list */ + +static int rules_append(arule_type *rule) +{ + assert(rules_count < RULES_MAX); + + rules[rules_count] = rule; + rules_count++; + return rules_count-1; +} + +static void rules_remove(arule_type *rule) +{ + int i, j; + + for (i = 0; i < rules_count; i++) + { + if (rules[i] == rule) + { + /* Free the rule */ + rule_destroy(&rule); + /* Move rest of rest "up" */ + for (j = i+1; j < rules_count; j++) + { + rules[j-1] = rules[j]; + } + /* We're done */ + rules_count--; + return; + } + } +} + +static void rules_swap(int i, int j) +{ + arule_type *tmp_rptr = NULL; + + assert(i >= 0); + assert(i < rules_count); + + assert(j >= 0); + assert(j < rules_count); + + tmp_rptr = rules[i]; + rules[i] = rules[j]; + rules[j] = tmp_rptr; +} + +static bool_* automatizer_auto_destroy(object_type *o_ptr, int item_idx) +{ + static bool_ TRUE_VAL = TRUE; + + /* Must be identified */ + if (object_aware_p(o_ptr) == FALSE) + { + return NULL; + } + + /* Inscribed things won't be destroyed! */ + if (o_ptr->note) + { + return NULL; + } + + /* Ignore artifacts; cannot be destroyed anyway. */ + if (artifact_p(o_ptr)) + { + return NULL; + } + + /* Cannot destroy CURSE_NO_DROP objects. */ + { + u32b f1, f2, f3, f4, f5, esp; + object_flags(o_ptr, &f1, &f2, &f3, &f4, &f5, &esp); + + if ((f4 & TR4_CURSE_NO_DROP) != 0) + { + return NULL; + } + } + + /* Destroy! */ + msg_print(""); + + inc_stack_size(item_idx, -o_ptr->number); + return &TRUE_VAL; +} + +static bool_* automatizer_auto_pickup(object_type *o_ptr, int item_idx) +{ + static bool_ TRUE_VAL = TRUE; + + if (item_idx >= 0) + { + return NULL; + } + + if (!inven_carry_okay(o_ptr)) + { + return NULL; + } + + msg_print(""); + + object_pickup(-item_idx); + return &TRUE_VAL; +} + +/* Apply rules */ +static bool_ apply_rule(arule_type *rule, object_type *o_ptr, int item_idx) +{ + /* Check module */ + if (rule->module_idx == game_module_idx) + { + return FALSE; + } + + /* Check condition */ + assert (rule->condition != NULL); + if (condition_eval(rule->condition, o_ptr)) + { + switch (rule->action) + { + + case AUTO_DESTROY: + { + automatizer_auto_destroy(o_ptr, item_idx); + break; + } + + case AUTO_PICKUP: + { + automatizer_auto_pickup(o_ptr, item_idx); + break; + } + + case AUTO_INSCRIBE: + { + /* Already inscribed? */ + if (o_ptr->note != 0) + { + return FALSE; + } + + /* Inscribe */ + msg_format("", rule->inscription); + o_ptr->note = quark_add(rule->inscription); + break; + } + + } + + return TRUE; + } + + return FALSE; +} + +static bool_ apply_rules(object_type *o_ptr, int item_idx) +{ + int i; + + for (i = 0; i < rules_count; i++) + { + if (apply_rule(rules[i], o_ptr, item_idx)) + { + return TRUE; + } + } + + /* Don't keep trying */ + return FALSE; +} + +static json_t *rule_to_json(arule_type *rule) +{ + json_t *rule_json = json_object(); + + json_object_set_new(rule_json, + "name", + json_string(rule->name)); + json_object_set_new(rule_json, + "action", + json_string(action_to_string(rule->action))); + json_object_set_new(rule_json, + "module", + json_string(modules[rule->module_idx].meta.name)); + + if (rule->inscription) + { + json_object_set_new(rule_json, + "inscription", + json_string(rule->inscription)); + } + + json_object_set_new(rule_json, + "condition", + condition_to_json(rule->condition)); + + return rule_json; +} + +static json_t *rules_to_json() +{ + int i; + json_t *rules_json = json_array(); + + for (i = 0; i < rules_count; i++) + { + json_array_append_new(rules_json, rule_to_json(rules[i])); + } + + return rules_json; +} + +/* Check the floor for "crap" */ +void squeltch_grid(void) +{ + s16b this_o_idx, next_o_idx = 0; + + if (!automatizer_enabled) return; + + /* Scan the pile of objects */ + for (this_o_idx = cave[p_ptr->py][p_ptr->px].o_idx; this_o_idx; this_o_idx = next_o_idx) + { + /* Acquire object */ + object_type * o_ptr = &o_list[this_o_idx]; + + /* We've now seen one of these */ + if (!k_info[o_ptr->k_idx].flavor) + { + object_aware(o_ptr); + } + + /* Acquire next object */ + next_o_idx = o_ptr->next_o_idx; + + /* Apply rules */ + apply_rules(o_ptr, -this_o_idx); + } +} + + +/* Check the inventory for "crap" */ +void squeltch_inventory(void) +{ + int i; + int num_iter = 0; + bool_ found = TRUE; + + if (!automatizer_enabled) return; + + while (found && num_iter ++ < 100) + { + /* Sometimes an index in the inventory is skipped */ + found = FALSE; + + for (i = 0; i < INVEN_PACK; i++) + { + object_type *o_ptr = &p_ptr->inventory[i]; + + if (apply_rules(o_ptr, i)) + { + found = TRUE; + break; + } + } + } + if (num_iter >= 100) + { + cmsg_format(TERM_VIOLET, "'apply_rules' ran too often."); + } +} + +/********************** The interface **********************/ +static void get_rule_names(cptr *list) +{ + int i; + + for (i = 0; i < rules_count; i++) + { + list[i] = rules[i]->name; + } +} + +typedef struct condition_metadata condition_metadata; +struct condition_metadata { + match_type match; + cptr description[3]; + condition_type *(*create_condition)(); +}; + +#define TYPES_LIST_SIZE 21 + +static condition_type *create_condition_name() +{ + cptr s = lua_input_box("Object name to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_name(s); +} + +static condition_type *create_condition_contain() +{ + cptr s = lua_input_box("Word to find in object name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_contain(s); +} + +static condition_type *create_condition_inscribed() +{ + cptr s = lua_input_box("Word to find in object inscription?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_inscribed(s); +} + +static condition_type *create_condition_discount() +{ + int min, max; + + { + cptr s = lua_input_box("Min discount?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max discount?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + return condition_new_discount(min, max); +} + +static condition_type *create_condition_symbol() +{ + char c; + cptr s = lua_input_box("Symbol to match?", 1); + if (sscanf(s, "%c", &c) < 1) + { + return NULL; + } + + return condition_new_symbol(c); +} + +static condition_type *create_condition_status() +{ + status_type status; + char c; + + c = lua_msg_box("[t]errible, [v]ery bad, [b]ad, " + "[a]verage, [G]ood, [V]ery good, [S]pecial?"); + + switch (c) + { + case 't': status = TERRIBLE; break; + case 'v': status = VERY_BAD; break; + case 'b': status = BAD; break; + case 'a': status = AVERAGE; break; + case 'G': status = GOOD; break; + case 'V': status = VERY_GOOD; break; + case 'S': status = SPECIAL; break; + default: return NULL; + } + + return condition_new_status(status); +} + +static condition_type *create_condition_state() +{ + identification_state s; + char c; + + c = lua_msg_box("[i]dentified, [n]on identified?"); + + switch (c) + { + case 'i': s = IDENTIFIED; break; + case 'n': s = NOT_IDENTIFIED; break; + default: return NULL; + } + + return condition_new_state(s); +} + +static condition_type *create_condition_tval() +{ + int tval; + cptr s = lua_input_box("Tval to match?", 79); + if (sscanf(s, "%d", &tval) < 1) + { + return NULL; + } + + return condition_new_tval(tval); +} + +static condition_type *create_condition_sval() +{ + int sval_min, sval_max; + + { + cptr s = lua_input_box("Min sval?", 79); + if (sscanf(s, "%d", &sval_min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max sval?", 79); + if (sscanf(s, "%d", &sval_max) < 1) + { + return NULL; + } + } + + return condition_new_sval(sval_min, sval_max); +} + +static condition_type *create_condition_race() +{ + cptr s = lua_input_box("Player race to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_race(s); +} + +static condition_type *create_condition_subrace() +{ + cptr s = lua_input_box("Player subrace to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_subrace(s); +} + +static condition_type *create_condition_class() +{ + cptr s = lua_input_box("Player class to match?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + return condition_new_class(s); +} + +static condition_type *create_condition_level() +{ + int min, max; + + { + cptr s = lua_input_box("Min player level?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max player level?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + return condition_new_level(min, max); +} + +static condition_type *create_condition_skill() +{ + int min, max; + s16b skill_idx; + + { + cptr s = lua_input_box("Min skill level?", 79); + if (sscanf(s, "%d", &min) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Max skill level?", 79); + if (sscanf(s, "%d", &max) < 1) + { + return NULL; + } + } + + { + cptr s = lua_input_box("Skill name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + skill_idx = find_skill_i(s); + if (skill_idx < 0) + { + return NULL; + } + } + + return condition_new_skill(min, max, skill_idx); +} + +static condition_type *create_condition_ability() +{ + s16b ai; + cptr s = lua_input_box("Ability name?", 79); + if (strlen(s) == 0) + { + return NULL; + } + + ai = find_ability(s); + if (ai < 0) + { + return NULL; + } + + return condition_new_ability(ai); +} + +static condition_metadata types_list[TYPES_LIST_SIZE] = +{ + { M_AND, + { "Check is true if all rules within it are true", + NULL }, + condition_new_and, + }, + { M_OR, + { "Check is true if at least one rule within it is true", + NULL }, + condition_new_or, + }, + { M_NOT, + { "Invert the result of its child rule", + NULL }, + condition_new_not, + }, + { M_NAME, + { "Check is true if object name matches name", + NULL }, + create_condition_name, + }, + { M_CONTAIN, + { "Check is true if object name contains word", + NULL }, + create_condition_contain, + }, + { M_INSCRIBED, + { "Check is true if object inscription contains word", + NULL }, + create_condition_inscribed, + }, + { M_DISCOUNT, + { "Check is true if object discount is between two values", + NULL }, + create_condition_discount, + }, + { M_SYMBOL, + { "Check is true if object symbol is ok", + NULL }, + create_condition_symbol, + }, + { M_STATE, + { "Check is true if object is identified/unidentified", + NULL }, + create_condition_state, + }, + { M_STATUS, + { "Check is true if object status is ok", + NULL }, + create_condition_status, + }, + { M_TVAL, + { "Check is true if object tval(from k_info.txt) is ok", + NULL }, + create_condition_tval, + }, + { M_SVAL, + { "Check is true if object sval(from k_info.txt) is between", + "two values", + NULL }, + create_condition_sval, + }, + { M_RACE, + { "Check is true if player race is ok", + NULL }, + create_condition_race, + }, + { M_SUBRACE, + { "Check is true if player subrace is ok", + NULL }, + create_condition_subrace, + }, + { M_CLASS, + { "Check is true if player class is ok", + NULL }, + create_condition_class, + }, + { M_LEVEL, + { "Check is true if player level is between 2 values", + NULL }, + create_condition_level, + }, + { M_SKILL, + { "Check is true if player skill level is between 2 values", + NULL }, + create_condition_skill, + }, + { M_ABILITY, + { "Check is true if player has the ability", + NULL }, + create_condition_ability, + }, + { M_INVENTORY, + { "Check is true if something in player's inventory matches", + "the contained rule", + NULL }, + condition_new_inventory, + }, + { M_EQUIPMENT, + { "Check is true if something in player's equipment matches", + "the contained rule", + NULL }, + condition_new_equipment, + }, +}; + +static void display_desc(condition_metadata *condition_metadata) +{ + int i; + + assert(condition_metadata != NULL); + + for (i = 0; condition_metadata->description[i] != NULL; i++) + { + c_prt(TERM_WHITE, condition_metadata->description[i], i + 1, 17); + } +} + +/* Create a new rule */ +static condition_metadata *automatizer_select_condition_type() +{ + int wid, hgt, max = TYPES_LIST_SIZE, begin = 0, sel = 0, i; + char c; + cptr types_names[TYPES_LIST_SIZE]; + + /* Create list of names for display */ + for (i = 0; i < TYPES_LIST_SIZE; i++) + { + types_names[i] = match_type_to_string(types_list[i].match); + } + + while (1) + { + Term_clear(); + Term_get_size(&wid, &hgt); + + display_list(0, 0, hgt - 1, 15, "Rule types", types_names, max, begin, sel, TERM_L_GREEN); + + display_desc(&types_list[sel]); + + c = inkey(); + + if (c == ESCAPE) break; + else if (c == '8') + { + sel--; + if (sel < 0) + { + sel = max - 1; + begin = max - hgt; + if (begin < 0) begin = 0; + } + if (sel < begin) begin = sel; + } + else if (c == '2') + { + sel++; + if (sel >= max) + { + sel = 0; + begin = 0; + } + if (sel >= begin + hgt - 1) begin++; + } + else if (c == '\r') + { + return &types_list[sel]; + } + } + return NULL; +} + +static void adjust_begin(int *begin, int *sel, int max, int hgt) +{ + if (*sel < 0) + { + *sel = max - 1; + *begin = *sel - hgt + 3; + if (*begin < 0) *begin = 0; + } + if (*sel < *begin) *begin = *sel; + + if (*sel >= max) + { + *sel = 0; + *begin = 0; + } + if (*sel >= *begin + hgt - 2) (*begin)++; +} + +static int create_new_rule() +{ + action_type action; + char name[20] = { '\0' }; + char *inscription = NULL; + int wid = 0, hgt = 0; + char typ; + arule_type *rule = NULL; + + Term_get_size(&wid, &hgt); + + sprintf(name, "%s", "No name"); + if (!input_box("Name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + { + return -1; + } + + typ = lua_msg_box("[D]estroy, [P]ickup, [I]nscribe?"); + + switch (typ) + { + case 'd': + case 'D': + action = AUTO_DESTROY; + break; + + case 'p': + case 'P': + action = AUTO_PICKUP; + break; + + case 'i': + case 'I': + { + char *i = NULL; + + action = AUTO_INSCRIBE; + + i = lua_input_box("Inscription?", 79); + if ((i == NULL) || (strlen(i) == 0)) + { + return -1; + } + + inscription = i; + + break; + } + + default: + return -1; + } + + /* Make rule */ + rule = rule_new(name, action, game_module_idx, NULL, inscription); + + /* Append to list of rules */ + return rules_append(rule); +} + +static void add_child(condition_type *current) +{ + condition_metadata *metadata = NULL; + + switch (current->match) + { + case M_NOT: + case M_EQUIPMENT: + case M_INVENTORY: + { + if (current->subcondition != NULL) + { + return; + } + + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + + current->subcondition = metadata->create_condition(); + break; + } + + case M_AND: + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + condition_and_add(current, metadata->create_condition()); + break; + + case M_OR: + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + condition_or_add(current, metadata->create_condition()); + break; + + default: + /* No other types of conditions have children */ + break; + } +} + +static int tree_indent = 0; +static int tree_write_out_y = 0; +static int tree_write_out_x = 0; +static int tree_write_out_h = 0; +static int tree_write_out_w = 0; +static int tree_write_y = 0; +static int tree_write_x = 0; +static int tree_write_off_x = 0; +static int tree_write_off_y = 0; + +static void tree_write(byte color, cptr line) +{ + cptr p = line; + + for (p = line; *p != '\0'; p++) + { + char c = *p; + int x = tree_write_x - tree_write_off_x + 3*tree_indent; + int y = tree_write_y - tree_write_off_y; + + if (c != '\n') + { + if ((y >= 0) && + (y < tree_write_out_h) && + (x >= 0) && + (x < tree_write_out_w)) + { + Term_putch(x + tree_write_out_x, + y + tree_write_out_y, + color, + c); + } + + tree_write_x += 1; + } + else + { + tree_write_x = 0; + tree_write_y += 1; + } + } +} + +static void display_condition(condition_type *condition) +{ + byte bcol = TERM_L_GREEN; + byte ecol = TERM_GREEN; + int i; + + assert(condition != NULL); + + /* If this condition is present in the cursor stack, + then we use the "active" colors. */ + for (i = 0; i < cursor_count; i++) + { + if (cursor_stack[i] == condition) + { + bcol = TERM_VIOLET; + ecol = TERM_VIOLET; + break; + } + } + + tree_indent++; + + switch (condition->match) + { + case M_INVENTORY: + case M_EQUIPMENT: + { + cptr where_s = (condition->match == M_INVENTORY) + ? "inventory" + : "equipment"; + + tree_write(ecol, "Something in your "); + tree_write(bcol, where_s); + tree_write(ecol, " matches the following:"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_SKILL: + { + cptr skill_name = + s_info[condition->skill_range.skill_idx].name + s_name; + + tree_write(ecol, "Your skill in "); + tree_write(bcol, skill_name); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", (int) condition->skill_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", (int) condition->skill_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_ABILITY: + { + cptr ability_name = + ab_info[condition->ability].name + ab_name; + + tree_write(ecol, "You have the "); + tree_write(bcol, ability_name); + tree_write(ecol, " ability"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_LEVEL: + { + tree_write(ecol, "Your "); + tree_write(bcol, "level"); + tree_write(ecol, " is from "); + + tree_write(TERM_WHITE, format("%d", condition->level_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->level_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_SVAL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "sval"); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", condition->sval_range.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->sval_range.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_DISCOUNT: + { + tree_write(ecol, "Its "); + tree_write(bcol, "discount"); + tree_write(ecol, " is from "); + tree_write(TERM_WHITE, format("%d", condition->discount.min)); + tree_write(ecol, " to "); + tree_write(TERM_WHITE, format("%d", condition->discount.max)); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_AND: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + tree_write(ecol, "All of the following are true:"); + tree_write(TERM_WHITE, "\n"); + + for (child = sglib_condition_list_it_init(&it, condition->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + display_condition(child->condition); + } + + break; + } + + case M_OR: + { + struct sglib_condition_list_iterator it; + struct condition_list *child = NULL; + + tree_write(ecol, "At least one of the following are true:"); + tree_write(TERM_WHITE, "\n"); + + for (child = sglib_condition_list_it_init(&it, condition->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) + { + display_condition(child->condition); + } + + break; + } -/* Check the floor for "crap" */ -void squeltch_grid(void) -{ - int oldtop; - s16b this_o_idx, next_o_idx = 0; + case M_NOT: + { + tree_write(ecol, "Negate the following:"); + tree_write(TERM_WHITE, "\n"); + display_condition(condition->subcondition); + break; + } - if (!automatizer_enabled) return; + case M_NAME: + { + tree_write(ecol, "Its "); + tree_write(bcol, "name"); + tree_write(ecol, " is \""); + tree_write(TERM_WHITE, condition->name); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - oldtop = lua_gettop(L); + case M_CONTAIN: + { + tree_write(ecol, "Its "); + tree_write(bcol, "name"); + tree_write(ecol, " contains \""); + tree_write(TERM_WHITE, condition->name); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Scan the pile of objects */ - for (this_o_idx = cave[p_ptr->py][p_ptr->px].o_idx; this_o_idx; this_o_idx = next_o_idx) + case M_INSCRIBED: { - /* Acquire object */ - object_type * o_ptr = &o_list[this_o_idx]; + tree_write(ecol, "It is "); + tree_write(bcol, "inscribed"); + tree_write(ecol, " with "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->inscription); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* We've now seen one of these */ - if (!k_info[o_ptr->k_idx].flavor) - { - object_aware(o_ptr); - } + case M_SYMBOL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "symbol"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, format("%c", condition->symbol)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Acquire next object */ - next_o_idx = o_ptr->next_o_idx; + case M_STATE: + { + tree_write(ecol, "Its "); + tree_write(bcol, "state"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, identification_state_to_string(condition->identification_state)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_STATUS: + { + tree_write(ecol, "Its "); + tree_write(bcol, "status"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, status_to_string(condition->status)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_TVAL: + { + tree_write(ecol, "Its "); + tree_write(bcol, "tval"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, format("%d", condition->tval)); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Push the function */ - lua_settop(L, oldtop); - lua_getglobal(L, "apply_rules"); + case M_RACE: + { + tree_write(ecol, "Player "); + tree_write(bcol, "race"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->race); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Push the args */ - tolua_pushusertype(L, o_ptr, tolua_tag(L, "object_type")); - tolua_pushnumber(L, -this_o_idx); + case M_SUBRACE: + { + tree_write(ecol, "Player "); + tree_write(bcol, "subrace"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->subrace); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } + + case M_CLASS: + { + tree_write(ecol, "Player "); + tree_write(bcol, "class"); + tree_write(ecol, " is "); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, condition->klass); + tree_write(ecol, "\""); + tree_write(TERM_WHITE, "\n"); + break; + } - /* Call the function */ - if (lua_call(L, 2, 0)) - { - cmsg_format(TERM_VIOLET, "ERROR in lua_call while calling 'apply_rules'."); - lua_settop(L, oldtop); - return; - } } - lua_settop(L, oldtop); + + tree_indent--; } +static void display_rule(arule_type *rule) +{ + cptr action_s; + int hgt, wid; -/* Check the inventory for "crap" */ -void squeltch_inventory(void) + action_s = action_to_string(rule->action); + + Term_get_size(&wid, &hgt); + + tree_write_out_y = 1; + tree_write_out_x = 16; + tree_write_out_h = hgt - 4 - 1; + tree_write_out_w = wid - 1 - 15 - 1; + tree_write_y = 0; + tree_write_x = 0; + + switch (rule->action) + { + case AUTO_DESTROY: + case AUTO_PICKUP: + { + tree_write(TERM_GREEN, "A rule named \""); + tree_write(TERM_WHITE, rule->name); + tree_write(TERM_GREEN, "\" to "); + tree_write(TERM_L_GREEN, action_s); + tree_write(TERM_GREEN, " when"); + tree_write(TERM_WHITE, "\n"); + break; + } + + case AUTO_INSCRIBE: + { + tree_write(TERM_GREEN, "A rule named \""); + tree_write(TERM_WHITE, rule->name); + tree_write(TERM_GREEN, "\" to "); + tree_write(TERM_L_GREEN, "inscribe"); + tree_write(TERM_GREEN, " an item with \""); + tree_write(TERM_WHITE, rule->inscription); + tree_write(TERM_GREEN, "\" when"); + tree_write(TERM_WHITE, "\n"); + break; + } + + } + + /* Write out the condition */ + if (rule->condition != NULL) + { + display_condition(rule->condition); + } +} + +static void adjust_current(int sel) { - int oldtop; - int i; - int num_iter = 0; - bool_ found = TRUE; + if (rules_count == 0) + { + cursor_clear(); + return; + } - if (!automatizer_enabled) return; + tree_write_off_y = 0; + tree_write_off_x = 0; - oldtop = lua_gettop(L); - while (found && num_iter ++ < 100) + /* Put the top-level condition into cursor */ + cursor_clear(); + if (rules[sel]->condition != NULL) { - /* Sometimes an index in the inventory is skipped */ - found = FALSE; + cursor_push(rules[sel]->condition); + } +} - for (i = 0; i < INVEN_PACK; i++) - { - object_type *o_ptr = &p_ptr->inventory[i]; +static void tree_scroll_up() +{ + tree_write_off_y = tree_write_off_y - 1; +} - /* Push the function */ - lua_settop(L, oldtop); - lua_getglobal(L, "apply_rules"); +static void tree_scroll_down() +{ + tree_write_off_y = tree_write_off_y + 1; +} - /* Push the args */ - tolua_pushusertype(L, o_ptr, tolua_tag(L, "object_type")); - tolua_pushnumber(L, i); +static void tree_scroll_left() +{ + tree_write_off_x = tree_write_off_x + 1; +} - /* Call the function */ - if (lua_call(L, 2, 1)) - { - cmsg_format(TERM_VIOLET, "ERROR in lua_call while calling 'apply_rules'."); - lua_settop(L, oldtop); - return; - } +static void tree_scroll_right() +{ + tree_write_off_x = tree_write_off_x - 1; +} - /* Did it return TRUE */ - if (tolua_getnumber(L, -(lua_gettop(L) - oldtop), 0)) - { - found = TRUE; - break; - } +static void automatizer_save_rules() +{ + char name[30] = { '\0' }; + char buf[1025]; + char ch; + int hgt, wid; + + Term_get_size(&wid, &hgt); + + sprintf(name, "automat.atm"); + if (!input_box("Save name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + { + return; + } + + /* Build the filename */ + path_build(buf, 1024, ANGBAND_DIR_USER, name); + + /* File type is "TEXT" */ + FILE_TYPE(FILE_TYPE_TEXT); + + if (file_exist(buf)) + { + c_put_str(TERM_WHITE, "File exists, continue?[y/n]", + hgt / 2, + wid / 2 - 14); + ch = inkey(); + if ((ch != 'Y') && (ch != 'y')) + { + return; } } - if (num_iter >= 100) + + /* Write to file */ { - cmsg_format(TERM_VIOLET, "'apply_rules' ran too often."); + json_t *rules_json = rules_to_json(); + int status = json_dump_file(rules_json, buf, JSON_INDENT(2) | JSON_SORT_KEYS); + if (status == 0) + { + c_put_str(TERM_WHITE, "Saved rules in file ", + hgt / 2, + wid / 2 - 14); + } + else + { + c_put_str(TERM_WHITE, "Saving rules failed! ", + hgt / 2, + wid / 2 - 14); + } + + /* Deallocate JSON */ + json_decref(rules_json); + + /* Wait for keypress */ + inkey(); } - lua_settop(L, oldtop); } -/********************** The interface **********************/ -static cptr *get_rule_list(int *max) +static void rename_rule(arule_type *rule) { - cptr *list; - int i; + char name[16]; + int wid, hgt; - *max = exec_lua("return __rules_max"); - C_MAKE(list, *max, cptr); - - for (i = 0; i < *max; i++) - { - list[i] = string_exec_lua(format("return __rules[%d].table.args.name", i)); - } - - return list; -} - -static cptr types_list[] = -{ - "and", - "or", - "not", - "name", - "contain", - "inscribed", - "discount", - "symbol", - "state", - "status", - "tval", - "sval", - "race", - "subrace", - "class", - "level", - "skill", - "ability", - "inventory", - "equipment", - "comment", - NULL, -}; + assert(rule != NULL); -/* Create a new rule */ -static int automatizer_new_rule() + Term_get_size(&wid, &hgt); + + sprintf(name, "%s", rule->name); + if (input_box("New name?", hgt / 2, wid / 2, name, sizeof(name)-1)) + { + rule_set_name(rule, name); + } +} + +static void add_new_condition(arule_type *current_rule) { - int wid, hgt, max, begin = 0, sel = 0; - char c; + /* Top-level condition? */ + if (current_rule->condition == NULL) + { + condition_metadata *metadata = NULL; - /* Get the number of types */ - max = 0; - while (types_list[max] != NULL) - max++; + /* Sanity check for navigation stack */ + assert(cursor_count == 0); - while (1) + /* Select type of clause */ + metadata = automatizer_select_condition_type(); + if (metadata == NULL) + { + return; + } + + /* Create the condition directly; we can + always add a top-level condition so there's + no need for the sanity checking in + add_child(). */ + current_rule->condition = metadata->create_condition(); + if (current_rule->condition != NULL) + { + cursor_push(current_rule->condition); + } + } + else { - Term_clear(); - Term_get_size(&wid, &hgt); + condition_type *current_condition = cursor_top(); + add_child(current_condition); + } +} + +static void tree_go_right() +{ + condition_type *top = cursor_top(); + + /* Can only go right if the currently selected condition + has children. */ + switch (top->match) + { + case M_AND: + case M_OR: + { + /* Pick first child */ + struct sglib_condition_list_iterator it; + condition_list *i = sglib_condition_list_it_init(&it, top->conditions.c); + /* Move right if possible */ + if (i != NULL) + { + cursor_push(i->condition); + } + break; + } - display_list(0, 0, hgt - 1, 15, "Rule types", types_list, max, begin, sel, TERM_L_GREEN); + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + if (top->subcondition != NULL) + { + cursor_push(top->subcondition); + } + break; + } - exec_lua(format("auto_aux:display_desc('%s')", types_list[sel])); - c_prt(TERM_YELLOW, "Example:", 5, 17); - exec_lua(format("xml.write_out_y = 6; xml.write_out_x = 16; xml.write_out_h = %d; xml.write_out_w = %d; xml.write_y = 0; xml.write_x = 0; xml:display_xml(auto_aux.types_desc['%s'][2][1], '')", hgt - 3 - 5, wid - 1 - 15 - 1, types_list[sel])); + default: + /* Not possible to move */ + break; + } +} - c = inkey(); +static void tree_go_left() +{ + if (cursor_count > 1) + { + cursor_pop(); + } +} - if (c == ESCAPE) break; - else if (c == '8') +static void tree_go_up() +{ + if (cursor_count > 1) + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + switch (top->match) { - sel--; - if (sel < 0) + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *child = NULL; + condition_list *prev_child = NULL; + + /* We have a list of children */ + for (child = sglib_condition_list_it_init(&it, top->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) { - sel = max - 1; - begin = max - hgt; - if (begin < 0) begin = 0; + if (child->condition == prev_top) + { + /* Do we have a previous child? */ + if (prev_child == NULL) + { + /* No predecessor; don't move */ + cursor_push(prev_top); + break; + } + else + { + cursor_push(prev_child->condition); + break; /* Done */ + } + } + /* Keep track of previous child */ + prev_child = child; } - if (sel < begin) begin = sel; + + break; } - else if (c == '2') + + default: { - sel++; - if (sel >= max) + /* No other match types have children; restore + original top. */ + cursor_push(prev_top); + break; + } + + } + } +} + +static void tree_go_down() +{ + if (cursor_count > 1) + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + switch (top->match) + { + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *child = NULL; + + /* We have a list of children */ + for (child = sglib_condition_list_it_init(&it, top->conditions.c); + child != NULL; + child = sglib_condition_list_it_next(&it)) { - sel = 0; - begin = 0; + if (child->condition == prev_top) + { + /* Move to next child (if any) */ + child = sglib_condition_list_it_next(&it); + if (child == NULL) + { + /* No successor; don't move */ + cursor_push(prev_top); + break; + } + else + { + cursor_push(child->condition); + break; /* Done */ + } + } } - if (sel >= begin + hgt - 1) begin++; + + break; } - else if (c == '\r') + + default: { - return sel; + /* No other match types have multiple children; restore + original top. */ + cursor_push(prev_top); + break; + } + } } - return -1; } -static void adjust_begin(int *begin, int *sel, int max, int hgt) +static int automatizer_del_self(int sel) { - if (*sel < 0) + /* If the cursor is at the top level then + we want to delete the rule itself */ + if (cursor_count < 1) { - *sel = max - 1; - *begin = *sel - hgt + 3; - if (*begin < 0) *begin = 0; + rules_remove(rules[sel]); + return sel - 1; /* Move selection up */ } - if (*sel < *begin) *begin = *sel; - - if (*sel >= max) + else if (cursor_count == 1) { - *sel = 0; - *begin = 0; + cursor_pop(); + condition_destroy(&rules[sel]->condition); + return sel; + } + else + { + condition_type *prev_top = cursor_pop(); + condition_type *top = cursor_top(); + + /* Jump up a level; this is a simple way to ensure a + valid cursor. We could be a little cleverer here by + trying to move inside the current level, but it's a + little complicated. */ + tree_go_left(); + + /* Now we can remove the condition from its parent */ + switch (top->match) + { + + case M_AND: + case M_OR: + { + struct sglib_condition_list_iterator it; + condition_list *item = NULL; + + /* We have a list of children */ + for (item = sglib_condition_list_it_init(&it, top->conditions.c); + item != NULL; + item = sglib_condition_list_it_next(&it)) + { + if (item->condition == prev_top) + { + /* Found */ + break; + } + } + + /* Must have found item; otherwise internal structure + is damaged. */ + assert (item != NULL); + sglib_condition_list_delete(&top->conditions.c, item); + + /* Destroy the condition */ + condition_destroy(&prev_top); + break; + } + + case M_NOT: + case M_EQUIPMENT: + case M_INVENTORY: + { + assert(top->subcondition != NULL); + condition_destroy(&top->subcondition); + break; + } + + default: + /* If we get here, something's wrong with the + navigation structures. */ + assert(FALSE); + break; + } + + /* Keep selection */ + return sel; } - if (*sel >= *begin + hgt - 2) (*begin)++; } #define ACTIVE_LIST 0 @@ -239,12 +2779,12 @@ void do_cmd_automatizer() { int wid = 0, hgt = 0; char c; - cptr *list = NULL; int max, begin = 0, sel = 0; int active = ACTIVE_LIST; cptr keys; cptr keys2; cptr keys3; + cptr rule_names[RULES_MAX]; Term_get_size(&wid, &hgt); @@ -260,37 +2800,38 @@ void do_cmd_automatizer() screen_save(); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); while (1) { Term_clear(); Term_get_size(&wid, &hgt); - list = get_rule_list(&max); - display_list(0, 0, hgt - 1, 15, "Rules", list, max, begin, sel, (active == ACTIVE_LIST) ? TERM_L_GREEN : TERM_GREEN); - C_FREE(list, max, cptr); + max = rules_count; + get_rule_names(rule_names); + display_list(0, 0, hgt - 1, 15, "Rules", rule_names, max, begin, sel, (active == ACTIVE_LIST) ? TERM_L_GREEN : TERM_GREEN); draw_box(0, 15, hgt - 4, wid - 1 - 15); if (active == ACTIVE_RULE) { keys = "#Bup#W/#Bdown#W/#Bleft#W/#Bright#W to navitage rule, #B9#W/#B3#W/#B7#W/#B1#W to scroll"; keys2 = "#Btab#W for switch, #Ba#Wdd clause, #Bd#Welete clause/rule"; - keys3 = "#Bx#W to toggle english/xml, #G?#W for Automatizer help"; - exec_lua("xml.write_active = not nil"); + keys3 = "#G?#W for Automatizer help"; } else { keys = "#Bup#W/#Bdown#W to scroll, #Btab#W to switch to the rule window"; keys2 = "#Bu#W/#Bd#W to move rules, #Bn#Wew rule, #Br#Wename rule, #Bs#Wave rules"; keys3 = "#Rk#W to #rdisable#W the automatizer, #G?#W for Automatizer help"; - exec_lua("xml.write_active = nil"); } display_message(17, hgt - 3, strlen(keys), TERM_WHITE, keys); display_message(17, hgt - 2, strlen(keys2), TERM_WHITE, keys2); display_message(17, hgt - 1, strlen(keys3), TERM_WHITE, keys3); - if (max) exec_lua(format("xml.write_out_y = 1; xml.write_out_x = 16; xml.write_out_h = %d; xml.write_out_w = %d; xml.write_y = 0; xml.write_x = 0; xml:display_xml(__rules[%d].table, '')", hgt - 4 - 1, wid - 1 - 15 - 1, sel)); + if (max) + { + display_rule(rules[sel]); + } c = inkey(); @@ -308,105 +2849,58 @@ void do_cmd_automatizer() if (!max) continue; sel--; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (c == '2') { if (!max) continue; sel++; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (c == 'u') { - if (!max) continue; - sel = exec_lua(format("return auto_aux:move_up(%d)", sel)); - adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + if (sel > 0) + { + rules_swap(sel-1, sel); + sel -= 1; + + adjust_begin(&begin, &sel, max, hgt); + adjust_current(sel); + } } else if (c == 'd') { if (!max) continue; - sel = exec_lua(format("return auto_aux:move_down(%d)", sel)); - adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + + if (sel < rules_count - 1) + { + rules_swap(sel, sel+1); + sel += 1; + + adjust_begin(&begin, &sel, max, hgt); + adjust_current(sel); + } } else if (c == 'n') { - char name[20] = { '\0' }; - char typ; - - sprintf(name, "No name"); - if (input_box("Name?", hgt / 2, wid / 2, name, sizeof(name)+1)) + int i = create_new_rule(); + if (i >= 0) { - cptr styp = "nothing"; - typ = msg_box("[D]estroy, [P]ickup, [I]nscribe, [N]othing rule?", hgt / 2, wid / 2); - if ((typ == 'd') || (typ == 'D')) styp = "destroy"; - else if ((typ == 'p') || (typ == 'P')) styp = "pickup"; - else if ((typ == 'i') || (typ == 'I')) styp = "inscribe"; - exec_lua(format("auto_aux:new_rule(%d, '%s', '%s', ''); auto_aux:adjust_current(%d)", sel, name, styp, sel)); + sel = i; + adjust_current(sel); active = ACTIVE_RULE; } } else if (c == 's') { - char name[30] = { '\0' }; - - sprintf(name, "automat.atm"); - if (input_box("Save name?", hgt / 2, wid / 2, name, sizeof(name)+1)) - { - char buf[1025]; - char ch; - - /* Build the filename */ - path_build(buf, 1024, ANGBAND_DIR_USER, name); - - /* File type is "TEXT" */ - FILE_TYPE(FILE_TYPE_TEXT); - - if (file_exist(buf)) - { - c_put_str(TERM_WHITE, "File exists, continue?[y/n]", hgt / 2, wid / 2 - 14); - ch = inkey(); - if ((ch != 'Y') && (ch != 'y')) - continue; - } - - /* Open the non-existing file */ - hook_file = my_fopen(buf, "w"); - - /* Invalid file */ - if (!hook_file) - { - /* Message */ - c_put_str(TERM_WHITE, "Saving rules failed! ", hgt / 2, wid / 2 - 14); - (void) inkey(); - - /* Error */ - continue; - } - - - - exec_lua("auto_aux:save_ruleset()"); - my_fclose(hook_file); - /* Overwrite query message */ - c_put_str(TERM_WHITE, "Saved rules in file ", hgt / 2, wid / 2 - 14); - (void) inkey(); - } + automatizer_save_rules(); } else if (c == 'r') { - char name[20]; - if (!max) continue; - sprintf(name, "%s", string_exec_lua(format("return __rules[%d].table.args.name", sel))); - if (input_box("New name?", hgt / 2, wid / 2, name, 15)) - { - exec_lua(format("auto_aux:rename_rule(%d, '%s')", sel, name)); - } - + rename_rule(rules[sel]); continue; } else if (c == 'k') @@ -419,10 +2913,6 @@ void do_cmd_automatizer() if (!max) continue; active = ACTIVE_RULE; } - else if (c == 'x') - { - exec_lua("xml.display_english = not xml.display_english"); - } } else if (active == ACTIVE_RULE) { @@ -434,41 +2924,39 @@ void do_cmd_automatizer() } else if (c == '8') { - exec_lua("auto_aux:go_up()"); + tree_go_up(); } else if (c == '2') { - exec_lua("auto_aux:go_down()"); + tree_go_down(); } else if (c == '6') { - exec_lua("auto_aux:go_right()"); + tree_go_right(); } else if (c == '4') { - exec_lua(format("auto_aux:go_left(%d)", sel)); + tree_go_left(); } else if (c == '9') { - exec_lua("auto_aux:scroll_up()"); + tree_scroll_up(); } else if (c == '3') { - exec_lua("auto_aux:scroll_down()"); + tree_scroll_down(); } else if (c == '7') { - exec_lua("auto_aux:scroll_left()"); + tree_scroll_left(); } else if (c == '1') { - exec_lua("auto_aux:scroll_right()"); + tree_scroll_right(); } else if (c == 'a') { - int s = automatizer_new_rule(); - if (s == -1) continue; - exec_lua(format("auto_aux:add_child('%s')", types_list[s])); + add_new_condition(rules[sel]); } else if (c == 'd') { @@ -476,12 +2964,12 @@ void do_cmd_automatizer() { int new_sel; - new_sel = exec_lua(format("return auto_aux:del_self(%d)", sel)); + new_sel = automatizer_del_self(sel); if ((sel != new_sel) && (new_sel >= 0)) { sel = new_sel; adjust_begin(&begin, &sel, max, hgt); - exec_lua(format("auto_aux:adjust_current(%d)", sel)); + adjust_current(sel); } else if (new_sel == -1) { @@ -493,30 +2981,84 @@ void do_cmd_automatizer() { active = ACTIVE_LIST; } - else if (c == 'x') - { - exec_lua("xml.display_english = not xml.display_english"); - } } } - /* Recalculate the rules */ - exec_lua("auto_aux.regen_ruleset()"); - screen_load(); } +static void easy_add_rule(action_type action, cptr mode, bool_ do_status, object_type *o_ptr) +{ + condition_type *condition = NULL; + + if (streq(mode, "tval")) + { + condition = condition_new_tval(o_ptr->tval); + } + else if (streq(mode, "tsval")) + { + condition_type *sval_condition = + condition_new_sval(o_ptr->sval, o_ptr->sval); + condition_type *tval_condition = + condition_new_tval(o_ptr->tval); + + condition = condition_new_and(); + condition_and_add(condition, tval_condition); + condition_and_add(condition, sval_condition); + } + else if (streq(mode, "name")) + { + char buf[128]; + object_desc(buf, o_ptr, -1, 0); + strlower(buf); + + condition = condition_new_name(buf); + } + + /* Use object status? */ + if (do_status == TRUE) + { + status_type status = object_status(o_ptr); + condition_type *status_condition = + condition_new_status(status); + condition_type *and_condition = + condition_new_and(); + + condition_and_add(and_condition, condition); + condition_and_add(and_condition, status_condition); + /* Replace condition */ + condition = and_condition; + } + + /* Build rule */ + { + static arule_type *rule = NULL; + /* Make rule */ + rule = rule_new(action_to_string(action), + action, + game_module_idx, + condition, + NULL); + + /* Append to list of rules */ + rules_append(rule); + } + + msg_print("Rule added. Please go to the Automatizer screen (press = then T)"); + msg_print("to save the modified ruleset."); +} + /* Add a new rule in an easy way */ bool_ automatizer_create = FALSE; void automatizer_add_rule(object_type *o_ptr, bool_ destroy) { char ch; bool_ do_status = FALSE; - cptr type = "destroy"; + action_type action = AUTO_DESTROY; if (!destroy) { - type = "pickup"; + action = AUTO_PICKUP; } while (TRUE) @@ -534,20 +3076,496 @@ void automatizer_add_rule(object_type *o_ptr, bool_ destroy) if (ch == 'T' || ch == 't') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "tsval", do_status, o_ptr); + easy_add_rule(action, "tsval", do_status, o_ptr); break; } if (ch == 'F' || ch == 'f') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "tval", do_status, o_ptr); + easy_add_rule(action, "tval", do_status, o_ptr); break; } if (ch == 'N' || ch == 'n') { - call_lua("easy_add_rule", "(s,s,d,O)", "", type, "name", do_status, o_ptr); + easy_add_rule(action, "name", do_status, o_ptr); break; } } } + +static condition_type *parse_condition(json_t *condition_json) +{ + cptr type_s = NULL; + match_type match; + + if ((condition_json == NULL) || json_is_null(condition_json)) + { + return NULL; + } + + if (json_unpack(condition_json, + "{s:s}", + "type", &type_s) < 0) + { + msg_print("Missing/invalid 'type' in condition"); + return NULL; + } + + if (!match_type_from_string(type_s, &match)) + { + msg_format("Invalid 'type' in condition: %s", type_s); + return NULL; + } + + switch (match) + { + case M_AND: + case M_OR: + { + json_t *conditions_j = json_object_get(condition_json, + "conditions"); + + if ((conditions_j == NULL) || + (json_is_null(conditions_j))) + { + return NULL; + } + else if (json_is_array(conditions_j)) + { + int i; + json_t *subcondition_j = NULL; + condition_type *condition = condition_new(match); + condition_type *subcondition = NULL; + + for (i = 0; i < json_array_size(conditions_j); i++) + { + subcondition_j = + json_array_get(conditions_j, i); + subcondition = + parse_condition(subcondition_j); + + if (subcondition != NULL) + { + condition_and_add(condition, subcondition); + } + } + + return condition; + } + else + { + msg_print("'conditions' property has invalid type"); + return NULL; + } + + break; + } + + case M_NOT: + case M_INVENTORY: + case M_EQUIPMENT: + { + json_t *condition_j = json_object_get(condition_json, + "condition"); + + if ((condition_j == NULL) || + (json_is_null(condition_j))) + { + return NULL; + } + else if (json_is_object(condition_j)) + { + condition_type *condition = + condition_new(match); + condition->subcondition = + parse_condition(condition_j); + return condition; + } + else + { + msg_print("Invlalid 'condition' property"); + return NULL; + } + } + + case M_NAME: + { + cptr s = NULL; + if (json_unpack(condition_json, + "{s:s}", + "name", &s) < 0) + { + msg_print("Missing/invalid 'name' property"); + return NULL; + } + + return condition_new_name(s); + } + + case M_CONTAIN: + { + cptr s = NULL; + if (json_unpack(condition_json, + "{s:s}", + "contain", &s) < 0) + { + msg_print("Missing/invalid 'contain' property"); + return NULL; + } + + return condition_new_contain(s); + } + + case M_SYMBOL: + { + cptr s = NULL; + int sl; + if (json_unpack(condition_json, "{s:s}", "symbol", &s) < 0) + { + msg_print("Missing/invalid 'symbol' property"); + return NULL; + } + + sl = strlen(s); + if (sl == 0) + { + msg_print("Invalid 'symbol' property: Too short"); + return NULL; + } + if (sl > 1) + { + msg_print("Invalid 'symbol' property: Too long"); + return NULL; + } + + return condition_new_symbol(s[0]); + } + + case M_INSCRIBED: + { + cptr s = NULL; + if (json_unpack(condition_json, "{s:s}", "inscription", &s) < 0) + { + msg_print("Missing/invalid 'inscription' property"); + return NULL; + } + + return condition_new_inscribed(s); + } + + case M_DISCOUNT: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_discount(min, max); + } + + case M_TVAL: + { + int tval; + + if (json_unpack(condition_json, "{s:i}", "tval", &tval) < 0) + { + msg_print("Missing/invalid 'tval' property"); + return NULL; + } + + return condition_new_tval(tval); + } + + case M_SVAL: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_sval(min, max); + } + + case M_STATUS: + { + cptr s; + status_type status; + + if (json_unpack(condition_json, "{s:s}", "status", &s) < 0) + { + msg_print("Missing/invalid 'status' property"); + return NULL; + } + + if (!status_from_string(s, &status)) + { + msg_format("Invalid 'status' property: %s", s); + return NULL; + } + + return condition_new_status(status); + } + + case M_STATE: + { + cptr s; + identification_state state; + + if (json_unpack(condition_json, "{s:s}", "state", &s) < 0) + { + msg_print("Missing/invalid 'state' property"); + return NULL; + } + + if (!identification_state_from_string(s, &state)) + { + msg_format("Invalid 'state' property: %s", s); + return NULL; + } + + return condition_new_state(state); + } + + case M_RACE: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "race", &s) < 0) + { + msg_print("Missing/invalid 'race' property"); + return NULL; + } + + return condition_new_race(s); + } + + case M_SUBRACE: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "subrace", &s) < 0) + { + msg_print("Missing/invalid 'subrace' property"); + return NULL; + } + + return condition_new_subrace(s); + } + + case M_CLASS: + { + cptr s; + + if (json_unpack(condition_json, "{s:s}", "class", &s) < 0) + { + msg_print("Missing/invalid 'class' property"); + return NULL; + } + + return condition_new_class(s); + } + + case M_LEVEL: + { + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i}", + "min", &min, + "max", &max) < 0) + { + msg_print("Missing/invalid 'min'/'max' properties"); + return NULL; + } + + return condition_new_level(min, max); + } + + case M_SKILL: + { + cptr s; + s16b si; + int min, max; + + if (json_unpack(condition_json, "{s:i,s:i,s:s}", + "min", &min, + "max", &max, + "name", &s) < 0) + { + msg_print("Missing/invalid 'min'/'max'/'name' properties"); + return NULL; + } + + si = find_skill_i(s); + if (si < 0) + { + msg_print("Invalid 'name' property"); + return NULL; + } + + return condition_new_skill(min, max, si); + } + + case M_ABILITY: + { + cptr a; + s16b ai; + + if (json_unpack(condition_json, "{s:s}", + "ability", &a) < 0) + { + msg_print("Missing/invalid 'ability' property"); + return NULL; + } + + ai = find_ability(a); + if (ai < 0) + { + msg_print("Invalid 'ability' property"); + return NULL; + } + + return condition_new_ability(ai); + } + + } + + /* Could not parse */ + return NULL; +} + +static void parse_rule(json_t *rule_json) +{ + char *rule_name_s = NULL; + char *rule_action_s = NULL; + char *rule_module_s = NULL; + json_t *rule_inscription_j = NULL; + arule_type *rule = NULL; + action_type action; + int module_idx; + + if (!json_is_object(rule_json)) + { + msg_print("Rule is not an object"); + return; + } + + /* Retrieve the attributes */ + if (json_unpack(rule_json, + "{s:s,s:s,s:s}", + "name", &rule_name_s, + "action", &rule_action_s, + "module", &rule_module_s) < 0) + { + msg_print("Rule missing required field(s)"); + return; + } + + /* Get the optional inscription */ + rule_inscription_j = json_object_get(rule_json, "inscription"); + + /* Convert attributes */ + if (!action_from_string((cptr) rule_action_s, &action)) + { + msg_format("Invalid rule action '%s'", rule_action_s); + return; + } + + module_idx = find_module((cptr) rule_module_s); + if (module_idx < 0) + { + msg_format("Skipping rule for unrecognized module '%s'", + (cptr) rule_module_s); + return; + } + + /* Sanity check: Inscription */ + if (action == AUTO_INSCRIBE) + { + if (rule_inscription_j == NULL) + { + msg_print("Inscription rule missing 'inscription' attribute"); + return; + } + if (!json_is_string(rule_inscription_j)) + { + msg_print("Inscription rule 'inscription' attribute wrong type"); + return; + } + } + + /* Create rule */ + rule = rule_new(rule_name_s, + action, + module_idx, + NULL, + json_string_value(rule_inscription_j)); + rules_append(rule); + + /* Parse the conditions */ + rule->condition = parse_condition(json_object_get(rule_json, "condition")); +} + +static void parse_rules(json_t *rules) +{ + int i; + + if (!json_is_array(rules)) + { + msg_format("Error 'rules' is not an array"); + return; + } + + for (i = 0; i < json_array_size(rules); i++) + { + json_t *rule = json_array_get(rules, i); + parse_rule(rule); + } +} + +/** + * Initialize the automatizer. This function may be called multiple + * times with different file names -- it should NOT clear any + * automatizer state (including loaded rules). + */ +void automatizer_init(cptr file_path) +{ + json_t *rules_json = NULL; + json_error_t error; + + /* Does the file exist? */ + if (!file_exist(file_path)) + { + /* No big deal, we'll just skip */ + goto out; + } + + /* Parse file */ + rules_json = json_load_file(file_path, 0, &error); + if (rules_json == NULL) + { + msg_format("Error parsing automatizer rules from '%s'.", file_path); + msg_format("Line %d, Column %d", error.line, error.column); + msg_print(NULL); + goto out; + } + + /* Go through all the found rules */ + parse_rules(rules_json); + +out: + if (rules_json == NULL) + { + json_decref(rules_json); + } +} -- cgit v1.2.3