diff options
Diffstat (limited to 'src')
30 files changed, 4362 insertions, 3581 deletions
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(¤t->condition); - next = current->next; - free(current); - } - } - - /* Free sub-condition if any */ - if (c->subcondition) - { - condition_destroy(&c->subcondition); - } - - /* Free name if any */ - if (c->name) - { - free(c->name); - c->name = NULL; - } - - /* Free inscription if any */ - if (c->inscription) - { - free(c->inscription); - c->inscription = NULL; - } - - /* Free subrace if any */ - if (c->subrace) - { - free(c->subrace); - c->subrace = NULL; - } - - /* Free race if any */ - if (c->race) - { - free(c->race); - c->race = NULL; - } - - /* Free class if any */ - if (c->klass) - { - free(c->klass); - c->klass = NULL; - } - - /* Free comment if any */ - if (c->comment) - { - free(c->comment); - c->comment = NULL; - } - - /* Free the condition itself */ - free(*cp); - *cp = NULL; -} - -static bool_ condition_eval(condition_type *c, object_type *o_ptr) -{ - bool_ is_and = (c->match == M_AND); - bool_ is_or = (c->match == M_OR); - - switch (c->match) - { - case M_AND: - case M_OR: - { - struct sglib_condition_list_iterator it; - struct condition_list *child = NULL; - - for (child = sglib_condition_list_it_init(&it, c->conditions.c); - child != NULL; - child = sglib_condition_list_it_next(&it)) - { - if (is_and && (!condition_eval(child->condition, o_ptr))) - { - return FALSE; - } - - if (is_or && condition_eval(child->condition, o_ptr)) - { - return TRUE; - } - } - - if (is_and) - { - return TRUE; - } - else - { - return FALSE; - } - } - - case M_NOT: - { - if (c->subcondition == NULL) - { - return TRUE; - } - - return !condition_eval(c->subcondition, o_ptr); - } - - case M_INVENTORY: - { - int i; - - if (c->subcondition == NULL) - { - return FALSE; - } - - for (i = 0; i < INVEN_WIELD; i++) - { - if (condition_eval(c->subcondition, &p_ptr->inventory[i])) - { - return TRUE; - } - } - - return FALSE; - } - - case M_EQUIPMENT: - { - int i; - - if (c->subcondition == NULL) - { - return FALSE; - } - - for (i = INVEN_WIELD; i < INVEN_TOTAL; i++) - { - if (condition_eval(c->subcondition, &p_ptr->inventory[i])) - { - return TRUE; - } - } - - return FALSE; - } - - case M_NAME: - { - char buf1[128]; - char buf2[128]; - - object_desc(buf1, o_ptr, -1, 0); - strlower(buf1); - - sprintf(buf2, "%s", c->name); - strlower(buf2); - - return streq(buf1, buf2); - } - - case M_CONTAIN: - { - char buf1[128]; - char buf2[128]; - - object_desc(buf1, o_ptr, -1, 0); - strlower(buf1); - - sprintf(buf2, "%s", c->name); - strlower(buf2); - - return (strstr(buf1, buf2) != NULL); - } - - case M_SYMBOL: - { - object_kind *k_ptr = &k_info[o_ptr->k_idx]; - - return k_ptr->d_char == c->symbol; - } - - case M_INSCRIBED: - { - char buf1[128]; - char buf2[128]; - - if (o_ptr->note == 0) - { - return FALSE; - } - - sprintf(buf1, "%s", quark_str(o_ptr->note)); - strlower(buf1); - - sprintf(buf2, "%s", c->inscription); - strlower(buf2); - - return (strstr(buf1, buf2) != NULL); - } - - case M_DISCOUNT: - { - return (object_aware_p(o_ptr) && - (o_ptr->discount >= c->discount.min) && - (o_ptr->discount <= c->discount.max)); - } - - case M_TVAL: - { - return (o_ptr->tval == c->tval); - } - - case M_SVAL: - { - return (object_aware_p(o_ptr) && - (o_ptr->sval >= c->sval_range.min) && - (o_ptr->sval <= c->sval_range.max)); - } - - case M_STATUS: - { - return c->status == object_status(o_ptr); - } - - case M_STATE: - { - switch (c->identification_state) - { - case IDENTIFIED: - return object_known_p(o_ptr); - case NOT_IDENTIFIED: - return !object_known_p(o_ptr); - default: - assert(FALSE); - } - } - - case M_RACE: - { - char buf1[128]; - char buf2[128]; - - sprintf(buf1, "%s", rp_ptr->title + rp_name); - strlower(buf1); - - sprintf(buf2, "%s", c->race); - strlower(buf2); - - return streq(buf1, buf2); - } - - case M_SUBRACE: - { - char buf1[128]; - char buf2[128]; - - sprintf(buf1, "%s", rmp_ptr->title + rmp_name); - strlower(buf1); - - sprintf(buf2, "%s", c->subrace); - strlower(buf2); - - return streq(buf1, buf2); - } - - case M_CLASS: - { - char buf1[128]; - char buf2[128]; - - sprintf(buf1, "%s", spp_ptr->title + c_name); - strlower(buf1); - - sprintf(buf2, "%s", c->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 |