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