1 /*
2 Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 /**
16 * @file
17 * Implementations of conditional action WML tags.
18 */
19
20 #include "game_events/conditional_wml.hpp"
21
22 #include "config.hpp"
23 #include "game_board.hpp"
24 #include "game_data.hpp"
25 #include "log.hpp"
26 #include "recall_list_manager.hpp"
27 #include "resources.hpp"
28 #include "scripting/game_lua_kernel.hpp"
29 #include "serialization/string_utils.hpp"
30 #include "team.hpp"
31 #include "terrain/filter.hpp"
32 #include "units/unit.hpp"
33 #include "units/filter.hpp"
34 #include "units/map.hpp"
35 #include "variable.hpp"
36
37 static lg::log_domain log_engine("engine");
38 #define WRN_NG LOG_STREAM(warn, log_engine)
39
40
41 // This file is in the game_events namespace.
42 namespace game_events {
43
44 namespace builtin_conditions {
45 std::vector<std::pair<int,int>> default_counts = utils::parse_ranges("1-99999");
46
have_unit(const vconfig & cfg)47 bool have_unit(const vconfig& cfg)
48 {
49 if(!resources::gameboard) {
50 return false;
51 }
52 std::vector<std::pair<int,int>> counts = cfg.has_attribute("count")
53 ? utils::parse_ranges(cfg["count"]) : default_counts;
54 int match_count = 0;
55 const unit_filter ufilt(cfg);
56 for(const unit &i : resources::gameboard->units()) {
57 if(i.hitpoints() > 0 && ufilt(i)) {
58 ++match_count;
59 if(counts == default_counts) {
60 // by default a single match is enough, so avoid extra work
61 break;
62 }
63 }
64 }
65 if(cfg["search_recall_list"].to_bool()) {
66 for(const team& team : resources::gameboard->teams()) {
67 if(counts == default_counts && match_count) {
68 break;
69 }
70 for(size_t t = 0; t < team.recall_list().size(); ++t) {
71 if(counts == default_counts && match_count) {
72 break;
73 }
74 scoped_recall_unit auto_store("this_unit", team.save_id_or_number(), t);
75 if(ufilt(*team.recall_list()[t])) {
76 ++match_count;
77 }
78 }
79 }
80 }
81 return in_ranges(match_count, counts);
82 }
83
have_location(const vconfig & cfg)84 bool have_location(const vconfig& cfg)
85 {
86 std::set<map_location> res;
87 terrain_filter(cfg, resources::filter_con).get_locations(res);
88
89 std::vector<std::pair<int,int>> counts = cfg.has_attribute("count")
90 ? utils::parse_ranges(cfg["count"]) : default_counts;
91 return in_ranges<int>(res.size(), counts);
92 }
93
variable_matches(const vconfig & values)94 bool variable_matches(const vconfig& values)
95 {
96 const std::string name = values["name"];
97 config::attribute_value value = resources::gamedata->get_variable_const(name);
98
99 #define TEST_STR_ATTR(name, test) \
100 do { \
101 if (values.has_attribute(name)) { \
102 std::string attr_str = values[name].str(); \
103 std::string str_value = value.str(); \
104 if (!(test)) return false; \
105 } \
106 } while (0)
107
108 #define TEST_NUM_ATTR(name, test) \
109 do { \
110 if (values.has_attribute(name)) { \
111 double attr_num = values[name].to_double(); \
112 double num_value = value.to_double(); \
113 if (!(test)) return false; \
114 } \
115 } while (0)
116
117 #define TEST_BOL_ATTR(name, test) \
118 do { \
119 if (values.has_attribute(name)) { \
120 bool attr_bool = values[name].to_bool(); \
121 bool bool_value = value.to_bool(); \
122 if (!(test)) return false; \
123 } \
124 } while (0)
125
126 TEST_STR_ATTR("equals", str_value == attr_str);
127 TEST_STR_ATTR("not_equals", str_value != attr_str);
128 TEST_NUM_ATTR("numerical_equals", num_value == attr_num);
129 TEST_NUM_ATTR("numerical_not_equals", num_value != attr_num);
130 TEST_NUM_ATTR("greater_than", num_value > attr_num);
131 TEST_NUM_ATTR("less_than", num_value < attr_num);
132 TEST_NUM_ATTR("greater_than_equal_to", num_value >= attr_num);
133 TEST_NUM_ATTR("less_than_equal_to", num_value <= attr_num);
134 TEST_BOL_ATTR("boolean_equals", bool_value == attr_bool);
135 TEST_BOL_ATTR("boolean_not_equals", bool_value != attr_bool);
136 TEST_STR_ATTR("contains", str_value.find(attr_str) != std::string::npos);
137
138 #undef TEST_STR_ATTR
139 #undef TEST_NUM_ATTR
140 #undef TEST_BOL_ATTR
141 return true;
142 }
143 }
144
145 namespace { // Support functions
internal_conditional_passed(const vconfig & cond)146 bool internal_conditional_passed(const vconfig& cond)
147 {
148 if(cond.has_child("true")) {
149 return true;
150 }
151 if(cond.has_child("false")) {
152 return false;
153 }
154
155 vconfig::all_children_iterator cond_end = cond.ordered_end();
156 static const std::set<std::string> skip =
157 {"then", "else", "elseif", "not", "and", "or", "do"};
158
159 for(vconfig::all_children_iterator it = cond.ordered_begin(); it != cond_end; ++it) {
160 std::string key = it.get_key();
161 bool result = true;
162 if(std::find(skip.begin(), skip.end(), key) == skip.end()) {
163 assert(resources::lua_kernel);
164 result = resources::lua_kernel->run_wml_conditional(key, it.get_child());
165 }
166 if (!result) {
167 return false;
168 }
169 }
170
171 return true;
172 }
173
174 } // end anonymous namespace (support functions)
175
176
conditional_passed(const vconfig & cond)177 bool conditional_passed(const vconfig& cond)
178 {
179 bool matches = internal_conditional_passed(cond);
180
181 // Handle [and], [or], and [not] with in-order precedence
182 vconfig::all_children_iterator cond_i = cond.ordered_begin();
183 vconfig::all_children_iterator cond_end = cond.ordered_end();
184 while(cond_i != cond_end)
185 {
186 const std::string& cond_name = cond_i.get_key();
187 const vconfig& cond_filter = cond_i.get_child();
188
189 // Handle [and]
190 if(cond_name == "and")
191 {
192 matches = matches && conditional_passed(cond_filter);
193 }
194 // Handle [or]
195 else if(cond_name == "or")
196 {
197 matches = matches || conditional_passed(cond_filter);
198 }
199 // Handle [not]
200 else if(cond_name == "not")
201 {
202 matches = matches && !conditional_passed(cond_filter);
203 }
204 ++cond_i;
205 }
206 return matches;
207 }
208
matches_special_filter(const config & cfg,const vconfig & filter)209 bool matches_special_filter(const config &cfg, const vconfig& filter)
210 {
211 if (!cfg) {
212 WRN_NG << "attempt to filter attack for an event with no attack data." << std::endl;
213 // better to not execute the event (so the problem is more obvious)
214 return false;
215 }
216 // Though it may seem wasteful to put this on the heap, it's necessary.
217 // matches_filter() could potentially call a WFL formula, which would call shared_from_this().
218 auto attack = std::make_shared<const attack_type>(cfg);
219 return attack->matches_filter(filter.get_parsed_config());
220 }
221
222 } // end namespace game_events
223