The Battle for Wesnoth  1.19.26+dev
game_board.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 "game_board.hpp"
17 #include "config.hpp"
18 #include "log.hpp"
19 #include "map/map.hpp"
21 #include "recall_list_manager.hpp"
22 #include "units/unit.hpp"
24 #include "utils/general.hpp"
25 
26 #include <set>
27 #include <vector>
28 
29 static lg::log_domain log_engine("enginerefac");
30 #define DBG_RG LOG_STREAM(debug, log_engine)
31 #define LOG_RG LOG_STREAM(info, log_engine)
32 #define WRN_RG LOG_STREAM(warn, log_engine)
33 #define ERR_RG LOG_STREAM(err, log_engine)
34 
35 static lg::log_domain log_engine_enemies("engine/enemies");
36 #define DBG_EE LOG_STREAM(debug, log_engine_enemies)
37 
39  : teams_()
40  , map_(std::make_unique<gamemap>(level["map_data"].str()))
41  , unit_id_manager_(level["next_underlying_unit_id"].to_size_t())
42  , units_()
43 {
44 }
45 
46 // TODO: Fix this so that we swap pointers to maps
47 // However, then anytime gameboard is overwritten, resources::gamemap must be updated. So might want to
48 // just get rid of resources::gamemap and replace with resources::gameboard->map() at that point.
49 void swap(game_board& one, game_board& other)
50 {
51  std::swap(one.teams_, other.teams_);
52  std::swap(one.units_, other.units_);
54  one.map_.swap(other.map_);
55 }
56 
57 void game_board::new_turn(int player_num)
58 {
59  for(unit& i : units_) {
60  if(i.side() == player_num) {
61  i.new_turn();
62  }
63  }
64 }
65 
66 void game_board::end_turn(int player_num)
67 {
68  for(unit& i : units_) {
69  if(i.side() == player_num) {
70  i.end_turn();
71  }
72  }
73 }
74 
76 {
77  for(unit& i : units_) {
78  i.set_user_end_turn(true);
79  }
80 }
81 
83 {
84  for(auto& u : units_) {
85  if(get_team(u.side()).persistent()) {
86  u.new_turn();
87  u.new_scenario();
88  }
89  }
90 
91  for(auto& t : teams_) {
92  if(t.persistent()) {
93  for(auto& up : t.recall_list()) {
94  up->new_scenario();
95  up->new_turn();
96  }
97  }
98  }
99 }
100 
101 void game_board::check_victory(bool& continue_level,
102  bool& found_player,
103  bool& found_network_player,
104  bool& cleared_villages,
105  std::set<unsigned>& not_defeated,
106  bool remove_from_carryover_on_defeat)
107 {
108  continue_level = true;
109  found_player = false;
110  found_network_player = false;
111  cleared_villages = false;
112 
113  not_defeated = std::set<unsigned>();
114 
115  for(const unit& i : units()) {
116  DBG_EE << "Found a unit: " << i.id() << " on side " << i.side();
117  const team& tm = get_team(i.side());
118  DBG_EE << "That team's defeat condition is: " << defeat_condition::get_string(tm.defeat_cond());
119  if(i.can_recruit() && tm.defeat_cond() == defeat_condition::type::no_leader_left) {
120  not_defeated.insert(i.side());
121  } else if(tm.defeat_cond() == defeat_condition::type::no_units_left) {
122  not_defeated.insert(i.side());
123  }
124  }
125 
126  for(team& tm : teams_) {
127  if(tm.defeat_cond() == defeat_condition::type::never) {
128  not_defeated.insert(tm.side());
129  }
130 
131  // Clear villages for teams that have no leader and
132  // mark side as lost if it should be removed from carryover.
133  if(not_defeated.find(tm.side()) == not_defeated.end()) {
134  tm.clear_villages();
135  // invalidate_all() is overkill and expensive but this code is
136  // run rarely so do it the expensive way.
137  cleared_villages = true;
138 
139  if(remove_from_carryover_on_defeat) {
140  tm.set_lost(true);
141  }
142  } else if(remove_from_carryover_on_defeat) {
143  tm.set_lost(false);
144  }
145  }
146 
147  for(std::set<unsigned>::iterator n = not_defeated.begin(); n != not_defeated.end(); ++n) {
148  std::size_t side = *n - 1;
149  DBG_EE << "Side " << (side + 1) << " is a not-defeated team";
150 
152  for(++m; m != not_defeated.end(); ++m) {
153  if(teams()[side].is_enemy(*m)) {
154  return;
155  }
156 
157  DBG_EE << "Side " << (side + 1) << " and " << *m << " are not enemies.";
158  }
159 
160  if(teams()[side].is_local_human()) {
161  found_player = true;
162  }
163 
164  if(teams()[side].is_network_human()) {
165  found_network_player = true;
166  }
167  }
168 
169  continue_level = false;
170 }
171 
172 unit_map::iterator game_board::find_visible_unit(const map_location& loc, const team& current_team, bool see_all)
173 {
174  if(!map_->on_board(loc)) {
175  return units_.end();
176  }
177 
179  if(!u.valid() || !u->is_visible_to_team(current_team, see_all)) {
180  return units_.end();
181  }
182 
183  return u;
184 }
185 
186 bool game_board::has_visible_unit(const map_location& loc, const team& current_team, bool see_all) const
187 {
188  if(!map_->on_board(loc)) {
189  return false;
190  }
191 
193  if(!u.valid() || !u->is_visible_to_team(current_team, see_all)) {
194  return false;
195  }
196 
197  return true;
198 }
199 
201 {
202  team& tm = get_team(side_num);
203 
204  tm.change_controller(ctrl);
205  tm.change_proxy(proxy);
206  tm.set_local(true);
207 
209 
210  unit_map::iterator leader = units_.find_leader(side_num);
211  if(leader.valid()) {
212  leader->rename(side_controller::get_string(ctrl) + std::to_string(side_num));
213  }
214 }
215 
217  int side_num, bool is_local, const std::string& pname, const std::string& controller_type)
218 {
219  team& tm = get_team(side_num);
220 
221  tm.set_local(is_local);
222 
223  // only changing the type of controller
224  if(controller_type == side_controller::ai && !tm.is_ai()) {
225  tm.make_ai();
226  return;
227  } else if(controller_type == side_controller::human && !tm.is_human()) {
228  tm.make_human();
229  return;
230  }
231 
232  if(pname.empty() || !tm.is_human()) {
233  return;
234  }
235 
236  tm.set_current_player(pname);
237 
238  unit_map::iterator leader = units_.find_leader(side_num);
239  if(leader.valid()) {
240  leader->rename(pname);
241  }
242 }
243 
245 {
246  switch(t.defeat_cond()) {
247  case defeat_condition::type::always:
248  return true;
249  case defeat_condition::type::no_leader_left:
250  return !units_.find_leader(t.side()).valid();
251  case defeat_condition::type::no_units_left:
252  for(const unit& u : units_) {
253  if(u.side() == t.side())
254  return false;
255  }
256  return true;
257  case defeat_condition::type::never:
258  default:
259  return false;
260  }
261 }
262 
264 {
265  get_team(u->side()).recall_list().add(u);
266  return true;
267 }
268 
269 utils::optional<std::string> game_board::replace_map(const gamemap& newmap)
270 {
271  utils::optional<std::string> ret;
272 
273  /* Remember the locations where a village is owned by a side. */
274  std::map<map_location, int> villages;
275  for(const auto& village : map_->villages()) {
276  const int owner = village_owner(village);
277  if(owner != 0) {
278  villages[village] = owner;
279  }
280  }
281 
282  for(unit_map::iterator itor = units_.begin(); itor != units_.end();) {
283  if(!newmap.on_board(itor->get_location())) {
284  if(!try_add_unit_to_recall_list(itor->get_location(), itor.get_shared_ptr())) {
285  ret = std::string("replace_map: Cannot add a unit that would become off-map to the recall list\n");
286  }
287  units_.erase(itor++);
288  } else {
289  ++itor;
290  }
291  }
292 
293  /* Disown villages that are no longer villages. */
294  for(const auto& village : villages) {
295  if(!newmap.is_village(village.first)) {
296  get_team(village.second).lose_village(village.first);
297  }
298  }
299 
300  *map_ = newmap;
301  return ret;
302 }
303 
305  const map_location& loc, const std::string& t_str, const std::string& mode_str, bool replace_if_failed)
306 {
307  // Code internalized from the implementation in lua.cpp
309  if(terrain == t_translation::NONE_TERRAIN) {
310  return false;
311  }
312 
314 
315  if(mode_str == "base") {
317  } else if(mode_str == "overlay") {
319  }
320 
321  return change_terrain(loc, terrain, mode, replace_if_failed);
322 }
323 
325  const t_translation::terrain_code& terrain,
327  bool replace_if_failed)
328 {
329  const auto [new_terrain, village_status] = map_->set_terrain(loc, terrain, mode, replace_if_failed);
330 
331  if(new_terrain == t_translation::NONE_TERRAIN) {
332  return false;
333  }
334 
335  /**
336  * When a hex changes from a village terrain to a non-village terrain, and
337  * a team owned that village it loses that village. When a hex changes from
338  * a non-village terrain to a village terrain and there is a unit on that
339  * hex it does not automatically capture the village. The reason for not
340  * capturing villages it that there are too many choices to make; should a
341  * unit loose its movement points, should capture events be fired. It is
342  * easier to do this as wanted by the author in WML.
343  *
344  * @note Teams keep their own list of the villages they own. Since this
345  * check only reflects the state of the gamemap itself, it's safe to query
346  * the village owner even after the map has been changed.
347  */
348  if(village_status == gamemap_base::village_state::former_village) {
349  if(int owner = village_owner(loc); owner != 0) {
350  get_team(owner).lose_village(loc);
351  }
352  }
353 
354  prefs::get().encounter_map_terrain(map_->get_terrain_info(new_terrain));
355  return true;
356 }
357 
359 {
360  cfg["next_underlying_unit_id"] = unit_id_manager_.get_save_id();
361 
362  for(const team& t : teams_) {
363  config& side = cfg.add_child("side");
364  t.write(side);
365  side["no_leader"] = true;
366  side["side"] = std::to_string(t.side());
367 
368  // current units
369  for(const unit& i : units_) {
370  if(i.side() == t.side()) {
371  config& u = side.add_child("unit");
372  i.get_location().write(u);
373  i.write(u, false);
374  }
375  }
376 
377  // recall list
378  for(const unit_const_ptr j : t.recall_list()) {
379  config& u = side.add_child("unit");
380  j->write(u);
381  }
382  }
383 
384  // write the map
385  cfg["map_data"] = map_->write();
386 }
387 
389  : m_(m)
390  , loc_(loc)
391  , temp_(m_.extract(loc))
392 {
393  u.mark_clone(true);
394  m_.add(loc, u);
395 }
396 
398  : m_(b.units_)
399  , loc_(loc)
400  , temp_(m_.extract(loc))
401 {
402  u.mark_clone(true);
403  m_.add(loc, u);
404 }
405 
407 {
408  try {
409  m_.erase(loc_);
410  if(temp_) {
411  m_.insert(temp_);
412  }
413  } catch(...) {
414  DBG_RG << "Caught exception in temporary_unit_placer destructor: " << utils::get_unknown_exception_type();
415  }
416 }
417 
419  : m_(m)
420  , loc_(loc)
421  , temp_(m_.extract(loc))
422 {
423 }
424 
426  : m_(b.units_)
427  , loc_(loc)
428  , temp_(m_.extract(loc))
429 {
430 }
431 
433 {
434  try {
435  if(temp_) {
436  m_.insert(temp_);
437  }
438  } catch(...) {
439  DBG_RG << "Caught exception in temporary_unit_remover destructor: " << utils::get_unknown_exception_type();
440  }
441 }
442 
443 /**
444  * Constructor
445  * This version will change the unit's current movement to @a new_moves while
446  * the unit is moved (and restored to its previous value upon this object's
447  * destruction).
448  */
450  : m_(m)
451  , src_(src)
452  , dst_(dst)
453  , old_moves_(-1)
454  , temp_(src == dst ? unit_ptr() : m_.extract(dst))
455  , stand_(stand)
456 {
457  auto [iter, success] = m_.move(src_, dst_);
458 
459  // Set the movement.
460  if(success) {
461  old_moves_ = iter->movement_left(true);
462  iter->set_movement(new_moves);
463  if(stand_) {
464  m_.find_unit_ptr(dst_)->anim_comp().set_standing();
465  }
466  }
467 }
468 
470 {
471  try {
472  auto [iter, success] = m_.move(dst_, src_);
473 
474  // Restore the movement?
475  if(success && old_moves_ >= 0) {
476  iter->set_movement(old_moves_);
477  if(stand_) {
478  m_.find_unit_ptr(src_)->anim_comp().set_standing();
479  }
480  }
481 
482  // Restore the extracted unit?
483  if(temp_) {
484  m_.insert(temp_);
485  }
486  } catch(...) {
487  DBG_RG << "Caught exception in temporary_unit_mover destructor: " << utils::get_unknown_exception_type();
488  }
489 }
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1063
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
config & add_child(std::string_view key)
Definition: config.cpp:436
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
Game board class.
Definition: game_board.hpp:47
void check_victory(bool &, bool &, bool &, bool &, std::set< unsigned > &, bool)
Definition: game_board.cpp:101
const team & get_team(int side) const
This getter takes a 1-based side number, not a 0-based team number.
bool has_visible_unit(const map_location &loc, const team &team, bool see_all=false) const
Definition: game_board.cpp:186
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:79
std::vector< team > teams_
Definition: game_board.hpp:48
void side_drop_to(int side_num, side_controller::type ctrl, side_proxy_controller::type proxy=side_proxy_controller::type::human)
Definition: game_board.cpp:200
unit_map units_
Definition: game_board.hpp:53
std::unique_ptr< gamemap > map_
Definition: game_board.hpp:51
bool change_terrain(const map_location &loc, const std::string &t, const std::string &mode, bool replace_if_failed)
Definition: game_board.cpp:304
utils::optional< std::string > replace_map(const gamemap &r)
Definition: game_board.cpp:269
game_board(const config &level)
Definition: game_board.cpp:38
void heal_all_survivors()
Definition: game_board.cpp:82
n_unit::id_manager unit_id_manager_
Definition: game_board.hpp:52
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:172
bool team_is_defeated(const team &t) const
Calculates whether a team is defeated.
Definition: game_board.cpp:244
void end_turn(int pnum)
Definition: game_board.cpp:66
void side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:216
bool try_add_unit_to_recall_list(const map_location &loc, const unit_ptr &u)
Definition: game_board.cpp:263
void set_all_units_user_end_turn()
Definition: game_board.cpp:75
void write_config(config &cfg) const
Definition: game_board.cpp:358
virtual const unit_map & units() const override
Definition: game_board.hpp:106
void new_turn(int pnum)
Definition: game_board.cpp:57
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
Encapsulates the map of the game.
Definition: map.hpp:176
bool is_village(const map_location &loc) const
Definition: map.cpp:60
std::size_t get_save_id() const
Used for saving id to savegame.
Definition: id.cpp:42
static prefs & get()
void encounter_map_terrain(const gamemap &map)
void add(const unit_ptr &ptr, int pos=-1)
Add a unit to the list.
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool is_ai() const
Definition: team.hpp:292
bool is_human() const
Definition: team.hpp:291
void change_proxy(side_proxy_controller::type proxy)
Definition: team.hpp:318
void set_local(bool local)
Definition: team.hpp:299
void set_current_player(const std::string &player)
Definition: team.hpp:244
defeat_condition::type defeat_cond() const
Definition: team.hpp:370
void change_controller(const std::string &new_controller)
Definition: team.hpp:302
void make_ai()
Definition: team.hpp:301
void make_human()
Definition: team.hpp:300
bool persistent() const
Definition: team.hpp:377
recall_list_manager & recall_list()
Definition: team.hpp:242
void lose_village(const map_location &)
Definition: team.cpp:450
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_ptr find_unit_ptr(const T &val)
Definition: map.hpp:387
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator begin()
Definition: map.hpp:418
std::size_t erase(const map_location &l)
Erases the unit at location l, if any.
Definition: map.cpp:289
umap_retval_pair_t add(const map_location &l, const unit &u)
Adds a copy of unit u at location l of the map.
Definition: map.cpp:76
unit_iterator find_leader(int side)
Definition: map.cpp:320
umap_retval_pair_t insert(const unit_ptr &p)
Inserts the unit pointed to by p into the map.
Definition: map.cpp:135
umap_retval_pair_t move(const map_location &src, const map_location &dst)
Moves a unit from location src to location dst.
Definition: map.cpp:92
This class represents a single unit of a specific type.
Definition: unit.hpp:39
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
std::size_t i
Definition: function.cpp:1031
void swap(game_board &one, game_board &other)
Definition: game_board.cpp:49
static lg::log_domain log_engine("enginerefac")
static lg::log_domain log_engine_enemies("engine/enemies")
#define DBG_RG
Definition: game_board.cpp:30
#define DBG_EE
Definition: game_board.cpp:36
unit & mark_clone(bool is_temporary)
Mark this unit as clone so it can be inserted to unit_map.
Definition: unit.cpp:2731
Standard logging facilities (interface).
terrain_code read_terrain_code(std::string_view str, const ter_layer filler)
Reads a single terrain from a string.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::string to_string(const Range &range, const Func &op)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::shared_ptr< const unit > unit_const_ptr
Definition: ptr.hpp:27
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Encapsulates the map of the game.
Definition: location.hpp:46
static std::string get_string(enum_type key)
Converts a enum to its string equivalent.
Definition: enum_base.hpp:46
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
const map_location src_
Definition: game_board.hpp:226
virtual ~temporary_unit_mover()
Definition: game_board.cpp:469
temporary_unit_mover(unit_map &m, const map_location &src, const map_location &dst, int new_moves, bool stand)
Constructor This version will change the unit's current movement to new_moves while the unit is moved...
Definition: game_board.cpp:449
const map_location dst_
Definition: game_board.hpp:227
virtual ~temporary_unit_placer()
Definition: game_board.cpp:406
temporary_unit_placer(unit_map &m, const map_location &loc, unit &u)
Definition: game_board.cpp:388
const map_location loc_
Definition: game_board.hpp:188
temporary_unit_remover(unit_map &m, const map_location &loc)
Definition: game_board.cpp:418
virtual ~temporary_unit_remover()
Definition: game_board.cpp:432
bool valid() const
Definition: map.hpp:273
static map_location::direction n
#define b