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