1 /*
2  Copyright (C) 2010 - 2018 by Gabriel Morin <gabrielmorin (at) gmail (dot) 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 /**
16  * @file
17  */
18 
19 #include "whiteboard/recall.hpp"
20 
21 #include "whiteboard/manager.hpp"
22 #include "whiteboard/side_actions.hpp"
23 #include "whiteboard/utility.hpp"
24 #include "whiteboard/visitor.hpp"
25 
26 #include "actions/create.hpp"
27 #include "display.hpp"
28 #include "fake_unit_manager.hpp"
29 #include "fake_unit_ptr.hpp"
30 #include "game_board.hpp"
31 #include "recall_list_manager.hpp"
32 #include "resources.hpp"
33 #include "replay_helper.hpp"
34 #include "statistics.hpp"
35 #include "synced_context.hpp"
36 #include "team.hpp"
37 #include "units/filter.hpp"
38 #include "units/unit.hpp"
39 #include "units/animation_component.hpp"
40 
41 namespace wb
42 {
43 
operator <<(std::ostream & s,recall_ptr recall)44 std::ostream& operator<<(std::ostream& s, recall_ptr recall)
45 {
46 	assert(recall);
47 	return recall->print(s);
48 }
operator <<(std::ostream & s,recall_const_ptr recall)49 std::ostream& operator<<(std::ostream& s, recall_const_ptr recall)
50 {
51 	assert(recall);
52 	return recall->print(s);
53 }
54 
print(std::ostream & s) const55 std::ostream& recall::print(std::ostream &s) const
56 {
57 	s << "Recalling " << fake_unit_->name() << " [" << fake_unit_->id() << "] on hex " << recall_hex_;
58 	return s;
59 }
60 
recall(size_t team_index,bool hidden,const unit & u,const map_location & recall_hex)61 recall::recall(size_t team_index, bool hidden, const unit& u, const map_location& recall_hex)
62 	: action(team_index,hidden)
63 	, temp_unit_(u.clone())
64 	, recall_hex_(recall_hex)
65 	, fake_unit_(u.clone())
66 	, original_mp_(0)
67 	, original_ap_(0)
68 	, original_recall_pos_(0)
69 {
70 	this->init();
71 }
72 
recall(const config & cfg,bool hidden)73 recall::recall(const config& cfg, bool hidden)
74 	: action(cfg,hidden)
75 	, temp_unit_()
76 	, recall_hex_(cfg.child("recall_hex_")["x"],cfg.child("recall_hex_")["y"], wml_loc())
77 	, fake_unit_()
78 	, original_mp_(0)
79 	, original_ap_(0)
80 	, original_recall_pos_(0)
81 {
82 	// Construct and validate temp_unit_
83 	size_t underlying_id = cfg["temp_unit_"];
84 	for(const unit_ptr & recall_unit : resources::gameboard->teams().at(team_index()).recall_list())
85 	{
86 		if(recall_unit->underlying_id()==underlying_id)
87 		{
88 			temp_unit_ = recall_unit;
89 			break;
90 		}
91 	}
92 	if(!temp_unit_.get()) {
93 		throw action::ctor_err("recall: Invalid underlying_id");
94 	}
95 
96 	fake_unit_.reset(temp_unit_->clone()); //makes copy of temp_unit_
97 
98 	this->init();
99 }
100 
init()101 void recall::init()
102 {
103 	fake_unit_->set_location(recall_hex_);
104 	fake_unit_->set_movement(0, true);
105 	fake_unit_->set_attacks(0);
106 	fake_unit_->anim_comp().set_ghosted(false);
107 	fake_unit_.place_on_fake_unit_manager( resources::fake_units);
108 }
109 
~recall()110 recall::~recall()
111 {
112 }
113 
accept(visitor & v)114 void recall::accept(visitor& v)
115 {
116 	v.visit(shared_from_this());
117 }
118 
execute(bool & success,bool & complete)119 void recall::execute(bool& success, bool& complete)
120 {
121 	team & current_team = resources::gameboard->teams().at(team_index());
122 
123 	assert(valid());
124 	assert(temp_unit_.get());
125 	temporary_unit_hider const raii(*fake_unit_);
126 	//Give back the spent gold so we don't get "not enough gold" message
127 	int cost = current_team.recall_cost();
128 	if (temp_unit_->recall_cost() > -1) {
129 		cost=temp_unit_->recall_cost();
130 	}
131 	current_team.get_side_actions()->change_gold_spent_by(-cost);
132 	bool const result = synced_context::run_and_throw("recall",
133 		replay_helper::get_recall(temp_unit_->id(), recall_hex_, map_location::null_location()),
134 		true,
135 		true,
136 		synced_context::ignore_error_function);
137 
138 	if (!result) {
139 		current_team.get_side_actions()->change_gold_spent_by(cost);
140 	}
141 	success = complete = result;
142 }
143 
apply_temp_modifier(unit_map & unit_map)144 void recall::apply_temp_modifier(unit_map& unit_map)
145 {
146 	assert(valid());
147 
148 
149 	DBG_WB << "Inserting future recall " << temp_unit_->name() << " [" << temp_unit_->id()
150 			<< "] at position " << temp_unit_->get_location() << ".\n";
151 
152 	//temporarily remove unit from recall list
153 	unit_ptr it = resources::gameboard->teams().at(team_index()).recall_list().extract_if_matches_id(temp_unit_->id(), &original_recall_pos_);
154 	assert(it);
155 
156 	//Usually (temp_unit_ == it) is true here, but wml might have changed the original unit in which case not doing 'temp_unit_ = it' would result in a gamestate change.
157 	temp_unit_ = it;
158 	original_mp_ = temp_unit_->movement_left(true);
159 	original_ap_ = temp_unit_->attacks_left(true);
160 
161 	temp_unit_->set_movement(0, true);
162 	temp_unit_->set_attacks(0);
163 	temp_unit_->set_location(recall_hex_);
164 
165 	//Add cost to money spent on recruits.
166 	int cost = resources::gameboard->teams().at(team_index()).recall_cost();
167 	if (it->recall_cost() > -1) {
168 		cost = it->recall_cost();
169 	}
170 
171 	// Temporarily insert unit into unit_map
172 	//unit map takes ownership of temp_unit
173 	unit_map.insert(temp_unit_);
174 
175 	resources::gameboard->teams().at(team_index()).get_side_actions()->change_gold_spent_by(cost);
176 	// Update gold in top bar
177 	display::get_singleton()->invalidate_game_status();
178 }
179 
remove_temp_modifier(unit_map & unit_map)180 void recall::remove_temp_modifier(unit_map& unit_map)
181 {
182 	temp_unit_ = unit_map.extract(recall_hex_);
183 	assert(temp_unit_.get());
184 
185 	temp_unit_->set_movement(original_mp_, true);
186 	temp_unit_->set_attacks(original_ap_);
187 
188 	original_mp_ = 0;
189 	original_ap_ = 0;
190 	//Put unit back into recall list
191 	resources::gameboard->teams().at(team_index()).recall_list().add(temp_unit_, original_recall_pos_);
192 }
193 
draw_hex(const map_location & hex)194 void recall::draw_hex(const map_location& hex)
195 {
196 	if (hex == recall_hex_)
197 	{
198 		const double x_offset = 0.5;
199 		const double y_offset = 0.7;
200 		//position 0,0 in the hex is the upper left corner
201 		std::stringstream number_text;
202 		unit &it = *get_unit();
203 		int cost = statistics::un_recall_unit_cost(it);
204 		if (cost < 0) {
205 			number_text << font::unicode_minus << resources::gameboard->teams().at(team_index()).recall_cost();
206 		}
207 		else {
208 			number_text << font::unicode_minus << cost;
209 		}
210 		size_t font_size = 16;
211 		color_t color {255, 0, 0}; //red
212 		display::get_singleton()->draw_text_in_hex(hex, display::LAYER_ACTIONS_NUMBERING,
213 						number_text.str(), font_size, color, x_offset, y_offset);
214 	}
215 }
216 
redraw()217 void recall::redraw()
218 {
219 	display::get_singleton()->invalidate(recall_hex_);
220 }
221 
check_validity() const222 action::error recall::check_validity() const
223 {
224 	//Check that destination hex is still free
225 	if(resources::gameboard->units().find(recall_hex_) != resources::gameboard->units().end()) {
226 		return LOCATION_OCCUPIED;
227 	}
228 	//Check that unit to recall is still in side's recall list
229 	if( !resources::gameboard->teams()[team_index()].recall_list().find_if_matches_id(temp_unit_->id()) ) {
230 		return UNIT_UNAVAILABLE;
231 	}
232 	//Check that there is still enough gold to recall this unit
233 	if(resources::gameboard->teams()[team_index()].recall_cost() > resources::gameboard->teams()[team_index()].gold()) {
234 		return NOT_ENOUGH_GOLD;
235 	}
236 	//Check that there is a leader available to recall this unit
237 	bool has_recruiter = any_recruiter(team_index() + 1, get_recall_hex(), [&](unit& leader) {
238 		const unit_filter ufilt(vconfig(leader.recall_filter()));
239 		return ufilt(*temp_unit_, map_location::null_location());
240 	});
241 
242 	if(!has_recruiter) {
243 		return NO_LEADER;
244 	}
245 
246 	return OK;
247 }
248 
249 ///@todo Find a better way to serialize unit_ because underlying_id isn't cutting it
to_config() const250 config recall::to_config() const
251 {
252 	config final_cfg = action::to_config();
253 
254 	final_cfg["type"] = "recall";
255 	final_cfg["temp_unit_"] = static_cast<int>(temp_unit_->underlying_id());
256 //	final_cfg["temp_cost_"] = temp_cost_; //Unnecessary
257 
258 	config loc_cfg;
259 	loc_cfg["x"]=recall_hex_.wml_x();
260 	loc_cfg["y"]=recall_hex_.wml_y();
261 	final_cfg.add_child("recall_hex_", std::move(loc_cfg));
262 
263 	return final_cfg;
264 }
265 
do_hide()266 void recall::do_hide() {fake_unit_->set_hidden(true);}
do_show()267 void recall::do_show() {fake_unit_->set_hidden(false);}
268 
269 } //end namespace wb
270