1 /*
2    Copyright (C) 2003 - 2018 by David White <dave@whitevine.net>
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 /**
16  * @file
17  * Recruiting, recalling.
18  */
19 
20 #include "actions/unit_creator.hpp"
21 
22 #include "actions/move.hpp" //for actions::get_village
23 
24 #include "config.hpp"
25 #include "display.hpp"
26 #include "filter_context.hpp"
27 #include "game_board.hpp"
28 #include "game_events/pump.hpp"
29 #include "preferences/game.hpp"
30 #include "game_data.hpp" // for resources::gamedata conversion variable_set
31 #include "gettext.hpp"
32 #include "log.hpp"
33 #include "map/map.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "resources.hpp" // for resources::gamedata
36 #include "team.hpp" //for team
37 #include "units/unit.hpp" // for unit
38 #include "units/udisplay.hpp" // for unit_display
39 #include "variable.hpp" // for vconfig
40 #include "deprecation.hpp"
41 
42 static lg::log_domain log_engine("engine");
43 #define DBG_NG LOG_STREAM(debug, log_engine)
44 #define LOG_NG LOG_STREAM(info, log_engine)
45 #define WARN_NG LOG_STREAM(warn, log_engine)
46 #define ERR_NG LOG_STREAM(err, log_engine)
47 
unit_creator(team & tm,const map_location & start_pos,game_board * board)48 unit_creator::unit_creator(team &tm, const map_location &start_pos, game_board* board)
49   : add_to_recall_(false)
50   , discover_(false)
51   , get_village_(false)
52   , invalidate_(false)
53   , rename_side_(false)
54   , show_(false)
55   , start_pos_(start_pos)
56   , team_(tm)
57   , board_(board ? board : resources::gameboard)
58 {
59 }
60 
61 
allow_show(bool b)62 unit_creator& unit_creator::allow_show(bool b)
63 {
64 	show_ = b;
65 	return *this;
66 }
67 
68 
allow_get_village(bool b)69 unit_creator& unit_creator::allow_get_village(bool b)
70 {
71 	get_village_ = b;
72 	return *this;
73 }
74 
75 
allow_rename_side(bool b)76 unit_creator& unit_creator::allow_rename_side(bool b)
77 {
78 	rename_side_ = b;
79 	return *this;
80 }
81 
allow_invalidate(bool b)82 unit_creator& unit_creator::allow_invalidate(bool b)
83 {
84 	invalidate_ = b;
85 	return *this;
86 }
87 
88 
allow_discover(bool b)89 unit_creator& unit_creator::allow_discover(bool b)
90 {
91 	discover_ = b;
92 	return *this;
93 }
94 
95 
allow_add_to_recall(bool b)96 unit_creator& unit_creator::allow_add_to_recall(bool b)
97 {
98 	add_to_recall_ = b;
99 	return *this;
100 }
101 
102 
find_location(const config & cfg,const unit * pass_check)103 map_location unit_creator::find_location(const config &cfg, const unit* pass_check)
104 {
105 
106 	DBG_NG << "finding location for unit with id=["<<cfg["id"]<<"] placement=["<<cfg["placement"]<<"] x=["<<cfg["x"]<<"] y=["<<cfg["y"]<<"] for side " << team_.side() << "\n";
107 
108 	std::vector<std::string> placements = utils::split(cfg["placement"]);
109 
110 	placements.push_back("map");
111 	placements.push_back("recall");
112 
113 	bool pass = cfg["passable"].to_bool(false);
114 	bool vacant = !cfg["overwrite"].to_bool(false);
115 
116 	for (const std::string& place : placements)
117 	{
118 		map_location loc;
119 
120 		if ( place == "recall" ) {
121 			return map_location::null_location();
122 		}
123 
124 		else if ( place == "leader"  ||  place == "leader_passable" ) {
125 			unit_map::const_iterator leader = board_->units().find_leader(team_.side());
126 			//todo: take 'leader in recall list' possibility into account
127 			if (leader.valid()) {
128 				loc = leader->get_location();
129 			} else {
130 				loc = start_pos_;
131 			}
132 			if(place == "leader_passable") {
133 				deprecated_message("placement=leader_passable", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Please use placement=leader and passable=yes instead");
134 				pass = true;
135 			}
136 		}
137 
138 		// "map", "map_passable", and "map_overwrite".
139 		else if(place == "map"  ||  place == "map_passable" || place == "map_overwrite") {
140 			if(cfg.has_attribute("location_id")) {
141 				loc = board_->map().special_location(cfg["location_id"]);
142 			}
143 			if(!loc.valid()) {
144 				loc = map_location(cfg, resources::gamedata);
145 			}
146 			if(place == "map_passable") {
147 				deprecated_message("placement=map_passable", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Please use placement=map and passable=yes instead");
148 				pass = true;
149 			} else if(place == "map_overwrite") {
150 				deprecated_message("placement=map_overwrite", DEP_LEVEL::PREEMPTIVE, {1, 15, 0}, "Please use placement=map and overwrite=yes instead");
151 				vacant = false;
152 			}
153 		}
154 
155 		if(loc.valid() && board_->map().on_board(loc)) {
156 			if(vacant) {
157 				loc = find_vacant_tile(loc, pathfind::VACANT_ANY,
158 				                       pass ? pass_check : nullptr, nullptr, board_);
159 			}
160 			if(loc.valid() && board_->map().on_board(loc)) {
161 				return loc;
162 			}
163 		}
164 	}
165 
166 	return map_location::null_location();
167 
168 }
169 
170 
add_unit(const config & cfg,const vconfig * vcfg)171 void unit_creator::add_unit(const config &cfg, const vconfig* vcfg)
172 {
173 	config temp_cfg(cfg);
174 	temp_cfg["side"] = team_.side();
175 
176 	const std::string& id =(cfg)["id"];
177 	bool animate = temp_cfg["animate"].to_bool();
178 	bool fire_event = temp_cfg["fire_event"].to_bool(true);
179 	temp_cfg.remove_attribute("animate");
180 
181 	unit_ptr recall_list_element = team_.recall_list().find_if_matches_id(id);
182 
183 	if ( !recall_list_element ) {
184 		//make the new unit
185 		unit_ptr new_unit = unit::create(temp_cfg, true, vcfg);
186 		map_location loc = find_location(temp_cfg, new_unit.get());
187 		if ( loc.valid() ) {
188 			//add the new unit to map
189 			board_->units().replace(loc, new_unit);
190 			LOG_NG << "inserting unit for side " << new_unit->side() << "\n";
191 			post_create(loc,*(board_->units().find(loc)),animate,fire_event);
192 		}
193 		else if ( add_to_recall_ ) {
194 			//add to recall list
195 			team_.recall_list().add(new_unit);
196 			DBG_NG << "inserting unit with id=["<<id<<"] on recall list for side " << new_unit->side() << "\n";
197 			preferences::encountered_units().insert(new_unit->type_id());
198 		}
199 	} else {
200 		//get unit from recall list
201 		map_location loc = find_location(temp_cfg, recall_list_element.get());
202 		if ( loc.valid() ) {
203 			board_->units().replace(loc, recall_list_element);
204 			LOG_NG << "inserting unit from recall list for side " << recall_list_element->side()<< " with id="<< id << "\n";
205 			post_create(loc,*(board_->units().find(loc)),animate,fire_event);
206 			//if id is not empty, delete units with this ID from recall list
207 			team_.recall_list().erase_if_matches_id( id);
208 		}
209 		else if ( add_to_recall_ ) {
210 			LOG_NG << "wanted to insert unit on recall list, but recall list for side " << (cfg)["side"] << "already contains id=" <<id<<"\n";
211 			return;
212 		}
213 	}
214 }
215 
216 
post_create(const map_location & loc,const unit & new_unit,bool anim,bool fire_event)217 void unit_creator::post_create(const map_location &loc, const unit &new_unit, bool anim, bool fire_event)
218 {
219 
220 	if (discover_) {
221 		preferences::encountered_units().insert(new_unit.type_id());
222 	}
223 
224 	bool show = show_ && (display::get_singleton() !=nullptr) && !display::get_singleton()->fogged(loc);
225 	bool animate = show && anim;
226 
227 	if (get_village_) {
228 		assert(resources::gameboard);
229 		if (board_->map().is_village(loc)) {
230 			actions::get_village(loc, new_unit.side());
231 		}
232 	}
233 
234 	// Only fire the events if it's safe; it's not if we're in the middle of play_controller::reset_gamestate()
235 	if (fire_event && resources::lua_kernel != nullptr) {
236 		resources::game_events->pump().fire("unit_placed", loc);
237 	}
238 
239 	if (display::get_singleton()!=nullptr) {
240 
241 		if (invalidate_ ) {
242 			display::get_singleton()->invalidate(loc);
243 		}
244 
245 		if (animate) {
246 			unit_display::unit_recruited(loc);
247 		}
248 	}
249 }
250