1 /*
2 Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #include "config.hpp"
16 #include "game_board.hpp"
17 #include "preferences/game.hpp"
18 #include "log.hpp"
19 #include "map/map.hpp"
20 #include "recall_list_manager.hpp"
21 #include "terrain/type_data.hpp"
22 #include "units/unit.hpp"
23
24 #include <set>
25 #include <vector>
26
27 static lg::log_domain log_engine("enginerefac");
28 #define DBG_RG LOG_STREAM(debug, log_engine)
29 #define LOG_RG LOG_STREAM(info, log_engine)
30 #define WRN_RG LOG_STREAM(warn, log_engine)
31 #define ERR_RG LOG_STREAM(err, log_engine)
32
33 static lg::log_domain log_engine_enemies("engine/enemies");
34 #define DBG_EE LOG_STREAM(debug, log_engine_enemies)
35
game_board(const ter_data_cache & tdata,const config & level)36 game_board::game_board(const ter_data_cache & tdata, const config & level)
37 : teams_()
38 , map_(new gamemap(tdata, level["map_data"]))
39 , unit_id_manager_(level["next_underlying_unit_id"])
40 , units_()
41 {
42 }
43
game_board(const game_board & other)44 game_board::game_board(const game_board & other)
45 : teams_(other.teams_)
46 , labels_(other.labels_)
47 , map_(new gamemap(*(other.map_)))
48 , unit_id_manager_(other.unit_id_manager_)
49 , units_(other.units_) {}
50
~game_board()51 game_board::~game_board() {}
52
53
54 //TODO: Fix this so that we swap pointers to maps
55 // However, then anytime gameboard is overwritten, resources::gamemap must be updated. So might want to
56 // just get rid of resources::gamemap and replace with resources::gameboard->map() at that point.
swap(game_board & one,game_board & other)57 void swap(game_board & one, game_board & other) {
58 std::swap(one.teams_, other.teams_);
59 std::swap(one.units_, other.units_);
60 std::swap(one.unit_id_manager_, other.unit_id_manager_);
61 one.map_.swap(other.map_);
62 }
63
new_turn(int player_num)64 void game_board::new_turn(int player_num) {
65 for (unit & i : units_) {
66 if (i.side() == player_num) {
67 i.new_turn();
68 }
69 }
70 }
71
end_turn(int player_num)72 void game_board::end_turn(int player_num) {
73 for (unit & i : units_) {
74 if (i.side() == player_num) {
75 i.end_turn();
76 }
77 }
78 }
79
set_all_units_user_end_turn()80 void game_board::set_all_units_user_end_turn() {
81 for (unit & i : units_) {
82 i.set_user_end_turn(true);
83 }
84 }
85
heal_all_survivors()86 void game_board::heal_all_survivors() {
87 for (unit_map::iterator it = units_.begin(); it != units_.end(); it++) {
88 unit_ptr un = it.get_shared_ptr();
89 if (teams_[un->side() - 1].persistent()) {
90 un->new_turn();
91 un->new_scenario();
92 }
93 }
94 }
95
check_victory(bool & continue_level,bool & found_player,bool & found_network_player,bool & cleared_villages,std::set<unsigned> & not_defeated,bool remove_from_carryover_on_defeat)96 void game_board::check_victory(bool & continue_level, bool & found_player, bool & found_network_player, bool & cleared_villages, std::set<unsigned> & not_defeated, bool remove_from_carryover_on_defeat)
97 {
98 continue_level = true;
99 found_player = false;
100 found_network_player = false;
101 cleared_villages = false;
102
103 not_defeated = std::set<unsigned>();
104
105 for (const unit & i : units())
106 {
107 DBG_EE << "Found a unit: " << i.id() << " on side " << i.side() << std::endl;
108 const team& tm = get_team(i.side());
109 DBG_EE << "That team's defeat condition is: " << tm.defeat_condition() << std::endl;
110 if (i.can_recruit() && tm.defeat_condition() == team::DEFEAT_CONDITION::NO_LEADER) {
111 not_defeated.insert(i.side());
112 } else if (tm.defeat_condition() == team::DEFEAT_CONDITION::NO_UNITS) {
113 not_defeated.insert(i.side());
114 }
115 }
116
117 for (team& tm : teams_)
118 {
119 if(tm.defeat_condition() == team::DEFEAT_CONDITION::NEVER)
120 {
121 not_defeated.insert(tm.side());
122 }
123 // Clear villages for teams that have no leader and
124 // mark side as lost if it should be removed from carryover.
125 if (not_defeated.find(tm.side()) == not_defeated.end())
126 {
127 tm.clear_villages();
128 // invalidate_all() is overkill and expensive but this code is
129 // run rarely so do it the expensive way.
130 cleared_villages = true;
131
132 if (remove_from_carryover_on_defeat)
133 {
134 tm.set_lost(true);
135 }
136 }
137 else if(remove_from_carryover_on_defeat)
138 {
139 tm.set_lost(false);
140 }
141 }
142
143 for (std::set<unsigned>::iterator n = not_defeated.begin(); n != not_defeated.end(); ++n) {
144 size_t side = *n - 1;
145
146 DBG_EE << "Side " << (side+1) << " is a not-defeated team" << std::endl;
147
148 std::set<unsigned>::iterator m(n);
149 for (++m; m != not_defeated.end(); ++m) {
150 if (teams()[side].is_enemy(*m)) {
151 return;
152 }
153 DBG_EE << "Side " << (side+1) << " and " << *m << " are not enemies." << std::endl;
154 }
155
156 if (teams()[side].is_local_human()) {
157 found_player = true;
158 }
159
160 if (teams()[side].is_network_human()) {
161 found_network_player = true;
162 }
163 }
164
165 continue_level = false;
166 }
167
find_visible_unit(const map_location & loc,const team & current_team,bool see_all)168 unit_map::iterator game_board::find_visible_unit(const map_location &loc,
169 const team& current_team, bool see_all)
170 {
171 if (!map_->on_board(loc)) return units_.end();
172 unit_map::iterator u = units_.find(loc);
173 if (!u.valid() || !u->is_visible_to_team(current_team, *this, see_all))
174 return units_.end();
175 return u;
176 }
177
has_visible_unit(const map_location & loc,const team & current_team,bool see_all) const178 bool game_board::has_visible_unit(const map_location & loc, const team& current_team, bool see_all) const
179 {
180 if (!map_->on_board(loc)) return false;
181 unit_map::const_iterator u = units_.find(loc);
182 if (!u.valid() || !u->is_visible_to_team(current_team, *this, see_all))
183 return false;
184 return true;
185 }
186
get_visible_unit(const map_location & loc,const team & current_team,bool see_all)187 unit* game_board::get_visible_unit(const map_location &loc,
188 const team ¤t_team, bool see_all)
189 {
190 unit_map::iterator ui = find_visible_unit(loc, current_team, see_all);
191 if (ui == units_.end()) return nullptr;
192 return &*ui;
193 }
194
side_drop_to(int side_num,team::CONTROLLER ctrl,team::PROXY_CONTROLLER proxy)195 void game_board::side_drop_to(int side_num, team::CONTROLLER ctrl, team::PROXY_CONTROLLER proxy) {
196 team& tm = get_team(side_num);
197
198 tm.change_controller(ctrl);
199 tm.change_proxy(proxy);
200 tm.set_local(true);
201
202 tm.set_current_player(ctrl.to_string() + std::to_string(side_num));
203
204 unit_map::iterator leader = units_.find_leader(side_num);
205 if (leader.valid()) leader->rename(ctrl.to_string() + std::to_string(side_num));
206 }
207
side_change_controller(int side_num,bool is_local,const std::string & pname)208 void game_board::side_change_controller(int side_num, bool is_local, const std::string& pname) {
209 team &tm = get_team(side_num);
210
211 tm.set_local(is_local);
212
213 if (pname.empty() || !tm.is_human()) {
214 return;
215 }
216
217 tm.set_current_player(pname);
218
219 unit_map::iterator leader = units_.find_leader(side_num);
220 if (leader.valid()) {
221 leader->rename(pname);
222 }
223 }
224
team_is_defeated(const team & t) const225 bool game_board::team_is_defeated(const team& t) const
226 {
227 switch(t.defeat_condition().v)
228 {
229 case team::DEFEAT_CONDITION::ALWAYS:
230 return true;
231 case team::DEFEAT_CONDITION::NO_LEADER:
232 return !units_.find_leader(t.side()).valid();
233 case team::DEFEAT_CONDITION::NO_UNITS:
234 for (const unit& u : units_)
235 {
236 if(u.side() == t.side())
237 return false;
238 }
239 return true;
240 case team::DEFEAT_CONDITION::NEVER:
241 default:
242 return false;
243 }
244 }
245
try_add_unit_to_recall_list(const map_location &,const unit_ptr u)246 bool game_board::try_add_unit_to_recall_list(const map_location&, const unit_ptr u)
247 {
248 get_team(u->side()).recall_list().add(u);
249 return true;
250 }
251
replace_map(const gamemap & newmap)252 boost::optional<std::string> game_board::replace_map(const gamemap & newmap) {
253 boost::optional<std::string> ret = boost::optional<std::string> ();
254
255 /* Remember the locations where a village is owned by a side. */
256 std::map<map_location, int> villages;
257 for(const auto& village : map_->villages()) {
258 const int owner = village_owner(village);
259 if(owner != -1) {
260 villages[village] = owner;
261 }
262 }
263
264 for (unit_map::iterator itor = units_.begin(); itor != units_.end(); ) {
265 if (!newmap.on_board(itor->get_location())) {
266 if (!try_add_unit_to_recall_list(itor->get_location(), itor.get_shared_ptr())) {
267 *ret = std::string("replace_map: Cannot add a unit that would become off-map to the recall list\n");
268 }
269 units_.erase(itor++);
270 } else {
271 ++itor;
272 }
273 }
274
275 /* Disown villages that are no longer villages. */
276 for(const auto& village : villages) {
277 if(!newmap.is_village(village.first)) {
278 teams_[village.second].lose_village(village.first);
279 }
280 }
281
282 *map_ = newmap;
283 return ret;
284 }
285
286
287
overlay_map(const gamemap & mask_map,const config & cfg,map_location loc)288 void game_board::overlay_map(const gamemap & mask_map, const config & cfg, map_location loc) {
289 map_->overlay(mask_map, cfg, loc);
290 }
291
change_terrain(const map_location & loc,const std::string & t_str,const std::string & mode_str,bool replace_if_failed)292 bool game_board::change_terrain(const map_location &loc, const std::string &t_str,
293 const std::string & mode_str, bool replace_if_failed)
294 {
295 //Code internalized from the implementation in lua.cpp
296 t_translation::terrain_code terrain = t_translation::read_terrain_code(t_str);
297 if (terrain == t_translation::NONE_TERRAIN) return false;
298
299 terrain_type_data::merge_mode mode = terrain_type_data::BOTH;
300
301 if (mode_str == "base") mode = terrain_type_data::BASE;
302 else if (mode_str == "overlay") mode = terrain_type_data::OVERLAY;
303
304 /*
305 * When a hex changes from a village terrain to a non-village terrain, and
306 * a team owned that village it loses that village. When a hex changes from
307 * a non-village terrain to a village terrain and there is a unit on that
308 * hex it does not automatically capture the village. The reason for not
309 * capturing villages it that there are too many choices to make; should a
310 * unit loose its movement points, should capture events be fired. It is
311 * easier to do this as wanted by the author in WML.
312 */
313
314 t_translation::terrain_code
315 old_t = map_->get_terrain(loc),
316 new_t = map_->tdata()->merge_terrains(old_t, terrain, mode, replace_if_failed);
317 if (new_t == t_translation::NONE_TERRAIN) return false;
318 preferences::encountered_terrains().insert(new_t);
319
320 if (map_->tdata()->is_village(old_t) && !map_->tdata()->is_village(new_t)) {
321 int owner = village_owner(loc);
322 if (owner != -1)
323 teams_[owner].lose_village(loc);
324 }
325
326 map_->set_terrain(loc, new_t);
327
328 for(const t_translation::terrain_code &ut : map_->underlying_union_terrain(loc)) {
329 preferences::encountered_terrains().insert(ut);
330 }
331 return true;
332 }
333
write_config(config & cfg) const334 void game_board::write_config(config & cfg) const
335 {
336 cfg["next_underlying_unit_id"] = unit_id_manager_.get_save_id();
337 for(std::vector<team>::const_iterator t = teams_.begin(); t != teams_.end(); ++t) {
338 int side_num = std::distance(teams_.begin(), t) + 1;
339
340 config& side = cfg.add_child("side");
341 t->write(side);
342 side["no_leader"] = true;
343 side["side"] = std::to_string(side_num);
344
345 //current units
346 for(const unit & i : units_) {
347 if(i.side() == side_num) {
348 config& u = side.add_child("unit");
349 i.get_location().write(u);
350 i.write(u);
351 }
352 }
353 //recall list
354 for(const unit_const_ptr & j : t->recall_list()) {
355 config& u = side.add_child("unit");
356 j->write(u);
357 }
358 }
359
360 //write the map
361 cfg["map_data"] = map_->write();
362 }
363
temporary_unit_placer(unit_map & m,const map_location & loc,unit & u)364 temporary_unit_placer::temporary_unit_placer(unit_map& m, const map_location& loc, unit& u)
365 : m_(m), loc_(loc), temp_(m_.extract(loc))
366 {
367 u.mark_clone(true);
368 m_.add(loc, u);
369 }
370
temporary_unit_placer(game_board & b,const map_location & loc,unit & u)371 temporary_unit_placer::temporary_unit_placer(game_board& b, const map_location& loc, unit& u)
372 : m_(b.units_), loc_(loc), temp_(m_.extract(loc))
373 {
374 u.mark_clone(true);
375 m_.add(loc, u);
376 }
377
~temporary_unit_placer()378 temporary_unit_placer::~temporary_unit_placer()
379 {
380 try {
381 m_.erase(loc_);
382 if(temp_) {
383 m_.insert(temp_);
384 }
385 } catch (...) {}
386 }
387
temporary_unit_remover(unit_map & m,const map_location & loc)388 temporary_unit_remover::temporary_unit_remover(unit_map& m, const map_location& loc)
389 : m_(m), loc_(loc), temp_(m_.extract(loc))
390 {
391 }
392
temporary_unit_remover(game_board & b,const map_location & loc)393 temporary_unit_remover::temporary_unit_remover(game_board& b, const map_location& loc)
394 : m_(b.units_), loc_(loc), temp_(m_.extract(loc))
395 {
396 }
397
~temporary_unit_remover()398 temporary_unit_remover::~temporary_unit_remover()
399 {
400 try {
401 if(temp_) {
402 m_.insert(temp_);
403 }
404 } catch (...) {}
405 }
406
407 /**
408 * Constructor
409 * This version will change the unit's current movement to @a new_moves while
410 * the unit is moved (and restored to its previous value upon this object's
411 * destruction).
412 */
temporary_unit_mover(unit_map & m,const map_location & src,const map_location & dst,int new_moves)413 temporary_unit_mover::temporary_unit_mover(unit_map& m, const map_location& src,
414 const map_location& dst, int new_moves)
415 : m_(m), src_(src), dst_(dst), old_moves_(-1),
416 temp_(src == dst ? unit_ptr() : m_.extract(dst))
417 {
418 std::pair<unit_map::iterator, bool> move_result = m_.move(src_, dst_);
419
420 // Set the movement.
421 if ( move_result.second )
422 {
423 old_moves_ = move_result.first->movement_left(true);
424 move_result.first->set_movement(new_moves);
425 }
426 }
427
temporary_unit_mover(game_board & b,const map_location & src,const map_location & dst,int new_moves)428 temporary_unit_mover::temporary_unit_mover(game_board& b, const map_location& src,
429 const map_location& dst, int new_moves)
430 : m_(b.units_), src_(src), dst_(dst), old_moves_(-1),
431 temp_(src == dst ? unit_ptr() : m_.extract(dst))
432 {
433 std::pair<unit_map::iterator, bool> move_result = m_.move(src_, dst_);
434
435 // Set the movement.
436 if ( move_result.second )
437 {
438 old_moves_ = move_result.first->movement_left(true);
439 move_result.first->set_movement(new_moves);
440 }
441 }
442
443 /**
444 * Constructor
445 * This version does not change (nor restore) the unit's movement.
446 */
temporary_unit_mover(unit_map & m,const map_location & src,const map_location & dst)447 temporary_unit_mover::temporary_unit_mover(unit_map& m, const map_location& src,
448 const map_location& dst)
449 : m_(m), src_(src), dst_(dst), old_moves_(-1),
450 temp_(src == dst ? unit_ptr() : m_.extract(dst))
451 {
452 m_.move(src_, dst_);
453 }
454
temporary_unit_mover(game_board & b,const map_location & src,const map_location & dst)455 temporary_unit_mover::temporary_unit_mover(game_board& b, const map_location& src,
456 const map_location& dst)
457 : m_(b.units_), src_(src), dst_(dst), old_moves_(-1),
458 temp_(src == dst ? unit_ptr() : m_.extract(dst))
459 {
460 m_.move(src_, dst_);
461 }
462
~temporary_unit_mover()463 temporary_unit_mover::~temporary_unit_mover()
464 {
465 try {
466 std::pair<unit_map::iterator, bool> move_result = m_.move(dst_, src_);
467
468 // Restore the movement?
469 if ( move_result.second && old_moves_ >= 0 )
470 move_result.first->set_movement(old_moves_);
471
472 // Restore the extracted unit?
473 if(temp_) {
474 m_.insert(temp_);
475 }
476 } catch (...) {}
477 }
478