The Battle for Wesnoth  1.17.23+dev
side_filter.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2023
3  by Yurii Chernyi <terraninfo@terraninfo.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "config.hpp"
19 #include "display_context.hpp"
20 #include "filter_context.hpp"
21 #include "log.hpp"
22 #include "recall_list_manager.hpp"
23 #include "side_filter.hpp"
24 #include "variable.hpp"
25 #include "team.hpp"
28 #include "play_controller.hpp"
29 #include "resources.hpp"
30 #include "synced_context.hpp"
31 #include "units/unit.hpp"
32 #include "units/filter.hpp"
33 #include "units/map.hpp"
34 #include "variable.hpp"
35 #include "filter_context.hpp"
36 #include "formula/callable_objects.hpp"
37 #include "formula/formula.hpp"
39 
40 static lg::log_domain log_engine_sf("engine/side_filter");
41 #define ERR_NG LOG_STREAM(err, log_engine_sf)
42 
43 static lg::log_domain log_wml("wml");
44 #define ERR_WML LOG_STREAM(err, log_wml)
45 
47 
48 side_filter::side_filter(const vconfig& cfg, const filter_context * fc, bool flat_tod)
49  : cfg_(cfg)
50  , flat_(flat_tod)
51  , side_string_()
52  , fc_(fc)
53 {
54 }
55 
56 side_filter::side_filter(const std::string &side_string, const filter_context * fc, bool flat_tod)
57  : cfg_(vconfig::empty_vconfig()), flat_(flat_tod), side_string_(side_string), fc_(fc)
58 {
59 }
60 
61 std::vector<int> side_filter::get_teams() const
62 {
63  assert(fc_);
64  //@todo: replace with better implementation
65  std::vector<int> result;
66  for(const team &t : fc_->get_disp_context().teams()) {
67  if (match(t)) {
68  result.push_back(t.side());
69  }
70  }
71  return result;
72 }
73 
74 static bool check_side_number(const team &t, const std::string &str)
75 {
76  std::vector<std::pair<int,int>> ranges = utils::parse_ranges_unsigned(str);
77  int side_number = t.side();
78 
79  std::vector<std::pair<int,int>>::const_iterator range, range_end = ranges.end();
80  for (range = ranges.begin(); range != range_end; ++range) {
81  if(side_number >= range->first && side_number <= range->second) {
82  return true;
83  }
84  }
85  return false;
86 }
87 
89 {
90  assert(fc_);
91 
92  if (cfg_.has_attribute("side_in")) {
93  if (!check_side_number(t,cfg_["side_in"])) {
94  return false;
95  }
96  }
97  if (cfg_.has_attribute("side")) {
98  if (!check_side_number(t,cfg_["side"])) {
99  return false;
100  }
101  }
102  if (!side_string_.empty()) {
104  return false;
105  }
106  }
107 
108  config::attribute_value cfg_team_name = cfg_["team_name"];
109  if (!cfg_team_name.blank()) {
110  const std::string& that_team_name = cfg_team_name;
111  const std::string& this_team_name = t.team_name();
112 
113  if(std::find(this_team_name.begin(), this_team_name.end(), ',') == this_team_name.end()) {
114  if(this_team_name != that_team_name) return false;
115  }
116  else {
117  const std::vector<std::string>& these_team_names = utils::split(this_team_name);
118  bool search_futile = true;
119  for(const std::string& this_single_team_name : these_team_names) {
120  if(this_single_team_name == that_team_name) {
121  search_futile = false;
122  break;
123  }
124  }
125  if(search_futile) return false;
126  }
127  }
128 
129  //Allow filtering on units
130  if(cfg_.has_child("has_unit")) {
131  const vconfig & ufilt_cfg = cfg_.child("has_unit");
132  if (!ufilter_) {
133  ufilter_.reset(new unit_filter(ufilt_cfg.make_safe()));
134  ufilter_->set_use_flat_tod(flat_);
135  }
136  bool found = false;
137  for(const unit &u : fc_->get_disp_context().units()) {
138  if (u.side() != t.side()) {
139  continue;
140  }
141  if (ufilter_->matches(u)) {
142  found = true;
143  break;
144  }
145  }
146  if(!found && ufilt_cfg["search_recall_list"].to_bool(false)) {
147  for(const unit_const_ptr u : t.recall_list()) {
148  scoped_recall_unit this_unit("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
149  if(ufilter_->matches(*u)) {
150  found = true;
151  break;
152  }
153  }
154  }
155  if (!found) {
156  return false;
157  }
158  }
159 
160  const vconfig& enemy_of = cfg_.child("enemy_of");
161  if(!enemy_of.null()) {
162  if (!enemy_filter_)
163  enemy_filter_.reset(new side_filter(enemy_of, fc_));
164  const std::vector<int>& teams = enemy_filter_->get_teams();
165  if(teams.empty()) return false;
166  for(const int side : teams) {
167  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
168  return false;
169  }
170  }
171 
172  const vconfig& allied_with = cfg_.child("allied_with");
173  if(!allied_with.null()) {
174  if (!allied_filter_)
175  allied_filter_.reset(new side_filter(allied_with, fc_));
176  const std::vector<int>& teams = allied_filter_->get_teams();
177  if(teams.empty()) return false;
178  for(const int side : teams) {
179  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
180  return false;
181  }
182  }
183 
184  const vconfig& has_enemy = cfg_.child("has_enemy");
185  if(!has_enemy.null()) {
186  if (!has_enemy_filter_)
187  has_enemy_filter_.reset(new side_filter(has_enemy, fc_));
188  const std::vector<int>& teams = has_enemy_filter_->get_teams();
189  bool found = false;
190  for(const int side : teams) {
191  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
192  {
193  found = true;
194  break;
195  }
196  }
197  if (!found) return false;
198  }
199 
200  const vconfig& has_ally = cfg_.child("has_ally");
201  if(!has_ally.null()) {
202  if (!has_ally_filter_)
203  has_ally_filter_.reset(new side_filter(has_ally, fc_));
204  const std::vector<int>& teams = has_ally_filter_->get_teams();
205  bool found = false;
206  for(const int side : teams) {
207  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
208  {
209  found = true;
210  break;
211  }
212  }
213  if (!found) return false;
214  }
215 
216 
217  const config::attribute_value cfg_controller = cfg_["controller"];
218  if (!cfg_controller.blank())
219  {
220  if (resources::controller->is_networked_mp() && synced_context::is_synced()) {
221  ERR_NG << "ignoring controller= in SSF due to danger of OOS errors";
222  }
223  else {
224  bool found = false;
225  for(const std::string& controller : utils::split(cfg_controller))
226  {
227  if(side_controller::get_string(t.controller()) == controller) {
228  found = true;
229  }
230  }
231  if(!found) {
232  return false;
233  }
234  }
235  }
236 
237  if (cfg_.has_attribute("formula")) {
238  try {
239  const wfl::team_callable callable(t);
240  const wfl::formula form(cfg_["formula"], new wfl::gamestate_function_symbol_table);
241  if(!form.evaluate(callable).as_bool()) {
242  return false;
243  }
244  return true;
245  } catch(const wfl::formula_error& e) {
246  lg::log_to_chat() << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
247  ERR_WML << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
248  // Formulae with syntax errors match nothing
249  return false;
250  }
251  }
252 
253  if (cfg_.has_attribute("lua_function")) {
254  std::string lua_function = cfg_["lua_function"].str();
255  if (!lua_function.empty() && fc_->get_lua_kernel()) {
256  if (!fc_->get_lua_kernel()->run_filter(lua_function.c_str(), t)) {
257  return false;
258  }
259  }
260  }
261 
262 
263  return true;
264 }
265 
266 bool side_filter::match(int side) const
267 {
268  assert(fc_);
269  return this->match((fc_->get_disp_context().get_team(side)));
270 }
271 
272 bool side_filter::match(const team& t) const
273 {
274  bool matches = match_internal(t);
275 
276  // Handle [and], [or], and [not] with in-order precedence
277  for(const auto& [key, filter] : cfg_.all_ordered()) {
278  // Handle [and]
279  if(key == "and") {
280  matches = matches && side_filter(filter, fc_, flat_).match(t);
281  }
282  // Handle [or]
283  else if(key == "or") {
284  matches = matches || side_filter(filter, fc_, flat_).match(t);
285  }
286  // Handle [not]
287  else if(key == "not") {
288  matches = matches && !side_filter(filter, fc_, flat_).match(t);
289  }
290  }
291 
292  return matches;
293 }
double t
Definition: astarsearch.cpp:65
Variant for storing WML attributes.
bool blank() const
Tests for an attribute that was never set.
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
virtual const std::vector< team > & teams() const =0
virtual const unit_map & units() const =0
virtual const display_context & get_disp_context() const =0
virtual game_lua_kernel * get_lua_kernel() const =0
bool run_filter(char const *name, const unit &u)
Runs a script from a unit filter.
std::unique_ptr< side_filter > has_ally_filter_
Definition: side_filter.hpp:62
std::vector< int > get_teams() const
Definition: side_filter.cpp:61
const filter_context * fc_
The filter context for this filter.
Definition: side_filter.hpp:57
std::string side_string_
Definition: side_filter.hpp:54
bool match(const team &t) const
bool match_internal(const team &t) const
Definition: side_filter.cpp:88
std::unique_ptr< side_filter > allied_filter_
Definition: side_filter.hpp:60
std::unique_ptr< side_filter > has_enemy_filter_
Definition: side_filter.hpp:63
const vconfig cfg_
Definition: side_filter.hpp:51
std::unique_ptr< side_filter > enemy_filter_
Definition: side_filter.hpp:61
side_filter(const std::string &side_string, const filter_context *fc, bool flat_tod=false)
Definition: side_filter.cpp:56
std::unique_ptr< unit_filter > ufilter_
Definition: side_filter.hpp:59
static bool is_synced()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:76
bool is_enemy(int n) const
Definition: team.hpp:231
This class represents a single unit of a specific type.
Definition: unit.hpp:135
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
bool has_attribute(const std::string &key) const
< Synonym for operator[]
Definition: variable.hpp:99
bool null() const
Definition: variable.hpp:72
vconfig child(const std::string &key) const
Returns a child of *this whose key is key.
Definition: variable.cpp:288
const vconfig & make_safe() const
instruct the vconfig to make a private copy of its underlying data.
Definition: variable.cpp:163
boost::iterator_range< all_children_iterator > all_ordered() const
Definition: variable.hpp:190
bool has_child(const std::string &key) const
Returns whether or not *this has a child whose key is key.
Definition: variable.cpp:315
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:40
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:316
Standard logging facilities (interface).
int side_number
Definition: game_info.hpp:40
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:468
std::function< int(lua_State *)> lua_function
play_controller * controller
Definition: resources.cpp:22
std::vector< std::pair< int, int > > parse_ranges_unsigned(const std::string &str)
Handles a comma-separated list of inputs to parse_range, in a context that does not expect negative v...
std::vector< std::string > split(const config_attribute_value &val)
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
#define ERR_NG
Definition: side_filter.cpp:41
#define ERR_WML
Definition: side_filter.cpp:44
static lg::log_domain log_engine_sf("engine/side_filter")
static bool check_side_number(const team &t, const std::string &str)
Definition: side_filter.cpp:74
static lg::log_domain log_wml("wml")
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
#define e