The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
advancement.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 - 2025
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 * Fighting.
18 */
19
21
22#include "actions/vision.hpp"
23
24#include "ai/manager.hpp" // for manager, holder
26#include "game_events/pump.hpp"
28#include "game_data.hpp" //resources::gamedata->phase()
29#include "gettext.hpp"
31#include "log.hpp"
32#include "play_controller.hpp" //resources::controller
33#include "random.hpp"
34#include "resources.hpp"
35#include "statistics.hpp"
37#include "units/unit.hpp"
38#include "units/types.hpp"
40#include "units/helper.hpp" //number_of_possible_advances
41#include "video.hpp"
43
44static lg::log_domain log_engine("engine");
45#define DBG_NG LOG_STREAM(debug, log_engine)
46#define LOG_NG LOG_STREAM(info, log_engine)
47#define WRN_NG LOG_STREAM(warn, log_engine)
48#define ERR_NG LOG_STREAM(err, log_engine)
49
50static lg::log_domain log_config("config");
51#define LOG_CF LOG_STREAM(info, log_config)
52
53static lg::log_domain log_display("display");
54#define LOG_DP LOG_STREAM(info, log_display)
55
56
57namespace
58{
59 int advance_unit_dialog(const map_location &loc)
60 {
61 const auto u_it = resources::gameboard->units().find(loc);
62 if(!u_it) {
63 ERR_NG << "advance_unit_dialog: unit not found";
64 return 0;
65 }
66 const unit& u = *u_it;
67 std::vector<unit_const_ptr> previews;
68
69 for (const std::string& advance : u.advances_to()) {
70 prefs::get().encountered_units().insert(advance);
71 previews.push_back(get_advanced_unit(u, advance));
72 }
73
74 std::size_t num_real_advances = previews.size();
75 bool always_display = false;
76
77 for (const config& advance : u.get_modification_advances()) {
78 if (advance["always_display"].to_bool()) {
79 always_display = true;
80 }
81 previews.push_back(get_amla_unit(u, advance));
82 }
83
84 if (previews.size() > 1 || always_display) {
85 gui2::dialogs::unit_advance dlg(previews, num_real_advances);
86
87 if(dlg.show()) {
88 return dlg.get_selected_index();
89 }
90
91 // This should be unreachable, since canceling is disabled for the dialog
92 assert(false && "Unit advance dialog was cancelled, which should be impossible.");
93 }
94
95 return 0;
96 }
97
98 bool animate_unit_advancement(const map_location &loc, std::size_t choice, const bool &fire_event, const bool animate)
99 {
100 const events::command_disabler cmd_disabler;
101
103 if (u == resources::gameboard->units().end()) {
104 LOG_DP << "animate_unit_advancement suppressed: invalid unit";
105 return false;
106 }
107 else if (!u->advances()) {
108 LOG_DP << "animate_unit_advancement suppressed: unit does not advance";
109 return false;
110 }
111
112 const std::vector<std::string>& options = u->advances_to();
113 std::vector<config> mod_options = u->get_modification_advances();
114
115 assert(options.size() + mod_options.size() > 0);
116 if (choice >= options.size() + mod_options.size()) {
117 LOG_DP << "animate_unit_advancement: invalid option, using first option";
118 choice = 0;
119 }
120
121 // When the unit advances, it fades to white, and then switches
122 // to the new unit, then fades back to the normal color
123
124 if (animate && !video::headless() && !resources::controller->is_skipping_replay()) {
125 unit_animator animator;
126 bool with_bars = true;
127 animator.add_animation(u.get_shared_ptr(), "levelout", u->get_location(), map_location(), 0, with_bars);
128 animator.start_animations();
129 animator.wait_for_end();
130 }
131
132 if (choice < options.size()) {
133 // chosen_unit is not a reference, since the unit may disappear at any moment.
134 std::string chosen_unit = options[choice];
135 ::advance_unit(loc, chosen_unit, fire_event);
136 }
137 else {
138 const config &mod_option = mod_options[choice - options.size()];
139 ::advance_unit(loc, &mod_option, fire_event);
140 }
141
144
145 if (animate && u != resources::gameboard->units().end() && !video::headless() && !resources::controller->is_skipping_replay()) {
146 unit_animator animator;
147 animator.add_animation(u.get_shared_ptr(), "levelin", u->get_location(), map_location(), 0, true);
148 animator.start_animations();
149 animator.wait_for_end();
150 animator.set_all_standing();
152 events::pump();
153 }
154
156
157 return true;
158 }
159
160 int get_advancement_index(const unit& u, const std::string& id)
161 {
162 const std::vector<std::string>& type_options = u.advances_to();
163 {
164 auto pick_iter = std::find(type_options.begin(), type_options.end(), id);
165 if(pick_iter != type_options.end()) {
166 return std::distance(type_options.begin(), pick_iter);
167 }
168 }
169 {
170 auto amla_options = u.get_modification_advances();
171 auto pick_iter = std::find_if(amla_options.begin(), amla_options.end(), [&](const config& adv){
172 return adv["id"].str() == id;
173 });
174 if(pick_iter != amla_options.end()) {
175 return type_options.size() + std::distance(amla_options.begin(), pick_iter);
176 }
177 }
178 return -1;
179 }
180
181 class unit_advancement_choice : public mp_sync::user_choice
182 {
183 public:
184 unit_advancement_choice(const map_location& loc, int total_opt, int side_num, bool force_dialog)
185 : loc_ (loc), nb_options_(total_opt), side_num_(side_num), force_dialog_(force_dialog)
186 {
187 }
188
189 virtual ~unit_advancement_choice()
190 {
191 }
192
193 virtual config query_user(int /*side*/) const
194 {
195 //the 'side' parameter might differ from side_num_-
196 int res = 0;
197 team& t = resources::gameboard->get_team(side_num_);
198 //i wonder how this got included here ?
200 bool is_current_side = resources::controller->current_side() == side_num_;
201 //note, that the advancements for networked sides are also determined on the current playing side.
202
203 //to make mp games equal we only allow selecting advancements to the current side.
204 //otherwise we'd give an unfair advantage to the side that hosts ai sides if units advance during ai turns.
205 if(!video::headless() && (force_dialog_ || (t.is_local_human() && !t.is_droid() && !t.is_idle() && (is_current_side || !is_mp))))
206 {
207 res = advance_unit_dialog(loc_);
208 }
209 else if(is_current_side && (t.is_local_ai() || t.is_network_ai() || t.is_empty()))
210 {
211 res = randomness::generator->get_random_int(0, nb_options_-1);
212
214 //if ai_advancement_ is the default advancement the following code will
215 //have no effect because get_advancements returns an empty list.
217 if(!u) {
218 ERR_NG << "unit_advancement_choice: unit not found";
219 return config{};
220 }
221
222 std::vector<std::string> allowed = ai_advancement.get_advancements(u);
223 for(const auto& adv_id : allowed) {
224 int res_new = get_advancement_index(*u, adv_id);
225 if(res_new != -1) {
226 // if the advancement ids were really unique we could also make this function return the
227 // advancements id instead of its index. But i dont think there are guaraenteed to be unique.
228 res = res_new;
229 break;
230 }
231 }
232 }
233 else
234 {
235 // we are in the situation, that the unit is owned by a human, but he's not allowed to do this decision.
236 // because it's a mp game and it's not his turn.
237 // default to the first unit listed in the unit's advancements
238 }
239 LOG_NG << "unit at position " << loc_ << " chose advancement number " << res;
240 config retv;
241 retv["value"] = res;
242 return retv;
243
244 }
245 virtual config random_choice(int /*side*/) const
246 {
247 config retv;
248 retv["value"] = 0;
249 return retv;
250 }
251 virtual std::string description() const
252 {
253 // TRANSLATORS: In networked games, when one player has the choice
254 // between multiple advancements of a unit, this text is sent to
255 // other players. It will be embedded within a message.
256 return _("waiting for^an advancement choice");
257 }
258 private:
259 const map_location loc_;
260 int nb_options_;
261 int side_num_;
262 bool force_dialog_;
263 };
264}
265
266/*
267advances the unit and stores data in the replay (or reads data from replay).
268*/
270{
271 //i just don't want infinite loops...
272 // the 20 is picked rather randomly.
273 for(int advacment_number = 0; advacment_number < 20; advacment_number++)
274 {
276 //this implies u.valid()
278 return;
279 }
280
281 if(params.fire_events_)
282 {
283 LOG_NG << "Firing pre advance event at " << params.loc_ <<".";
284 resources::game_events->pump().fire("pre_advance", params.loc_);
285 //TODO: maybe use id instead of location here ?.
286 u = resources::gameboard->units().find(params.loc_);
288 {
289 LOG_NG << "pre advance event aborted advancing.";
290 return;
291 }
292 }
293 //we don't want to let side 1 decide it during start/prestart.
294 //The "0" parameter here is the default and gets resolves to "current player"
295 int side_for = resources::gamedata->has_current_player() ? 0: u->side();
296 config selected = mp_sync::get_user_choice("choose",
297 unit_advancement_choice(params.loc_, unit_helper::number_of_possible_advances(*u), u->side(), params.force_dialog_), side_for);
298 //calls actions::advance_unit.
299 bool result = animate_unit_advancement(params.loc_, selected["value"].to_size_t(), params.fire_events_, params.animate_);
300
301 DBG_NG << "animate_unit_advancement result = " << result;
302 u = resources::gameboard->units().find(params.loc_);
303 // level 10 unit gives 80 XP and the highest mainline is level 5
304 if (u.valid() && u->experience() > 80)
305 {
306 WRN_NG << "Unit has too many (" << u->experience() << ") XP left; cascade leveling goes on still.";
307 }
308 }
309 ERR_NG << "unit at " << params.loc_ << " tried to advance more than 20 times. Advancing was aborted";
310}
311
312unit_ptr get_advanced_unit(const unit &u, const std::string& advance_to)
313{
314 const unit_type *new_type = unit_types.find(advance_to);
315 if (!new_type) {
316 throw game::game_error("Could not find the unit being advanced"
317 " to: " + advance_to);
318 }
319 unit_ptr new_unit = u.clone();
320 new_unit->set_experience(new_unit->experience_overflow());
321 new_unit->advance_to(*new_type);
322 new_unit->heal_fully();
323 new_unit->set_state(unit::STATE_POISONED, false);
324 new_unit->set_state(unit::STATE_SLOWED, false);
325 new_unit->set_state(unit::STATE_PETRIFIED, false);
326 new_unit->set_user_end_turn(false);
327 new_unit->set_hidden(false);
328 return new_unit;
329}
330
331
332/**
333 * Returns the AMLA-advanced version of a unit (with traits and items retained).
334 */
335unit_ptr get_amla_unit(const unit &u, const config &mod_option)
336{
337 unit_ptr amla_unit = u.clone();
338 amla_unit->set_experience(amla_unit->experience_overflow());
339 amla_unit->add_modification("advancement", mod_option);
340 return amla_unit;
341}
342
343
344void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
345{
347 if(!u.valid()) {
348 return;
349 }
350 // original_type is not a reference, since the unit may disappear at any moment.
351 std::string original_type = u->type_id();
352
353 // "advance" event.
354 if(fire_event)
355 {
356 LOG_NG << "Firing advance event at " << loc <<".";
357 resources::game_events->pump().fire("advance",loc);
358
359 if (!u.valid() || u->experience() < u->max_experience() ||
360 u->type_id() != original_type)
361 {
362 LOG_NG << "WML has invalidated the advancing unit. Aborting.";
363 return;
364 }
365 // In case WML moved the unit:
366 loc = u->get_location();
367 }
368
369 // This is not normally necessary, but if a unit loses power when leveling
370 // (e.g. loses "jamming" or ambush), it could be discovered as a result of
371 // the advancement.
372 std::vector<int> not_seeing = actions::get_sides_not_seeing(*u);
373
374 // Create the advanced unit.
375 auto [new_unit, use_amla] = utils::visit(
376 [u](const auto& v) {
377 if constexpr(utils::decayed_is_same<std::string, decltype(v)>) {
378 return std::pair(get_advanced_unit(*u, v), false);
379 } else {
380 return std::pair(get_amla_unit(*u, *v), true);
381 }
382 },
383 advance_to);
384
385 new_unit->set_location(loc);
386 if ( !use_amla )
387 {
389 prefs::get().encountered_units().insert(new_unit->type_id());
390 LOG_CF << "Added '" << new_unit->type_id() << "' to the encountered units.";
391 }
392 u->anim_comp().clear_haloes();
394 resources::whiteboard->on_kill_unit();
395 u = resources::gameboard->units().insert(new_unit).first;
396
397 // Update fog/shroud.
399 clearer.clear_unit(loc, *new_unit);
400
401 // "post_advance" event.
402 if(fire_event)
403 {
404 LOG_NG << "Firing post_advance event at " << loc << ".";
405 resources::game_events->pump().fire("post_advance",loc);
406 }
407
408 // "sighted" event(s).
409 clearer.fire_events();
410 if ( u.valid() )
411 actions::actor_sighted(*u, &not_seeing);
412
413 resources::whiteboard->on_gamestate_change();
414}
map_location loc
Definition move.cpp:172
unit_ptr get_advanced_unit(const unit &u, const std::string &advance_to)
Returns the advanced version of a unit (with traits and items retained).
#define WRN_NG
void advance_unit(map_location loc, const advancement_option &advance_to, bool fire_event)
Function which will advance the unit at loc to 'advance_to'.
unit_ptr get_amla_unit(const unit &u, const config &mod_option)
Returns the AMLA-advanced version of a unit (with traits and items retained).
static lg::log_domain log_engine("engine")
#define ERR_NG
#define LOG_DP
void advance_unit_at(const advance_unit_params &params)
static lg::log_domain log_display("display")
#define DBG_NG
#define LOG_CF
#define LOG_NG
static lg::log_domain log_config("config")
Various functions that implement advancements of units.
utils::variant< std::string, const config * > advancement_option
Managing the AIs lifecycle - headers TODO: Refactor history handling and internal commands.
double t
Class to encapsulate fog/shroud clearing and the resultant sighted events.
Definition vision.hpp:72
game_events::pump_result_t fire_events()
Fires the sighted events that were earlier recorded by fog/shroud clearing.
Definition vision.cpp:541
bool clear_unit(const map_location &view_loc, team &view_team, std::size_t viewer_id, int sight_range, bool slowed, const movetype::terrain_costs &costs, const map_location &real_loc, const std::set< map_location > *known_units=nullptr, std::size_t *enemy_count=nullptr, std::size_t *friend_count=nullptr, move_unit_spectator *spectator=nullptr, bool instant=true)
Clears shroud (and fog) around the provided location for view_team based on sight_range,...
Definition vision.cpp:330
static manager & get_singleton()
Definition manager.hpp:140
const ai::unit_advancements_aspect & get_advancement_aspect_for_side(side_number side)
Definition manager.cpp:572
const std::vector< std::string > get_advancements(const unit_map::const_iterator &unit) const
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition display.cpp:2927
void invalidate_all()
Function to invalidate all tiles.
Definition display.cpp:2920
static display * get_singleton()
Returns the display object if a display object exists.
Definition display.hpp:102
team & get_team(int i)
virtual const unit_map & units() const override
bool has_current_player() const
returns where there is currently a well defiend "current player", that is for example not the case du...
static game_display * get_singleton()
void invalidate_unit()
Function to invalidate that unit status displayed on the sidebar.
game_events::wml_event_pump & pump()
Definition manager.cpp:257
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition pump.cpp:399
bool show(const unsigned auto_close_time=0)
Shows the window.
int current_side() const
Returns the number of the side whose turn it is.
virtual bool is_networked_mp() const
statistics_t & statistics()
static prefs & get()
std::set< std::string > & encountered_units()
int get_random_int(int min, int max)
Definition random.hpp:51
void advance_unit(const unit &u)
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
void wait_for_end() const
void add_animation(unit_const_ptr animated_unit, const unit_animation *animation, const map_location &src=map_location::null_location(), bool with_bars=false, const std::string &text="", const color_t text_color={0, 0, 0})
void start_animations()
void set_all_standing()
unit_iterator find(std::size_t id)
Definition map.cpp:302
std::size_t erase(const map_location &l)
Erases the unit at location l, if any.
Definition map.cpp:289
umap_retval_pair_t insert(const unit_ptr &p)
Inserts the unit pointed to by p into the map.
Definition map.cpp:135
const unit_type * find(const std::string &key, unit_type::BUILD_STATUS status=unit_type::FULL) const
Finds a unit_type by its id() and makes sure it is built to the specified level.
Definition types.cpp:1253
A single unit type that the player may recruit.
Definition types.hpp:43
This class represents a single unit of a specific type.
Definition unit.hpp:39
unit_ptr clone() const
Definition unit.hpp:127
static std::string _(const char *str)
Definition gettext.hpp:100
@ STATE_SLOWED
Definition unit.hpp:775
@ STATE_PETRIFIED
The unit is poisoned - it loses health each turn.
Definition unit.hpp:777
@ STATE_POISONED
The unit is slowed - it moves slower and does less damage.
Definition unit.hpp:776
std::vector< config > get_modification_advances() const
Gets any non-typed advanced options set by modifications.
Definition unit.cpp:1893
const advances_to_t & advances_to() const
Gets the possible types this unit can advance to on level-up.
Definition unit.hpp:150
Standard logging facilities (interface).
std::vector< int > get_sides_not_seeing(const unit &target)
Returns the sides that cannot currently see target.
Definition vision.cpp:590
game_events::pump_result_t actor_sighted(const unit &target, const std::vector< int > *cache)
Fires sighted events for the sides that can see target.
Definition vision.cpp:614
void pump()
Process all events currently in the queue.
Definition events.cpp:480
config get_user_choice(const std::string &name, const user_choice &uch, int side=0)
rng * generator
This generator is automatically synced during synced context.
Definition random.cpp:60
game_board * gameboard
Definition resources.cpp:20
game_data * gamedata
Definition resources.cpp:22
game_events::manager * game_events
Definition resources.cpp:24
play_controller * controller
Definition resources.cpp:21
std::shared_ptr< wb::manager > whiteboard
Definition resources.cpp:33
bool will_certainly_advance(const unit_map::iterator &u)
Encapsulates the logic for deciding whether an iterator u points to a unit that can advance.
Definition helper.cpp:39
int number_of_possible_advances(const unit &u)
Determines the total number of available advancements (of any kind) for a given unit.
Definition helper.cpp:34
constexpr bool decayed_is_same
Equivalent to as std::is_same_v except both types are passed through std::decay first.
Definition general.hpp:32
bool headless()
The game is running headless.
Definition video.cpp:146
std::shared_ptr< unit > unit_ptr
Definition ptr.hpp:26
Define the game's event mechanism.
advances the unit at loc if it has enough experience, maximum 20 times.
Error used for any general game error, e.g.
Encapsulates the map of the game.
Definition location.hpp:46
Interface for querying local choices.
virtual config query_user(int side) const =0
virtual std::string description() const
virtual config random_choice(int side) const =0
bool valid() const
Definition map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition map.hpp:217
unit_type_data unit_types
Definition types.cpp:1499
Various functions implementing vision (through fog of war and shroud).