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 &current_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