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