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