From b05113a48a2031282b5f4e65b97407a7bc8438c1 Mon Sep 17 00:00:00 2001 From: Bardur Arantsson Date: Tue, 19 Jun 2012 20:03:42 +0200 Subject: C++: Move Automatizer to C++ --- src/squelch/CMakeLists.txt | 9 + src/squelch/automatizer.cc | 275 ++++++++++ src/squelch/condition.cc | 1068 +++++++++++++++++++++++++++++++++++++ src/squelch/condition_metadata.cc | 492 +++++++++++++++++ src/squelch/cursor.cc | 93 ++++ src/squelch/object_status.cc | 149 ++++++ src/squelch/rule.cc | 325 +++++++++++ src/squelch/tree_printer.cc | 89 ++++ 8 files changed, 2500 insertions(+) create mode 100644 src/squelch/CMakeLists.txt create mode 100644 src/squelch/automatizer.cc create mode 100644 src/squelch/condition.cc create mode 100644 src/squelch/condition_metadata.cc create mode 100644 src/squelch/cursor.cc create mode 100644 src/squelch/object_status.cc create mode 100644 src/squelch/rule.cc create mode 100644 src/squelch/tree_printer.cc (limited to 'src/squelch') 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 Automatizer::to_json() const +{ + auto rules_json = std::shared_ptr(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 ()> factory) +{ + m_rules.at(m_selected_rule)->add_new_condition( + m_cursor.get(), + factory); +} + +void Automatizer::get_rule_names(std::vector *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 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 + +#include "tome/squelch/cursor.hpp" +#include "tome/squelch/tree_printer.hpp" +#include "angband.h" +#include "quark.h" + +namespace squelch { + +EnumStringMap &match_mapping() +{ + // TODO: This is quite ugly and leads to valgrind complaints + static auto m = new EnumStringMap { + { 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_mapping() +{ + // TODO: This is quite ugly and leads to valgrind complaints + static auto m = new EnumStringMap { + { 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::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) +{ + 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 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(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 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(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 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(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 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(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 p) { + return p.get() == condition; + }); +} + +std::shared_ptr GroupingCondition::first_child() +{ + if (!m_conditions.empty()) + { + return m_conditions.front(); + } + return nullptr; +} + +std::shared_ptr GroupingCondition::previous_child(Condition *current) +{ + std::shared_ptr 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 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 > 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 >(); + } + else if (!json_is_array(conditions_j)) + { + msg_print("'conditions' property has invalid type"); + return std::vector< std::shared_ptr >(); + } + else + { + std::vector< std::shared_ptr > 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 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 AndCondition::from_json(json_t *j) +{ + auto condition = std::make_shared(); + 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 OrCondition::from_json(json_t *j) +{ + std::shared_ptr condition = + std::make_shared(); + + 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 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(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 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(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 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(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 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(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 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(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 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(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 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(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 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(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 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(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 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(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 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(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 () > 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 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 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 NotCondition::from_json(json_t *j) +{ + return std::make_shared(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 InventoryCondition::from_json(json_t *j) +{ + return std::make_shared( + 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 EquipmentCondition::from_json(json_t *j) +{ + return std::make_shared( + 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 + +#include "tome/squelch/object_status.hpp" +#include "angband.h" + +namespace squelch { + +static std::shared_ptr create_condition_name() +{ + cptr s = lua_input_box("Object name to match?", 79); + if (strlen(s) == 0) + { + return nullptr; + } + + return std::make_shared(s); +} + +static std::shared_ptr 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(s); +} + +static std::shared_ptr 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(s); +} + +static std::shared_ptr 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(min, max); +} + +static std::shared_ptr 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(c); +} + +static std::shared_ptr 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(status); +} + +static std::shared_ptr 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(s); +} + +static bool in_byte_range(int x) +{ + return (x >= 0) && (x < 256); +} + +static std::shared_ptr 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(tval); +} + +static std::shared_ptr 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(sval_min, sval_max); +} + +static std::shared_ptr create_condition_race() +{ + cptr s = lua_input_box("Player race to match?", 79); + if (strlen(s) == 0) + { + return nullptr; + } + + return std::make_shared(s); +} + +static std::shared_ptr create_condition_subrace() +{ + cptr s = lua_input_box("Player subrace to match?", 79); + if (strlen(s) == 0) + { + return nullptr; + } + + return std::make_shared(s); +} + +static std::shared_ptr create_condition_class() +{ + cptr s = lua_input_box("Player class to match?", 79); + if (strlen(s) == 0) + { + return nullptr; + } + + return std::make_shared(s); +} + +static std::shared_ptr 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(min, max); +} + +static std::shared_ptr 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(skill_idx, min, max); +} + +static std::shared_ptr 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(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 new_condition_interactive() +{ + static std::vector 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 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(); + case match_type::OR: + return std::make_shared(); + case match_type::NOT: + return std::make_shared(); + 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(); + case match_type::EQUIPMENT: + return std::make_shared(); + + } + } + } + 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 +#include + +#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 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 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 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_mapping() +{ + // TODO: This is quite ugly and leads to valgrind complaints + static auto m = new EnumStringMap { + { 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_mapping() +{ + static auto m = new EnumStringMap { + { 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 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::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::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( + rule_name_s, module_idx, condition, inscription); + } + + case action_type::AUTO_PICKUP: + return std::make_shared( + rule_name_s, module_idx, condition); + + case action_type::AUTO_DESTROY: + return std::make_shared( + 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(""); + 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(""); + 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("", 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 -- cgit v1.2.3