The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
22#include "units/unit.hpp"
24#include "utils/general.hpp"
25
26#include <set>
27#include <vector>
28
29static 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
35static 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.
49void swap(game_board& one, game_board& other)
50{
51 std::swap(one.teams_, other.teams_);
52 std::swap(one.units_, other.units_);
53 std::swap(one.unit_id_manager_, other.unit_id_manager_);
54 one.map_.swap(other.map_);
55}
56
57void 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
66void 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
101void 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());
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
140 tm.set_lost(true);
141 }
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
151 std::set<unsigned>::iterator m(n);
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()) {
166 }
167 }
168
169 continue_level = false;
170}
171
172unit_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
186bool 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
200void game_board::side_drop_to(int side_num, side_controller::type ctrl, side_proxy_controller::type proxy)
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
208 tm.set_current_player(side_controller::get_string(ctrl) + std::to_string(side_num));
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
269utils::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
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 */
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)
map_location loc
Definition move.cpp:172
double t
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.
virtual const gamemap & map() const override
virtual const std::vector< team > & teams() const override
void check_victory(bool &, bool &, bool &, bool &, std::set< unsigned > &, bool)
bool has_visible_unit(const map_location &loc, const team &team, bool see_all=false) const
std::vector< team > teams_
void side_drop_to(int side_num, side_controller::type ctrl, side_proxy_controller::type proxy=side_proxy_controller::type::human)
unit_map units_
std::unique_ptr< gamemap > map_
bool change_terrain(const map_location &loc, const std::string &t, const std::string &mode, bool replace_if_failed)
utils::optional< std::string > replace_map(const gamemap &r)
game_board(const config &level)
void heal_all_survivors()
n_unit::id_manager unit_id_manager_
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
bool team_is_defeated(const team &t) const
Calculates whether a team is defeated.
void end_turn(int pnum)
team & get_team(int i)
void side_change_controller(int side_num, bool is_local, const std::string &pname, const std::string &controller_type)
bool try_add_unit_to_recall_list(const map_location &loc, const unit_ptr &u)
void set_all_units_user_end_turn()
void write_config(config &cfg) const
virtual const unit_map & units() const override
void new_turn(int pnum)
bool empty() const
Tell if the map is of 0 size.
Definition map.hpp:68
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
recall_list_manager & recall_list()
Definition team.hpp:242
bool persistent() const
Definition team.hpp:377
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
void swap(game_board &one, game_board &other)
static lg::log_domain log_engine("enginerefac")
static lg::log_domain log_engine_enemies("engine/enemies")
#define DBG_RG
#define DBG_EE
unit & mark_clone(bool is_temporary)
Mark this unit as clone so it can be inserted to unit_map.
Definition unit.cpp:2741
Standard logging facilities (interface).
STL namespace.
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
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition general.cpp:23
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...
const map_location src_
virtual ~temporary_unit_mover()
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...
const map_location dst_
virtual ~temporary_unit_placer()
temporary_unit_placer(unit_map &m, const map_location &loc, unit &u)
const map_location loc_
temporary_unit_remover(unit_map &m, const map_location &loc)
virtual ~temporary_unit_remover()
bool valid() const
Definition map.hpp:273
static map_location::direction n
#define i
#define b