The Battle for Wesnoth  1.19.13+dev
filter.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2025
3  by Chris Beck <render787@gmail.com>
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 #include "units/filter.hpp"
17 
18 #include "log.hpp"
19 
20 #include "display_context.hpp"
21 #include "config.hpp"
22 #include "game_data.hpp"
23 #include "game_version.hpp" // for version_info
24 #include "map/map.hpp"
25 #include "map/location.hpp"
26 #include "scripting/game_lua_kernel.hpp" //Needed for lua kernel
27 #include "side_filter.hpp"
28 #include "team.hpp"
29 #include "terrain/filter.hpp"
30 #include "units/unit.hpp"
31 #include "units/types.hpp"
32 #include "variable.hpp" // needed for vconfig, scoped unit
34 #include "formula/formula.hpp"
36 #include "formula/string_utils.hpp"
37 #include "resources.hpp"
38 #include "deprecation.hpp"
39 
40 static lg::log_domain log_config("config");
41 #define ERR_CF LOG_STREAM(err, log_config)
42 #define WRN_CF LOG_STREAM(warn, log_config)
43 #define DBG_CF LOG_STREAM(debug, log_config)
44 
45 static lg::log_domain log_wml("wml");
46 #define ERR_WML LOG_STREAM(err, log_wml)
47 
48 using namespace unit_filter_impl;
49 
51  : cfg_(cfg)
52  , fc_(resources::filter_con)
53  , use_flat_tod_(false)
54  , impl_(cfg_)
55  , max_matches_(-1)
56 {
57 }
58 
59 bool unit_filter::matches(const unit& u) const {
61 }
62 
63 bool unit_filter::matches(const unit & u, const unit & u2) const {
65 }
66 
67 std::vector<const unit *> unit_filter::all_matches_on_map(const map_location* loc, const unit* other_unit) const
68 {
69  std::vector<const unit *> ret;
70  int max_matches = max_matches_;
71 
72  for (const unit & u : fc_->get_disp_context().units()) {
73  if (impl_.matches(unit_filter_impl::unit_filter_args{u, loc ? *loc : u.get_location(), other_unit, fc_, use_flat_tod_})) {
74  if(max_matches == 0) {
75  return ret;
76  }
77  --max_matches;
78  ret.push_back(&u);
79  }
80  }
81  return ret;
82 }
83 
85  const unit_map & units = fc_->get_disp_context().units();
86  for(unit_map::const_iterator u = units.begin(); u != units.end(); ++u) {
87  if (matches(*u, u->get_location())) {
88  return u.get_shared_ptr();
89  }
90  }
91  return unit_const_ptr();
92 }
93 
94 namespace {
95 bool same_unit(const unit& u, const unit& unit)
96 {
97  return (u.get_location() == unit.get_location() || u.id() == unit.id());
98 }
99 }
100 
101 namespace {
102 
103 struct unit_filter_xy : public unit_filter_base
104 {
105  unit_filter_xy(const std::string& x, const std::string& y) : x_(x), y_(y) {}
106 
107  virtual bool matches(const unit_filter_args& args) const override
108  {
111 
112  if(x.empty() && y.empty()) {
113  return false;
114  }
115  else if(x == "recall" && y == "recall") {
116  return !args.context().get_disp_context().map().on_board(args.loc);
117  }
118  else {
119  return args.loc.matches_range(x, y);
120  }
121  }
122 
123  const std::string x_;
124  const std::string y_;
125 };
126 
127 struct unit_filter_adjacent : public unit_filter_base
128 {
129  unit_filter_adjacent(const vconfig& cfg)
130  : child_(cfg)
131  , cfg_(cfg)
132  {
133  }
134 
135  virtual bool matches(const unit_filter_args& args) const override
136  {
137  const unit_map& units = args.context().get_disp_context().units();
138  const auto adjacent = get_adjacent_tiles(args.loc);
139  int match_count = 0;
140  std::size_t radius = cfg_["radius"].to_int(1);
141 
142  config::attribute_value i_adjacent = cfg_["adjacent"];
143  std::vector<map_location::direction> dirs;
144  for(const unit& u : units) {
145  const map_location& from_loc = u.get_location();
146  std::size_t distance = distance_between(from_loc, args.loc);
147  if(same_unit(u, args.u) || distance > radius || !child_.matches(unit_filter_args{u, from_loc, &args.u, args.fc, args.use_flat_tod} )) {
148  continue;
149  }
150  int dir = 0;
151  for(unsigned j = 0; j < adjacent.size(); ++j) {
152  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], from_loc) == (distance - 1) : adjacent[j] == from_loc;
153  if(adj_or_dist) {
154  dir = j;
155  break;
156  }
157  }
158  assert(dir >= 0 && dir <= 5);
159  map_location::direction direction{ dir };
160  if(!i_adjacent.empty()) { //key adjacent defined
161  if(!utils::contains(map_location::parse_directions(i_adjacent), direction)) {
162  continue;
163  }
164  }
165  auto is_enemy = cfg_["is_enemy"];
166  if (!is_enemy.empty() && is_enemy.to_bool() != args.context().get_disp_context().get_team(args.u.side()).is_enemy(u.side())) {
167  continue;
168  }
169  ++match_count;
170  }
171 
172  config::attribute_value i_count = cfg_["count"];
173  if(i_count.empty() && match_count == 0) {
174  return false;
175  }
176  if(!i_count.empty() && !in_ranges<int>(match_count, utils::parse_ranges_unsigned(i_count.str()))) {
177  return false;
178  }
179  return true;
180  }
181 
182  const unit_filter_compound child_;
183  const vconfig cfg_;
184 };
185 
186 
187 template<typename F>
188 struct unit_filter_child_literal : public unit_filter_base
189 {
190  unit_filter_child_literal(const vconfig& v, const F& f) : v_(v) , f_(f) {}
191  virtual bool matches(const unit_filter_args& args) const override
192  {
193  return f_(v_, args);
194  }
195  vconfig v_;
196  F f_;
197 };
198 
199 template<typename T, typename F>
200 struct unit_filter_attribute_parsed : public unit_filter_base
201 {
202  unit_filter_attribute_parsed(T&& v, F&& f) : v_(std::move(v)), f_(std::move(f)) {}
203  virtual bool matches(const unit_filter_args& args) const override
204  {
205  return f_(v_, args);
206  }
207  T v_;
208  F f_;
209 };
210 
211 template<typename C, typename F>
212 struct unit_filter_attribute_literal : public unit_filter_base
213 {
214  unit_filter_attribute_literal(std::string&& v, C&& c, F&& f) : v_(std::move(v)), c_(std::move(c)), f_(std::move(f)) {}
215  virtual bool matches(const unit_filter_args& args) const override
216  {
219  return f_(c_(v), args);
220  }
221  std::string v_;
222  C c_;
223  F f_;
224 };
225 
226 class contains_dollar_visitor
227 #ifdef USING_BOOST_VARIANT
228  : public boost::static_visitor<bool>
229 #endif
230 {
231 public:
232  contains_dollar_visitor() {}
233 
234 
235  template<typename T>
236  bool operator()(const T&) const { return false; }
237 
238  bool operator()(const t_string&) const { return true; }
239 
240  bool operator()(const std::string& s) const
241  {
242  return s.find('$') != std::string::npos;
243  }
244 };
245 
246 }
247 
248 
249 unit_filter_compound::unit_filter_compound(const vconfig& cfg)
250  : children_()
251  , cond_children_()
252 {
253  fill(cfg);
254 }
255 
257 {
258  bool res;
259 
260  if(args.loc.valid()) {
261  scoped_xy_unit auto_store("this_unit", args.u.get_location(), args.context().get_disp_context().units());
262  if (args.u2) {
263  const map_location& loc2 = args.u2->get_location();
264  scoped_xy_unit u2_auto_store("other_unit", loc2, args.context().get_disp_context().units());
265  res = filter_impl(args);
266  } else {
267  res = filter_impl(args);
268  }
269  } else {
270  // If loc is invalid, then this is a recall list unit (already been scoped)
271  res = filter_impl(args);
272  }
273 
274  // Handle [and], [or], and [not] with in-order precedence
275  for(const auto & filter : cond_children_) {
276  switch (filter.first) {
277  case conditional_type::type::filter_and:
278  res = res && filter.second.matches(args);
279  break;
280  case conditional_type::type::filter_or:
281  res = res || filter.second.matches(args);
282  break;
283  case conditional_type::type::filter_not:
284  res = res && !filter.second.matches(args);
285  break;
286  }
287  }
288  return res;
289 }
290 
292 {
293  for(const auto & filter : children_) {
294  if (!filter->matches(args)) {
295  return false;
296  }
297  }
298  return true;
299 }
300 
301 template<typename F>
303 {
304  children_.emplace_back(new unit_filter_child_literal<F>(c, func));
305 }
306 
307 template<typename C, typename F>
309 {
310  if(v.blank()) {
311  }
312  else if(v.apply_visitor(contains_dollar_visitor())) {
313  children_.emplace_back(new unit_filter_attribute_literal<C, F>(std::move(v.str()), std::move(conv), std::move(func)));
314  }
315  else {
316  children_.emplace_back(new unit_filter_attribute_parsed<decltype(conv(v)), F>(std::move(conv(v)), std::move(func)));
317  }
318 }
319 
321  {
322  const config& literal = cfg.get_config();
323 
324  //optimisation
325  if(literal.empty()) { return; }
326 
327  create_attribute(literal["name"],
328  [](const config::attribute_value& c) { return c.t_str(); },
329  [](const t_string& str, const unit_filter_args& args) { return str == args.u.name(); }
330  );
331 
332  create_attribute(literal["id"],
333  [](const config::attribute_value& c) { return utils::split(c.str()); },
334  [](const std::vector<std::string>& id_list, const unit_filter_args& args)
335  {
336  return std::find(id_list.begin(), id_list.end(), args.u.id()) != id_list.end();
337  }
338  );
339 
340  create_attribute(literal["type"],
341  [](const config::attribute_value& c) { return utils::split(c.str()); },
342  [](const std::vector<std::string>& types, const unit_filter_args& args)
343  {
344  return std::find(types.begin(), types.end(), args.u.type_id()) != types.end();
345  }
346  );
347 
348  create_attribute(literal["type_adv_tree"],
349  [](const config::attribute_value& c) { return utils::split(c.str()); },
350  [](const std::vector<std::string>& types, const unit_filter_args& args)
351  {
352  std::set<std::string> types_expanded;
353  for(const std::string& type : types) {
354  if(types_expanded.count(type)) {
355  continue;
356  }
357  if(const unit_type* ut = unit_types.find(type)) {
358  const auto& tree = ut->advancement_tree();
359  types_expanded.insert(tree.begin(), tree.end());
360  types_expanded.insert(type);
361  }
362  }
363  return types_expanded.find(args.u.type_id()) != types_expanded.end();
364  }
365  );
366 
367  create_attribute(literal["variation"],
368  [](const config::attribute_value& c) { return utils::split(c.str()); },
369  [](const std::vector<std::string>& types, const unit_filter_args& args)
370  {
371  return std::find(types.begin(), types.end(), args.u.variation()) != types.end();
372  }
373  );
374 
375  create_attribute(literal["has_variation"],
376  [](const config::attribute_value& c) { return utils::split(c.str()); },
377  [](const std::vector<std::string>& types, const unit_filter_args& args)
378  {
379  // If this unit is a variation itself then search in the base unit's variations.
380  const unit_type* const type = args.u.variation().empty() ? &args.u.type() : unit_types.find(args.u.type().parent_id());
381  assert(type);
382 
383  for(const std::string& variation_id : types) {
384  if (type->has_variation(variation_id)) {
385  return true;
386  }
387  }
388  return false;
389  }
390  );
391 
392  create_attribute(literal["ability"],
393  [](const config::attribute_value& c) { return utils::split(c.str()); },
394  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
395  {
396  for(const std::string& ability_id : abilities) {
397  if (args.u.has_ability_by_id(ability_id)) {
398  return true;
399  }
400  }
401  return false;
402  }
403  );
404 
405  create_attribute(literal["ability_type"],
406  [](const config::attribute_value& c) { return utils::split(c.str()); },
407  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
408  {
409  for(const std::string& ability : abilities) {
410  if (args.u.has_ability_type(ability)) {
411  return true;
412  }
413  }
414  return false;
415  }
416  );
417 
418  create_attribute(literal["ability_id_active"],
419  [](const config::attribute_value& c) { return utils::split_set(c.str()); },
420  [](const std::set<std::string>& abilities, const unit_filter_args& args)
421  {
422  const unit_map& units = args.context().get_disp_context().units();
423  for(const auto [key, cfg] : args.u.abilities().all_children_view()) {
424  if(abilities.count(cfg["id"]) != 0 && args.u.get_self_ability_bool(cfg, key, args.loc)) {
425  return true;
426  }
427  }
428 
429  for(const unit& unit : units) {
430  if(!unit.has_ability_distant() || unit.incapacitated() || same_unit(unit, args.u)) {
431  continue;
432  }
433  const map_location& from_loc = unit.get_location();
434  std::size_t distance = distance_between(from_loc, args.loc);
435  if(distance > *unit.has_ability_distant()) {
436  continue;
437  }
438  utils::optional<int> dir;
439  const auto adjacent = get_adjacent_tiles(from_loc);
440  for(std::size_t j = 0; j < adjacent.size(); ++j) {
441  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], args.loc) == (distance - 1) : adjacent[j] == args.loc;
442  if(adj_or_dist) {
443  dir = j;
444  break;
445  }
446  }
447  for(const auto [key, cfg] : unit.abilities().all_children_view()) {
448  if(abilities.count(cfg["id"]) != 0 && args.u.get_adj_ability_bool(cfg, key, distance, *dir, args.loc, unit, from_loc)) {
449  return true;
450  }
451  }
452  }
453  return false;
454  }
455  );
456 
457  create_attribute(literal["ability_type_active"],
458  [](const config::attribute_value& c) { return utils::split(c.str()); },
459  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
460  {
461  for(const std::string& ability : abilities) {
462  if (!args.u.get_abilities(ability, args.loc).empty()) {
463  return true;
464  }
465  }
466  return false;
467  }
468  );
469 
470  create_attribute(literal["trait"],
471  [](const config::attribute_value& c)
472  {
473  auto res = utils::split(c.str());
474  std::sort(res.begin(), res.end());
475  return res;
476 
477  },
478  [](const std::vector<std::string>& check_traits, const unit_filter_args& args)
479  {
480 
481  std::vector<std::string> have_traits = args.u.get_traits_list();
482  std::vector<std::string> isect;
483  std::sort(have_traits.begin(), have_traits.end());
484  std::set_intersection(check_traits.begin(), check_traits.end(), have_traits.begin(), have_traits.end(), std::back_inserter(isect));
485  return !isect.empty();
486  }
487  );
488 
489  create_attribute(literal["race"],
490  [](const config::attribute_value& c) { return utils::split(c.str()); },
491  [](const std::vector<std::string>& races, const unit_filter_args& args)
492  {
493  return std::find(races.begin(), races.end(), args.u.race()->id()) != races.end();
494  }
495  );
496 
497  create_attribute(literal["gender"],
498  [](const config::attribute_value& c) { return string_gender(c.str()); },
499  [](unit_race::GENDER gender, const unit_filter_args& args)
500  {
501  return gender == args.u.gender();
502  }
503  );
504 
505  create_attribute(literal["upkeep"],
507  {
508  try {
509  return c.apply_visitor(unit::upkeep_parser_visitor());
510  } catch(std::invalid_argument&) {
511  return unit::upkeep_full();
512  }
513  },
514  [](unit::upkeep_t upkeep, const unit_filter_args& args)
515  {
516  return args.u.upkeep() == utils::visit(unit::upkeep_value_visitor{args.u}, upkeep);
517  }
518  );
519 
520  create_attribute(literal["side"],
521  [](const config::attribute_value& c)
522  {
523  std::vector<int> res;
524  for(const std::string& s : utils::split(c.str())) {
525  try {
526  res.push_back(std::stoi(s));
527  } catch(std::invalid_argument&) {
528  WRN_CF << "ignored invalid side='" << s << "' in filter";
529  }
530  }
531  return res;
532  },
533  [](const std::vector<int>& sides, const unit_filter_args& args)
534  {
535  return std::find(sides.begin(), sides.end(), args.u.side()) != sides.end();
536  }
537  );
538 
539  create_attribute(literal["status"],
540  [](const config::attribute_value& c) { return utils::split(c.str()); },
541  [](const std::vector<std::string>& statuses, const unit_filter_args& args)
542  {
543  for(const std::string& status : statuses) {
544  if (args.u.get_state(status)) {
545  return true;
546  }
547  }
548  return false;
549  }
550  );
551 
552  create_attribute(literal["has_weapon"],
553  [](const config::attribute_value& c) { return c.str(); },
554  [](const std::string& weapon, const unit_filter_args& args)
555  {
556 
557  for(const attack_type& a : args.u.attacks()) {
558  if(a.id() == weapon) {
559  return true;
560  }
561  }
562  return false;
563  }
564  );
565 
566  create_attribute(literal["role"],
567  [](const config::attribute_value& c) { return c.str(); },
568  [](const std::string& role, const unit_filter_args& args)
569  {
570  return args.u.get_role() == role;
571  }
572  );
573 
574  create_attribute(literal["alignment"],
575  [](const config::attribute_value& c) { return c.str(); },
576  [](const std::string& alignment, const unit_filter_args& args)
577  {
578  return unit_alignments::get_string(args.u.alignment()) == alignment;
579  }
580  );
581 
582  create_attribute(literal["ai_special"],
583  [](const config::attribute_value& c) { return c.str(); },
584  [](const std::string& ai_special, const unit_filter_args& args)
585  {
586  return (ai_special == "guardian") == args.u.get_state(unit::STATE_GUARDIAN);
587  }
588  );
589 
590  create_attribute(literal["usage"],
591  [](const config::attribute_value& c) { return utils::split(c.str()); },
592  [](const std::vector<std::string>& usages, const unit_filter_args& args)
593  {
594  for(const std::string& usage : usages) {
595  if(args.u.usage() == usage) {
596  return true;
597  }
598  }
599  return false;
600  }
601  );
602 
603  create_attribute(literal["canrecruit"],
604  [](const config::attribute_value& c) { return c.to_bool(); },
605  [](bool canrecruit, const unit_filter_args& args)
606  {
607  return args.u.can_recruit() == canrecruit;
608  }
609  );
610 
611  create_attribute(literal["recall_cost"],
612  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
613  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
614  {
615  return in_ranges(args.u.recall_cost(), ranges);
616  }
617  );
618 
619  create_attribute(literal["level"],
620  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
621  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
622  {
623  return in_ranges(args.u.level(), ranges);
624  }
625  );
626 
627  create_attribute(literal["defense"],
628  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
629  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
630  {
631  int actual_defense = args.u.defense_modifier(args.context().get_disp_context().map().get_terrain(args.loc));
632  return in_ranges(actual_defense, ranges);
633  }
634  );
635 
636  create_attribute(literal["movement_cost"],
637  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
638  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
639  {
640  int actual_cost = args.u.movement_cost(args.context().get_disp_context().map().get_terrain(args.loc));
641  return in_ranges(actual_cost, ranges);
642  }
643  );
644 
645  create_attribute(literal["vision_cost"],
646  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
647  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
648  {
649  int actual_cost = args.u.vision_cost(args.context().get_disp_context().map().get_terrain(args.loc));
650  return in_ranges(actual_cost, ranges);
651  }
652  );
653 
654  create_attribute(literal["jamming_cost"],
655  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
656  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
657  {
658  int actual_cost = args.u.jamming_cost(args.context().get_disp_context().map().get_terrain(args.loc));
659  return in_ranges(actual_cost, ranges);
660  }
661  );
662 
663  create_attribute(literal["lua_function"],
664  [](const config::attribute_value& c) { return c.str(); },
665  [](const std::string& lua_function, const unit_filter_args& args)
666  {
667  if (game_lua_kernel * lk = args.context().get_lua_kernel()) {
668  return lk->run_filter(lua_function.c_str(), args.u);
669  }
670  return true;
671  }
672  );
673 
674  create_attribute(literal["formula"],
675  [](const config::attribute_value& c)
676  {
677  try {
679  } catch(const wfl::formula_error& e) {
680  lg::log_to_chat() << "Formula error while evaluating formula in unit filter: " << e.type << " at "
681  << e.filename << ':' << e.line << ")\n";
682  ERR_WML << "Formula error while evaluating formula in unit filter: " << e.type << " at "
683  << e.filename << ':' << e.line << ")";
684  return wfl::formula("");
685  }
686  },
687  [](const wfl::formula& form, const unit_filter_args& args)
688  {
689  try {
690  const wfl::unit_callable main(args.loc, args.u);
691  wfl::map_formula_callable callable(main.fake_ptr());
692  if (args.u2) {
693  auto secondary = std::make_shared<wfl::unit_callable>(*args.u2);
694  callable.add("other", wfl::variant(secondary));
695  // It's not destroyed upon scope exit because the variant holds a reference
696  }
697  if(!form.evaluate(callable).as_bool()) {
698  return false;
699  }
700  return true;
701  } catch(const wfl::formula_error& e) {
702  lg::log_to_chat() << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
703  ERR_WML << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
704  // Formulae with syntax errors match nothing
705  return false;
706  }
707  }
708  );
709 
710  create_attribute(literal["find_in"],
711  [](const config::attribute_value& c) { return c.str(); },
712  [](const std::string& find_in, const unit_filter_args& args)
713  {
714  // Allow filtering by searching a stored variable of units
715  if (const game_data * gd = args.context().get_game_data()) {
716  try
717  {
718  for (const config& c : gd->get_variable_access_read(find_in).as_array())
719  {
720  if(c["id"] == args.u.id()) {
721  return true;
722  }
723  }
724  return false;
725  }
726  catch(const invalid_variablename_exception&)
727  {
728  return false;
729  }
730  }
731  return true;
732  }
733  );
734 
735  if (!literal["x"].blank() || !literal["y"].blank()) {
736  children_.emplace_back(new unit_filter_xy(literal["x"], literal["y"]));
737  }
738 
739  for(auto child : cfg.all_ordered()) {
740  auto cond = conditional_type::get_enum(child.first);
741  if(cond) {
742  cond_children_.emplace_back(std::piecewise_construct_t(), std::tuple(*cond), std::tuple(child.second));
743  }
744  else if (child.first == "filter_wml") {
745  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
746  config fwml = c.get_parsed_config();
747 
748  /* Check if the filter only cares about variables.
749  If so, no need to serialize the whole unit. */
750  config::all_children_itors ci = fwml.all_children_range();
751  if (fwml.all_children_count() == 1 && fwml.attribute_count() == 1 && ci.front().key == "variables") {
752  return args.u.variables().matches(ci.front().cfg);
753  } else {
754  config ucfg;
755  args.u.write(ucfg);
756  return ucfg.matches(fwml);
757  }
758  });
759  }
760  else if (child.first == "filter_vision") {
761  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
762  std::set<int> viewers;
763  side_filter ssf(c, args.fc);
764  std::vector<int> sides = ssf.get_teams();
765  viewers.insert(sides.begin(), sides.end());
766 
767  for (const int viewer : viewers) {
768  bool fogged = args.context().get_disp_context().get_team(viewer).fogged(args.loc);
769  // Check is_enemy() before invisible() to prevent infinite recursion in [abilities][hides][filter_self][filter_vision]
770  bool hiding = args.context().get_disp_context().get_team(viewer).is_enemy(args.u.side()) && args.u.invisible(args.loc);
771  bool unit_hidden = fogged || hiding;
772  if (c["visible"].to_bool(true) != unit_hidden) {
773  return true;
774  }
775  }
776  return false;
777  });
778  }
779  else if (child.first == "filter_adjacent") {
780  children_.emplace_back(new unit_filter_adjacent(child.second));
781  }
782  else if (child.first == "filter_location") {
783  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
784  return terrain_filter(c, args.fc, args.use_flat_tod).match(args.loc);
785  });
786  }
787  else if (child.first == "filter_side") {
788  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
789  return side_filter(c, args.fc).match(args.u.side());
790  });
791  }
792  else if ((child.first == "filter_ability") || (child.first == "experimental_filter_ability")) {
793  if(child.first == "experimental_filter_ability"){
794  deprecated_message("experimental_filter_ability", DEP_LEVEL::INDEFINITE, "", "Use filter_ability instead.");
795  }
796  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
797  if(!(c.get_parsed_config())["active"].to_bool()){
798  for(const auto [key, cfg] : args.u.abilities().all_children_view()) {
799  if(args.u.ability_matches_filter(cfg, key, c.get_parsed_config())) {
800  return true;
801  }
802  }
803  } else {
804  const unit_map& units = args.context().get_disp_context().units();
805  for(const auto [key, cfg] : args.u.abilities().all_children_view()) {
806  if(args.u.ability_matches_filter(cfg, key, c.get_parsed_config())) {
807  if (args.u.get_self_ability_bool(cfg, key, args.loc)) {
808  return true;
809  }
810  }
811  }
812 
813  if(c.get_parsed_config()["affect_adjacent"].to_bool(true)) {
814  for(const unit& unit : units) {
815  if(!unit.has_ability_distant() || unit.incapacitated() || same_unit(unit, args.u)) {
816  continue;
817  }
818  const map_location& from_loc = unit.get_location();
819  std::size_t distance = distance_between(from_loc, args.loc);
820  if(distance > *unit.has_ability_distant()) {
821  continue;
822  }
823  utils::optional<int> dir;
824  const auto adjacent = get_adjacent_tiles(from_loc);
825  for(std::size_t j = 0; j < adjacent.size(); ++j) {
826  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], args.loc) == (distance - 1) : adjacent[j] == args.loc;
827  if(adj_or_dist) {
828  dir = j;
829  break;
830  }
831  }
832  for(const auto [key, cfg] : unit.abilities().all_children_view()) {
833  if(args.u.get_adj_ability_bool(cfg, key, distance, *dir, args.loc, unit, from_loc)) {
834  return true;
835  }
836  }
837  }
838  }
839  }
840  return false;
841  });
842  }
843  else if (child.first == "has_attack") {
844  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
845  for(const attack_type& a : args.u.attacks()) {
846  if(a.matches_filter(c.get_parsed_config())) {
847  return true;
848  }
849  }
850  return false;
851  });
852  }
853  else {
854  std::stringstream errmsg;
855  errmsg << "encountered a child [" << child.first << "] of a standard unit filter, it is being ignored";
856  DBG_CF << errmsg.str();
857  }
858 
859  }
860  }
map_location loc
Definition: move.cpp:172
Variant for storing WML attributes.
std::string str(const std::string &fallback="") const
auto apply_visitor(const V &visitor) const
Visitor support: Applies a visitor to the underlying variant.
bool blank() const
Tests for an attribute that was never set.
bool empty() const
Tests for an attribute that either was never set or was set to "".
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
bool empty() const
Definition: config.cpp:845
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
virtual const gamemap & map() 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
virtual const game_data * get_game_data() const =0
terrain_code get_terrain(const map_location &loc) const
Looks up terrain at a particular location.
Definition: map.cpp:301
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:384
bool is_enemy(int n) const
Definition: team.hpp:267
Visitor helper class to parse the upkeep value from a config.
Definition: unit.hpp:1247
Visitor helper class to fetch the appropriate upkeep value.
Definition: unit.hpp:1197
bool empty() const
Definition: unit.hpp:89
unit_filter(const vconfig &cfg)
Definition: filter.cpp:50
const filter_context * fc_
Definition: filter.hpp:183
int max_matches_
Definition: filter.hpp:186
bool use_flat_tod_
Definition: filter.hpp:184
bool matches(const unit &u, const map_location &loc) const
Determine if *this matches filter at a specified location.
Definition: filter.hpp:123
std::vector< const unit * > all_matches_on_map(const map_location *loc=nullptr, const unit *other_unit=nullptr) const
Definition: filter.cpp:67
unit_const_ptr first_match_on_map() const
Definition: filter.cpp:84
unit_filter_impl::unit_filter_compound impl_
Definition: filter.hpp:185
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator begin()
Definition: map.hpp:418
const std::string & id() const
Definition: race.hpp:35
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition: types.cpp:1224
A single unit type that the player may recruit.
Definition: types.hpp:43
const std::string & parent_id() const
The id of the original type from which this (variation) descended.
Definition: types.hpp:148
This class represents a single unit of a specific type.
Definition: unit.hpp:132
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
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:313
Definitions for the interface to Wesnoth Markup Language (WML).
std::string deprecated_message(const std::string &elem_name, DEP_LEVEL level, const version_info &version, const std::string &detail)
Definition: deprecation.cpp:29
const config * cfg
Interfaces for manipulating version numbers of engine, add-ons, etc.
bool get_adj_ability_bool(const config &cfg, const std::string &ability, std::size_t dist, int dir, const map_location &loc, const unit &from, const map_location &from_loc) const
Checks whether this unit is affected by a given ability, and that that ability is active.
Definition: abilities.cpp:1709
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:623
unit_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:251
bool get_self_ability_bool(const config &cfg, const std::string &ability, const map_location &loc) const
Checks whether this unit currently possesses a given ability, and that that ability is active.
Definition: abilities.cpp:1699
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1505
const config & abilities() const
Definition: unit.hpp:1838
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:490
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:919
int level() const
The current level of this unit.
Definition: unit.hpp:574
std::string usage() const
Gets this unit's usage.
Definition: unit.hpp:701
const std::string & get_role() const
Gets this unit's role.
Definition: unit.hpp:684
int recall_cost() const
How much gold it costs to recall this unit, or -1 if the side's default recall cost is used.
Definition: unit.hpp:655
const std::string & variation() const
The ID of the variation of this unit's type.
Definition: unit.hpp:587
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1423
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1958
const unit_race * race() const
Gets this unit's race.
Definition: unit.hpp:508
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:354
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:627
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:379
int side() const
The side this unit belongs to.
Definition: unit.hpp:342
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:480
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:402
@ STATE_GUARDIAN
The unit cannot be healed.
Definition: unit.hpp:874
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1754
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:941
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1431
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1514
int vision_cost(const t_translation::terrain_code &terrain) const
Get the unit's vision cost on a particular terrain.
Definition: unit.hpp:1524
int jamming_cost(const t_translation::terrain_code &terrain) const
Get the unit's jamming cost on a particular terrain.
Definition: unit.hpp:1534
int upkeep() const
Gets the amount of gold this unit costs a side per turn.
Definition: unit.cpp:1728
utils::optional< std::size_t > has_ability_distant() const
Gets if this unit own ability with [affect_adjacent] subtags.
Definition: unit.hpp:1321
utils::variant< upkeep_full, upkeep_loyal, int > upkeep_t
Definition: unit.hpp:1190
std::vector< std::string > get_traits_list() const
Gets a list of the traits this unit currently has, including hidden traits.
Definition: unit.hpp:1143
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:583
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:550
std::function< int(lua_State *)> lua_function
game_data * gamedata
Definition: resources.cpp:22
filter_context * filter_con
Definition: resources.cpp:23
constexpr auto filter
Definition: ranges.hpp:38
std::string interpolate_variables_into_string(const std::string &str, const string_map *const symbols)
Function which will interpolate variables, starting with '$' in the string 'str' with the equivalent ...
std::set< std::string > split_set(std::string_view s, char sep, const int flags)
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
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
pump_impl & impl_
Definition: pump.cpp:131
unit_race::GENDER string_gender(const std::string &str, unit_race::GENDER def)
Definition: race.cpp:147
int main(int, char **)
Definition: sdl2.cpp:25
Encapsulates the map of the game.
Definition: location.hpp:45
static std::vector< direction > parse_directions(const std::string &str)
Parse_directions takes a comma-separated list, and filters out any invalid directions.
Definition: location.cpp:138
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
bool matches_range(const std::string &xloc, const std::string &yloc) const
Definition: location.cpp:320
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
static constexpr utils::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
const filter_context & context() const
Definition: filter.hpp:62
virtual bool matches(const unit_filter_args &) const =0
bool filter_impl(const unit_filter_args &u) const
Definition: filter.cpp:291
void fill(const vconfig &cfg)
Definition: filter.cpp:320
void create_child(const vconfig &c, F func)
Definition: filter.cpp:302
void create_attribute(const config::attribute_value &c, C conv, F func)
Definition: filter.cpp:308
std::vector< std::pair< conditional_type::type, unit_filter_compound > > cond_children_
Definition: filter.hpp:97
virtual bool matches(const unit_filter_args &u) const override
Definition: filter.cpp:256
std::vector< std::shared_ptr< unit_filter_base > > children_
Definition: filter.hpp:96
mock_char c
static map_location::direction s
unit_type_data unit_types
Definition: types.cpp:1463
#define DBG_CF
Definition: filter.cpp:43
#define ERR_WML
Definition: filter.cpp:46
static lg::log_domain log_wml("wml")
static lg::log_domain log_config("config")
#define e
#define f