The Battle for Wesnoth  1.19.12+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
33 #include "formula/callable_objects.hpp"
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 
96 struct unit_filter_xy : public unit_filter_base
97 {
98  unit_filter_xy(const std::string& x, const std::string& y) : x_(x), y_(y) {}
99 
100  virtual bool matches(const unit_filter_args& args) const override
101  {
104 
105  if(x.empty() && y.empty()) {
106  return false;
107  }
108  else if(x == "recall" && y == "recall") {
109  return !args.context().get_disp_context().map().on_board(args.loc);
110  }
111  else {
112  return args.loc.matches_range(x, y);
113  }
114  }
115 
116  const std::string x_;
117  const std::string y_;
118 };
119 
120 struct unit_filter_adjacent : public unit_filter_base
121 {
122  unit_filter_adjacent(const vconfig& cfg)
123  : child_(cfg)
124  , cfg_(cfg)
125  {
126  }
127 
128  virtual bool matches(const unit_filter_args& args) const override
129  {
130  const unit_map& units = args.context().get_disp_context().units();
131  const auto adjacent = get_adjacent_tiles(args.loc);
132  int match_count=0;
133 
134  config::attribute_value i_adjacent = cfg_["adjacent"];
135  std::vector<map_location::direction> dirs;
136  if (i_adjacent.empty()) {
138  } else {
139  dirs = map_location::parse_directions(i_adjacent);
140  }
141  for (map_location::direction dir : dirs) {
142  unit_map::const_iterator unit_itor = units.find(adjacent[static_cast<int>(dir)]);
143  if (unit_itor == units.end() || !child_.matches(unit_filter_args{*unit_itor, unit_itor->get_location(), &args.u, args.fc, args.use_flat_tod} )) {
144  continue;
145  }
146  auto is_enemy = cfg_["is_enemy"];
147  if (!is_enemy.empty() && is_enemy.to_bool() != args.context().get_disp_context().get_team(args.u.side()).is_enemy(unit_itor->side())) {
148  continue;
149  }
150  ++match_count;
151  }
152 
153  static std::vector<std::pair<int,int>> default_counts = utils::parse_ranges_unsigned("1-6");
154  config::attribute_value i_count = cfg_["count"];
155  return in_ranges(match_count, !i_count.blank() ? utils::parse_ranges_unsigned(i_count) : default_counts);
156  }
157 
158  const unit_filter_compound child_;
159  const vconfig cfg_;
160 };
161 
162 
163 template<typename F>
164 struct unit_filter_child_literal : public unit_filter_base
165 {
166  unit_filter_child_literal(const vconfig& v, const F& f) : v_(v) , f_(f) {}
167  virtual bool matches(const unit_filter_args& args) const override
168  {
169  return f_(v_, args);
170  }
171  vconfig v_;
172  F f_;
173 };
174 
175 template<typename T, typename F>
176 struct unit_filter_attribute_parsed : public unit_filter_base
177 {
178  unit_filter_attribute_parsed(T&& v, F&& f) : v_(std::move(v)), f_(std::move(f)) {}
179  virtual bool matches(const unit_filter_args& args) const override
180  {
181  return f_(v_, args);
182  }
183  T v_;
184  F f_;
185 };
186 
187 template<typename C, typename F>
188 struct unit_filter_attribute_literal : public unit_filter_base
189 {
190  unit_filter_attribute_literal(std::string&& v, C&& c, F&& f) : v_(std::move(v)), c_(std::move(c)), f_(std::move(f)) {}
191  virtual bool matches(const unit_filter_args& args) const override
192  {
195  return f_(c_(v), args);
196  }
197  std::string v_;
198  C c_;
199  F f_;
200 };
201 
202 class contains_dollar_visitor
203 #ifdef USING_BOOST_VARIANT
204  : public boost::static_visitor<bool>
205 #endif
206 {
207 public:
208  contains_dollar_visitor() {}
209 
210 
211  template<typename T>
212  bool operator()(const T&) const { return false; }
213 
214  bool operator()(const t_string&) const { return true; }
215 
216  bool operator()(const std::string& s) const
217  {
218  return s.find('$') != std::string::npos;
219  }
220 };
221 
222 }
223 
224 
225 unit_filter_compound::unit_filter_compound(const vconfig& cfg)
226  : children_()
227  , cond_children_()
228 {
229  fill(cfg);
230 }
231 
233 {
234  bool res;
235 
236  if(args.loc.valid()) {
237  scoped_xy_unit auto_store("this_unit", args.u.get_location(), args.context().get_disp_context().units());
238  if (args.u2) {
239  const map_location& loc2 = args.u2->get_location();
240  scoped_xy_unit u2_auto_store("other_unit", loc2, args.context().get_disp_context().units());
241  res = filter_impl(args);
242  } else {
243  res = filter_impl(args);
244  }
245  } else {
246  // If loc is invalid, then this is a recall list unit (already been scoped)
247  res = filter_impl(args);
248  }
249 
250  // Handle [and], [or], and [not] with in-order precedence
251  for(const auto & filter : cond_children_) {
252  switch (filter.first) {
253  case conditional_type::type::filter_and:
254  res = res && filter.second.matches(args);
255  break;
256  case conditional_type::type::filter_or:
257  res = res || filter.second.matches(args);
258  break;
259  case conditional_type::type::filter_not:
260  res = res && !filter.second.matches(args);
261  break;
262  }
263  }
264  return res;
265 }
266 
268 {
269  for(const auto & filter : children_) {
270  if (!filter->matches(args)) {
271  return false;
272  }
273  }
274  return true;
275 }
276 
277 template<typename F>
279 {
280  children_.emplace_back(new unit_filter_child_literal<F>(c, func));
281 }
282 
283 template<typename C, typename F>
285 {
286  if(v.blank()) {
287  }
288  else if(v.apply_visitor(contains_dollar_visitor())) {
289  children_.emplace_back(new unit_filter_attribute_literal<C, F>(std::move(v.str()), std::move(conv), std::move(func)));
290  }
291  else {
292  children_.emplace_back(new unit_filter_attribute_parsed<decltype(conv(v)), F>(std::move(conv(v)), std::move(func)));
293  }
294 }
295 
296 namespace {
297 
298  struct ability_match
299  {
300  std::string tag_name;
301  const config* cfg;
302  };
303 
304  void get_ability_children_id(std::vector<ability_match>& id_result,
305  const config& parent, const std::string& id) {
306  for (const auto [key, cfg] : parent.all_children_view())
307  {
308  if(cfg["id"] == id) {
309  ability_match special = { key, &cfg };
310  id_result.push_back(special);
311  }
312  }
313  }
314 }
315 
317  {
318  const config& literal = cfg.get_config();
319 
320  //optimisation
321  if(literal.empty()) { return; }
322 
323  create_attribute(literal["name"],
324  [](const config::attribute_value& c) { return c.t_str(); },
325  [](const t_string& str, const unit_filter_args& args) { return str == args.u.name(); }
326  );
327 
328  create_attribute(literal["id"],
329  [](const config::attribute_value& c) { return utils::split(c.str()); },
330  [](const std::vector<std::string>& id_list, const unit_filter_args& args)
331  {
332  return std::find(id_list.begin(), id_list.end(), args.u.id()) != id_list.end();
333  }
334  );
335 
336  create_attribute(literal["type"],
337  [](const config::attribute_value& c) { return utils::split(c.str()); },
338  [](const std::vector<std::string>& types, const unit_filter_args& args)
339  {
340  return std::find(types.begin(), types.end(), args.u.type_id()) != types.end();
341  }
342  );
343 
344  create_attribute(literal["type_adv_tree"],
345  [](const config::attribute_value& c) { return utils::split(c.str()); },
346  [](const std::vector<std::string>& types, const unit_filter_args& args)
347  {
348  std::set<std::string> types_expanded;
349  for(const std::string& type : types) {
350  if(types_expanded.count(type)) {
351  continue;
352  }
353  if(const unit_type* ut = unit_types.find(type)) {
354  const auto& tree = ut->advancement_tree();
355  types_expanded.insert(tree.begin(), tree.end());
356  types_expanded.insert(type);
357  }
358  }
359  return types_expanded.find(args.u.type_id()) != types_expanded.end();
360  }
361  );
362 
363  create_attribute(literal["variation"],
364  [](const config::attribute_value& c) { return utils::split(c.str()); },
365  [](const std::vector<std::string>& types, const unit_filter_args& args)
366  {
367  return std::find(types.begin(), types.end(), args.u.variation()) != types.end();
368  }
369  );
370 
371  create_attribute(literal["has_variation"],
372  [](const config::attribute_value& c) { return utils::split(c.str()); },
373  [](const std::vector<std::string>& types, const unit_filter_args& args)
374  {
375  // If this unit is a variation itself then search in the base unit's variations.
376  const unit_type* const type = args.u.variation().empty() ? &args.u.type() : unit_types.find(args.u.type().parent_id());
377  assert(type);
378 
379  for(const std::string& variation_id : types) {
380  if (type->has_variation(variation_id)) {
381  return true;
382  }
383  }
384  return false;
385  }
386  );
387 
388  create_attribute(literal["ability"],
389  [](const config::attribute_value& c) { return utils::split(c.str()); },
390  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
391  {
392  for(const std::string& ability_id : abilities) {
393  if (args.u.has_ability_by_id(ability_id)) {
394  return true;
395  }
396  }
397  return false;
398  }
399  );
400 
401  create_attribute(literal["ability_type"],
402  [](const config::attribute_value& c) { return utils::split(c.str()); },
403  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
404  {
405  for(const std::string& ability : abilities) {
406  if (args.u.has_ability_type(ability)) {
407  return true;
408  }
409  }
410  return false;
411  }
412  );
413 
414  create_attribute(literal["ability_id_active"],
415  [](const config::attribute_value& c) { return utils::split(c.str()); },
416  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
417  {
418  const unit_map& units = args.context().get_disp_context().units();
419  for(const std::string& ability : abilities) {
420  std::vector<ability_match> ability_id_matches_self;
421  get_ability_children_id(ability_id_matches_self, args.u.abilities(), ability);
422  for(const ability_match& entry : ability_id_matches_self) {
423  if (args.u.get_self_ability_bool(*entry.cfg, entry.tag_name, args.loc)) {
424  return true;
425  }
426  }
427 
428  for(const unit& unit : units) {
429  if(!unit.has_ability_distant() || unit.incapacitated() || &unit == args.u.shared_from_this().get()) {
430  continue;
431  }
432  const map_location& from_loc = unit.get_location();
433  std::size_t distance = distance_between(from_loc, args.loc);
434  if(distance > *unit.has_ability_distant()) {
435  continue;
436  }
437  utils::optional<int> dir;
438  const auto adjacent = get_adjacent_tiles(from_loc);
439  for(std::size_t j = 0; j < adjacent.size(); ++j) {
440  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], args.loc) == (distance - 1) : adjacent[j] == args.loc;
441  if(adj_or_dist) {
442  dir = j;
443  break;
444  }
445  }
446  std::vector<ability_match> ability_id_matches_adj;
447  get_ability_children_id(ability_id_matches_adj, unit.abilities(), ability);
448  for(const ability_match& entry : ability_id_matches_adj) {
449  if(args.u.get_adj_ability_bool(*entry.cfg, entry.tag_name, distance, *dir, args.loc, unit, from_loc)) {
450  return true;
451  }
452  }
453  }
454  }
455  return false;
456  }
457  );
458 
459  create_attribute(literal["ability_type_active"],
460  [](const config::attribute_value& c) { return utils::split(c.str()); },
461  [](const std::vector<std::string>& abilities, const unit_filter_args& args)
462  {
463  for(const std::string& ability : abilities) {
464  if (!args.u.get_abilities(ability, args.loc).empty()) {
465  return true;
466  }
467  }
468  return false;
469  }
470  );
471 
472  create_attribute(literal["trait"],
473  [](const config::attribute_value& c)
474  {
475  auto res = utils::split(c.str());
476  std::sort(res.begin(), res.end());
477  return res;
478 
479  },
480  [](const std::vector<std::string>& check_traits, const unit_filter_args& args)
481  {
482 
483  std::vector<std::string> have_traits = args.u.get_traits_list();
484  std::vector<std::string> isect;
485  std::sort(have_traits.begin(), have_traits.end());
486  std::set_intersection(check_traits.begin(), check_traits.end(), have_traits.begin(), have_traits.end(), std::back_inserter(isect));
487  return !isect.empty();
488  }
489  );
490 
491  create_attribute(literal["race"],
492  [](const config::attribute_value& c) { return utils::split(c.str()); },
493  [](const std::vector<std::string>& races, const unit_filter_args& args)
494  {
495  return std::find(races.begin(), races.end(), args.u.race()->id()) != races.end();
496  }
497  );
498 
499  create_attribute(literal["gender"],
500  [](const config::attribute_value& c) { return string_gender(c.str()); },
501  [](unit_race::GENDER gender, const unit_filter_args& args)
502  {
503  return gender == args.u.gender();
504  }
505  );
506 
507  create_attribute(literal["upkeep"],
509  {
510  try {
511  return c.apply_visitor(unit::upkeep_parser_visitor());
512  } catch(std::invalid_argument&) {
513  return unit::upkeep_full();
514  }
515  },
516  [](unit::upkeep_t upkeep, const unit_filter_args& args)
517  {
518  return args.u.upkeep() == utils::visit(unit::upkeep_value_visitor{args.u}, upkeep);
519  }
520  );
521 
522  create_attribute(literal["side"],
523  [](const config::attribute_value& c)
524  {
525  std::vector<int> res;
526  for(const std::string& s : utils::split(c.str())) {
527  try {
528  res.push_back(std::stoi(s));
529  } catch(std::invalid_argument&) {
530  WRN_CF << "ignored invalid side='" << s << "' in filter";
531  }
532  }
533  return res;
534  },
535  [](const std::vector<int>& sides, const unit_filter_args& args)
536  {
537  return std::find(sides.begin(), sides.end(), args.u.side()) != sides.end();
538  }
539  );
540 
541  create_attribute(literal["status"],
542  [](const config::attribute_value& c) { return utils::split(c.str()); },
543  [](const std::vector<std::string>& statuses, const unit_filter_args& args)
544  {
545  for(const std::string& status : statuses) {
546  if (args.u.get_state(status)) {
547  return true;
548  }
549  }
550  return false;
551  }
552  );
553 
554  create_attribute(literal["has_weapon"],
555  [](const config::attribute_value& c) { return c.str(); },
556  [](const std::string& weapon, const unit_filter_args& args)
557  {
558 
559  for(const attack_type& a : args.u.attacks()) {
560  if(a.id() == weapon) {
561  return true;
562  }
563  }
564  return false;
565  }
566  );
567 
568  create_attribute(literal["role"],
569  [](const config::attribute_value& c) { return c.str(); },
570  [](const std::string& role, const unit_filter_args& args)
571  {
572  return args.u.get_role() == role;
573  }
574  );
575 
576  create_attribute(literal["alignment"],
577  [](const config::attribute_value& c) { return c.str(); },
578  [](const std::string& alignment, const unit_filter_args& args)
579  {
580  return unit_alignments::get_string(args.u.alignment()) == alignment;
581  }
582  );
583 
584  create_attribute(literal["ai_special"],
585  [](const config::attribute_value& c) { return c.str(); },
586  [](const std::string& ai_special, const unit_filter_args& args)
587  {
588  return (ai_special == "guardian") == args.u.get_state(unit::STATE_GUARDIAN);
589  }
590  );
591 
592  create_attribute(literal["usage"],
593  [](const config::attribute_value& c) { return utils::split(c.str()); },
594  [](const std::vector<std::string>& usages, const unit_filter_args& args)
595  {
596  for(const std::string& usage : usages) {
597  if(args.u.usage() == usage) {
598  return true;
599  }
600  }
601  return false;
602  }
603  );
604 
605  create_attribute(literal["canrecruit"],
606  [](const config::attribute_value& c) { return c.to_bool(); },
607  [](bool canrecruit, const unit_filter_args& args)
608  {
609  return args.u.can_recruit() == canrecruit;
610  }
611  );
612 
613  create_attribute(literal["recall_cost"],
614  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
615  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
616  {
617  return in_ranges(args.u.recall_cost(), ranges);
618  }
619  );
620 
621  create_attribute(literal["level"],
622  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
623  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
624  {
625  return in_ranges(args.u.level(), ranges);
626  }
627  );
628 
629  create_attribute(literal["defense"],
630  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
631  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
632  {
633  int actual_defense = args.u.defense_modifier(args.context().get_disp_context().map().get_terrain(args.loc));
634  return in_ranges(actual_defense, ranges);
635  }
636  );
637 
638  create_attribute(literal["movement_cost"],
639  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
640  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
641  {
642  int actual_cost = args.u.movement_cost(args.context().get_disp_context().map().get_terrain(args.loc));
643  return in_ranges(actual_cost, ranges);
644  }
645  );
646 
647  create_attribute(literal["vision_cost"],
648  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
649  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
650  {
651  int actual_cost = args.u.vision_cost(args.context().get_disp_context().map().get_terrain(args.loc));
652  return in_ranges(actual_cost, ranges);
653  }
654  );
655 
656  create_attribute(literal["jamming_cost"],
657  [](const config::attribute_value& c) { return utils::parse_ranges_unsigned(c.str()); },
658  [](const std::vector<std::pair<int,int>>& ranges, const unit_filter_args& args)
659  {
660  int actual_cost = args.u.jamming_cost(args.context().get_disp_context().map().get_terrain(args.loc));
661  return in_ranges(actual_cost, ranges);
662  }
663  );
664 
665  create_attribute(literal["lua_function"],
666  [](const config::attribute_value& c) { return c.str(); },
667  [](const std::string& lua_function, const unit_filter_args& args)
668  {
669  if (game_lua_kernel * lk = args.context().get_lua_kernel()) {
670  return lk->run_filter(lua_function.c_str(), args.u);
671  }
672  return true;
673  }
674  );
675 
676  create_attribute(literal["formula"],
677  [](const config::attribute_value& c)
678  {
679  try {
681  } catch(const wfl::formula_error& e) {
682  lg::log_to_chat() << "Formula error while evaluating formula in unit filter: " << e.type << " at "
683  << e.filename << ':' << e.line << ")\n";
684  ERR_WML << "Formula error while evaluating formula in unit filter: " << e.type << " at "
685  << e.filename << ':' << e.line << ")";
686  return wfl::formula("");
687  }
688  },
689  [](const wfl::formula& form, const unit_filter_args& args)
690  {
691  try {
692  const wfl::unit_callable main(args.loc, args.u);
693  wfl::map_formula_callable callable(main.fake_ptr());
694  if (args.u2) {
695  auto secondary = std::make_shared<wfl::unit_callable>(*args.u2);
696  callable.add("other", wfl::variant(secondary));
697  // It's not destroyed upon scope exit because the variant holds a reference
698  }
699  if(!form.evaluate(callable).as_bool()) {
700  return false;
701  }
702  return true;
703  } catch(const wfl::formula_error& e) {
704  lg::log_to_chat() << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")\n";
705  ERR_WML << "Formula error in unit filter: " << e.type << " at " << e.filename << ':' << e.line << ")";
706  // Formulae with syntax errors match nothing
707  return false;
708  }
709  }
710  );
711 
712  create_attribute(literal["find_in"],
713  [](const config::attribute_value& c) { return c.str(); },
714  [](const std::string& find_in, const unit_filter_args& args)
715  {
716  // Allow filtering by searching a stored variable of units
717  if (const game_data * gd = args.context().get_game_data()) {
718  try
719  {
720  for (const config& c : gd->get_variable_access_read(find_in).as_array())
721  {
722  if(c["id"] == args.u.id()) {
723  return true;
724  }
725  }
726  return false;
727  }
728  catch(const invalid_variablename_exception&)
729  {
730  return false;
731  }
732  }
733  return true;
734  }
735  );
736 
737  if (!literal["x"].blank() || !literal["y"].blank()) {
738  children_.emplace_back(new unit_filter_xy(literal["x"], literal["y"]));
739  }
740 
741  for(auto child : cfg.all_ordered()) {
742  auto cond = conditional_type::get_enum(child.first);
743  if(cond) {
744  cond_children_.emplace_back(std::piecewise_construct_t(), std::tuple(*cond), std::tuple(child.second));
745  }
746  else if (child.first == "filter_wml") {
747  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
748  config fwml = c.get_parsed_config();
749 
750  /* Check if the filter only cares about variables.
751  If so, no need to serialize the whole unit. */
752  config::all_children_itors ci = fwml.all_children_range();
753  if (fwml.all_children_count() == 1 && fwml.attribute_count() == 1 && ci.front().key == "variables") {
754  return args.u.variables().matches(ci.front().cfg);
755  } else {
756  config ucfg;
757  args.u.write(ucfg);
758  return ucfg.matches(fwml);
759  }
760  });
761  }
762  else if (child.first == "filter_vision") {
763  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
764  std::set<int> viewers;
765  side_filter ssf(c, args.fc);
766  std::vector<int> sides = ssf.get_teams();
767  viewers.insert(sides.begin(), sides.end());
768 
769  for (const int viewer : viewers) {
770  bool fogged = args.context().get_disp_context().get_team(viewer).fogged(args.loc);
771  // Check is_enemy() before invisible() to prevent infinite recursion in [abilities][hides][filter_self][filter_vision]
772  bool hiding = args.context().get_disp_context().get_team(viewer).is_enemy(args.u.side()) && args.u.invisible(args.loc);
773  bool unit_hidden = fogged || hiding;
774  if (c["visible"].to_bool(true) != unit_hidden) {
775  return true;
776  }
777  }
778  return false;
779  });
780  }
781  else if (child.first == "filter_adjacent") {
782  children_.emplace_back(new unit_filter_adjacent(child.second));
783  }
784  else if (child.first == "filter_location") {
785  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
786  return terrain_filter(c, args.fc, args.use_flat_tod).match(args.loc);
787  });
788  }
789  else if (child.first == "filter_side") {
790  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
791  return side_filter(c, args.fc).match(args.u.side());
792  });
793  }
794  else if ((child.first == "filter_ability") || (child.first == "experimental_filter_ability")) {
795  if(child.first == "experimental_filter_ability"){
796  deprecated_message("experimental_filter_ability", DEP_LEVEL::INDEFINITE, "", "Use filter_ability instead.");
797  }
798  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
799  if(!(c.get_parsed_config())["active"].to_bool()){
800  for(const auto [key, cfg] : args.u.abilities().all_children_view()) {
801  if(args.u.ability_matches_filter(cfg, key, c.get_parsed_config())) {
802  return true;
803  }
804  }
805  } else {
806  const unit_map& units = args.context().get_disp_context().units();
807  for(const auto [key, cfg] : args.u.abilities().all_children_view()) {
808  if(args.u.ability_matches_filter(cfg, key, c.get_parsed_config())) {
809  if (args.u.get_self_ability_bool(cfg, key, args.loc)) {
810  return true;
811  }
812  }
813  }
814 
815  if(c.get_parsed_config()["affect_adjacent"].to_bool(true)) {
816  for(const unit& unit : units) {
817  if(!unit.has_ability_distant() || unit.incapacitated() || &unit == args.u.shared_from_this().get()) {
818  continue;
819  }
820  const map_location& from_loc = unit.get_location();
821  std::size_t distance = distance_between(from_loc, args.loc);
822  if(distance > *unit.has_ability_distant()) {
823  continue;
824  }
825  utils::optional<int> dir;
826  const auto adjacent = get_adjacent_tiles(from_loc);
827  for(std::size_t j = 0; j < adjacent.size(); ++j) {
828  bool adj_or_dist = distance != 1 ? distance_between(adjacent[j], args.loc) == (distance - 1) : adjacent[j] == args.loc;
829  if(adj_or_dist) {
830  dir = j;
831  break;
832  }
833  }
834  for(const auto [key, cfg] : unit.abilities().all_children_view()) {
835  if(args.u.get_adj_ability_bool(cfg, key, distance, *dir, args.loc, unit, from_loc)) {
836  return true;
837  }
838  }
839  }
840  }
841  }
842  return false;
843  });
844  }
845  else if (child.first == "has_attack") {
846  create_child(child.second, [](const vconfig& c, const unit_filter_args& args) {
847  for(const attack_type& a : args.u.attacks()) {
848  if(a.matches_filter(c.get_parsed_config())) {
849  return true;
850  }
851  }
852  return false;
853  });
854  }
855  else {
856  std::stringstream errmsg;
857  errmsg << "encountered a child [" << child.first << "] of a standard unit filter, it is being ignored";
858  DBG_CF << errmsg.str();
859  }
860 
861  }
862  }
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:234
Visitor helper class to parse the upkeep value from a config.
Definition: unit.hpp:1250
Visitor helper class to fetch the appropriate upkeep value.
Definition: unit.hpp:1200
bool empty() const
Definition: unit.hpp:90
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 find(std::size_t id)
Definition: map.cpp:302
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:1214
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:133
A variable-expanding proxy for the config class.
Definition: variable.hpp:45
const config & get_config() const
Definition: variable.hpp:75
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
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:1780
bool has_ability_type(const std::string &ability) const
Check if the unit has an ability of a specific type.
Definition: abilities.cpp:589
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:243
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:1770
bool has_ability_by_id(const std::string &ability) const
Check if the unit has an ability of a specific ID.
Definition: unit.cpp:1514
const config & abilities() const
Definition: unit.hpp:1841
unit_alignments::type alignment() const
The alignment of this unit.
Definition: unit.hpp:491
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:920
int level() const
The current level of this unit.
Definition: unit.hpp:575
std::string usage() const
Gets this unit's usage.
Definition: unit.hpp:702
const std::string & get_role() const
Gets this unit's role.
Definition: unit.hpp:685
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:656
const std::string & variation() const
The ID of the variation of this unit's type.
Definition: unit.hpp:588
bool get_state(const std::string &state) const
Check if the unit is affected by a status effect.
Definition: unit.cpp:1432
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1970
const unit_race * race() const
Gets this unit's race.
Definition: unit.hpp:509
const unit_type & type() const
This unit's type, accounting for gender and variation.
Definition: unit.hpp:355
bool can_recruit() const
Whether this unit can recruit other units - ie, are they a leader unit.
Definition: unit.hpp:628
const std::string & id() const
Gets this unit's id.
Definition: unit.hpp:380
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
unit_race::GENDER gender() const
The gender of this unit.
Definition: unit.hpp:481
const t_string & name() const
Gets this unit's translatable display name.
Definition: unit.hpp:403
@ STATE_GUARDIAN
The unit cannot be healed.
Definition: unit.hpp:875
int defense_modifier(const t_translation::terrain_code &terrain) const
The unit's defense on a given terrain.
Definition: unit.cpp:1766
attack_itors attacks()
Gets an iterator over this unit's attacks.
Definition: unit.hpp:942
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1434
int movement_cost(const t_translation::terrain_code &terrain) const
Get the unit's movement cost on a particular terrain.
Definition: unit.hpp:1517
int vision_cost(const t_translation::terrain_code &terrain) const
Get the unit's vision cost on a particular terrain.
Definition: unit.hpp:1527
int jamming_cost(const t_translation::terrain_code &terrain) const
Get the unit's jamming cost on a particular terrain.
Definition: unit.hpp:1537
int upkeep() const
Gets the amount of gold this unit costs a side per turn.
Definition: unit.cpp:1740
utils::optional< std::size_t > has_ability_distant() const
Gets if this unit own ability with [affect_adjacent] subtags.
Definition: unit.hpp:1324
utils::variant< upkeep_full, upkeep_loyal, int > upkeep_t
Definition: unit.hpp:1193
std::vector< std::string > get_traits_list() const
Gets a list of the traits this unit currently has, including hidden traits.
Definition: unit.hpp:1146
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::vector< std::pair< int, int > > default_counts
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:517
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::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
static std::vector< direction > all_directions()
Definition: location.cpp:61
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:267
void fill(const vconfig &cfg)
Definition: filter.cpp:316
void create_child(const vconfig &c, F func)
Definition: filter.cpp:278
void create_attribute(const config::attribute_value &c, C conv, F func)
Definition: filter.cpp:284
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:232
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:1453
#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