The Battle for Wesnoth  1.19.0+dev
side_filter.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 - 2024
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 "formula/callable_objects.hpp"
35 #include "formula/formula.hpp"
37 
38 static lg::log_domain log_engine_sf("engine/side_filter");
39 #define ERR_NG LOG_STREAM(err, log_engine_sf)
40 
41 static lg::log_domain log_wml("wml");
42 #define ERR_WML LOG_STREAM(err, log_wml)
43 
45 
46 side_filter::side_filter(const vconfig& cfg, const filter_context * fc, bool flat_tod)
47  : cfg_(cfg)
48  , flat_(flat_tod)
49  , side_string_()
50  , fc_(fc)
51 {
52 }
53 
54 side_filter::side_filter(const std::string &side_string, const filter_context * fc, bool flat_tod)
55  : cfg_(vconfig::empty_vconfig()), flat_(flat_tod), side_string_(side_string), fc_(fc)
56 {
57 }
58 
59 std::vector<int> side_filter::get_teams() const
60 {
61  assert(fc_);
62  //@todo: replace with better implementation
63  std::vector<int> result;
64  for(const team &t : fc_->get_disp_context().teams()) {
65  if (match(t)) {
66  result.push_back(t.side());
67  }
68  }
69  return result;
70 }
71 
72 static bool check_side_number(const team &t, const std::string &str)
73 {
74  std::vector<std::pair<int,int>> ranges = utils::parse_ranges_unsigned(str);
75  int side_number = t.side();
76 
77  std::vector<std::pair<int,int>>::const_iterator range, range_end = ranges.end();
78  for (range = ranges.begin(); range != range_end; ++range) {
79  if(side_number >= range->first && side_number <= range->second) {
80  return true;
81  }
82  }
83  return false;
84 }
85 
87 {
88  assert(fc_);
89 
90  if (cfg_.has_attribute("side_in")) {
91  if (!check_side_number(t,cfg_["side_in"])) {
92  return false;
93  }
94  }
95  if (cfg_.has_attribute("side")) {
96  if (!check_side_number(t,cfg_["side"])) {
97  return false;
98  }
99  }
100  if (!side_string_.empty()) {
102  return false;
103  }
104  }
105 
106  config::attribute_value cfg_team_name = cfg_["team_name"];
107  if (!cfg_team_name.blank()) {
108  const std::string& that_team_name = cfg_team_name;
109  const std::string& this_team_name = t.team_name();
110 
111  if(std::find(this_team_name.begin(), this_team_name.end(), ',') == this_team_name.end()) {
112  if(this_team_name != that_team_name) return false;
113  }
114  else {
115  const std::vector<std::string>& these_team_names = utils::split(this_team_name);
116  bool search_futile = true;
117  for(const std::string& this_single_team_name : these_team_names) {
118  if(this_single_team_name == that_team_name) {
119  search_futile = false;
120  break;
121  }
122  }
123  if(search_futile) return false;
124  }
125  }
126 
127  //Allow filtering on units
128  if(cfg_.has_child("has_unit")) {
129  const vconfig & ufilt_cfg = cfg_.child("has_unit");
130  if (!ufilter_) {
131  ufilter_.reset(new unit_filter(ufilt_cfg.make_safe()));
132  ufilter_->set_use_flat_tod(flat_);
133  }
134  bool found = false;
135  for(const unit &u : fc_->get_disp_context().units()) {
136  if (u.side() != t.side()) {
137  continue;
138  }
139  if (ufilter_->matches(u)) {
140  found = true;
141  break;
142  }
143  }
144  if(!found && ufilt_cfg["search_recall_list"].to_bool(false)) {
145  for(const unit_const_ptr u : t.recall_list()) {
146  scoped_recall_unit this_unit("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
147  if(ufilter_->matches(*u)) {
148  found = true;
149  break;
150  }
151  }
152  }
153  if (!found) {
154  return false;
155  }
156  }
157 
158  const vconfig& enemy_of = cfg_.child("enemy_of");
159  if(!enemy_of.null()) {
160  if (!enemy_filter_)
161  enemy_filter_.reset(new side_filter(enemy_of, fc_));
162  const std::vector<int>& teams = enemy_filter_->get_teams();
163  if(teams.empty()) return false;
164  for(const int side : teams) {
165  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
166  return false;
167  }
168  }
169 
170  const vconfig& allied_with = cfg_.child("allied_with");
171  if(!allied_with.null()) {
172  if (!allied_filter_)
173  allied_filter_.reset(new side_filter(allied_with, fc_));
174  const std::vector<int>& teams = allied_filter_->get_teams();
175  if(teams.empty()) return false;
176  for(const int side : teams) {
177  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
178  return false;
179  }
180  }
181 
182  const vconfig& has_enemy = cfg_.child("has_enemy");
183  if(!has_enemy.null()) {
184  if (!has_enemy_filter_)
185  has_enemy_filter_.reset(new side_filter(has_enemy, fc_));
186  const std::vector<int>& teams = has_enemy_filter_->get_teams();
187  bool found = false;
188  for(const int side : teams) {
189  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
190  {
191  found = true;
192  break;
193  }
194  }
195  if (!found) return false;
196  }
197 
198  const vconfig& has_ally = cfg_.child("has_ally");
199  if(!has_ally.null()) {
200  if (!has_ally_filter_)
201  has_ally_filter_.reset(new side_filter(has_ally, fc_));
202  const std::vector<int>& teams = has_ally_filter_->get_teams();
203  bool found = false;
204  for(const int side : teams) {
205  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
206  {
207  found = true;
208  break;
209  }
210  }
211  if (!found) return false;
212  }
213 
214 
215  const config::attribute_value cfg_controller = cfg_["controller"];
216  if (!cfg_controller.blank())
217  {
218  if (resources::controller->is_networked_mp() && synced_context::is_synced()) {
219  ERR_NG << "ignoring controller= in SSF due to danger of OOS errors";
220  }
221  else {
222  bool found = false;
223  for(const std::string& controller : utils::split(cfg_controller))
224  {
225  if(side_controller::get_string(t.controller()) == controller) {
226  found = true;
227  }
228  }
229  if(!found) {
230  return false;
231  }
232  }
233  }
234 
235  if (cfg_.has_attribute("formula")) {
236  try {
237  const wfl::team_callable callable(t);
238  const wfl::formula form(cfg_["formula"], new wfl::gamestate_function_symbol_table);
239  if(!form.evaluate(callable).as_bool()) {
240  return false;
241  }
242  return true;
243  } catch(const wfl::formula_error& e) {
244  lg::log_to_chat() << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
245  ERR_WML << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
246  // Formulae with syntax errors match nothing
247  return false;
248  }
249  }
250 
251  if (cfg_.has_attribute("lua_function")) {
252  std::string lua_function = cfg_["lua_function"].str();
253  if (!lua_function.empty() && fc_->get_lua_kernel()) {
254  if (!fc_->get_lua_kernel()->run_filter(lua_function.c_str(), t)) {
255  return false;
256  }
257  }
258  }
259 
260 
261  return true;
262 }
263 
264 bool side_filter::match(int side) const
265 {
266  assert(fc_);
267  return this->match((fc_->get_disp_context().get_team(side)));
268 }
269 
270 bool side_filter::match(const team& t) const
271 {
272  bool matches = match_internal(t);
273 
274  // Handle [and], [or], and [not] with in-order precedence
275  for(const auto& [key, filter] : cfg_.all_ordered()) {
276  // Handle [and]
277  if(key == "and") {
278  matches = matches && side_filter(filter, fc_, flat_).match(t);
279  }
280  // Handle [or]
281  else if(key == "or") {
282  matches = matches || side_filter(filter, fc_, flat_).match(t);
283  }
284  // Handle [not]
285  else if(key == "not") {
286  matches = matches && !side_filter(filter, fc_, flat_).match(t);
287  }
288  }
289 
290  return matches;
291 }
double t
Definition: astarsearch.cpp:63
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:60
std::vector< int > get_teams() const
Definition: side_filter.cpp:59
const filter_context * fc_
The filter context for this filter.
Definition: side_filter.hpp:55
std::string side_string_
Definition: side_filter.hpp:52
bool match(const team &t) const
bool match_internal(const team &t) const
Definition: side_filter.cpp:86
std::unique_ptr< side_filter > allied_filter_
Definition: side_filter.hpp:58
std::unique_ptr< side_filter > has_enemy_filter_
Definition: side_filter.hpp:61
const vconfig cfg_
Definition: side_filter.hpp:49
std::unique_ptr< side_filter > enemy_filter_
Definition: side_filter.hpp:59
side_filter(const std::string &side_string, const filter_context *fc, bool flat_tod=false)
Definition: side_filter.cpp:54
std::unique_ptr< unit_filter > ufilter_
Definition: side_filter.hpp:57
static bool is_synced()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool is_enemy(int n) const
Definition: team.hpp:229
This class represents a single unit of a specific type.
Definition: unit.hpp:133
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:313
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:544
std::function< int(lua_State *)> lua_function
play_controller * controller
Definition: resources.cpp:21
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:39
#define ERR_WML
Definition: side_filter.cpp:42
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:72
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