The Battle for Wesnoth  1.19.7+dev
game_board.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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"]))
41  , unit_id_manager_(level["next_underlying_unit_id"].to_size_t())
42  , units_()
43 {
44 }
45 
47  : teams_(other.teams_)
48  , labels_(other.labels_)
49  , map_(new gamemap(*(other.map_)))
50  , unit_id_manager_(other.unit_id_manager_)
51  , units_(other.units_)
52 {
53 }
54 
56 {
57 }
58 
59 // TODO: Fix this so that we swap pointers to maps
60 // However, then anytime gameboard is overwritten, resources::gamemap must be updated. So might want to
61 // just get rid of resources::gamemap and replace with resources::gameboard->map() at that point.
62 void swap(game_board& one, game_board& other)
63 {
64  std::swap(one.teams_, other.teams_);
65  std::swap(one.units_, other.units_);
67  one.map_.swap(other.map_);
68 }
69 
70 void game_board::new_turn(int player_num)
71 {
72  for(unit& i : units_) {
73  if(i.side() == player_num) {
74  i.new_turn();
75  }
76  }
77 }
78 
79 void game_board::end_turn(int player_num)
80 {
81  for(unit& i : units_) {
82  if(i.side() == player_num) {
83  i.end_turn();
84  }
85  }
86 }
87 
89 {
90  for(unit& i : units_) {
91  i.set_user_end_turn(true);
92  }
93 }
94 
96 {
97  for(auto& u : units_) {
98  if(get_team(u.side()).persistent()) {
99  u.new_turn();
100  u.new_scenario();
101  }
102  }
103 
104  for(auto& t : teams_) {
105  if(t.persistent()) {
106  for(auto& up : t.recall_list()) {
107  up->new_scenario();
108  up->new_turn();
109  }
110  }
111  }
112 }
113 
114 void game_board::check_victory(bool& continue_level,
115  bool& found_player,
116  bool& found_network_player,
117  bool& cleared_villages,
118  std::set<unsigned>& not_defeated,
119  bool remove_from_carryover_on_defeat)
120 {
121  continue_level = true;
122  found_player = false;
123  found_network_player = false;
124  cleared_villages = false;
125 
126  not_defeated = std::set<unsigned>();
127 
128  for(const unit& i : units()) {
129  DBG_EE << "Found a unit: " << i.id() << " on side " << i.side();
130  const team& tm = get_team(i.side());
131  DBG_EE << "That team's defeat condition is: " << defeat_condition::get_string(tm.defeat_cond());
132  if(i.can_recruit() && tm.defeat_cond() == defeat_condition::type::no_leader_left) {
133  not_defeated.insert(i.side());
134  } else if(tm.defeat_cond() == defeat_condition::type::no_units_left) {
135  not_defeated.insert(i.side());
136  }
137  }
138 
139  for(team& tm : teams_) {
140  if(tm.defeat_cond() == defeat_condition::type::never) {
141  not_defeated.insert(tm.side());
142  }
143 
144  // Clear villages for teams that have no leader and
145  // mark side as lost if it should be removed from carryover.
146  if(not_defeated.find(tm.side()) == not_defeated.end()) {
147  tm.clear_villages();
148  // invalidate_all() is overkill and expensive but this code is
149  // run rarely so do it the expensive way.
150  cleared_villages = true;
151 
152  if(remove_from_carryover_on_defeat) {
153  tm.set_lost(true);
154  }
155  } else if(remove_from_carryover_on_defeat) {
156  tm.set_lost(false);
157  }
158  }
159 
160  for(std::set<unsigned>::iterator n = not_defeated.begin(); n != not_defeated.end(); ++n) {
161  std::size_t side = *n - 1;
162  DBG_EE << "Side " << (side + 1) << " is a not-defeated team";
163 
165  for(++m; m != not_defeated.end(); ++m) {
166  if(teams()[side].is_enemy(*m)) {
167  return;
168  }
169 
170  DBG_EE << "Side " << (side + 1) << " and " << *m << " are not enemies.";
171  }
172 
173  if(teams()[side].is_local_human()) {
174  found_player = true;
175  }
176 
177  if(teams()[side].is_network_human()) {
178  found_network_player = true;
179  }
180  }
181 
182  continue_level = false;
183 }
184 
185 unit_map::iterator game_board::find_visible_unit(const map_location& loc, const team& current_team, bool see_all)
186 {
187  if(!map_->on_board(loc)) {
188  return units_.end();
189  }
190 
192  if(!u.valid() || !u->is_visible_to_team(current_team, see_all)) {
193  return units_.end();
194  }
195 
196  return u;
197 }
198 
199 bool game_board::has_visible_unit(const map_location& loc, const team& current_team, bool see_all) const
200 {
201  if(!map_->on_board(loc)) {
202  return false;
203  }
204 
206  if(!u.valid() || !u->is_visible_to_team(current_team, see_all)) {
207  return false;
208  }
209 
210  return true;
211 }
212 
214 {
215  team& tm = get_team(side_num);
216 
217  tm.change_controller(ctrl);
218  tm.change_proxy(proxy);
219  tm.set_local(true);
220 
221  tm.set_current_player(side_controller::get_string(ctrl) + std::to_string(side_num));
222 
223  unit_map::iterator leader = units_.find_leader(side_num);
224  if(leader.valid()) {
225  leader->rename(side_controller::get_string(ctrl) + std::to_string(side_num));
226  }
227 }
228 
230  int side_num, bool is_local, const std::string& pname, const std::string& controller_type)
231 {
232  team& tm = get_team(side_num);
233 
234  tm.set_local(is_local);
235 
236  // only changing the type of controller
237  if(controller_type == side_controller::ai && !tm.is_ai()) {
238  tm.make_ai();
239  return;
240  } else if(controller_type == side_controller::human && !tm.is_human()) {
241  tm.make_human();
242  return;
243  }
244 
245  if(pname.empty() || !tm.is_human()) {
246  return;
247  }
248 
249  tm.set_current_player(pname);
250 
251  unit_map::iterator leader = units_.find_leader(side_num);
252  if(leader.valid()) {
253  leader->rename(pname);
254  }
255 }
256 
258 {
259  switch(t.defeat_cond()) {
260  case defeat_condition::type::always:
261  return true;
262  case defeat_condition::type::no_leader_left:
263  return !units_.find_leader(t.side()).valid();
264  case defeat_condition::type::no_units_left:
265  for(const unit& u : units_) {
266  if(u.side() == t.side())
267  return false;
268  }
269  return true;
270  case defeat_condition::type::never:
271  default:
272  return false;
273  }
274 }
275 
277 {
278  get_team(u->side()).recall_list().add(u);
279  return true;
280 }
281 
282 utils::optional<std::string> game_board::replace_map(const gamemap& newmap)
283 {
284  utils::optional<std::string> ret;
285 
286  /* Remember the locations where a village is owned by a side. */
287  std::map<map_location, int> villages;
288  for(const auto& village : map_->villages()) {
289  const int owner = village_owner(village);
290  if(owner != 0) {
291  villages[village] = owner;
292  }
293  }
294 
295  for(unit_map::iterator itor = units_.begin(); itor != units_.end();) {
296  if(!newmap.on_board(itor->get_location())) {
297  if(!try_add_unit_to_recall_list(itor->get_location(), itor.get_shared_ptr())) {
298  ret = std::string("replace_map: Cannot add a unit that would become off-map to the recall list\n");
299  }
300  units_.erase(itor++);
301  } else {
302  ++itor;
303  }
304  }
305 
306  /* Disown villages that are no longer villages. */
307  for(const auto& village : villages) {
308  if(!newmap.is_village(village.first)) {
309  get_team(village.second).lose_village(village.first);
310  }
311  }
312 
313  *map_ = newmap;
314  return ret;
315 }
316 
318  const map_location& loc, const std::string& t_str, const std::string& mode_str, bool replace_if_failed)
319 {
320  // Code internalized from the implementation in lua.cpp
322  if(terrain == t_translation::NONE_TERRAIN) {
323  return false;
324  }
325 
327 
328  if(mode_str == "base") {
330  } else if(mode_str == "overlay") {
332  }
333 
334  return change_terrain(loc, terrain, mode, replace_if_failed);
335 }
336 
337 bool game_board::change_terrain(const map_location &loc, const t_translation::terrain_code &terrain, terrain_type_data::merge_mode& mode, bool replace_if_failed) {
338  /*
339  * When a hex changes from a village terrain to a non-village terrain, and
340  * a team owned that village it loses that village. When a hex changes from
341  * a non-village terrain to a village terrain and there is a unit on that
342  * hex it does not automatically capture the village. The reason for not
343  * capturing villages it that there are too many choices to make; should a
344  * unit loose its movement points, should capture events be fired. It is
345  * easier to do this as wanted by the author in WML.
346  */
347  t_translation::terrain_code old_t = map_->get_terrain(loc);
348  t_translation::terrain_code new_t = map_->tdata()->merge_terrains(old_t, terrain, mode, replace_if_failed);
349 
350  if(new_t == t_translation::NONE_TERRAIN) {
351  return false;
352  }
353 
354  prefs::get().encountered_terrains().insert(new_t);
355 
356  if(map_->tdata()->is_village(old_t) && !map_->tdata()->is_village(new_t)) {
357  int owner = village_owner(loc);
358  if(owner != 0)
359  get_team(owner).lose_village(loc);
360  }
361 
362  map_->set_terrain(loc, new_t);
363 
364  for(const t_translation::terrain_code& ut : map_->underlying_union_terrain(loc)) {
365  prefs::get().encountered_terrains().insert(ut);
366  }
367 
368  return true;
369 }
370 
372 {
373  cfg["next_underlying_unit_id"] = unit_id_manager_.get_save_id();
374 
375  for(const team& t : teams_) {
376  config& side = cfg.add_child("side");
377  t.write(side);
378  side["no_leader"] = true;
379  side["side"] = std::to_string(t.side());
380 
381  // current units
382  for(const unit& i : units_) {
383  if(i.side() == t.side()) {
384  config& u = side.add_child("unit");
385  i.get_location().write(u);
386  i.write(u, false);
387  }
388  }
389 
390  // recall list
391  for(const unit_const_ptr j : t.recall_list()) {
392  config& u = side.add_child("unit");
393  j->write(u);
394  }
395  }
396 
397  // write the map
398  cfg["map_data"] = map_->write();
399 }
400 
402  : m_(m)
403  , loc_(loc)
404  , temp_(m_.extract(loc))
405 {
406  u.mark_clone(true);
407  m_.add(loc, u);
408 }
409 
411  : m_(b.units_)
412  , loc_(loc)
413  , temp_(m_.extract(loc))
414 {
415  u.mark_clone(true);
416  m_.add(loc, u);
417 }
418 
420 {
421  try {
422  m_.erase(loc_);
423  if(temp_) {
424  m_.insert(temp_);
425  }
426  } catch(...) {
427  DBG_RG << "Caught exception in temporary_unit_placer destructor: " << utils::get_unknown_exception_type();
428  }
429 }
430 
432  : m_(m)
433  , loc_(loc)
434  , temp_(m_.extract(loc))
435 {
436 }
437 
439  : m_(b.units_)
440  , loc_(loc)
441  , temp_(m_.extract(loc))
442 {
443 }
444 
446 {
447  try {
448  if(temp_) {
449  m_.insert(temp_);
450  }
451  } catch(...) {
452  DBG_RG << "Caught exception in temporary_unit_remover destructor: " << utils::get_unknown_exception_type();
453  }
454 }
455 
456 /**
457  * Constructor
458  * This version will change the unit's current movement to @a new_moves while
459  * the unit is moved (and restored to its previous value upon this object's
460  * destruction).
461  */
463  : m_(m)
464  , src_(src)
465  , dst_(dst)
466  , old_moves_(-1)
467  , temp_(src == dst ? unit_ptr() : m_.extract(dst))
468  , stand_(stand)
469 {
470  auto [iter, success] = m_.move(src_, dst_);
471 
472  // Set the movement.
473  if(success) {
474  old_moves_ = iter->movement_left(true);
475  iter->set_movement(new_moves);
476  if(stand_) {
477  m_.find_unit_ptr(dst_)->anim_comp().set_standing();
478  }
479  }
480 }
481 
483 {
484  try {
485  auto [iter, success] = m_.move(dst_, src_);
486 
487  // Restore the movement?
488  if(success && old_moves_ >= 0) {
489  iter->set_movement(old_moves_);
490  if(stand_) {
491  m_.find_unit_ptr(src_)->anim_comp().set_standing();
492  }
493  }
494 
495  // Restore the extracted unit?
496  if(temp_) {
497  m_.insert(temp_);
498  }
499  } catch(...) {
500  DBG_RG << "Caught exception in temporary_unit_mover destructor: " << utils::get_unknown_exception_type();
501  }
502 }
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:158
config & add_child(config_key_type key)
Definition: config.cpp:440
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:114
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:199
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
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:213
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:317
utils::optional< std::string > replace_map(const gamemap &r)
Definition: game_board.cpp:282
game_board(const config &level)
Definition: game_board.cpp:38
void heal_all_survivors()
Definition: game_board.cpp:95
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:185
bool team_is_defeated(const team &t) const
Calculates whether a team is defeated.
Definition: game_board.cpp:257
void end_turn(int pnum)
Definition: game_board.cpp:79
void side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
Definition: game_board.cpp:229
bool try_add_unit_to_recall_list(const map_location &loc, const unit_ptr &u)
Definition: game_board.cpp:276
void set_all_units_user_end_turn()
Definition: game_board.cpp:88
void write_config(config &cfg) const
Definition: game_board.cpp:371
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual ~game_board()
Definition: game_board.cpp:55
void new_turn(int pnum)
Definition: game_board.cpp:70
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
Encapsulates the map of the game.
Definition: map.hpp:172
bool is_village(const map_location &loc) const
Definition: map.cpp:66
std::size_t get_save_id() const
Used for saving id to savegame.
Definition: id.cpp:42
std::set< t_translation::terrain_code > & encountered_terrains()
static prefs & get()
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:75
bool is_ai() const
Definition: team.hpp:256
int side() const
Definition: team.hpp:180
bool is_human() const
Definition: team.hpp:255
void change_proxy(side_proxy_controller::type proxy)
Definition: team.hpp:282
void set_local(bool local)
Definition: team.hpp:263
void set_current_player(const std::string &player)
Definition: team.hpp:208
defeat_condition::type defeat_cond() const
Definition: team.hpp:333
void change_controller(const std::string &new_controller)
Definition: team.hpp:266
void make_ai()
Definition: team.hpp:265
void make_human()
Definition: team.hpp:264
void clear_villages()
Definition: team.hpp:175
bool persistent() const
Definition: team.hpp:340
recall_list_manager & recall_list()
Definition: team.hpp:206
void lose_village(const map_location &)
Definition: team.cpp:471
void set_lost(bool value=true)
Definition: team.hpp:342
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:133
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1029
map_location loc_
void swap(game_board &one, game_board &other)
Definition: game_board.cpp:62
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:2681
Standard logging facilities (interface).
terrain_code read_terrain_code(std::string_view str, const ter_layer filler)
Reads a single terrain from a string.
const 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::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:45
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:229
virtual ~temporary_unit_mover()
Definition: game_board.cpp:482
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:462
const map_location dst_
Definition: game_board.hpp:230
virtual ~temporary_unit_placer()
Definition: game_board.cpp:419
temporary_unit_placer(unit_map &m, const map_location &loc, unit &u)
Definition: game_board.cpp:401
const map_location loc_
Definition: game_board.hpp:191
temporary_unit_remover(unit_map &m, const map_location &loc)
Definition: game_board.cpp:431
virtual ~temporary_unit_remover()
Definition: game_board.cpp:445
bool valid() const
Definition: map.hpp:273
static map_location::direction n
#define b