The Battle for Wesnoth  1.19.7+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  return in_ranges(t.side(), utils::parse_ranges_unsigned(str));
75 }
76 
78 {
79  assert(fc_);
80 
81  if (cfg_.has_attribute("side_in")) {
82  if (!check_side_number(t,cfg_["side_in"])) {
83  return false;
84  }
85  }
86  if (cfg_.has_attribute("side")) {
87  if (!check_side_number(t,cfg_["side"])) {
88  return false;
89  }
90  }
91  if (!side_string_.empty()) {
93  return false;
94  }
95  }
96 
97  config::attribute_value cfg_team_name = cfg_["team_name"];
98  if (!cfg_team_name.blank()) {
99  const std::string& that_team_name = cfg_team_name;
100  const std::string& this_team_name = t.team_name();
101 
102  if(std::find(this_team_name.begin(), this_team_name.end(), ',') == this_team_name.end()) {
103  if(this_team_name != that_team_name) return false;
104  }
105  else {
106  const std::vector<std::string>& these_team_names = utils::split(this_team_name);
107  bool search_futile = true;
108  for(const std::string& this_single_team_name : these_team_names) {
109  if(this_single_team_name == that_team_name) {
110  search_futile = false;
111  break;
112  }
113  }
114  if(search_futile) return false;
115  }
116  }
117 
118  //Allow filtering on units
119  if(cfg_.has_child("has_unit")) {
120  const vconfig & ufilt_cfg = cfg_.child("has_unit");
121  if (!ufilter_) {
122  ufilter_.reset(new unit_filter(ufilt_cfg.make_safe()));
123  ufilter_->set_use_flat_tod(flat_);
124  }
125  bool found = false;
126  for(const unit &u : fc_->get_disp_context().units()) {
127  if (u.side() != t.side()) {
128  continue;
129  }
130  if (ufilter_->matches(u)) {
131  found = true;
132  break;
133  }
134  }
135  if(!found && ufilt_cfg["search_recall_list"].to_bool(false)) {
136  for(const unit_const_ptr u : t.recall_list()) {
137  scoped_recall_unit this_unit("this_unit", t.save_id_or_number(), t.recall_list().find_index(u->id()));
138  if(ufilter_->matches(*u)) {
139  found = true;
140  break;
141  }
142  }
143  }
144  if (!found) {
145  return false;
146  }
147  }
148 
149  const vconfig& enemy_of = cfg_.child("enemy_of");
150  if(!enemy_of.null()) {
151  if (!enemy_filter_)
152  enemy_filter_.reset(new side_filter(enemy_of, fc_));
153  const std::vector<int>& teams = enemy_filter_->get_teams();
154  if(teams.empty()) return false;
155  for(const int side : teams) {
156  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
157  return false;
158  }
159  }
160 
161  const vconfig& allied_with = cfg_.child("allied_with");
162  if(!allied_with.null()) {
163  if (!allied_filter_)
164  allied_filter_.reset(new side_filter(allied_with, fc_));
165  const std::vector<int>& teams = allied_filter_->get_teams();
166  if(teams.empty()) return false;
167  for(const int side : teams) {
168  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
169  return false;
170  }
171  }
172 
173  const vconfig& has_enemy = cfg_.child("has_enemy");
174  if(!has_enemy.null()) {
175  if (!has_enemy_filter_)
176  has_enemy_filter_.reset(new side_filter(has_enemy, fc_));
177  const std::vector<int>& teams = has_enemy_filter_->get_teams();
178  bool found = false;
179  for(const int side : teams) {
180  if(fc_->get_disp_context().get_team(side).is_enemy(t.side()))
181  {
182  found = true;
183  break;
184  }
185  }
186  if (!found) return false;
187  }
188 
189  const vconfig& has_ally = cfg_.child("has_ally");
190  if(!has_ally.null()) {
191  if (!has_ally_filter_)
192  has_ally_filter_.reset(new side_filter(has_ally, fc_));
193  const std::vector<int>& teams = has_ally_filter_->get_teams();
194  bool found = false;
195  for(const int side : teams) {
196  if(!fc_->get_disp_context().get_team(side).is_enemy(t.side()))
197  {
198  found = true;
199  break;
200  }
201  }
202  if (!found) return false;
203  }
204 
205 
206  const config::attribute_value cfg_controller = cfg_["controller"];
207  if (!cfg_controller.blank())
208  {
209  if (resources::controller->is_networked_mp() && synced_context::is_synced()) {
210  ERR_NG << "ignoring controller= in SSF due to danger of OOS errors";
211  }
212  else {
213  bool found = false;
214  for(const std::string& controller : utils::split(cfg_controller))
215  {
216  if(side_controller::get_string(t.controller()) == controller) {
217  found = true;
218  }
219  }
220  if(!found) {
221  return false;
222  }
223  }
224  }
225 
226  if (cfg_.has_attribute("formula")) {
227  try {
228  const wfl::team_callable callable(t);
229  const wfl::formula form(cfg_["formula"], new wfl::gamestate_function_symbol_table);
230  if(!form.evaluate(callable).as_bool()) {
231  return false;
232  }
233  return true;
234  } catch(const wfl::formula_error& e) {
235  lg::log_to_chat() << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
236  ERR_WML << "Formula error in side filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
237  // Formulae with syntax errors match nothing
238  return false;
239  }
240  }
241 
242  if (cfg_.has_attribute("lua_function")) {
243  std::string lua_function = cfg_["lua_function"].str();
244  if (!lua_function.empty() && fc_->get_lua_kernel()) {
245  if (!fc_->get_lua_kernel()->run_filter(lua_function.c_str(), t)) {
246  return false;
247  }
248  }
249  }
250 
251 
252  return true;
253 }
254 
255 bool side_filter::match(int side) const
256 {
257  assert(fc_);
258  return this->match((fc_->get_disp_context().get_team(side)));
259 }
260 
261 bool side_filter::match(const team& t) const
262 {
263  bool matches = match_internal(t);
264 
265  // Handle [and], [or], and [not] with in-order precedence
266  for(const auto& [key, filter] : cfg_.all_ordered()) {
267  // Handle [and]
268  if(key == "and") {
269  matches = matches && side_filter(filter, fc_, flat_).match(t);
270  }
271  // Handle [or]
272  else if(key == "or") {
273  matches = matches || side_filter(filter, fc_, flat_).match(t);
274  }
275  // Handle [not]
276  else if(key == "not") {
277  matches = matches && !side_filter(filter, fc_, flat_).match(t);
278  }
279  }
280 
281  return matches;
282 }
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:77
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:75
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
Definitions for the interface to Wesnoth Markup Language (WML).
Standard logging facilities (interface).
bool in_ranges(const Cmp c, const std::vector< std::pair< Cmp, Cmp >> &ranges)
Definition: math.hpp:87
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:520
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)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:140
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