summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBardur Arantsson <bardur@scientician.net>2012-06-19 20:03:42 +0200
committerBardur Arantsson <bardur@scientician.net>2013-09-27 14:46:40 +0200
commitb05113a48a2031282b5f4e65b97407a7bc8438c1 (patch)
treeab5042ebb89b8a6b5728b6843c22608c14e21393
parent1d65909b257cc84282da4a1192072615b716138c (diff)
C++: Move Automatizer to C++
-rw-r--r--.gitignore1
-rw-r--r--src/CMakeLists.txt14
-rw-r--r--src/dungeon.c2
-rw-r--r--src/externs.h3
-rw-r--r--src/include/tome/enum_string_map.hpp58
-rw-r--r--src/include/tome/squelch/automatizer.hpp159
-rw-r--r--src/include/tome/squelch/automatizer_fwd.hpp10
-rw-r--r--src/include/tome/squelch/condition.hpp635
-rw-r--r--src/include/tome/squelch/condition_fwd.hpp15
-rw-r--r--src/include/tome/squelch/condition_metadata.hpp12
-rw-r--r--src/include/tome/squelch/condition_metadata_fwd.hpp14
-rw-r--r--src/include/tome/squelch/cursor.hpp50
-rw-r--r--src/include/tome/squelch/cursor_fwd.hpp10
-rw-r--r--src/include/tome/squelch/object_status.hpp28
-rw-r--r--src/include/tome/squelch/object_status_fwd.hpp15
-rw-r--r--src/include/tome/squelch/rule.hpp165
-rw-r--r--src/include/tome/squelch/rule_fwd.hpp16
-rw-r--r--src/include/tome/squelch/tree_printer.hpp49
-rw-r--r--src/include/tome/squelch/tree_printer_fwd.hpp10
-rw-r--r--src/init2.c7
-rw-r--r--src/squelch/CMakeLists.txt9
-rw-r--r--src/squelch/automatizer.cc275
-rw-r--r--src/squelch/condition.cc1068
-rw-r--r--src/squelch/condition_metadata.cc492
-rw-r--r--src/squelch/cursor.cc93
-rw-r--r--src/squelch/object_status.cc149
-rw-r--r--src/squelch/rule.cc325
-rw-r--r--src/squelch/tree_printer.cc89
-rw-r--r--src/squeltch.c3572
-rw-r--r--src/squeltch.cc598
-rw-r--r--src/variable.c1
31 files changed, 4363 insertions, 3581 deletions
diff --git a/.gitignore b/.gitignore
index d7793155..f0ddf2c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.o
+lib*.a
*.~*
*.#*
CMakeFiles
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 03c1771b..7f84a2d0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,10 +1,17 @@
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
+
+# Add subdirectories
+ADD_SUBDIRECTORY (squelch)
+
+# Sources
SET(SRCS
main-gcu.c main-x11.c main-xaw.c main-sdl.c main-gtk2.c
z-rand.c z-util.c z-form.c z-virt.c z-term.c
variable.c tables.c plots.c util.c cave.c dungeon.c
melee1.c melee2.c messages.c modules.c
q_god.c
- object1.c object2.c randart.c squeltch.c traps.c
+ object1.c object2.c randart.c squeltch.cc traps.c
monster1.c monster2.c monster3.c
xtra1.c xtra2.c skills.c powers.c gods.c
spells1.c spells2.c spells3.c spells4.c spells5.c spells6.c
@@ -38,12 +45,9 @@ if(WIN32)
endif(MINGW)
endif(WIN32)
-
# tome executable
-INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
-INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include)
ADD_EXECUTABLE(tome ${EXECUTABLE_OPTIONS} ${SRCS})
-TARGET_LINK_LIBRARIES(tome ${LIBS})
+TARGET_LINK_LIBRARIES(tome squelch ${LIBS})
# Installation
INSTALL(TARGETS tome
diff --git a/src/dungeon.c b/src/dungeon.c
index 8df85977..0b2551bf 100644
--- a/src/dungeon.c
+++ b/src/dungeon.c
@@ -5379,7 +5379,7 @@ static void load_all_pref_files(void)
* 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); */
+ /* automatizer_load(buf); */
}
/*
diff --git a/src/externs.h b/src/externs.h
index 8fd9e5e5..97e886a0 100644
--- a/src/externs.h
+++ b/src/externs.h
@@ -2223,7 +2223,8 @@ 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);
+extern void automatizer_init();
+extern void automatizer_load(cptr file_name);
/*
diff --git a/src/include/tome/enum_string_map.hpp b/src/include/tome/enum_string_map.hpp
new file mode 100644
index 00000000..80ba7833
--- /dev/null
+++ b/src/include/tome/enum_string_map.hpp
@@ -0,0 +1,58 @@
+#ifndef H_ae6d0de3_c72e_4e67_9da2_975283fbd009
+#define H_ae6d0de3_c72e_4e67_9da2_975283fbd009
+
+#include <boost/bimap.hpp>
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <cassert>
+
+/**
+ * Bidirectional mapping between enumerated values
+ * and strings.
+ */
+template <class E>
+class EnumStringMap : boost::noncopyable {
+
+private:
+ typedef boost::bimap< E, std::string > bimap_type;
+ typedef typename bimap_type::value_type value_type;
+
+ bimap_type bimap;
+
+public:
+ explicit EnumStringMap(std::initializer_list< std::pair<E, const char *> > in) {
+ for (auto es : in)
+ {
+ bimap.insert(value_type(es.first, es.second));
+ }
+ // Sanity check that there were no
+ // duplicate mappings.
+ assert(bimap.size() == in.size());
+ }
+
+ const char *stringify(E e) {
+ auto i = bimap.left.find(e);
+ assert(i != bimap.left.end() && "Missing mapping for enumerated value");
+ return i->second.c_str();
+ }
+
+ E parse(const char *s) {
+ E e;
+ bool result = parse(s, &e);
+ assert(result && "Missing string->enum mapping");
+ return e;
+ }
+
+ bool parse(const char *s, E *e) {
+ auto i = bimap.right.find(s);
+ if (i == bimap.right.end())
+ {
+ return false;
+ }
+
+ *e = i->second;
+ return true;
+ }
+};
+
+#endif
diff --git a/src/include/tome/squelch/automatizer.hpp b/src/include/tome/squelch/automatizer.hpp
new file mode 100644
index 00000000..fae52a2b
--- /dev/null
+++ b/src/include/tome/squelch/automatizer.hpp
@@ -0,0 +1,159 @@
+#ifndef H_53108fce_b059_4a82_99db_e1d4970ebd77
+#define H_53108fce_b059_4a82_99db_e1d4970ebd77
+
+#include <boost/noncopyable.hpp>
+#include <memory>
+#include <vector>
+#include <jansson.h>
+
+#include "tome/squelch/rule_fwd.hpp"
+#include "tome/squelch/cursor_fwd.hpp"
+#include "tome/squelch/tree_printer_fwd.hpp"
+#include "tome/squelch/condition_fwd.hpp"
+#include "types_fwd.h"
+
+namespace squelch {
+
+/**
+ * Automatizer
+ */
+class Automatizer : public boost::noncopyable
+{
+public:
+ Automatizer(std::shared_ptr<TreePrinter> tree_printer,
+ std::shared_ptr<Cursor> cursor)
+ : m_selected_rule(0)
+ , m_begin(0)
+ , m_tree_printer(tree_printer)
+ , m_cursor(cursor)
+ , m_rules() {
+ }
+
+ /**
+ * Append a rule
+ */
+ int append_rule(std::shared_ptr<Rule> rule);
+
+ /**
+ * Swap two rules
+ */
+ void swap_rules(int i, int j);
+
+ /**
+ * Apply all rules to the given object
+ */
+ bool apply_rules(object_type *o_ptr, int item_idx) const;
+
+ /**
+ * Build a JSON data structure to represent
+ * all the rules.
+ */
+ std::shared_ptr<json_t> to_json() const;
+
+ /**
+ * Load rules from a JSON data structure.
+ */
+ void load_json(json_t *json);
+
+ /**
+ * Remove currently selected condition or rule.
+ */
+ int remove_current_selection();
+
+ /**
+ * Reset view.
+ */
+ void reset_view();
+
+ /**
+ * Show current rule
+ */
+ void show_current() const;
+
+ /**
+ * Scroll view up
+ */
+ void scroll_up();
+
+ /**
+ * Scroll view down
+ */
+ void scroll_down();
+
+ /**
+ * Scroll view left
+ */
+ void scroll_left();
+
+ /**
+ * Scroll view right
+ */
+ void scroll_right();
+
+ /**
+ * Move selection up
+ */
+ void move_up();
+
+ /**
+ * Move selection down
+ */
+ void move_down();
+
+ /**
+ * Move selection left
+ */
+ void move_left();
+
+ /**
+ * Move selection right
+ */
+ void move_right();
+
+ /**
+ * Add new condition to selected rule
+ */
+ void add_new_condition(std::function<std::shared_ptr<Condition> ()> factory);
+
+ /**
+ * Get rule names. The names are not stable across multiple
+ * calls to methods on this class.
+ */
+ void get_rule_names(std::vector<const char *> *names) const;
+
+ /**
+ * Get current number of rules.
+ */
+ int rules_count() const;
+
+ /**
+ * Get the "beginning" rule.
+ */
+ int rules_begin() const;
+
+ /**
+ * Select a new rule.
+ */
+ void select_rule(int selected_rule);
+
+ /**
+ * Return selected rule index
+ */
+ int selected_rule() const;
+
+ /**
+ * Return selected rule
+ */
+ std::shared_ptr<Rule> current_rule();
+
+private:
+ int m_selected_rule;
+ int m_begin;
+ std::shared_ptr<TreePrinter> m_tree_printer;
+ std::shared_ptr<Cursor> m_cursor;
+ std::vector < std::shared_ptr < Rule > > m_rules;
+};
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/automatizer_fwd.hpp b/src/include/tome/squelch/automatizer_fwd.hpp
new file mode 100644
index 00000000..068f297a
--- /dev/null
+++ b/src/include/tome/squelch/automatizer_fwd.hpp
@@ -0,0 +1,10 @@
+#ifndef H_cbf79126_fd24_4f80_8f2d_9dd69cb7010f
+#define H_cbf79126_fd24_4f80_8f2d_9dd69cb7010f
+
+namespace squelch {
+
+class Automatizer;
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/condition.hpp b/src/include/tome/squelch/condition.hpp
new file mode 100644
index 00000000..7aad5ff1
--- /dev/null
+++ b/src/include/tome/squelch/condition.hpp
@@ -0,0 +1,635 @@
+#ifndef H_e31ae6cc_eb8a_4909_ad6c_da485e4264a2
+#define H_e31ae6cc_eb8a_4909_ad6c_da485e4264a2
+
+#include "tome/squelch/condition_fwd.hpp"
+
+#include <boost/noncopyable.hpp>
+#include <functional>
+#include <memory>
+#include <cstdint>
+#include <jansson.h>
+
+#include "tome/squelch/cursor_fwd.hpp"
+#include "tome/squelch/tree_printer_fwd.hpp"
+#include "tome/squelch/object_status_fwd.hpp"
+#include "tome/enum_string_map.hpp"
+#include "types_fwd.h"
+
+namespace squelch {
+
+/**
+ * Types of matches used for conditions.
+ */
+enum class match_type {
+ AND , OR , NOT , NAME , CONTAIN ,
+ INSCRIBED, DISCOUNT, SYMBOL , STATE , STATUS ,
+ TVAL , SVAL , RACE , SUBRACE , CLASS ,
+ LEVEL , SKILL , ABILITY, INVENTORY, EQUIPMENT };
+
+/**
+ * Bidirectional map between enumeration values and strings.
+ */
+EnumStringMap<match_type> &match_mapping();
+
+/**
+ * Identification states an object can have: identified or not
+ * identified.
+ */
+enum class identification_state {
+ IDENTIFIED, NOT_IDENTIFIED };
+
+/**
+ * Biredectional map between identification_state values and strings.
+ */
+EnumStringMap<identification_state> &identification_state_mapping();
+
+/**
+ * Condition represents a tree of checks which
+ * can be applied to objects, the player, etc.
+ */
+class Condition : public boost::noncopyable
+{
+public:
+ Condition(match_type match_) : match(match_) {
+ }
+
+ void display(TreePrinter *, Cursor *) const;
+
+ virtual bool is_match(object_type *) const = 0;
+
+ virtual ~Condition() {
+ }
+
+public:
+ json_t *to_json() const;
+
+ virtual void add_child(ConditionFactory const &factory) {
+ // Default is to not support children.
+ };
+
+ virtual void remove_child(Condition *c) {
+ // Nothing to do by default.
+ }
+
+ virtual std::shared_ptr<Condition> first_child() {
+ // No children.
+ return nullptr;
+ }
+
+ virtual std::shared_ptr<Condition> previous_child(Condition *) {
+ // Default no children, so no predecessor.
+ return nullptr;
+ }
+
+ virtual std::shared_ptr<Condition> next_child(Condition *) {
+ // Default no children, so no successor.
+ return nullptr;
+ }
+
+ /**
+ * Parse condition from JSON
+ */
+ static std::shared_ptr<Condition> parse_condition(json_t *);
+
+ /**
+ * Convert an (optional) condition to JSON.
+ */
+ static json_t *optional_to_json(std::shared_ptr<Condition> condition);
+
+protected:
+ virtual void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const = 0;
+ virtual void to_json(json_t *) const = 0;
+
+ // What do we want to match?
+ match_type match;
+};
+
+/**
+ * Check for a specific TVAL
+ */
+class TvalCondition : public Condition
+{
+public:
+ TvalCondition(uint8_t tval)
+ : Condition(match_type::TVAL)
+ , m_tval(tval) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ uint8_t m_tval;
+};
+
+/**
+ * Check for object name
+ */
+class NameCondition : public Condition
+{
+public:
+ NameCondition(std::string name) :
+ Condition(match_type::NAME),
+ m_name(name) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_name;
+};
+
+/**
+ * Check for infix of object name
+ */
+class ContainCondition : public Condition
+{
+public:
+ ContainCondition(std::string contain) :
+ Condition(match_type::CONTAIN),
+ m_contain(contain) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_contain;
+};
+
+/**
+ * Check for specific SVAL
+ */
+class SvalCondition : public Condition
+{
+public:
+ SvalCondition(uint8_t min, uint8_t max)
+ : Condition(match_type::SVAL)
+ , m_min(min)
+ , m_max(max) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ uint8_t m_min;
+ uint8_t m_max;
+};
+
+/**
+ * Groupings of subconditions
+ */
+class GroupingCondition : public Condition
+{
+public:
+ GroupingCondition(match_type match,
+ std::vector< std::shared_ptr<Condition> > conditions = std::vector< std::shared_ptr<Condition> >())
+ : Condition(match)
+ , m_conditions(conditions) {
+ }
+
+ virtual void add_condition(std::shared_ptr<Condition> condition) {
+ if (condition)
+ {
+ m_conditions.push_back(condition);
+ }
+ }
+
+ // Child manipulation
+ virtual void add_child(ConditionFactory const &factory) override;
+ virtual void remove_child(Condition *condition) override;
+ virtual std::shared_ptr<Condition> first_child() override;
+ virtual std::shared_ptr<Condition> previous_child(Condition *) override;
+ virtual std::shared_ptr<Condition> next_child(Condition *current) override;
+
+ // Parse a list of conditions from JSON property
+ static std::vector< std::shared_ptr<Condition> > parse_conditions(json_t *);
+
+protected:
+ void to_json(json_t *) const override;
+
+protected:
+ std::vector< std::shared_ptr<Condition> > m_conditions;
+};
+
+/**
+ * Conditions that are AND'ed together
+ */
+class AndCondition : public GroupingCondition
+{
+public:
+ AndCondition() : GroupingCondition(match_type::AND) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+};
+
+/**
+ * Conditions that are OR'ed together
+ */
+class OrCondition : public GroupingCondition
+{
+public:
+ OrCondition() : GroupingCondition(match_type::OR) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+};
+
+/**
+ * Check for object status
+ */
+class StatusCondition : public Condition
+{
+public:
+ StatusCondition(status_type status)
+ : Condition(match_type::STATUS)
+ , m_status(status) {
+ }
+
+public:
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ status_type m_status;
+};
+
+/**
+ * Check for player race
+ */
+class RaceCondition : public Condition
+{
+public:
+ RaceCondition(std::string race)
+ : Condition(match_type::RACE)
+ , m_race(race) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_race;
+};
+
+/**
+ * Check player subrace
+ */
+class SubraceCondition : public Condition
+{
+public:
+ SubraceCondition(std::string subrace)
+ : Condition(match_type::SUBRACE)
+ , m_subrace(subrace) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_subrace;
+};
+
+/**
+ * Check player class
+ */
+class ClassCondition : public Condition
+{
+public:
+ ClassCondition(std::string klass)
+ : Condition(match_type::CLASS)
+ , m_class(klass) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_class;
+};
+
+/**
+ * Check object inscription
+ */
+class InscriptionCondition : public Condition
+{
+public:
+ InscriptionCondition(std::string inscription)
+ : Condition(match_type::INSCRIBED)
+ , m_inscription(inscription) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ std::string m_inscription;
+};
+
+/**
+ * Check object discount
+ */
+class DiscountCondition : public Condition
+{
+public:
+ DiscountCondition(int min, int max)
+ : Condition(match_type::DISCOUNT)
+ , m_min(min)
+ , m_max(max) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ int m_min;
+ int m_max;
+};
+
+/**
+ * Check player level
+ */
+class LevelCondition : public Condition
+{
+public:
+ LevelCondition(int min, int max)
+ : Condition(match_type::LEVEL)
+ , m_min(min)
+ , m_max(max) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ int m_min;
+ int m_max;
+};
+
+/**
+ * Check player's skill level
+ */
+class SkillCondition : public Condition
+{
+public:
+ SkillCondition(uint16_t skill_idx, uint16_t min, uint16_t max)
+ : Condition(match_type::SKILL)
+ , m_skill_idx(skill_idx)
+ , m_min(min)
+ , m_max(max) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ uint16_t m_skill_idx;
+ uint16_t m_min;
+ uint16_t m_max;
+};
+
+/**
+ * Check identification state
+ */
+class StateCondition : public Condition
+{
+public:
+ StateCondition(identification_state state)
+ : Condition(match_type::STATE)
+ , m_state(state) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ identification_state m_state;
+};
+
+/**
+ * Check object symbol
+ */
+class SymbolCondition : public Condition
+{
+public:
+ SymbolCondition(char symbol)
+ : Condition(match_type::SYMBOL)
+ , m_symbol(symbol) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ char m_symbol;
+};
+
+/**
+ * Check if player has a particular ability
+ */
+class AbilityCondition : public Condition
+{
+public:
+ AbilityCondition(uint16_t ability_idx)
+ : Condition(match_type::ABILITY)
+ , m_ability_idx(ability_idx) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+
+ void to_json(json_t *) const override;
+
+private:
+ uint16_t m_ability_idx;
+};
+
+/**
+ * Condition with a single subcondition
+ */
+class SingleSubconditionCondition : public Condition
+{
+public:
+ SingleSubconditionCondition(match_type match,
+ std::shared_ptr<Condition> subcondition)
+ : Condition(match)
+ , m_subcondition(subcondition) {
+ }
+
+ virtual void add_child(std::function< std::shared_ptr<Condition> () > const &factory) override;
+
+ virtual void remove_child(Condition *c) override;
+
+ virtual std::shared_ptr<Condition> first_child() override;
+
+protected:
+ void to_json(json_t *) const override;
+
+ static std::shared_ptr<Condition> parse_single_subcondition(
+ json_t *condition_json);
+
+protected:
+ std::shared_ptr<Condition> m_subcondition;
+};
+
+/**
+ * Condition which negates another condition
+ */
+class NotCondition : public SingleSubconditionCondition
+{
+public:
+ NotCondition(std::shared_ptr<Condition> subcondition = nullptr)
+ : SingleSubconditionCondition(match_type::NOT, subcondition) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+};
+
+/**
+ * Condition which checks if player inventory contains object(s)
+ * satisfying another condition.
+ */
+class InventoryCondition : public SingleSubconditionCondition
+{
+public:
+ InventoryCondition(std::shared_ptr<Condition> subcondition = nullptr)
+ : SingleSubconditionCondition(match_type::INVENTORY, subcondition) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+};
+
+/**
+ * Condition which checks if player equipment contains object(s)
+ * satisfying another condition.
+ */
+class EquipmentCondition : public SingleSubconditionCondition
+{
+public:
+ EquipmentCondition(std::shared_ptr<Condition> subcondition = nullptr)
+ : SingleSubconditionCondition(match_type::EQUIPMENT, subcondition) {
+ }
+
+ bool is_match(object_type *) const override;
+
+ static std::shared_ptr<Condition> from_json(json_t *);
+
+protected:
+ void write_tree(TreePrinter *, Cursor *, uint8_t, uint8_t) const override;
+};
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/condition_fwd.hpp b/src/include/tome/squelch/condition_fwd.hpp
new file mode 100644
index 00000000..744e7884
--- /dev/null
+++ b/src/include/tome/squelch/condition_fwd.hpp
@@ -0,0 +1,15 @@
+#ifndef H_1122f873_e83d_4af8_b6a8_a9e8db195473
+#define H_1122f873_e83d_4af8_b6a8_a9e8db195473
+
+#include <functional>
+#include <memory>
+
+namespace squelch {
+
+enum class match_type : int;
+class Condition;
+typedef std::function< std::shared_ptr<Condition> () > ConditionFactory;
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/condition_metadata.hpp b/src/include/tome/squelch/condition_metadata.hpp
new file mode 100644
index 00000000..fbb26bc3
--- /dev/null
+++ b/src/include/tome/squelch/condition_metadata.hpp
@@ -0,0 +1,12 @@
+#ifndef H_787198a1_aa3e_45c9_bc9f_fd7f490db78c
+#define H_787198a1_aa3e_45c9_bc9f_fd7f490db78c
+
+#include "tome/squelch/condition_metadata_fwd.hpp"
+
+namespace squelch {
+
+std::shared_ptr<Condition> new_condition_interactive();
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/condition_metadata_fwd.hpp b/src/include/tome/squelch/condition_metadata_fwd.hpp
new file mode 100644
index 00000000..1f57481b
--- /dev/null
+++ b/src/include/tome/squelch/condition_metadata_fwd.hpp
@@ -0,0 +1,14 @@
+#ifndef H_7922f591_bdfd_491d_8561_b11225285fea
+#define H_7922f591_bdfd_491d_8561_b11225285fea
+
+#include "tome/squelch/condition_fwd.hpp"
+
+#include <memory>
+
+namespace squelch {
+
+std::shared_ptr<Condition> new_condition_interactive();
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/cursor.hpp b/src/include/tome/squelch/cursor.hpp
new file mode 100644
index 00000000..8dbfc6bf
--- /dev/null
+++ b/src/include/tome/squelch/cursor.hpp
@@ -0,0 +1,50 @@
+#ifndef H_8a10111d_64a1_4af9_a85b_24ec8922d3fa
+#define H_8a10111d_64a1_4af9_a85b_24ec8922d3fa
+
+#include <boost/noncopyable.hpp>
+#include <deque>
+
+#include "tome/squelch/condition_fwd.hpp"
+
+namespace squelch {
+
+/**
+ * Cursor which maintains selected condition(s)
+ */
+class Cursor : public boost::noncopyable
+{
+public:
+ bool is_selected(Condition const *condition) const;
+
+ void push(Condition *condition) {
+ m_conditions.push_back(condition);
+ }
+
+ Condition *pop();
+
+ Condition *current();
+
+ void clear() {
+ m_conditions.clear();
+ }
+
+ bool empty() const {
+ return m_conditions.empty();
+ }
+
+ std::size_t size() const {
+ return m_conditions.size();
+ }
+
+ void move_left();
+ void move_right();
+ void move_up();
+ void move_down();
+
+private:
+ std::deque<Condition *> m_conditions;
+};
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/cursor_fwd.hpp b/src/include/tome/squelch/cursor_fwd.hpp
new file mode 100644
index 00000000..f07e9aa9
--- /dev/null
+++ b/src/include/tome/squelch/cursor_fwd.hpp
@@ -0,0 +1,10 @@
+#ifndef H_a4caa98a_1044_4192_b1af_27c2e8790cae
+#define H_a4caa98a_1044_4192_b1af_27c2e8790cae
+
+namespace squelch {
+
+class Cursor;
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/object_status.hpp b/src/include/tome/squelch/object_status.hpp
new file mode 100644
index 00000000..c52a35ea
--- /dev/null
+++ b/src/include/tome/squelch/object_status.hpp
@@ -0,0 +1,28 @@
+#ifndef H_e3f9ebbe_ff9a_4687_a847_6101f094b483
+#define H_e3f9ebbe_ff9a_4687_a847_6101f094b483
+
+#include "tome/enum_string_map.hpp"
+
+namespace squelch {
+
+/**
+ * Types of statuses for objects, e.g. "special" for artifacts and
+ * "average" for plain objects with no plusses.
+ */
+enum class status_type {
+ BAD , VERY_BAD, AVERAGE, GOOD, VERY_GOOD,
+ SPECIAL, TERRIBLE, NONE, CHEST_EMPTY, CHEST_DISARMED };
+
+/**
+ * Bidirectional map between status_type values and strings.
+ */
+EnumStringMap<status_type> &status_mapping();
+
+/**
+ * Find the status of an object
+ */
+status_type object_status(object_type *o_ptr);
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/object_status_fwd.hpp b/src/include/tome/squelch/object_status_fwd.hpp
new file mode 100644
index 00000000..10725050
--- /dev/null
+++ b/src/include/tome/squelch/object_status_fwd.hpp
@@ -0,0 +1,15 @@
+#ifndef H_3261a8ad_ee1c_4a2b_9d21_7c9955f09542
+#define H_3261a8ad_ee1c_4a2b_9d21_7c9955f09542
+
+#include "types_fwd.h"
+#include "tome/enum_string_map.hpp"
+
+namespace squelch {
+
+enum class status_type;
+EnumStringMap<status_type> &status_mapping();
+status_type object_status(object_type *o_ptr);
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/rule.hpp b/src/include/tome/squelch/rule.hpp
new file mode 100644
index 00000000..d5cc5953
--- /dev/null
+++ b/src/include/tome/squelch/rule.hpp
@@ -0,0 +1,165 @@
+#ifndef H_c3dd858a_4663_44a0_8871_ff6c8148a613
+#define H_c3dd858a_4663_44a0_8871_ff6c8148a613
+
+#include <jansson.h>
+#include <memory>
+
+#include "tome/squelch/condition_fwd.hpp"
+#include "tome/squelch/cursor_fwd.hpp"
+#include "tome/squelch/tree_printer_fwd.hpp"
+#include "tome/enum_string_map.hpp"
+#include "types_fwd.h"
+
+namespace squelch {
+
+/**
+ * Types of automatizer actions: destroy, pick up, and inscribe.
+ */
+enum class action_type { AUTO_DESTROY, AUTO_PICKUP, AUTO_INSCRIBE };
+
+/**
+ * Bidirectional map between rule actions and strings.
+ */
+EnumStringMap<action_type> &action_mapping();
+
+/**
+ * Rules are the representation of "when condition X is true, do Y".
+ */
+class Rule : public boost::noncopyable
+{
+public:
+ Rule(std::string name,
+ action_type action,
+ int module_idx,
+ std::shared_ptr<Condition> condition)
+ : m_name(name)
+ , m_action(action)
+ , m_module_idx(module_idx)
+ , m_condition(condition) {
+ }
+
+ /**
+ * Set the name of the rule
+ */
+ void set_name(const char *new_name);
+
+ /**
+ * Get the name of the rule
+ */
+ const char *get_name() const;
+
+ /**
+ * Get condition
+ */
+ std::shared_ptr<Condition> get_condition() const;
+
+ /**
+ * Add new condition using a factory to instantiate the
+ * condition only if the rule permits adding a condition.
+ */
+ void add_new_condition(Cursor *cursor,
+ ConditionFactory const &factory);
+
+ /**
+ * Remove currently selected condition
+ */
+ void delete_selected_condition(Cursor *cursor);
+
+ /**
+ * Write out tree representing rule
+ */
+ void write_tree(TreePrinter *p, Cursor *cursor) const;
+
+ /**
+ * Apply rule to object
+ */
+ bool apply_rule(object_type *o_ptr, int item_idx) const;
+
+ /**
+ * Convert rule to JSON
+ */
+ virtual json_t *to_json() const;
+
+ /**
+ * Parse rule from JSON
+ */
+ static std::shared_ptr<Rule> parse_rule(json_t *);
+
+protected:
+ virtual bool do_apply_rule(object_type *, int) const = 0;
+ virtual void do_write_tree(TreePrinter *p) const = 0;
+
+protected:
+ // Rule name
+ std::string m_name;
+ // What does the rule do?
+ action_type m_action;
+ // Which module does this rule apply to?
+ int m_module_idx;
+ // Condition to check
+ std::shared_ptr<Condition> m_condition;
+};
+
+/**
+ * Rule for destroying matching objects
+ */
+class DestroyRule : public Rule
+{
+public:
+ DestroyRule(std::string name,
+ int module_idx,
+ std::shared_ptr<Condition> condition)
+ : Rule(name, action_type::AUTO_DESTROY, module_idx, condition) {
+ }
+
+protected:
+ virtual bool do_apply_rule(object_type *o_ptr, int item_idx) const override;
+ virtual void do_write_tree(TreePrinter *p) const override;
+};
+
+/**
+ * Rule for picking up matching objects
+ */
+class PickUpRule : public Rule
+{
+public:
+
+ PickUpRule(std::string name,
+ int module_idx,
+ std::shared_ptr<Condition> condition)
+ : Rule(name, action_type::AUTO_PICKUP, module_idx, condition) {
+ }
+
+protected:
+ virtual void do_write_tree(TreePrinter *p) const override;
+ virtual bool do_apply_rule(object_type *o_ptr, int item_idx) const override;
+};
+
+/**
+ * Rule for inscribing matching objects
+ */
+class InscribeRule : public Rule
+{
+public:
+ InscribeRule(std::string name,
+ int module_idx,
+ std::shared_ptr<Condition> condition,
+ std::string inscription)
+ : Rule(name, action_type::AUTO_INSCRIBE, module_idx, condition)
+ , m_inscription(inscription) {
+ }
+
+ json_t *to_json() const override;
+
+protected:
+ virtual void do_write_tree(TreePrinter *p) const;
+ virtual bool do_apply_rule(object_type *o_ptr, int) const;
+
+private:
+ // Inscription to use for inscription rules.
+ std::string m_inscription;
+};
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/rule_fwd.hpp b/src/include/tome/squelch/rule_fwd.hpp
new file mode 100644
index 00000000..091e77ef
--- /dev/null
+++ b/src/include/tome/squelch/rule_fwd.hpp
@@ -0,0 +1,16 @@
+#ifndef H_4a8d2cfb_182c_4138_983d_606a9ac70784
+#define H_4a8d2cfb_182c_4138_983d_606a9ac70784
+
+#include "tome/enum_string_map.hpp"
+
+namespace squelch {
+
+enum class action_type;
+
+EnumStringMap<action_type> &action_mapping();
+
+class Rule;
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/tree_printer.hpp b/src/include/tome/squelch/tree_printer.hpp
new file mode 100644
index 00000000..e8ee1e56
--- /dev/null
+++ b/src/include/tome/squelch/tree_printer.hpp
@@ -0,0 +1,49 @@
+#ifndef H_3d6cc652_c674_4a84_911d_e8ec35cc992a
+#define H_3d6cc652_c674_4a84_911d_e8ec35cc992a
+
+#include <boost/noncopyable.hpp>
+#include <cstdint>
+
+namespace squelch {
+
+/**
+ * Printing trees.
+ */
+class TreePrinter : boost::noncopyable
+{
+public:
+ TreePrinter();
+
+ void indent();
+
+ void dedent();
+
+ void reset();
+
+ void reset_scroll();
+
+ void scroll_up();
+
+ void scroll_down();
+
+ void scroll_left();
+
+ void scroll_right();
+
+ void write(uint8_t color, const char *line);
+
+private:
+ int m_indent;
+ int m_write_out_y;
+ int m_write_out_x;
+ int m_write_out_h;
+ int m_write_out_w;
+ int m_write_y;
+ int m_write_x;
+ int m_write_off_x;
+ int m_write_off_y;
+};
+
+} // namespace
+
+#endif
diff --git a/src/include/tome/squelch/tree_printer_fwd.hpp b/src/include/tome/squelch/tree_printer_fwd.hpp
new file mode 100644
index 00000000..d7d75453
--- /dev/null
+++ b/src/include/tome/squelch/tree_printer_fwd.hpp
@@ -0,0 +1,10 @@
+#ifndef H_4ce68eb3_c475_4fc4_8a70_0590c16dc684
+#define H_4ce68eb3_c475_4fc4_8a70_0590c16dc684
+
+namespace squelch {
+
+class TreePrinter;
+
+} // namespace
+
+#endif
diff --git a/src/init2.c b/src/init2.c
index 9c1c2afa..a38cccee 100644
--- a/src/init2.c
+++ b/src/init2.c
@@ -2698,6 +2698,9 @@ void init_angband(void)
/* Init random artifact names */
build_prob(artifact_names_list);
+ /* Initialize the automatizer */
+ automatizer_init();
+
/*** Load default user pref files ***/
/* Initialise feature info */
@@ -2727,9 +2730,9 @@ void init_angband(void)
/* Process that file */
process_pref_file(buf);
- /* Initialise the automatizer */
+ /* Load automatizer rules */
path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "automat.atm");
- automatizer_init(buf);
+ automatizer_load(buf);
/* Done */
note("[Initialisation complete]");
diff --git a/src/squelch/CMakeLists.txt b/src/squelch/CMakeLists.txt
new file mode 100644
index 00000000..7b1495ba
--- /dev/null
+++ b/src/squelch/CMakeLists.txt
@@ -0,0 +1,9 @@
+ADD_LIBRARY(squelch
+ automatizer.cc
+ condition.cc
+ condition_metadata.cc
+ cursor.cc
+ object_status.cc
+ rule.cc
+ tree_printer.cc
+)
diff --git a/src/squelch/automatizer.cc b/src/squelch/automatizer.cc
new file mode 100644
index 00000000..0c0f571a
--- /dev/null
+++ b/src/squelch/automatizer.cc
@@ -0,0 +1,275 @@
+#include "tome/squelch/automatizer_fwd.hpp"
+#include "tome/squelch/automatizer.hpp"
+
+#include "tome/squelch/rule.hpp"
+#include "tome/squelch/cursor.hpp"
+#include "tome/squelch/tree_printer.hpp"
+#include "angband.h"
+
+namespace squelch {
+
+/**
+ * Parse rules from JSON array
+ */
+static std::vector< std::shared_ptr < Rule > > parse_rules(json_t *rules_j)
+{
+ std::vector< std::shared_ptr < Rule > > rules;
+
+ if (!json_is_array(rules_j))
+ {
+ msg_format("Error 'rules' is not an array");
+ return rules;
+ }
+
+ for (size_t i = 0; i < json_array_size(rules_j); i++)
+ {
+ json_t *rule_j = json_array_get(rules_j, i);
+ auto rule = Rule::parse_rule(rule_j);
+ if (rule)
+ {
+ rules.push_back(rule);
+ }
+ }
+
+ return rules;
+}
+
+//----------------------------------------------------------
+// Automatizer
+//----------------------------------------------------------
+
+int Automatizer::append_rule(std::shared_ptr< Rule > rule)
+{
+ m_rules.push_back(rule);
+ return m_rules.size() - 1;
+}
+
+void Automatizer::swap_rules(int i, int j)
+{
+ swap(m_rules.at(i), m_rules.at(j));
+}
+
+bool Automatizer::apply_rules(object_type *o_ptr, int item_idx) const
+{
+ for (auto rule : m_rules)
+ {
+ if (rule->apply_rule(o_ptr, item_idx))
+ {
+ return true;
+ }
+ }
+
+ return true;
+}
+
+std::shared_ptr<json_t> Automatizer::to_json() const
+{
+ auto rules_json = std::shared_ptr<json_t>(json_array(), &json_decref);
+
+ for (auto rule : m_rules)
+ {
+ json_array_append_new(rules_json.get(), rule->to_json());
+ }
+
+ return rules_json;
+}
+
+void Automatizer::load_json(json_t *json)
+{
+ // Go through all the found rules
+ auto rules = parse_rules(json);
+
+ // Load rule
+ for (auto rule : rules)
+ {
+ append_rule(rule);
+ }
+}
+
+int Automatizer::remove_current_selection()
+{
+ assert(!m_rules.empty());
+
+ // Previously selected rule
+ int prev_selected_rule = m_selected_rule;
+ int new_selected_rule;
+
+ // If the cursor is at the top level then we want to delete
+ // the rule itself
+ if (m_cursor->size() < 1)
+ {
+ // Remove rule
+ m_rules.erase(m_rules.begin() + m_selected_rule);
+ // Select previous
+ new_selected_rule = prev_selected_rule - 1;
+ }
+ else
+ {
+ // Delete the currently selected condition in rule.
+ m_rules.at(m_selected_rule)->delete_selected_condition(m_cursor.get());
+ // Keep selection
+ new_selected_rule = m_selected_rule;
+ }
+
+ // Do we need to adjust to select a different rule?
+ if ((prev_selected_rule != new_selected_rule) && (new_selected_rule >= 0))
+ {
+ select_rule(new_selected_rule);
+ }
+
+ // Return the selected rule.
+ return m_selected_rule;
+}
+
+void Automatizer::reset_view()
+{
+ // Clear cursor
+ m_cursor->clear();
+
+ // Empty rules?
+ if (m_rules.empty())
+ {
+ return;
+ }
+
+ // Reset scroll position
+ m_tree_printer->reset_scroll();
+
+ // Put the top-level condition into cursor
+ auto condition = m_rules.at(m_selected_rule)->get_condition();
+ if (condition)
+ {
+ m_cursor->push(condition.get());
+ }
+}
+
+void Automatizer::show_current() const
+{
+ if (m_rules.empty())
+ {
+ return;
+ }
+
+ m_tree_printer->reset();
+ m_rules.at(m_selected_rule)->write_tree(
+ m_tree_printer.get(),
+ m_cursor.get());
+}
+
+void Automatizer::scroll_up()
+{
+ m_tree_printer->scroll_up();
+}
+
+void Automatizer::scroll_down()
+{
+ m_tree_printer->scroll_down();
+}
+
+void Automatizer::scroll_left()
+{
+ m_tree_printer->scroll_left();
+}
+
+void Automatizer::scroll_right()
+{
+ m_tree_printer->scroll_right();
+}
+
+void Automatizer::move_up()
+{
+ m_cursor->move_up();
+}
+
+void Automatizer::move_down()
+{
+ m_cursor->move_down();
+}
+
+void Automatizer::move_left()
+{
+ m_cursor->move_left();
+}
+
+void Automatizer::move_right()
+{
+ m_cursor->move_right();
+}
+
+void Automatizer::add_new_condition(std::function<std::shared_ptr<Condition> ()> factory)
+{
+ m_rules.at(m_selected_rule)->add_new_condition(
+ m_cursor.get(),
+ factory);
+}
+
+void Automatizer::get_rule_names(std::vector<const char *> *names) const
+{
+ names->resize(m_rules.size());
+ for (size_t i = 0; i < m_rules.size(); i++)
+ {
+ (*names)[i] = m_rules.at(i)->get_name();
+ }
+}
+
+int Automatizer::rules_count() const
+{
+ return m_rules.size();
+}
+
+int Automatizer::rules_begin() const
+{
+ return m_begin;
+}
+
+void Automatizer::select_rule(int selected_rule)
+{
+ m_selected_rule = selected_rule;
+
+ int wid, hgt;
+ Term_get_size(&wid, &hgt);
+
+ // Adjust selection to conform to bounds.
+ {
+ if (m_selected_rule < 0)
+ {
+ m_selected_rule = m_rules.size() - 1;
+ m_begin = m_selected_rule - hgt + 3;
+ if (m_begin < 0)
+ {
+ m_begin = 0;
+ }
+ }
+
+ if (m_selected_rule < m_begin)
+ {
+ m_begin = m_selected_rule;
+ }
+
+ if (m_selected_rule >= m_rules.size())
+ {
+ m_selected_rule = 0;
+ m_begin = 0;
+ }
+
+ if (m_selected_rule >= m_begin + hgt - 2)
+ {
+ m_begin++;
+ }
+ }
+
+ // Adjust tree printer and cursor.
+ reset_view();
+}
+
+int Automatizer::selected_rule() const
+{
+ return m_selected_rule;
+}
+
+std::shared_ptr<Rule> Automatizer::current_rule()
+{
+ return m_rules.at(m_selected_rule);
+}
+
+} // namespace
diff --git a/src/squelch/condition.cc b/src/squelch/condition.cc
new file mode 100644
index 00000000..2dc921ea
--- /dev/null
+++ b/src/squelch/condition.cc
@@ -0,0 +1,1068 @@
+#include "tome/squelch/condition_fwd.hpp"
+#include "tome/squelch/condition.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+
+#include "tome/squelch/cursor.hpp"
+#include "tome/squelch/tree_printer.hpp"
+#include "angband.h"
+#include "quark.h"
+
+namespace squelch {
+
+EnumStringMap<match_type> &match_mapping()
+{
+ // TODO: This is quite ugly and leads to valgrind complaints
+ static auto m = new EnumStringMap<match_type> {
+ { match_type::AND, "and" },
+ { match_type::OR, "or" },
+ { match_type::NOT, "not" },
+ { match_type::NAME, "name" },
+ { match_type::CONTAIN, "contain" },
+ { match_type::INSCRIBED, "inscribed" },
+ { match_type::DISCOUNT, "discount" },
+ { match_type::SYMBOL, "symbol" },
+ { match_type::STATE, "state" },
+ { match_type::STATUS, "status" },
+ { match_type::TVAL, "tval" },
+ { match_type::SVAL, "sval" },
+ { match_type::RACE, "race" },
+ { match_type::SUBRACE, "subrace" },
+ { match_type::CLASS, "class" },
+ { match_type::LEVEL, "level" },
+ { match_type::SKILL, "skill" },
+ { match_type::ABILITY, "ability" },
+ { match_type::INVENTORY, "inventory" },
+ { match_type::EQUIPMENT, "equipment" } };
+ return *m;
+};
+
+EnumStringMap<identification_state> &identification_state_mapping()
+{
+ // TODO: This is quite ugly and leads to valgrind complaints
+ static auto m = new EnumStringMap<identification_state> {
+ { identification_state::IDENTIFIED, "identified" },
+ { identification_state::NOT_IDENTIFIED, "not identified" } };
+ return *m;
+}
+
+json_t *Condition::to_json() const
+{
+ json_t *j = json_object();
+ json_object_set_new(j, "type",
+ json_string(match_mapping().stringify(match)));
+ to_json(j);
+ return j;
+}
+
+void Condition::display(TreePrinter *tree_printer, Cursor *cursor) const
+{
+ assert(tree_printer);
+
+ // Use normal or "selected" colours?
+ uint8_t bcol = TERM_L_GREEN;
+ uint8_t ecol = TERM_GREEN;
+ if (cursor->is_selected(this))
+ {
+ bcol = TERM_VIOLET;
+ ecol = TERM_VIOLET;
+ }
+
+ // Indent a level and display tree.
+ tree_printer->indent();
+ write_tree(tree_printer, cursor, ecol, bcol);
+ tree_printer->dedent();
+}
+
+std::shared_ptr<Condition> Condition::parse_condition(json_t *condition_json)
+{
+ // Parsers for concrete types of conditions.
+ static std::map< match_type,
+ std::function< std::shared_ptr< Condition > ( json_t * ) > > parsers {
+ { match_type::AND, &AndCondition::from_json },
+ { match_type::OR, &OrCondition::from_json },
+ { match_type::NOT, &NotCondition::from_json },
+ { match_type::INVENTORY, &InventoryCondition::from_json },
+ { match_type::EQUIPMENT, &EquipmentCondition::from_json },
+ { match_type::NAME, &NameCondition::from_json },
+ { match_type::CONTAIN, &ContainCondition::from_json },
+ { match_type::SYMBOL, &SymbolCondition::from_json },
+ { match_type::INSCRIBED, &InscriptionCondition::from_json },
+ { match_type::DISCOUNT, &DiscountCondition::from_json },
+ { match_type::TVAL, &TvalCondition::from_json },
+ { match_type::SVAL, &SvalCondition::from_json },
+ { match_type::STATUS, &StatusCondition::from_json },
+ { match_type::STATE, &StateCondition::from_json },
+ { match_type::RACE, &RaceCondition::from_json },
+ { match_type::SUBRACE, &SubraceCondition::from_json },
+ { match_type::CLASS, &ClassCondition::from_json },
+ { match_type::LEVEL, &LevelCondition::from_json },
+ { match_type::SKILL, &SkillCondition::from_json },
+ { match_type::ABILITY, &AbilityCondition::from_json } };
+
+ if ((condition_json == nullptr) || json_is_null(condition_json))
+ {
+ return nullptr;
+ }
+
+ cptr type_s = nullptr;
+ if (json_unpack(condition_json,
+ "{s:s}",
+ "type", &type_s) < 0)
+ {
+ msg_print("Missing/invalid 'type' in condition");
+ return nullptr;
+ }
+
+ match_type match;
+ if (!match_mapping().parse(type_s, &match))
+ {
+ msg_format("Invalid 'type' in condition: %s", type_s);
+ return nullptr;
+ }
+
+ // Look up parser and... parse
+ auto parser_i = parsers.find(match);
+ if (parser_i != parsers.end())
+ {
+ return parser_i->second(condition_json);
+ }
+
+ assert(false && "Missing parser");
+ return nullptr;
+}
+
+json_t *Condition::optional_to_json(std::shared_ptr<Condition> condition)
+{
+ return condition
+ ? condition->to_json()
+ : json_null();
+}
+
+bool TvalCondition::is_match(object_type *o_ptr) const
+{
+ return (o_ptr->tval == m_tval);
+}
+
+std::shared_ptr<Condition> TvalCondition::from_json(json_t *j)
+{
+ int tval;
+
+ if (json_unpack(j, "{s:i}", "tval", &tval) < 0)
+ {
+ msg_print("Missing/invalid 'tval' property");
+ return nullptr;
+ }
+
+ return std::make_shared<TvalCondition>(tval);
+}
+
+void TvalCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "tval", json_integer(m_tval));
+}
+
+void TvalCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "tval");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, format("%d", (int) m_tval));
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+bool NameCondition::is_match(object_type *o_ptr) const
+{
+ char buf1[128];
+ object_desc(buf1, o_ptr, -1, 0);
+
+ return boost::algorithm::iequals(m_name, buf1);
+}
+
+std::shared_ptr<Condition> NameCondition::from_json(json_t *j)
+{
+ cptr s = nullptr;
+ if (json_unpack(j, "{s:s}", "name", &s) < 0)
+ {
+ msg_print("Missing/invalid 'name' property");
+ return nullptr;
+ }
+ return std::make_shared<NameCondition>(s);
+}
+
+void NameCondition::write_tree(TreePrinter *p, Cursor *cursor, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "name");
+ p->write(ecol, " is \"");
+ p->write(TERM_WHITE, m_name.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void NameCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "name", json_string(m_name.c_str()));
+}
+
+bool ContainCondition::is_match(object_type *o_ptr) const
+{
+ char buf1[128];
+ object_desc(buf1, o_ptr, -1, 0);
+ return boost::algorithm::icontains(buf1, m_contain);
+}
+
+std::shared_ptr<Condition> ContainCondition::from_json(json_t *j)
+{
+ cptr s = nullptr;
+ if (json_unpack(j, "{s:s}", "contain", &s) < 0)
+ {
+ msg_print("Missing/invalid 'contain' property");
+ return nullptr;
+ }
+ return std::make_shared<ContainCondition>(s);
+}
+
+void ContainCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "name");
+ p->write(ecol, " contains \"");
+ p->write(TERM_WHITE, m_contain.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void ContainCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "contain", json_string(m_contain.c_str()));
+}
+
+bool SvalCondition::is_match(object_type *o_ptr) const
+{
+ return (object_aware_p(o_ptr) &&
+ (o_ptr->sval >= m_min) &&
+ (o_ptr->sval <= m_max));
+}
+
+std::shared_ptr<Condition> SvalCondition::from_json(json_t *j)
+{
+ int min, max;
+
+ if (json_unpack(j, "{s:i,s:i}",
+ "min", &min,
+ "max", &max) < 0)
+ {
+ msg_print("Missing/invalid 'min'/'max' properties");
+ return nullptr;
+ }
+
+ return std::make_shared<SvalCondition>(min, max);
+}
+
+void SvalCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "sval");
+ p->write(ecol, " is from ");
+ p->write(TERM_WHITE, format("%d", m_min));
+ p->write(ecol, " to ");
+ p->write(TERM_WHITE, format("%d", m_max));
+ p->write(TERM_WHITE, "\n");
+}
+
+void SvalCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "min", json_integer(m_min));
+ json_object_set_new(j, "max", json_integer(m_max));
+}
+
+void GroupingCondition::add_child(ConditionFactory const &factory)
+{
+ auto c_ptr = factory();
+ if (c_ptr)
+ {
+ m_conditions.push_back(c_ptr);
+ }
+}
+
+void GroupingCondition::remove_child(Condition *condition)
+{
+ std::remove_if(
+ std::begin(m_conditions),
+ std::end(m_conditions),
+ [&] (std::shared_ptr<Condition> p) {
+ return p.get() == condition;
+ });
+}
+
+std::shared_ptr<Condition> GroupingCondition::first_child()
+{
+ if (!m_conditions.empty())
+ {
+ return m_conditions.front();
+ }
+ return nullptr;
+}
+
+std::shared_ptr<Condition> GroupingCondition::previous_child(Condition *current)
+{
+ std::shared_ptr<Condition> prev_condition;
+
+ for (auto condition_p : m_conditions)
+ {
+ if (condition_p.get() == current)
+ {
+ // Do we have a previous child?
+ if (prev_condition)
+ {
+ return prev_condition;
+ }
+ else
+ {
+ // No predecessor
+ return nullptr;
+ }
+ }
+ // Keep track of predecessor
+ prev_condition = condition_p;
+ }
+
+ return nullptr;
+}
+
+std::shared_ptr<Condition> GroupingCondition::next_child(Condition *current)
+{
+ for (auto it = m_conditions.begin();
+ it != m_conditions.end();
+ it++)
+ {
+ if (it->get() == current)
+ {
+ it++;
+ // Move to next child (if any)
+ if (it == m_conditions.end())
+ {
+ // No successor
+ return nullptr;
+ }
+
+ return *it;
+ }
+ }
+
+ return nullptr;
+}
+
+std::vector< std::shared_ptr<Condition> > GroupingCondition::parse_conditions(json_t *j)
+{
+ json_t *conditions_j = json_object_get(j, "conditions");
+
+ if ((conditions_j == nullptr) ||
+ (json_is_null(conditions_j)))
+ {
+ return std::vector< std::shared_ptr<Condition> >();
+ }
+ else if (!json_is_array(conditions_j))
+ {
+ msg_print("'conditions' property has invalid type");
+ return std::vector< std::shared_ptr<Condition> >();
+ }
+ else
+ {
+ std::vector< std::shared_ptr<Condition> > subconditions;
+ for (size_t i = 0; i < json_array_size(conditions_j); i++)
+ {
+ json_t *subcondition_j =
+ json_array_get(conditions_j, i);
+
+ std::shared_ptr<Condition> subcondition =
+ parse_condition(subcondition_j);
+
+ if (subcondition != nullptr)
+ {
+ subconditions.push_back(subcondition);
+ }
+ }
+ return subconditions;
+ }
+}
+
+void GroupingCondition::to_json(json_t *j) const
+{
+ json_t *ja = json_array();
+ for (auto condition_p : m_conditions)
+ {
+ json_array_append_new(ja, optional_to_json(condition_p));
+ }
+ json_object_set_new(j, "conditions", ja);
+}
+
+bool AndCondition::is_match(object_type *o_ptr) const
+{
+ for (auto condition_p : m_conditions)
+ {
+ if (!condition_p->is_match(o_ptr))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+std::shared_ptr<Condition> AndCondition::from_json(json_t *j)
+{
+ auto condition = std::make_shared<AndCondition>();
+ for (auto subcondition : parse_conditions(j))
+ {
+ condition->add_condition(subcondition);
+ }
+ return condition;
+}
+
+void AndCondition::write_tree(TreePrinter *p, Cursor *c, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "All of the following are true:");
+ p->write(TERM_WHITE, "\n");
+
+ for (auto condition_p : m_conditions)
+ {
+ condition_p->display(p, c);
+ }
+}
+
+bool OrCondition::is_match(object_type *o_ptr) const
+{
+ for (auto condition_p : m_conditions)
+ {
+ if (condition_p->is_match(o_ptr))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::shared_ptr<Condition> OrCondition::from_json(json_t *j)
+{
+ std::shared_ptr<OrCondition> condition =
+ std::make_shared<OrCondition>();
+
+ for (auto subcondition : parse_conditions(j))
+ {
+ condition->add_condition(subcondition);
+ }
+
+ return condition;
+}
+
+void OrCondition::write_tree(TreePrinter *p, Cursor *c, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "At least one of the following are true:");
+ p->write(TERM_WHITE, "\n");
+
+ for (auto condition_p : m_conditions)
+ {
+ condition_p->display(p, c);
+ }
+}
+
+bool StatusCondition::is_match(object_type *o_ptr) const
+{
+ return m_status == object_status(o_ptr);
+}
+
+std::shared_ptr<Condition> StatusCondition::from_json(json_t *j)
+{
+ cptr s;
+ if (json_unpack(j, "{s:s}", "status", &s) < 0)
+ {
+ msg_print("Missing/invalid 'status' property");
+ return nullptr;
+ }
+
+ status_type status;
+ if (!status_mapping().parse(s, &status))
+ {
+ msg_format("Invalid 'status' property: %s", s);
+ return nullptr;
+ }
+
+ return std::make_shared<StatusCondition>(status);
+}
+
+void StatusCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "status");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, status_mapping().stringify(m_status));
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void StatusCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "status", json_string(status_mapping().stringify(m_status)));
+}
+
+bool RaceCondition::is_match(object_type *o_ptr) const
+{
+ return boost::algorithm::iequals(m_race,
+ rp_ptr->title + rp_name);
+}
+
+std::shared_ptr<Condition> RaceCondition::from_json(json_t *j)
+{
+ cptr s;
+
+ if (json_unpack(j, "{s:s}", "race", &s) < 0)
+ {
+ msg_print("Missing/invalid 'race' property");
+ return nullptr;
+ }
+
+ return std::make_shared<RaceCondition>(s);
+}
+
+void RaceCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Player ");
+ p->write(bcol, "race");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, m_race.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void RaceCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "race", json_string(m_race.c_str()));
+}
+
+bool SubraceCondition::is_match(object_type *o_ptr) const
+{
+ return boost::algorithm::iequals(m_subrace,
+ rmp_ptr->title + rmp_name);
+}
+
+std::shared_ptr<Condition> SubraceCondition::from_json(json_t *j)
+{
+ cptr s;
+
+ if (json_unpack(j, "{s:s}", "subrace", &s) < 0)
+ {
+ msg_print("Missing/invalid 'subrace' property");
+ return nullptr;
+ }
+
+ return std::make_shared<SubraceCondition>(s);
+}
+
+void SubraceCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Player ");
+ p->write(bcol, "subrace");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, m_subrace.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void SubraceCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "subrace", json_string(m_subrace.c_str()));
+}
+
+bool ClassCondition::is_match(object_type *o_ptr) const
+{
+ return boost::algorithm::iequals(m_class, spp_ptr->title + c_name);
+}
+
+std::shared_ptr<Condition> ClassCondition::from_json(json_t *j)
+{
+ cptr s;
+
+ if (json_unpack(j, "{s:s}", "class", &s) < 0)
+ {
+ msg_print("Missing/invalid 'class' property");
+ return nullptr;
+ }
+
+ return std::make_shared<ClassCondition>(s);
+}
+
+void ClassCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Player ");
+ p->write(bcol, "class");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, m_class.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void ClassCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "class", json_string(m_class.c_str()));
+}
+
+bool InscriptionCondition::is_match(object_type *o_ptr) const
+{
+ if (o_ptr->note == 0)
+ {
+ return false;
+ }
+ return boost::algorithm::icontains(
+ quark_str(o_ptr->note),
+ m_inscription);
+}
+
+std::shared_ptr<Condition> InscriptionCondition::from_json(json_t *j)
+{
+ cptr s = nullptr;
+ if (json_unpack(j, "{s:s}", "inscription", &s) < 0)
+ {
+ msg_print("Missing/invalid 'inscription' property");
+ return nullptr;
+ }
+ return std::make_shared<InscriptionCondition>(s);
+}
+
+void InscriptionCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "It is ");
+ p->write(bcol, "inscribed");
+ p->write(ecol, " with ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, m_inscription.c_str());
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void InscriptionCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "inscription", json_string(m_inscription.c_str()));
+}
+
+bool DiscountCondition::is_match(object_type *o_ptr) const
+{
+ return (object_aware_p(o_ptr) &&
+ (o_ptr->discount >= m_min) &&
+ (o_ptr->discount <= m_max));
+}
+
+std::shared_ptr<Condition> DiscountCondition::from_json(json_t *j)
+{
+ int min, max;
+
+ if (json_unpack(j, "{s:i,s:i}",
+ "min", &min,
+ "max", &max) < 0)
+ {
+ msg_print("Missing/invalid 'min'/'max' properties");
+ return nullptr;
+ }
+
+ return std::make_shared<DiscountCondition>(min, max);
+}
+
+void DiscountCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "discount");
+ p->write(ecol, " is from ");
+ p->write(TERM_WHITE, format("%d", m_min));
+ p->write(ecol, " to ");
+ p->write(TERM_WHITE, format("%d", m_max));
+ p->write(TERM_WHITE, "\n");
+}
+
+void DiscountCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "min", json_integer(m_min));
+ json_object_set_new(j, "max", json_integer(m_max));
+}
+
+bool LevelCondition::is_match(object_type *) const
+{
+ return ((p_ptr->lev >= m_min) &&
+ (p_ptr->lev <= m_max));
+}
+
+std::shared_ptr<Condition> LevelCondition::from_json(json_t *j)
+{
+ int min, max;
+ if (json_unpack(j, "{s:i,s:i}",
+ "min", &min,
+ "max", &max) < 0)
+ {
+ msg_print("Missing/invalid 'min'/'max' properties");
+ return nullptr;
+ }
+
+ return std::make_shared<LevelCondition>(min, max);
+}
+
+void LevelCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Your ");
+ p->write(bcol, "level");
+ p->write(ecol, " is from ");
+
+ p->write(TERM_WHITE, format("%d", m_min));
+ p->write(ecol, " to ");
+ p->write(TERM_WHITE, format("%d", m_max));
+ p->write(TERM_WHITE, "\n");
+}
+
+void LevelCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "min", json_integer(m_min));
+ json_object_set_new(j, "max", json_integer(m_max));
+}
+
+bool SkillCondition::is_match(object_type *) const
+{
+ uint16_t sk = get_skill(m_skill_idx);
+ return ((sk >= m_min) &&
+ (sk <= m_max));
+}
+
+std::shared_ptr<Condition> SkillCondition::from_json(json_t *j)
+{
+ cptr s;
+ int min, max;
+ if (json_unpack(j, "{s:i,s:i,s:s}",
+ "min", &min,
+ "max", &max,
+ "name", &s) < 0)
+ {
+ msg_print("Missing/invalid 'min'/'max'/'name' properties");
+ return nullptr;
+ }
+
+ uint16_t si = find_skill_i(s);
+ if (si < 0)
+ {
+ msg_print("Invalid 'name' property");
+ return nullptr;
+ }
+
+ return std::make_shared<SkillCondition>(si, min, max);
+}
+
+void SkillCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ cptr skill_name = s_info[m_skill_idx].name + s_name;
+
+ p->write(ecol, "Your skill in ");
+ p->write(bcol, skill_name);
+ p->write(ecol, " is from ");
+ p->write(TERM_WHITE, format("%d", (int) m_min));
+ p->write(ecol, " to ");
+ p->write(TERM_WHITE, format("%d", (int) m_max));
+ p->write(TERM_WHITE, "\n");
+}
+
+void SkillCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "name",
+ json_string(s_info[m_skill_idx].name + s_name));
+ json_object_set_new(j, "min",
+ json_integer(m_min));
+ json_object_set_new(j, "max",
+ json_integer(m_max));
+}
+
+bool StateCondition::is_match(object_type *o_ptr) const
+{
+ switch (m_state)
+ {
+ case identification_state::IDENTIFIED:
+ return object_known_p(o_ptr);
+ case identification_state::NOT_IDENTIFIED:
+ return !object_known_p(o_ptr);
+ }
+
+ assert(false);
+ return false;
+}
+
+std::shared_ptr<Condition> StateCondition::from_json(json_t *j)
+{
+ cptr s;
+ if (json_unpack(j, "{s:s}", "state", &s) < 0)
+ {
+ msg_print("Missing/invalid 'state' property");
+ return nullptr;
+ }
+
+ identification_state state;
+ if (!identification_state_mapping().parse(s, &state))
+ {
+ msg_format("Invalid 'state' property: %s", s);
+ return nullptr;
+ }
+
+ return std::make_shared<StateCondition>(state);
+}
+
+void StateCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "state");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, identification_state_mapping().stringify(m_state));
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void StateCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "state",
+ json_string(identification_state_mapping().
+ stringify(m_state)));
+}
+
+bool SymbolCondition::is_match(object_type *o_ptr) const
+{
+ object_kind *k_ptr = &k_info[o_ptr->k_idx];
+ return k_ptr->d_char == m_symbol;
+}
+
+std::shared_ptr<Condition> SymbolCondition::from_json(json_t *j)
+{
+ cptr s_ = nullptr;
+ if (json_unpack(j, "{s:s}", "symbol", &s_) < 0)
+ {
+ msg_print("Missing/invalid 'symbol' property");
+ return nullptr;
+ }
+
+ std::string s(s_);
+ if (s.empty())
+ {
+ msg_print("Invalid 'symbol' property: Too short");
+ return nullptr;
+ }
+ if (s.size() > 1)
+ {
+ msg_print("Invalid 'symbol' property: Too long");
+ return nullptr;
+ }
+
+ return std::make_shared<SymbolCondition>(s[0]);
+}
+
+void SymbolCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ p->write(ecol, "Its ");
+ p->write(bcol, "symbol");
+ p->write(ecol, " is ");
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, format("%c", m_symbol));
+ p->write(ecol, "\"");
+ p->write(TERM_WHITE, "\n");
+}
+
+void SymbolCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "symbol",
+ json_string(format("%c", m_symbol)));
+}
+
+bool AbilityCondition::is_match(object_type *) const
+{
+ return has_ability(m_ability_idx);
+}
+
+std::shared_ptr<Condition> AbilityCondition::from_json(json_t *j)
+{
+ cptr a;
+ if (json_unpack(j, "{s:s}", "ability", &a) < 0)
+ {
+ msg_print("Missing/invalid 'ability' property");
+ return nullptr;
+ }
+
+ uint16_t ai = find_ability(a);
+ if (ai < 0)
+ {
+ msg_print("Invalid 'ability' property");
+ return nullptr;
+ }
+
+ return std::make_shared<AbilityCondition>(ai);
+}
+
+void AbilityCondition::write_tree(TreePrinter *p, Cursor *, uint8_t ecol, uint8_t bcol) const
+{
+ cptr ability_s = ab_info[m_ability_idx].name + ab_name;
+
+ p->write(ecol, "You have the ");
+ p->write(bcol, ability_s);
+ p->write(ecol, " ability");
+ p->write(TERM_WHITE, "\n");
+}
+
+void AbilityCondition::to_json(json_t *j) const
+{
+ cptr ability_s = ab_info[m_ability_idx].name + ab_name;
+ json_object_set_new(j, "ability", json_string(ability_s));
+}
+
+void SingleSubconditionCondition::add_child(std::function< std::shared_ptr<Condition> () > const &factory)
+{
+ // If we already have a subcondition then we cannot
+ // add one.
+ if (!m_subcondition)
+ {
+ m_subcondition = factory();
+ }
+}
+
+void SingleSubconditionCondition::remove_child(Condition *c)
+{
+ if (m_subcondition.get() == c) {
+ m_subcondition.reset();
+ }
+}
+
+std::shared_ptr<Condition> SingleSubconditionCondition::first_child()
+{
+ return m_subcondition;
+}
+
+void SingleSubconditionCondition::to_json(json_t *j) const
+{
+ json_object_set_new(j, "condition",
+ optional_to_json(m_subcondition));
+}
+
+std::shared_ptr<Condition> SingleSubconditionCondition::parse_single_subcondition(json_t *in_json)
+{
+ json_t *condition_j =
+ json_object_get(in_json, "condition");
+
+ if ((condition_j == nullptr) ||
+ (json_is_null(condition_j)))
+ {
+ return nullptr;
+ }
+ else if (!json_is_object(condition_j))
+ {
+ msg_format("Invalid 'condition' property");
+ return nullptr;
+ }
+ else
+ {
+ return parse_condition(condition_j);
+ }
+}
+
+bool NotCondition::is_match(object_type *o_ptr) const
+{
+ if (!m_subcondition)
+ {
+ return true;
+ }
+
+ return !m_subcondition->is_match(o_ptr);
+}
+
+std::shared_ptr<Condition> NotCondition::from_json(json_t *j)
+{
+ return std::make_shared<NotCondition>(parse_single_subcondition(j));
+}
+
+void NotCondition::write_tree(TreePrinter *p, Cursor *c, byte ecol, byte bcol) const
+{
+ p->write(ecol, "Negate the following:");
+ p->write(TERM_WHITE, "\n");
+ if (m_subcondition)
+ {
+ m_subcondition->display(p, c);
+ }
+}
+
+bool InventoryCondition::is_match(object_type *) const
+{
+ if (!m_subcondition)
+ {
+ return false;
+ }
+
+ for (int i = 0; i < INVEN_WIELD; i++)
+ {
+ if (m_subcondition->is_match(&p_ptr->inventory[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::shared_ptr<Condition> InventoryCondition::from_json(json_t *j)
+{
+ return std::make_shared<InventoryCondition>(
+ parse_single_subcondition(j));
+}
+
+void InventoryCondition::write_tree(TreePrinter *p, Cursor *c, byte ecol, byte bcol) const
+{
+ p->write(ecol, "Something in your ");
+ p->write(bcol, "inventory");
+ p->write(ecol, " matches the following:");
+ if (m_subcondition)
+ {
+ m_subcondition->display(p, c);
+ }
+ p->write(TERM_WHITE, "\n");
+}
+
+bool EquipmentCondition::is_match(object_type *) const
+{
+ if (!m_subcondition)
+ {
+ return false;
+ }
+
+ for (int i = INVEN_WIELD; i < INVEN_TOTAL; i++)
+ {
+ if (m_subcondition->is_match(&p_ptr->inventory[i]))
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::shared_ptr<Condition> EquipmentCondition::from_json(json_t *j)
+{
+ return std::make_shared<EquipmentCondition>(
+ parse_single_subcondition(j));
+}
+
+void EquipmentCondition::write_tree(TreePrinter *p, Cursor *c, byte ecol, byte bcol) const
+{
+ p->write(ecol, "Something in your ");
+ p->write(bcol, "equipment");
+ p->write(ecol, " matches the following:");
+ if (m_subcondition)
+ {
+ m_subcondition->display(p, c);
+ }
+ p->write(TERM_WHITE, "\n");
+}
+
+} // namespace
diff --git a/src/squelch/condition_metadata.cc b/src/squelch/condition_metadata.cc
new file mode 100644
index 00000000..766d44aa
--- /dev/null
+++ b/src/squelch/condition_metadata.cc
@@ -0,0 +1,492 @@
+#include "tome/squelch/condition_metadata.hpp"
+#include "tome/squelch/condition.hpp"
+
+#include <vector>
+
+#include "tome/squelch/object_status.hpp"
+#include "angband.h"
+
+namespace squelch {
+
+static std::shared_ptr<Condition> create_condition_name()
+{
+ cptr s = lua_input_box("Object name to match?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<NameCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_contain()
+{
+ cptr s = lua_input_box("Word to find in object name?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<ContainCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_inscribed()
+{
+ cptr s = lua_input_box("Word to find in object inscription?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<InscriptionCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_discount()
+{
+ int min, max;
+
+ {
+ cptr s = lua_input_box("Min discount?", 79);
+ if (sscanf(s, "%d", &min) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ {
+ cptr s = lua_input_box("Max discount?", 79);
+ if (sscanf(s, "%d", &max) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ return std::make_shared<DiscountCondition>(min, max);
+}
+
+static std::shared_ptr<Condition> create_condition_symbol()
+{
+ char c;
+ cptr s = lua_input_box("Symbol to match?", 1);
+ if (sscanf(s, "%c", &c) < 1)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<SymbolCondition>(c);
+}
+
+static std::shared_ptr<Condition> 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 = status_type::TERRIBLE; break;
+ case 'v': status = status_type::VERY_BAD; break;
+ case 'b': status = status_type::BAD; break;
+ case 'a': status = status_type::AVERAGE; break;
+ case 'G': status = status_type::GOOD; break;
+ case 'V': status = status_type::VERY_GOOD; break;
+ case 'S': status = status_type::SPECIAL; break;
+ default: return nullptr;
+ }
+
+ return std::make_shared<StatusCondition>(status);
+}
+
+static std::shared_ptr<Condition> create_condition_state()
+{
+ char c = lua_msg_box("[i]dentified, [n]on identified?");
+
+ identification_state s;
+ switch (c)
+ {
+ case 'i': s = identification_state::IDENTIFIED; break;
+ case 'n': s = identification_state::NOT_IDENTIFIED; break;
+ default: return nullptr;
+ }
+
+ return std::make_shared<StateCondition>(s);
+}
+
+static bool in_byte_range(int x)
+{
+ return (x >= 0) && (x < 256);
+}
+
+static std::shared_ptr<Condition> create_condition_tval()
+{
+ cptr s = lua_input_box("Tval to match?", 79);
+ int tval;
+ if (sscanf(s, "%d", &tval) < 1)
+ {
+ return nullptr;
+ }
+
+ if (!in_byte_range(tval))
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<TvalCondition>(tval);
+}
+
+static std::shared_ptr<Condition> create_condition_sval()
+{
+ int sval_min, sval_max;
+
+ {
+ cptr s = lua_input_box("Min sval?", 79);
+ if ((sscanf(s, "%d", &sval_min) < 1) ||
+ (!in_byte_range(sval_min)))
+ {
+ return nullptr;
+ }
+ }
+
+ {
+ cptr s = lua_input_box("Max sval?", 79);
+ if ((sscanf(s, "%d", &sval_max) < 1) ||
+ (!in_byte_range(sval_max)))
+ {
+ return nullptr;
+ }
+ }
+
+ return std::make_shared<SvalCondition>(sval_min, sval_max);
+}
+
+static std::shared_ptr<Condition> create_condition_race()
+{
+ cptr s = lua_input_box("Player race to match?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<RaceCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_subrace()
+{
+ cptr s = lua_input_box("Player subrace to match?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<SubraceCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_class()
+{
+ cptr s = lua_input_box("Player class to match?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<ClassCondition>(s);
+}
+
+static std::shared_ptr<Condition> create_condition_level()
+{
+ int min, max;
+
+ {
+ cptr s = lua_input_box("Min player level?", 79);
+ if (sscanf(s, "%d", &min) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ {
+ cptr s = lua_input_box("Max player level?", 79);
+ if (sscanf(s, "%d", &max) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ return std::make_shared<LevelCondition>(min, max);
+}
+
+static std::shared_ptr<Condition> create_condition_skill()
+{
+ int min, max;
+
+ {
+ cptr s = lua_input_box("Min skill level?", 79);
+ if (sscanf(s, "%d", &min) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ {
+ cptr s = lua_input_box("Max skill level?", 79);
+ if (sscanf(s, "%d", &max) < 1)
+ {
+ return nullptr;
+ }
+ }
+
+ s16b skill_idx;
+ {
+ cptr s = lua_input_box("Skill name?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ skill_idx = find_skill_i(s);
+ if (skill_idx < 0)
+ {
+ return nullptr;
+ }
+ }
+
+ return std::make_shared<SkillCondition>(skill_idx, min, max);
+}
+
+static std::shared_ptr<Condition> create_condition_ability()
+{
+ cptr s = lua_input_box("Ability name?", 79);
+ if (strlen(s) == 0)
+ {
+ return nullptr;
+ }
+
+ s16b ai = find_ability(s);
+ if (ai < 0)
+ {
+ return nullptr;
+ }
+
+ return std::make_shared<AbilityCondition>(ai);
+}
+
+static void display_desc(match_type match_type_)
+{
+ int i = 0;
+ auto line = [&i] (const char *s) {
+ c_prt(TERM_WHITE, s, i + 1, 17);
+ i++;
+ };
+
+ switch (match_type_)
+ {
+ case match_type::AND:
+ line("Check is true if all rules within it are true");
+ break;
+
+ case match_type::OR:
+ line("Check is true if at least one rule within it is true");
+ break;
+
+ case match_type::NOT:
+ line("Invert the result of its child rule");
+ break;
+
+ case match_type::NAME:
+ line("Check is true if object name matches name");
+ break;
+
+ case match_type::CONTAIN:
+ line("Check is true if object name contains word");
+ break;
+
+ case match_type::INSCRIBED:
+ line("Check is true if object inscription contains word");
+ break;
+
+ case match_type::DISCOUNT:
+ line("Check is true if object discount is between two values");
+ break;
+
+ case match_type::SYMBOL:
+ line("Check is true if object symbol is ok");
+ break;
+
+ case match_type::STATE:
+ line("Check is true if object is identified/unidentified");
+ break;
+
+ case match_type::STATUS:
+ line("Check is true if object status is ok");
+ break;
+
+ case match_type::TVAL:
+ line("Check is true if object tval(from k_info.txt) is ok");
+
+ case match_type::SVAL:
+ line("Check is true if object sval(from k_info.txt) is between");
+ line("two values");
+ break;
+
+ case match_type::RACE:
+ line("Check is true if player race is ok");
+ break;
+
+ case match_type::SUBRACE:
+ line("Check is true if player subrace is ok");
+ break;
+
+ case match_type::CLASS:
+ line("Check is true if player class is ok");
+ break;
+
+ case match_type::LEVEL:
+ line("Check is true if player level is between 2 values");
+ break;
+
+ case match_type::SKILL:
+ line("Check is true if player skill level is between 2 values");
+ break;
+
+ case match_type::ABILITY:
+ line("Check is true if player has the ability");
+ break;
+
+ case match_type::INVENTORY:
+ line("Check is true if something in player's inventory matches");
+ line("the contained rule");
+ break;
+
+ case match_type::EQUIPMENT:
+ line("Check is true if something in player's equipment matches");
+ line("the contained rule");
+ break;
+ }
+}
+
+std::shared_ptr<Condition> new_condition_interactive()
+{
+ static std::vector<match_type> condition_types = {
+ match_type::AND,
+ match_type::OR,
+ match_type::NOT,
+ match_type::NAME,
+ match_type::CONTAIN,
+ match_type::INSCRIBED,
+ match_type::DISCOUNT,
+ match_type::SYMBOL,
+ match_type::STATE,
+ match_type::STATUS,
+ match_type::TVAL,
+ match_type::SVAL,
+ match_type::RACE,
+ match_type::SUBRACE,
+ match_type::CLASS,
+ match_type::LEVEL,
+ match_type::SKILL,
+ match_type::ABILITY,
+ match_type::INVENTORY,
+ match_type::EQUIPMENT
+ };
+ static std::vector<const char *> condition_type_names;
+
+ // Fill in types names?
+ if (condition_type_names.empty())
+ {
+ for (auto condition_type : condition_types)
+ {
+ condition_type_names.push_back(
+ match_mapping().stringify(condition_type));
+ }
+ }
+
+ // Choose
+ int begin = 0, sel = 0;
+ while (1)
+ {
+ int wid, hgt;
+ Term_clear();
+ Term_get_size(&wid, &hgt);
+
+ display_list(0, 0, hgt - 1, 15, "Rule types", condition_type_names.data(), condition_types.size(), begin, sel, TERM_L_GREEN);
+
+ display_desc(condition_types[sel]);
+
+ char c = inkey();
+
+ if (c == ESCAPE) break;
+ else if (c == '8')
+ {
+ sel--;
+ if (sel < 0)
+ {
+ sel = condition_types.size() - 1;
+ begin = condition_types.size() - hgt;
+ if (begin < 0) begin = 0;
+ }
+ if (sel < begin) begin = sel;
+ }
+ else if (c == '2')
+ {
+ sel++;
+ if (sel >= condition_types.size())
+ {
+ sel = 0;
+ begin = 0;
+ }
+ if (sel >= begin + hgt - 1) begin++;
+ }
+ else if (c == '\r')
+ {
+ switch (condition_types[sel])
+ {
+ case match_type::AND:
+ return std::make_shared<AndCondition>();
+ case match_type::OR:
+ return std::make_shared<OrCondition>();
+ case match_type::NOT:
+ return std::make_shared<NotCondition>();
+ case match_type::NAME:
+ return create_condition_name();
+ case match_type::CONTAIN:
+ return create_condition_contain();
+ case match_type::INSCRIBED:
+ return create_condition_inscribed();
+ case match_type::DISCOUNT:
+ return create_condition_discount();
+ case match_type::SYMBOL:
+ return create_condition_symbol();
+ case match_type::STATE:
+ return create_condition_state();
+ case match_type::STATUS:
+ return create_condition_status();
+ case match_type::TVAL:
+ return create_condition_tval();
+ case match_type::SVAL:
+ return create_condition_sval();
+ case match_type::RACE:
+ return create_condition_race();
+ case match_type::SUBRACE:
+ return create_condition_subrace();
+ case match_type::CLASS:
+ return create_condition_class();
+ case match_type::LEVEL:
+ return create_condition_level();
+ case match_type::SKILL:
+ return create_condition_skill();
+ case match_type::ABILITY:
+ return create_condition_ability();
+ case match_type::INVENTORY:
+ return std::make_shared<InventoryCondition>();
+ case match_type::EQUIPMENT:
+ return std::make_shared<EquipmentCondition>();
+
+ }
+ }
+ }
+ return nullptr;
+}
+
+} // namespace
diff --git a/src/squelch/cursor.cc b/src/squelch/cursor.cc
new file mode 100644
index 00000000..db2f416d
--- /dev/null
+++ b/src/squelch/cursor.cc
@@ -0,0 +1,93 @@
+#include "tome/squelch/cursor_fwd.hpp"
+#include "tome/squelch/cursor.hpp"
+
+#include <algorithm>
+#include <cassert>
+
+#include "tome/squelch/condition.hpp"
+
+namespace squelch {
+
+bool Cursor::is_selected(Condition const *condition) const
+{
+ return std::end(m_conditions) !=
+ std::find(std::begin(m_conditions),
+ std::end(m_conditions),
+ condition);
+}
+
+Condition *Cursor::pop()
+{
+ assert(!m_conditions.empty());
+ Condition *c = m_conditions.back();
+ m_conditions.pop_back();
+ return c;
+}
+
+Condition *Cursor::current()
+{
+ assert(!m_conditions.empty());
+ return m_conditions.back();
+}
+
+void Cursor::move_right()
+{
+ // Go right if the currently selected condition has children.
+ std::shared_ptr<Condition> c = current()->first_child();
+ if (c)
+ {
+ push(c.get());
+ }
+}
+
+void Cursor::move_left()
+{
+ if (size() > 1)
+ {
+ pop();
+ }
+}
+
+void Cursor::move_up()
+{
+ if (size() > 1)
+ {
+ Condition *prev_top = pop();
+
+ // Find previous child
+ std::shared_ptr<Condition> prev_condition =
+ current()->previous_child(prev_top);
+
+ // Do we have a previous child?
+ if (prev_condition)
+ {
+ push(prev_condition.get());
+ }
+ else
+ {
+ push(prev_top);
+ }
+ }
+}
+
+void Cursor::move_down()
+{
+ if (size() > 1)
+ {
+ Condition *prev_top = pop();
+
+ std::shared_ptr<Condition> next_condition =
+ current()->next_child(prev_top);
+
+ if (next_condition)
+ {
+ push(next_condition.get());
+ }
+ else
+ {
+ push(prev_top);
+ }
+ }
+}
+
+} // namespace
diff --git a/src/squelch/object_status.cc b/src/squelch/object_status.cc
new file mode 100644
index 00000000..53d2055a
--- /dev/null
+++ b/src/squelch/object_status.cc
@@ -0,0 +1,149 @@
+#include "tome/squelch/object_status_fwd.hpp"
+#include "tome/squelch/object_status.hpp"
+
+#include "angband.h"
+
+namespace squelch {
+
+EnumStringMap<status_type> &status_mapping()
+{
+ // TODO: This is quite ugly and leads to valgrind complaints
+ static auto m = new EnumStringMap<status_type> {
+ { status_type::BAD, "bad" },
+ { status_type::VERY_BAD, "very bad" },
+ { status_type::AVERAGE, "average" },
+ { status_type::GOOD, "good" },
+ { status_type::VERY_GOOD, "very good" },
+ { status_type::SPECIAL, "special" },
+ { status_type::TERRIBLE, "terrible" },
+ { status_type::NONE, "none" },
+ { status_type::CHEST_EMPTY, "(empty chest)" },
+ { status_type::CHEST_DISARMED, "(disarmed chest)" } };
+ return *m;
+}
+
+status_type object_status(object_type *o_ptr)
+{
+ if (!object_known_p(o_ptr))
+ {
+ switch (o_ptr->sense)
+ {
+ case SENSE_CURSED: return status_type::BAD;
+ case SENSE_WORTHLESS: return status_type::VERY_BAD;
+ case SENSE_AVERAGE: return status_type::AVERAGE;
+ case SENSE_GOOD_LIGHT: return status_type::GOOD;
+ case SENSE_GOOD_HEAVY: return status_type::GOOD;
+ case SENSE_EXCELLENT: return status_type::VERY_GOOD;
+ case SENSE_SPECIAL: return status_type::SPECIAL;
+ case SENSE_TERRIBLE: return status_type::TERRIBLE;
+ default: return status_type::NONE;
+ }
+ }
+ else
+ {
+ s16b slot = wield_slot_ideal(o_ptr, TRUE);
+
+ if (artifact_p(o_ptr))
+ {
+ if (!(o_ptr->ident & IDENT_CURSED))
+ {
+ return status_type::SPECIAL;
+ }
+ else
+ {
+ return status_type::TERRIBLE;
+ }
+ }
+ else if ((o_ptr->name2 > 0) ||
+ (o_ptr->name2b > 0))
+ {
+ if (!(o_ptr->ident & IDENT_CURSED))
+ {
+ return status_type::VERY_GOOD;
+ }
+ else
+ {
+ return status_type::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 status_type::BAD;
+ }
+ else if (o_ptr->to_h + o_ptr->to_d > 0)
+ {
+ return status_type::GOOD;
+ }
+ else
+ {
+ return status_type::AVERAGE;
+ }
+ }
+ else if ((slot >= INVEN_BODY) &&
+ (slot <= INVEN_FEET))
+ {
+ if (o_ptr->to_a < 0)
+ {
+ return status_type::BAD;
+ }
+ else if (o_ptr->to_a > 0)
+ {
+ return status_type::GOOD;
+ }
+ else
+ {
+ return status_type::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 status_type::BAD;
+ }
+ else
+ {
+ return status_type::AVERAGE;
+ }
+ }
+ else if (slot == INVEN_NECK)
+ {
+ if (o_ptr->pval < 0)
+ {
+ return status_type::BAD;
+ }
+ else
+ {
+ return status_type::AVERAGE;
+ }
+ }
+ else if (o_ptr->tval == TV_CHEST)
+ {
+ if (o_ptr->pval == 0)
+ {
+ return status_type::CHEST_EMPTY;
+ }
+ else if (o_ptr->pval < 0)
+ {
+ return status_type::CHEST_DISARMED;
+ }
+ else
+ {
+ return status_type::AVERAGE;
+ }
+ }
+ else
+ {
+ return status_type::AVERAGE;
+ }
+ }
+}
+
+} // namespace
diff --git a/src/squelch/rule.cc b/src/squelch/rule.cc
new file mode 100644
index 00000000..def034fd
--- /dev/null
+++ b/src/squelch/rule.cc
@@ -0,0 +1,325 @@
+#include "tome/squelch/rule_fwd.hpp"
+#include "tome/squelch/rule.hpp"
+
+#include "tome/squelch/cursor.hpp"
+#include "tome/squelch/condition.hpp"
+#include "tome/squelch/tree_printer.hpp"
+#include "angband.h"
+#include "quark.h"
+
+namespace squelch {
+
+EnumStringMap<action_type> &action_mapping()
+{
+ static auto m = new EnumStringMap<action_type> {
+ { action_type::AUTO_DESTROY, "destroy" },
+ { action_type::AUTO_PICKUP, "pickup" },
+ { action_type::AUTO_INSCRIBE, "inscribe" } };
+ return *m;
+}
+
+void Rule::set_name(const char *new_name)
+{
+ assert(new_name != nullptr);
+ m_name = new_name;
+}
+
+const char *Rule::get_name() const
+{
+ return m_name.c_str();
+}
+
+std::shared_ptr<Condition> Rule::get_condition() const
+{
+ return m_condition;
+}
+
+json_t *Rule::to_json() const
+{
+ json_t *rule_json = json_object();
+ json_object_set_new(rule_json,
+ "name",
+ json_string(m_name.c_str()));
+ json_object_set_new(rule_json,
+ "action",
+ json_string(action_mapping().stringify(m_action)));
+ json_object_set_new(rule_json,
+ "module",
+ json_string(modules[m_module_idx].meta.name));
+ json_object_set_new(rule_json,
+ "condition",
+ Condition::optional_to_json(m_condition));
+ return rule_json;
+}
+
+void Rule::add_new_condition(Cursor *cursor,
+ ConditionFactory const &factory)
+{
+ // Top-level condition?
+ if (!m_condition)
+ {
+ // Sanity check for navigation stack
+ assert(cursor->empty());
+
+ // Create new top-level condition
+ m_condition = factory();
+
+ // Select the condition
+ if (m_condition)
+ {
+ cursor->push(m_condition.get());
+ }
+ }
+ else
+ {
+ cursor->current()->add_child(factory);
+ }
+}
+
+void Rule::delete_selected_condition(Cursor *cursor)
+{
+ assert(cursor->size() >= 1);
+
+ if (cursor->size() == 1)
+ {
+ cursor->pop();
+ m_condition.reset();
+ }
+ else
+ {
+ Condition *prev_top = cursor->pop();
+ Condition *top = cursor->current();
+
+ // 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
+ // gets a little complicated.
+ cursor->move_left();
+
+ // Now we can remove the condition from its parent
+ top->remove_child(prev_top);
+ }
+}
+
+void Rule::write_tree(TreePrinter *tree_printer, Cursor *cursor) const
+{
+ // Write out the main rule
+ do_write_tree(tree_printer);
+
+ // Write out the condition
+ if (m_condition)
+ {
+ m_condition->display(tree_printer, cursor);
+ }
+}
+
+bool Rule::apply_rule(object_type *o_ptr, int item_idx) const
+{
+ // Check module
+ if (m_module_idx != game_module_idx)
+ {
+ return false;
+ }
+
+ // Check condition
+ if (m_condition && m_condition->is_match(o_ptr))
+ {
+ return do_apply_rule(o_ptr, item_idx);
+ }
+
+ // Doesn't apply
+ return false;
+}
+
+std::shared_ptr<Rule> Rule::parse_rule(json_t *rule_json)
+{
+ if (!json_is_object(rule_json))
+ {
+ msg_print("Rule is not an object");
+ return nullptr;
+ }
+
+ // Retrieve the attributes
+ char *rule_name_s = nullptr;
+ char *rule_action_s = nullptr;
+ char *rule_module_s = nullptr;
+ 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 nullptr;
+ }
+
+ // Convert attributes
+ action_type action;
+ if (!action_mapping().parse((cptr) rule_action_s, &action))
+ {
+ msg_format("Invalid rule action '%s'", rule_action_s);
+ return nullptr;
+ }
+
+ int 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 nullptr;
+ }
+
+ // Parse condition
+ std::shared_ptr<Condition> condition =
+ Condition::parse_condition(json_object_get(rule_json, "condition"));
+
+ // Parse rule
+ switch (action)
+ {
+ case action_type::AUTO_INSCRIBE:
+ {
+ json_t *rule_inscription_j = json_object_get(rule_json, "inscription");
+
+ if (rule_inscription_j == nullptr)
+ {
+ msg_print("Inscription rule missing 'inscription' attribute");
+ return nullptr;
+ }
+ if (!json_is_string(rule_inscription_j))
+ {
+ msg_print("Inscription rule 'inscription' attribute wrong type");
+ return nullptr;
+ }
+
+ std::string inscription =
+ json_string_value(rule_inscription_j);
+ return std::make_shared<InscribeRule>(
+ rule_name_s, module_idx, condition, inscription);
+ }
+
+ case action_type::AUTO_PICKUP:
+ return std::make_shared<PickUpRule>(
+ rule_name_s, module_idx, condition);
+
+ case action_type::AUTO_DESTROY:
+ return std::make_shared<DestroyRule>(
+ rule_name_s, module_idx, condition);
+ }
+
+ assert(false);
+ return nullptr;
+}
+
+
+void DestroyRule::do_write_tree(TreePrinter *p) const
+{
+ p->write(TERM_GREEN, "A rule named \"");
+ p->write(TERM_WHITE, m_name.c_str());
+ p->write(TERM_GREEN, "\" to ");
+ p->write(TERM_L_GREEN, "destroy");
+ p->write(TERM_GREEN, " when");
+ p->write(TERM_WHITE, "\n");
+}
+
+bool DestroyRule::do_apply_rule(object_type *o_ptr, int item_idx) const
+{
+ // Must be identified
+ if (object_aware_p(o_ptr) == FALSE)
+ {
+ return false;
+ }
+
+ // Never destroy inscribed items
+ if (o_ptr->note)
+ {
+ return false;
+ }
+
+ // Ignore artifacts; cannot be destroyed anyway
+ if (artifact_p(o_ptr))
+ {
+ return false;
+ }
+
+ // 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 false;
+ }
+ }
+
+ // Destroy
+ msg_print("<Auto-destroy>");
+ inc_stack_size(item_idx, -o_ptr->number);
+ return true;
+}
+
+void PickUpRule::do_write_tree(TreePrinter *p) const
+{
+ p->write(TERM_GREEN, "A rule named \"");
+ p->write(TERM_WHITE, m_name.c_str());
+ p->write(TERM_GREEN, "\" to ");
+ p->write(TERM_L_GREEN, "pick up");
+ p->write(TERM_GREEN, " when");
+ p->write(TERM_WHITE, "\n");
+}
+
+bool PickUpRule::do_apply_rule(object_type *o_ptr, int item_idx) const
+{
+ if (item_idx >= 0)
+ {
+ return false;
+ }
+
+ if (!inven_carry_okay(o_ptr))
+ {
+ return false;
+ }
+
+ msg_print("<Auto-pickup>");
+ object_pickup(-item_idx);
+ return true;
+}
+
+json_t *InscribeRule::to_json() const
+{
+ json_t *j = Rule::to_json();
+
+ json_object_set_new(j,
+ "inscription",
+ json_string(m_inscription.c_str()));
+
+ return j;
+}
+
+void InscribeRule::do_write_tree(TreePrinter *p) const
+{
+ p->write(TERM_GREEN, "A rule named \"");
+ p->write(TERM_WHITE, m_name.c_str());
+ p->write(TERM_GREEN, "\" to ");
+ p->write(TERM_L_GREEN, "inscribe");
+ p->write(TERM_GREEN, " an item with \"");
+ p->write(TERM_WHITE, m_inscription.c_str());
+ p->write(TERM_GREEN, "\" when");
+ p->write(TERM_WHITE, "\n");
+}
+
+bool InscribeRule::do_apply_rule(object_type *o_ptr, int) const
+{
+ // Already inscribed?
+ if (o_ptr->note != 0)
+ {
+ return false;
+ }
+
+ // Inscribe
+ msg_format("<Auto-Inscribe {%s}>", m_inscription.c_str());
+ o_ptr->note = quark_add(m_inscription.c_str());
+ return true;
+}
+
+} // namespace
diff --git a/src/squelch/tree_printer.cc b/src/squelch/tree_printer.cc
new file mode 100644
index 00000000..812933a9
--- /dev/null
+++ b/src/squelch/tree_printer.cc
@@ -0,0 +1,89 @@
+#include "tome/squelch/tree_printer_fwd.hpp"
+#include "tome/squelch/tree_printer.hpp"
+
+#include "angband.h"
+
+namespace squelch {
+
+TreePrinter::TreePrinter() : m_indent(0)
+{
+ int wid, hgt;
+ // Output window
+ Term_get_size(&wid, &hgt);
+ m_write_out_y = 1;
+ m_write_out_x = 16;
+ m_write_out_h = hgt - 4 - 1;
+ m_write_out_w = wid - 1 - 15 - 1;
+ // Set position
+ reset();
+ reset_scroll();
+}
+
+void TreePrinter::indent() {
+ m_indent++;
+}
+
+void TreePrinter::dedent() {
+ m_indent--;
+}
+
+void TreePrinter::reset() {
+ m_write_x = 0;
+ m_write_y = 0;
+}
+
+void TreePrinter::reset_scroll() {
+ m_write_off_y = 0;
+ m_write_off_x = 0;
+}
+
+void TreePrinter::scroll_up() {
+ m_write_off_y--;
+}
+
+void TreePrinter::scroll_down() {
+ m_write_off_y++;
+}
+
+void TreePrinter::scroll_left() {
+ m_write_off_x++;
+}
+
+void TreePrinter::scroll_right() {
+ m_write_off_x--;
+}
+
+void TreePrinter::write(uint8_t color, cptr line)
+{
+ cptr p = line;
+
+ for (p = line; *p != '\0'; p++)
+ {
+ char c = *p;
+ int x = m_write_x - m_write_off_x + 3*m_indent;
+ int y = m_write_y - m_write_off_y;
+
+ if (c != '\n')
+ {
+ if ((y >= 0) &&
+ (y < m_write_out_h) &&
+ (x >= 0) &&
+ (x < m_write_out_w))
+ {
+ Term_putch(x + m_write_out_x,
+ y + m_write_out_y,
+ color,
+ c);
+ }
+
+ m_write_x += 1;
+ }
+ else
+ {
+ m_write_x = 0;
+ m_write_y += 1;
+ }
+ }
+}
+
+} // namespace
diff --git a/src/squeltch.c b/src/squeltch.c
deleted file mode 100644
index 0c1e2902..00000000
--- a/src/squeltch.c
+++ /dev/null
@@ -1,3572 +0,0 @@
-/* File: squeltch.c */
-
-/* Purpose: Automatizer */
-
-/*
- * Copyright (c) 2002 DarkGod
- *
- * This software may be copied and distributed for educational, research, and
- * not for profit purposes provided that this copyright and statement are
- * included in all such copies.
- */
-
-#include "angband.h"
-
-#include <jansson.h>
-
-#include "quark.h"
-
-#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(&current->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->klass);
- 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;
-}
-
-/*
- * 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("<Auto-destroy>");
-
- 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("<Auto-pickup>");
-
- 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("<Auto-Inscribe {%s}>", 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;
- }
-
- case M_NOT:
- {
- tree_write(ecol, "Negate the following:");
- tree_write(TERM_WHITE, "\n");
- display_condition(condition->subcondition);
- break;
- }
-
- 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;
- }
-
- 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;
- }
-
- case M_INSCRIBED:
- {
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- }
-
- tree_indent--;
-}
-
-static void display_rule(arule_type *rule)
-{
- cptr action_s;
- int hgt, wid;
-
- 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)
-{
- if (rules_count == 0)
- {
- cursor_clear();
- return;
- }
-
- tree_write_off_y = 0;
- tree_write_off_x = 0;
-
- /* Put the top-level condition into cursor */
- cursor_clear();
- if (rules[sel]->condition != NULL)
- {
- cursor_push(rules[sel]->condition);
- }
-}
-
-static void tree_scroll_up()
-{
- tree_write_off_y = tree_write_off_y - 1;
-}
-
-static void tree_scroll_down()
-{
- tree_write_off_y = tree_write_off_y + 1;
-}
-
-static void tree_scroll_left()
-{
- tree_write_off_x = tree_write_off_x + 1;
-}
-
-static void tree_scroll_right()
-{
- tree_write_off_x = tree_write_off_x - 1;
-}
-
-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;
- }
- }
-
- /* Write to file */
- {
- 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();
- }
-}
-
-static void rename_rule(arule_type *rule)
-{
- char name[16];
- int wid, hgt;
-
- assert(rule != NULL);
-
- 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)
-{
- /* Top-level condition? */
- if (current_rule->condition == NULL)
- {
- condition_metadata *metadata = NULL;
-
- /* Sanity check for navigation stack */
- assert(cursor_count == 0);
-
- /* 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
- {
- 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;
- }
-
- case M_NOT:
- case M_INVENTORY:
- case M_EQUIPMENT:
- {
- if (top->subcondition != NULL)
- {
- cursor_push(top->subcondition);
- }
- break;
- }
-
- default:
- /* Not possible to move */
- break;
- }
-}
-
-static void tree_go_left()
-{
- if (cursor_count > 1)
- {
- cursor_pop();
- }
-}
-
-static void tree_go_up()
-{
- 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;
- 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))
- {
- 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;
- }
-
- break;
- }
-
- default:
- {
- /* 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))
- {
- 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 */
- }
- }
- }
-
- break;
- }
-
- default:
- {
- /* No other match types have multiple children; restore
- original top. */
- cursor_push(prev_top);
- break;
- }
-
- }
- }
-}
-
-static int automatizer_del_self(int sel)
-{
- /* If the cursor is at the top level then
- we want to delete the rule itself */
- if (cursor_count < 1)
- {
- rules_remove(rules[sel]);
- return sel - 1; /* Move selection up */
- }
- else if (cursor_count == 1)
- {
- 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;
- }
-}
-
-#define ACTIVE_LIST 0
-#define ACTIVE_RULE 1
-void do_cmd_automatizer()
-{
- int wid = 0, hgt = 0;
- char c;
- 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);
-
- if (!automatizer_enabled)
- {
- if (msg_box("Automatizer is currently disabled, enable it? (y/n)", hgt / 2, wid / 2) == 'y')
- {
- automatizer_enabled = TRUE;
- }
- else
- return;
- }
-
- screen_save();
-
- adjust_current(sel);
-
- while (1)
- {
- Term_clear();
- Term_get_size(&wid, &hgt);
-
- 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 = "#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";
- }
- 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)
- {
- display_rule(rules[sel]);
- }
-
- c = inkey();
-
- if (c == ESCAPE) break;
- if (active == ACTIVE_LIST)
- {
- if (c == '?')
- {
- screen_save();
- show_file("automat.txt", "Automatizer help", 0, 0);
- screen_load();
- }
- else if (c == '8')
- {
- if (!max) continue;
- sel--;
- adjust_begin(&begin, &sel, max, hgt);
- adjust_current(sel);
- }
- else if (c == '2')
- {
- if (!max) continue;
- sel++;
- adjust_begin(&begin, &sel, max, hgt);
- adjust_current(sel);
- }
- else if (c == 'u')
- {
- 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;
-
- 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')
- {
- int i = create_new_rule();
- if (i >= 0)
- {
- sel = i;
- adjust_current(sel);
- active = ACTIVE_RULE;
- }
- }
- else if (c == 's')
- {
- automatizer_save_rules();
- }
- else if (c == 'r')
- {
- if (!max) continue;
-
- rename_rule(rules[sel]);
- continue;
- }
- else if (c == 'k')
- {
- automatizer_enabled = FALSE;
- break;
- }
- else if (c == '\t')
- {
- if (!max) continue;
- active = ACTIVE_RULE;
- }
- }
- else if (active == ACTIVE_RULE)
- {
- if (c == '?')
- {
- screen_save();
- show_file("automat.txt", "Automatizer help", 0, 0);
- screen_load();
- }
- else if (c == '8')
- {
- tree_go_up();
- }
- else if (c == '2')
- {
- tree_go_down();
- }
- else if (c == '6')
- {
- tree_go_right();
- }
- else if (c == '4')
- {
- tree_go_left();
- }
- else if (c == '9')
- {
- tree_scroll_up();
- }
- else if (c == '3')
- {
- tree_scroll_down();
- }
- else if (c == '7')
- {
- tree_scroll_left();
- }
- else if (c == '1')
- {
- tree_scroll_right();
- }
- else if (c == 'a')
- {
- add_new_condition(rules[sel]);
- }
- else if (c == 'd')
- {
- if (max)
- {
- int new_sel;
-
- new_sel = automatizer_del_self(sel);
- if ((sel != new_sel) && (new_sel >= 0))
- {
- sel = new_sel;
- adjust_begin(&begin, &sel, max, hgt);
- adjust_current(sel);
- }
- else if (new_sel == -1)
- {
- active = ACTIVE_LIST;
- }
- }
- }
- else if (c == '\t')
- {
- active = ACTIVE_LIST;
- }
- }
- }
-
- 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;
- action_type action = AUTO_DESTROY;
-
- if (!destroy)
- {
- action = AUTO_PICKUP;
- }
-
- while (TRUE)
- {
- if (!get_com(format("%s all of the same [T]ype, [F]amily or [N]ame, also use [S]tatus (%s)? ", (destroy) ? "Destroy" : "Pickup", (do_status) ? "Yes" : "No"), &ch))
- {
- break;
- }
-
- if (ch == 'S' || ch == 's')
- {
- do_status = !do_status;
- continue;
- }
-
- if (ch == 'T' || ch == 't')
- {
- easy_add_rule(action, "tsval", do_status, o_ptr);
- break;
- }
-
- if (ch == 'F' || ch == 'f')
- {
- easy_add_rule(action, "tval", do_status, o_ptr);
- break;
- }
-
- if (ch == 'N' || ch == 'n')
- {
- 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);
- }
-}
diff --git a/src/squeltch.cc b/src/squeltch.cc
new file mode 100644
index 00000000..560b3d01
--- /dev/null
+++ b/src/squeltch.cc
@@ -0,0 +1,598 @@
+/* File: squeltch.c */
+
+/* Purpose: Automatizer */
+
+/*
+ * Copyright (c) 2002 DarkGod
+ * Copyright (c) 2012 Bardur Arantsson
+ *
+ * This software may be copied and distributed for educational, research, and
+ * not for profit purposes provided that this copyright and statement are
+ * included in all such copies.
+ */
+
+#include "angband.h"
+
+#include <jansson.h>
+#include <algorithm>
+#include <memory>
+#include <deque>
+#include <list>
+#include <string>
+#include <vector>
+
+#include "tome/squelch/tree_printer.hpp"
+#include "tome/squelch/condition.hpp"
+#include "tome/squelch/condition_metadata.hpp"
+#include "tome/squelch/rule.hpp"
+#include "tome/squelch/cursor.hpp"
+#include "tome/squelch/object_status.hpp"
+#include "tome/squelch/automatizer.hpp"
+
+using squelch::action_type;
+using squelch::action_mapping;
+
+using squelch::status_type;
+
+using squelch::Rule;
+using squelch::DestroyRule;
+using squelch::PickUpRule;
+using squelch::InscribeRule;
+
+using squelch::Condition;
+using squelch::AndCondition;
+using squelch::TvalCondition;
+using squelch::SvalCondition;
+using squelch::NameCondition;
+using squelch::StatusCondition;
+
+static squelch::Automatizer *automatizer = nullptr;
+
+void squeltch_grid(void)
+{
+ if (!automatizer_enabled)
+ {
+ return;
+ }
+
+ // Scan the pile of objects
+ s16b next_o_idx = 0;
+ for (s16b 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
+ automatizer->apply_rules(o_ptr, -this_o_idx);
+ }
+}
+
+void squeltch_inventory(void)
+{
+ if (!automatizer_enabled)
+ {
+ return;
+ }
+
+ for (int num_iter = 0; num_iter < 100; num_iter++)
+ {
+ for (int i = 0; i < INVEN_PACK; i++)
+ {
+ object_type *o_ptr = &p_ptr->inventory[i];
+ if (automatizer->apply_rules(o_ptr, i))
+ {
+ return;
+ }
+ }
+ }
+
+ cmsg_format(TERM_VIOLET, "'apply_rules' ran too often.");
+}
+
+static int create_new_rule()
+{
+ char name[20] = { '\0' };
+ int wid = 0, hgt = 0;
+
+ Term_get_size(&wid, &hgt);
+
+ sprintf(name, "%s", "No name");
+ if (!input_box("Name?", hgt / 2, wid / 2, name, sizeof(name)+1))
+ {
+ return -1;
+ }
+
+ char typ = lua_msg_box("[D]estroy, [P]ickup, [I]nscribe?");
+
+ std::shared_ptr<Rule> rule;
+ switch (typ)
+ {
+ case 'd':
+ case 'D':
+ rule = std::make_shared<DestroyRule>(name, game_module_idx, nullptr);
+ break;
+
+ case 'p':
+ case 'P':
+ rule = std::make_shared<PickUpRule>(name, game_module_idx, nullptr);
+ break;
+
+ case 'i':
+ case 'I':
+ {
+ cptr i = lua_input_box("Inscription?", 79);
+ if ((i == nullptr) || (strlen(i) == 0))
+ {
+ return -1;
+ }
+
+ rule = std::make_shared<InscribeRule>(
+ name, game_module_idx, nullptr, std::string(i));
+
+ break;
+ }
+
+ default:
+ return -1;
+ }
+
+ return automatizer->append_rule(rule);
+}
+
+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;
+ }
+ }
+
+ // Write to file
+ {
+ auto rules_json = automatizer->to_json();
+
+ int status = json_dump_file(rules_json.get(), 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);
+ }
+
+ // Wait for keypress
+ inkey();
+ }
+}
+
+static void rename_rule(Rule *rule)
+{
+ char name[16];
+ int wid, hgt;
+
+ assert(rule != nullptr);
+
+ Term_get_size(&wid, &hgt);
+
+ sprintf(name, "%s", rule->get_name());
+ if (input_box("New name?", hgt / 2, wid / 2, name, sizeof(name)-1))
+ {
+ rule->set_name(name);
+ }
+}
+
+#define ACTIVE_LIST 0
+#define ACTIVE_RULE 1
+void do_cmd_automatizer()
+{
+ int wid = 0, hgt = 0;
+ int active = ACTIVE_LIST;
+ cptr keys;
+ cptr keys2;
+ cptr keys3;
+ std::vector<cptr> rule_names;
+
+ Term_get_size(&wid, &hgt);
+
+ if (!automatizer_enabled)
+ {
+ if (msg_box("Automatizer is currently disabled, enable it? (y/n)", hgt / 2, wid / 2) == 'y')
+ {
+ automatizer_enabled = TRUE;
+ }
+ else
+ return;
+ }
+
+ screen_save();
+
+ automatizer->reset_view();
+
+ while (1)
+ {
+ Term_clear();
+ Term_get_size(&wid, &hgt);
+
+ automatizer->get_rule_names(&rule_names);
+
+ display_list(0, 0, hgt - 1, 15, "Rules", rule_names.data(), automatizer->rules_count(), automatizer->rules_begin(), automatizer->selected_rule(), (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 = "#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";
+ }
+ 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);
+
+ automatizer->show_current();
+
+ char c = inkey();
+
+ if (c == ESCAPE) break;
+ if (active == ACTIVE_LIST)
+ {
+ if (c == '?')
+ {
+ screen_save();
+ show_file("automat.txt", "Automatizer help", 0, 0);
+ screen_load();
+ }
+ else if (c == '8')
+ {
+ if (!automatizer->rules_count()) continue;
+
+ automatizer->select_rule(
+ automatizer->selected_rule() - 1);
+ }
+ else if (c == '2')
+ {
+ if (!automatizer->rules_count()) continue;
+
+ automatizer->select_rule(
+ automatizer->selected_rule() + 1);
+ }
+ else if (c == 'u')
+ {
+ int sel = automatizer->selected_rule();
+ if (sel > 0)
+ {
+ automatizer->swap_rules(sel-1, sel);
+ automatizer->select_rule(sel-1);
+ }
+ }
+ else if (c == 'd')
+ {
+ if (!automatizer->rules_count()) continue;
+
+ int sel = automatizer->selected_rule();
+ if (sel < automatizer->rules_count() - 1)
+ {
+ automatizer->swap_rules(sel, sel+1);
+ automatizer->select_rule(sel+1);
+ }
+ }
+ else if (c == 'n')
+ {
+ int i = create_new_rule();
+ if (i >= 0)
+ {
+ automatizer->select_rule(i);
+ active = ACTIVE_RULE;
+ }
+ }
+ else if (c == 's')
+ {
+ automatizer_save_rules();
+ }
+ else if (c == 'r')
+ {
+ if (!automatizer->rules_count()) continue;
+
+ rename_rule(automatizer->current_rule().get());
+ continue;
+ }
+ else if (c == 'k')
+ {
+ automatizer_enabled = FALSE;
+ break;
+ }
+ else if (c == '\t')
+ {
+ if (!automatizer->rules_count()) continue;
+
+ active = ACTIVE_RULE;
+ }
+ }
+ else if (active == ACTIVE_RULE)
+ {
+ if (c == '?')
+ {
+ screen_save();
+ show_file("automat.txt", "Automatizer help", 0, 0);
+ screen_load();
+ }
+ else if (c == '8')
+ {
+ automatizer->move_up();
+ }
+ else if (c == '2')
+ {
+ automatizer->move_down();
+ }
+ else if (c == '6')
+ {
+ automatizer->move_right();
+ }
+ else if (c == '4')
+ {
+ automatizer->move_left();
+ }
+ else if (c == '9')
+ {
+ automatizer->scroll_up();
+ }
+ else if (c == '3')
+ {
+ automatizer->scroll_down();
+ }
+ else if (c == '7')
+ {
+ automatizer->scroll_left();
+ }
+ else if (c == '1')
+ {
+ automatizer->scroll_right();
+ }
+ else if (c == 'a')
+ {
+ automatizer->add_new_condition(
+ squelch::new_condition_interactive);
+ }
+ else if (c == 'd')
+ {
+ if (!automatizer->rules_count())
+ {
+ continue;
+ }
+
+ int new_sel =
+ automatizer->remove_current_selection();
+
+ if (new_sel == -1)
+ {
+ active = ACTIVE_LIST;
+ }
+ }
+ else if (c == '\t')
+ {
+ active = ACTIVE_LIST;
+ }
+ }
+ }
+
+ screen_load();
+}
+
+enum class add_rule_mode { TVAL, TSVAL, NAME };
+
+static void easy_add_rule(action_type action, add_rule_mode mode, bool do_status, object_type *o_ptr)
+{
+ std::shared_ptr<Condition> condition;
+
+ switch (mode)
+ {
+
+ case add_rule_mode::TVAL:
+ condition = std::make_shared<TvalCondition>(o_ptr->tval);
+ break;
+
+ case add_rule_mode::TSVAL:
+ {
+ auto andCondition = std::make_shared<AndCondition>();
+
+ andCondition->add_condition(
+ std::make_shared<TvalCondition>(o_ptr->tval));
+ andCondition->add_condition(
+ std::make_shared<SvalCondition>(o_ptr->sval, o_ptr->sval));
+
+ condition = andCondition;
+ break;
+ }
+
+ case add_rule_mode::NAME:
+ {
+ char buf[128];
+ object_desc(buf, o_ptr, -1, 0);
+
+ condition = std::make_shared<NameCondition>(buf);
+ break;
+ }
+
+ }
+
+ // Use object status?
+ if (do_status)
+ {
+ status_type status = squelch::object_status(o_ptr);
+
+ auto andCondition = std::make_shared<AndCondition>();
+
+ andCondition->add_condition(
+ std::shared_ptr<Condition>(condition)); // cycle
+ andCondition->add_condition(
+ std::make_shared<StatusCondition>(status));
+
+ // Replace condition; breaks cycle
+ condition = andCondition;
+ }
+
+ // Rule name
+ auto rule_name = action_mapping().stringify(action);
+
+ // Append to list of rules
+ switch (action)
+ {
+ case action_type::AUTO_DESTROY:
+ automatizer->append_rule(
+ std::make_shared<DestroyRule>(
+ rule_name, game_module_idx, condition));
+ break;
+
+ case action_type::AUTO_PICKUP:
+ automatizer->append_rule(
+ std::make_shared<PickUpRule>(
+ rule_name, game_module_idx, condition));
+ break;
+
+ case action_type::AUTO_INSCRIBE:
+ assert(false);
+ break;
+ }
+
+ msg_print("Rule added. Please go to the Automatizer screen (press = then T)");
+ msg_print("to save the modified ruleset.");
+}
+
+void automatizer_add_rule(object_type *o_ptr, bool_ destroy)
+{
+ bool do_status = false;
+ action_type action = destroy
+ ? action_type::AUTO_DESTROY
+ : action_type::AUTO_PICKUP;
+
+ while (true)
+ {
+ char ch;
+
+ if (!get_com(format("%s all of the same [T]ype, [F]amily or [N]ame, also use [S]tatus (%s)? ", (destroy) ? "Destroy" : "Pickup", (do_status) ? "Yes" : "No"), &ch))
+ {
+ break;
+ }
+
+ if (ch == 'S' || ch == 's')
+ {
+ do_status = !do_status;
+ continue;
+ }
+
+ if (ch == 'T' || ch == 't')
+ {
+ easy_add_rule(action, add_rule_mode::TSVAL, do_status, o_ptr);
+ break;
+ }
+
+ if (ch == 'F' || ch == 'f')
+ {
+ easy_add_rule(action, add_rule_mode::TVAL, do_status, o_ptr);
+ break;
+ }
+
+ if (ch == 'N' || ch == 'n')
+ {
+ easy_add_rule(action, add_rule_mode::NAME, do_status, o_ptr);
+ break;
+ }
+ }
+}
+
+/**
+ * Initialize the automatizer.
+ */
+void automatizer_init()
+{
+ // Only permit single initialization.
+ assert(automatizer == nullptr);
+
+ // Set up dependencies
+ auto tree_printer(std::make_shared<squelch::TreePrinter>());
+ auto cursor(std::make_shared<squelch::Cursor>());
+
+ // Initialize
+ automatizer = new squelch::Automatizer(tree_printer, cursor);
+}
+
+/**
+ * Load automatizer file. This function may be called multiple times
+ * with different file names -- it should NOT clear any automatizer
+ * state (including loaded rules).
+ */
+void automatizer_load(cptr file_path)
+{
+ assert(file_path != NULL);
+ assert(automatizer != NULL);
+
+ // Does the file exist?
+ if (!file_exist(file_path))
+ {
+ return; // Not fatal; just skip
+ }
+
+ // Parse file
+ json_error_t error;
+ std::shared_ptr<json_t> rules_json(
+ json_load_file(file_path, 0, &error),
+ &json_decref);
+ if (rules_json == nullptr)
+ {
+ msg_format("Error parsing automatizer rules from '%s'.", file_path);
+ msg_format("Line %d, Column %d", error.line, error.column);
+ msg_print(nullptr);
+ return;
+ }
+
+ // Load rules
+ automatizer->load_json(rules_json.get());
+}
diff --git a/src/variable.c b/src/variable.c
index ac67e308..a921432c 100644
--- a/src/variable.c
+++ b/src/variable.c
@@ -1405,6 +1405,7 @@ bool_ option_ingame_help = TRUE;
* Automatizer enabled status
*/
bool_ automatizer_enabled = FALSE;
+bool_ automatizer_create = FALSE;
/*
* Location of the last teleportation thath affected the level