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 #include "teambuilder.hpp"
15 
16 #include "actions/create.hpp"
17 #include "config.hpp"
18 #include "game_board.hpp"
19 #include "log.hpp"
20 #include "map/map.hpp"
21 #include "team.hpp"
22 #include "units/unit.hpp"
23 #include "units/map.hpp"
24 #include "resources.hpp"
25 #include "gettext.hpp"
26 #include "game_errors.hpp"
27 #include "serialization/string_utils.hpp"
28 
29 #include <deque>
30 #include <vector>
31 
32 static lg::log_domain log_engine_tc("engine/team_construction");
33 #define ERR_NG_TC LOG_STREAM(err, log_engine_tc)
34 #define WRN_NG_TC LOG_STREAM(warn, log_engine_tc)
35 #define LOG_NG_TC LOG_STREAM(info, log_engine_tc)
36 #define DBG_NG_TC LOG_STREAM(debug, log_engine_tc)
37 
38 class team_builder {
39 public:
team_builder(const config & side_cfg,std::vector<team> & teams,const config & level,game_board & board,int num)40 	team_builder(const config& side_cfg, std::vector<team>& teams,
41 		     const config& level, game_board& board, int num)
42 		: gold_info_ngold_(0)
43 		, leader_configs_()
44 		, level_(level)
45 		, board_(board)
46 		, player_exists_(false)
47 		, seen_ids_()
48 		, side_(num)
49 		, side_cfg_(side_cfg)
50 		, t_(nullptr)
51 		, teams_(teams)
52 		, unit_configs_()
53 	{
54 	}
55 
build_team_stage_one()56 	void build_team_stage_one()
57 	{
58 		//initialize the context variables and flags, find relevant tags, set up everything
59 		init();
60 
61 		//find out the correct qty of gold and handle gold carryover.
62 		gold();
63 
64 		//create a new instance of team and push it to back of resources::gameboard->teams() vector
65 		new_team();
66 
67 		assert(t_!=nullptr);
68 
69 		//set team objectives if necessary
70 		objectives();
71 
72 		// If the game state specifies additional units that can be recruited by the player, add them.
73 		previous_recruits();
74 
75 		//place leader
76 		leader();
77 
78 		//prepare units, populate obvious recall lists elements
79 		prepare_units();
80 
81 	}
82 
83 
build_team_stage_two()84 	void build_team_stage_two()
85 	{
86 		//place units
87 		//this is separate stage because we need to place units only after every other team is constructed
88 		place_units();
89 
90 	}
91 
92 protected:
93 
94 	int gold_info_ngold_;
95 	std::deque<config> leader_configs_;
96 	//only used for objectives
97 	const config &level_;
98 	game_board &board_;
99 	//only used for debug message
100 	bool player_exists_;
101 	std::set<std::string> seen_ids_;
102 	int side_;
103 	const config &side_cfg_;
104 	team *t_;
105 	std::vector<team> &teams_;
106 	std::vector<const config*> unit_configs_;
107 
log_step(const char * s) const108 	void log_step(const char *s) const
109 	{
110 		LOG_NG_TC << "team "<<side_<<" construction: "<< s << std::endl;
111 	}
112 
113 
init()114 	void init()
115 	{
116 		if (side_cfg_["side"].to_int(side_) != side_) {
117 			ERR_NG_TC << "found invalid side=" << side_cfg_["side"].to_int(side_) << " in definition of side number " << side_ << std::endl;
118 		}
119 		t_ = &teams_[side_ - 1];
120 		log_step("init");
121 
122 		//track whether a [player] tag with persistence information exists (in addition to the [side] tag)
123 		player_exists_ = false;
124 
125 		if(board_.map().empty()) {
126 			throw game::load_game_failed("Map not found");
127 		}
128 
129 		DBG_NG_TC << "snapshot: " << utils::bool_string(player_exists_) <<std::endl;
130 
131 		unit_configs_.clear();
132 		seen_ids_.clear();
133 
134 	}
135 
136 
gold()137 	void gold()
138 	{
139 		log_step("gold");
140 
141 		gold_info_ngold_ = side_cfg_["gold"];
142 
143 		DBG_NG_TC << "set gold to '" << gold_info_ngold_ << "'\n";
144 	}
145 
146 
new_team()147 	void new_team()
148 	{
149 		log_step("new team");
150 		t_->build(side_cfg_, board_.map(), gold_info_ngold_);
151 	}
152 
153 
objectives()154 	void objectives()
155 	{
156 		log_step("objectives");
157 		// If this team has no objectives, set its objectives
158 		// to the level-global "objectives"
159 		// this is only used by the default mp 'Defeat enemy leader' objectives
160 		if (t_->objectives().empty())
161 			t_->set_objectives(level_["objectives"], false);
162 	}
163 
164 
previous_recruits()165 	void previous_recruits()
166 	{
167 		log_step("previous recruits");
168 		// If the game state specifies units that
169 		// can be recruited for the player, add them.
170 		if (!side_cfg_) return;
171 		if (const config::attribute_value *v = side_cfg_.get("previous_recruits")) {
172 			for (const std::string &rec : utils::split(*v)) {
173 				DBG_NG_TC << "adding previous recruit: " << rec << '\n';
174 				t_->add_recruit(rec);
175 			}
176 		}
177 	}
178 
179 
180 
181 
handle_unit(const config & u,const char * origin)182 	void handle_unit(const config &u, const char *origin)
183 	{
184 		DBG_NG_TC
185 			<< "unit from "<<origin
186 			<< ": type=["<<u["type"]
187 			<< "] id=["<<u["id"]
188 			<< "] placement=["<<u["placement"]
189 			<< "] x=["<<u["x"]
190 			<< "] y=["<<u["y"]
191 			<<"]"<< std::endl;
192 
193 		if (u["type"].empty()) {
194 			WRN_NG_TC << "warning: when building level, skipping a unit (id=[" << u["id"] << "]) from " << origin
195 			<< " with no type information,\n"
196 			<< "for side:\n" << side_cfg_.debug() << std::endl;
197 
198 			return ;
199 		}
200 
201 		const std::string &id = u["id"];
202 		if (!id.empty()) {
203 			if ( seen_ids_.find(id)!=seen_ids_.end() ) {
204 				//seen before
205 				config u_tmp = u;
206 				u_tmp["side"] = std::to_string(side_);
207 				t_->recall_list().add(unit::create(u_tmp,true));
208 			} else {
209 				//not seen before
210 				unit_configs_.push_back(&u);
211 				seen_ids_.insert(id);
212 			}
213 
214 		} else {
215 			unit_configs_.push_back(&u);
216 		}
217 	}
218 
handle_leader(const config & leader)219 	void handle_leader(const config &leader)
220 	{
221 		// Make a persistent copy of the config.
222 		leader_configs_.push_back(leader);
223 		config & stored = leader_configs_.back();
224 
225 		// Remove the attributes used to define a side.
226 		for (const std::string & attr : team::attributes) {
227 			stored.remove_attribute(attr);
228 		}
229 
230 		// Provide some default values, if not specified.
231 		config::attribute_value &a1 = stored["canrecruit"];
232 		if (a1.blank()) a1 = true;
233 		config::attribute_value &a2 = stored["placement"];
234 		if (a2.blank()) a2 = "map,leader";
235 
236 		// Add the leader to the list of units to create.
237 		handle_unit(stored, "leader_cfg");
238 	}
239 
leader()240 	void leader()
241 	{
242 		log_step("leader");
243 		// If this side tag describes the leader of the side, we can simply add it to front of unit queue
244 		// there was a hack: if this side tag describes the leader of the side,
245 		// we may replace the leader with someone from recall list who can recruit, but take positioning from [side]
246 		// this hack shall be removed, since it messes up with 'multiple leaders'
247 
248 		// If this side tag describes the leader of the side
249 		if (!side_cfg_["type"].empty() && side_cfg_["type"] != "null" ) {
250 			handle_leader(side_cfg_);
251 		}
252 		for (const config &l : side_cfg_.child_range("leader")) {
253 			handle_leader(l);
254 		}
255 	}
256 
257 
prepare_units()258 	void prepare_units()
259 	{
260 		//if this is a start-of-scenario save then  playcampaign.cpp merged
261 		//units in [replay_start][side] merged with [side] already
262 		//units that are in '[scenario][side]' are 'first'
263 
264 		//for create-or-recall semantics to work: for each unit with non-empty
265 		//id, unconditionally put OTHER, later, units with same id directly to
266 		//recall list, not including them in unit_configs_
267 		for (const config &su : side_cfg_.child_range("unit")) {
268 			handle_unit(su, "side_cfg");
269 		}
270 	}
271 
272 
place_units()273 	void place_units()
274 	{
275 		log_step("place units");
276 		unit_creator uc(*t_, board_.map().starting_position(side_), &board_);
277 		uc
278 			.allow_add_to_recall(true)
279 			.allow_discover(true)
280 			.allow_get_village(false)
281 			.allow_invalidate(false)
282 			.allow_rename_side(true)
283 			.allow_show(false);
284 
285 		for (const config *u : unit_configs_) {
286 			try {
287 				uc.add_unit(*u);
288 			}
289 			catch (const unit_type::error& e) {
290 				ERR_NG_TC << e.what() << "\n";
291 			}
292 		}
293 	}
294 
295 };
296 
create_team_builder(const config & side_cfg,std::vector<team> & teams,const config & level,game_board & board,int num)297 team_builder_ptr create_team_builder(const config& side_cfg,
298 					 std::vector<team>& teams,
299 					 const config& level, game_board& board, int num)
300 {
301 	return team_builder_ptr(new team_builder(side_cfg, teams, level, board, num));
302 }
303 
build_team_stage_one(team_builder_ptr tb_ptr)304 void build_team_stage_one(team_builder_ptr tb_ptr)
305 {
306 	tb_ptr->build_team_stage_one();
307 }
308 
build_team_stage_two(team_builder_ptr tb_ptr)309 void build_team_stage_two(team_builder_ptr tb_ptr)
310 {
311 	tb_ptr->build_team_stage_two();
312 }
313