The Battle for Wesnoth  1.19.28+dev
move.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Movement.
19  */
20 
21 #include "actions/move.hpp"
22 
23 #include "actions/undo.hpp"
24 #include "actions/vision.hpp"
25 
26 #include "game_events/pump.hpp"
28 #include "gettext.hpp"
29 #include "hotkey/hotkey_item.hpp"
31 #include "log.hpp"
32 #include "map/map.hpp"
33 #include "mouse_handler_base.hpp"
34 #include "pathfind/pathfind.hpp"
35 #include "pathfind/teleport.hpp"
36 #include "replay.hpp"
37 #include "replay_helper.hpp"
38 #include "synced_context.hpp"
39 #include "play_controller.hpp"
40 #include "resources.hpp"
41 #include "units/udisplay.hpp"
42 #include "font/standard_colors.hpp"
43 #include "formula/string_utils.hpp"
44 #include "team.hpp"
45 #include "units/unit.hpp"
47 #include "whiteboard/manager.hpp"
48 
49 #include <deque>
50 #include <set>
51 
52 static lg::log_domain log_engine("engine");
53 #define ERR_NG LOG_STREAM(err, log_engine)
54 #define DBG_NG LOG_STREAM(debug, log_engine)
55 
56 
57 namespace actions {
58 
59 
61 {
62  seen_friends_.push_back(u);
63 }
64 
65 
67 {
68  seen_enemies_.push_back(u);
69 }
70 
71 
73 {
74  return ambusher_;
75 }
76 
77 
79 {
80  return failed_teleport_;
81 }
82 
83 
84 const std::vector<unit_map::const_iterator>& move_unit_spectator::get_seen_enemies() const
85 {
86  return seen_enemies_;
87 }
88 
89 
90 const std::vector<unit_map::const_iterator>& move_unit_spectator::get_seen_friends() const
91 {
92  return seen_friends_;
93 }
94 
95 
97 {
98  return unit_;
99 }
100 
101 
103  : ambusher_(units.end())
104  , failed_teleport_(units.end())
105  , seen_enemies_()
106  , seen_friends_()
107  , unit_(units.end())
108  , tiles_entered_(0)
109  , interrupted_(false)
110 {
111 }
112 
114  : ambusher_()
115  , failed_teleport_()
116  , seen_enemies_()
117  , seen_friends_()
118  , unit_()
119  , tiles_entered_(0)
120  , interrupted_(false)
121 {
122 }
123 
124 
126 {
127 }
128 
130 {
131  ambusher_ = units.end();
132  failed_teleport_ = units.end();
133  seen_enemies_.clear();
134  seen_friends_.clear();
135  unit_ = units.end();
136  tiles_entered_ = 0;
137  interrupted_ = false;
138 }
139 
140 void move_unit_spectator::error(const std::string& message)
141 {
142  ERR_NG << message;
143 }
144 
146 {
147  ambusher_ = u;
148 }
149 
150 
152 {
153  failed_teleport_ = u;
154 }
155 
156 
158 {
159  unit_ = u;
160 }
161 
162 namespace {
163 
164 /**
165  * The number of the side that preivously owned the village that the unit stepped on
166  * Note, that recruit/recall actions can also take a village if the unit was recruits/recalled onto a village
167  */
168 struct take_village_step : undo_action
169 {
173 
174  take_village_step(
175  int orig_village_owner, bool time_bonus, map_location loc)
176  : original_village_owner(orig_village_owner)
177  , take_village_timebonus(time_bonus)
178  , loc(loc)
179  {
180  }
181 
182  take_village_step(const config& cfg)
183  : original_village_owner(cfg["village_owner"].to_int())
184  , take_village_timebonus(cfg["village_timebonus"].to_bool())
185  , loc(cfg)
186  {
187  }
188 
189  static const char* get_type_impl() { return "take_village"; }
190  virtual const char* get_type() const { return get_type_impl(); }
191 
192  virtual ~take_village_step() { }
193 
194  /** Writes this into the provided config. */
195  virtual void write(config& cfg) const
196  {
198  cfg["village_owner"] = original_village_owner;
199  cfg["village_timebonus"] = take_village_timebonus;
200  loc.write(cfg);
201  }
202 
203  /** Undoes this action. */
204  virtual bool undo(int)
205  {
206  team& current_team = resources::controller->current_team();
207  if(resources::gameboard->map().is_village(loc)) {
208  get_village(loc, original_village_owner, nullptr, false);
209  // MP_COUNTDOWN take away capture bonus
211  current_team.decrement_action_bonus_count();
212  }
213  }
214  return true;
215  }
216 };
217 
218 static auto reg_undo_take_village_step = undo_action_container::subaction_factory<take_village_step>();
219 
220 }
221 game_events::pump_result_t get_village(const map_location& loc, int side, bool *action_timebonus, bool fire_event)
222 {
223  std::vector<team> &teams = resources::gameboard->teams();
224  team *t = static_cast<unsigned>(side - 1) < teams.size() ? &teams[side - 1] : nullptr;
225  if (t && t->owns_village(loc)) {
227  }
228 
229  bool not_defeated = t && !resources::gameboard->team_is_defeated(*t);
230 
231  bool grants_timebonus = false;
232 
233  int old_owner_side = 0;
234  // We strip the village off all other sides, unless it is held by an ally
235  // and our side is already defeated (and thus we can't occupy it)
236  for(team& tm : teams) {
237  int i_side = tm.side();
238  if (!t || not_defeated || t->is_enemy(i_side)) {
239  if(tm.owns_village(loc)) {
240  old_owner_side = i_side;
241  tm.lose_village(loc);
242  }
243  if (side != i_side && action_timebonus) {
244  grants_timebonus = true;
245  }
246  }
247  }
248 
249  if (!t) {
251  }
252 
253  if(grants_timebonus) {
254  t->increment_action_bonus_count();
255  *action_timebonus = true;
256  }
257 
258  if(not_defeated) {
259  if (display::get_singleton() != nullptr) {
261  }
262  resources::undo_stack->add_custom<take_village_step>(old_owner_side, grants_timebonus, loc);
263  return t->get_village(loc, old_owner_side, fire_event ? resources::gamedata : nullptr);
264  }
265 
267 }
268 
269 
270 namespace { // Private helpers for move_unit()
271 
272  /** Helper class for move_unit(). */
273  class unit_mover {
274  typedef std::vector<map_location>::const_iterator route_iterator;
275 
276  public:
277  unit_mover(const unit_mover&) = delete;
278  unit_mover& operator=(const unit_mover&) = delete;
279 
280  unit_mover(const std::vector<map_location> & route,
281  move_unit_spectator *move_spectator,
282  bool skip_sightings, bool skip_ally_sightings);
283  ~unit_mover();
284 
285  /** Determines how far along the route the unit can expect to move this turn. */
286  bool check_expected_movement();
287  /** Attempts to move the unit along the expected path. */
288  void try_actual_movement(bool show);
289  /** Does some bookkeeping and event firing, for use after movement. */
290  void post_move();
291  /** Shows the various on-screen messages, for use after movement. */
292  void feedback() const;
293  /** Attempts to teleport the unit to a map_location. */
294  void try_teleport(bool show);
295 
296  /** After checking expected movement, this is the expected path. */
297  std::vector<map_location> expected_path() const
298  { return std::vector<map_location>(begin_, expected_end_); }
299  /** After moving, this is the final hex reached. */
300  const map_location & final_hex() const
301  { return *move_loc_; }
302  /** The number of hexes actually entered. */
303  std::size_t steps_travelled() const
304  { return move_loc_ - begin_; }
305  /** After moving, use this to detect if movement was less than expected. */
306  bool stopped_early() const { return expected_end_ != real_end_; }
307  /**
308  * After moving, use this to detect if something happened that would
309  * interrupt movement (even if movement ended for a different reason).
310  */
311  bool interrupted(bool include_end_of_move_events=true) const
312  {
313  return ambushed_ || blocked() || sighted_ || teleport_failed_ ||
314  (include_end_of_move_events ? (wml_removed_unit_ || wml_move_aborted_): event_mutated_mid_move_ ) ||
315  !move_it_.valid();
316  }
317 
318  private: // functions
319  /** Returns whether or not movement was blocked by a non-ambushing enemy. */
320  bool blocked() const { return blocked_loc_ != map_location::null_location(); }
321  /** Checks the expected route for hidden units. */
322  void cache_hidden_units(const route_iterator & start,
323  const route_iterator & stop);
324  /** Fires the enter_hex or exit_hex event and updates our data as needed. */
325  void fire_hex_event(const std::string & event_name,
326  const route_iterator & current,
327  const route_iterator & other);
328  /** AI moves are supposed to not change the "goto" order. */
329  bool is_ai_move() const
330  {
331  return spectator_ && spectator_->is_ai_move();
332  }
333  /** Checks how far it appears we can move this turn. */
334  route_iterator plot_turn(const route_iterator & start,
335  const route_iterator & stop);
336  /** Updates our stored info after a WML event might have changed something. */
337  void post_wml(game_events::pump_result_t pump_res, const route_iterator & step);
338  void post_wml(game_events::pump_result_t pump_res) { return post_wml(pump_res, full_end_); }
339  /** Fires the sighted events that were raised earlier. */
340  void pump_sighted(const route_iterator & from);
341  /** Returns the ambush alert (if any) for the given unit. */
342  static std::string read_ambush_string(const unit & ambusher);
343  /** Reveals the unit at the indicated location. */
344  void reveal_ambusher(const map_location & hex, bool update_alert=true);
345 
346  /** Returns whether or not undoing this move should be blocked. */
347  bool undo_blocked() const
348  { return ambushed_ || blocked() || wml_removed_unit_ || wml_undo_disabled_ || fog_changed_ ||
350 
351  // The remaining private functions are suggested to be inlined because
352  // each is used in only one place. (They are separate functions to ease
353  // code reading.)
354 
355  /** Checks for ambushers around @a hex, setting flags as appropriate. */
356  inline void check_for_ambushers(const map_location & hex);
357  /** Makes sure the path is not obstructed by a unit. */
358  inline bool check_for_obstructing_unit(const map_location & hex,
359  const map_location & prev_hex);
360  /** Moves the unit the next step. */
361  inline bool do_move(const route_iterator & step_from,
362  const route_iterator & step_to,
364 
365  /** Teleports the unit. */
366  inline bool do_teleport(unit_display::unit_movement_animator& animator);
367 
368  /** Clears fog/shroud and handles units being sighted. */
369  inline void handle_fog(const map_location & hex, bool new_animation);
370  inline bool is_reasonable_stop(const map_location & hex) const;
371  /** Reveals the units stored in ambushers_ (and blocked_loc_). */
372  inline void reveal_ambushers();
373  /** Makes sure the units in ambushers_ still exist. */
374  inline void validate_ambushers();
375 
376  private: // data
377  // (The order of the fields is somewhat important for the constructor.)
378 
379  // Movement parameters (these decrease the number of parameters needed
380  // for individual functions).
381  move_unit_spectator * const spectator_;
382  const bool skip_sighting_;
385  // Needed to interface with unit_display::unit_mover_animation.
386  const std::vector<map_location> & route_;
387 
388  // The route to traverse.
389  const route_iterator begin_;
390  const route_iterator full_end_; // The end of the plotted route.
391  route_iterator expected_end_; // The end of this turn's portion of the plotted route.
392  route_iterator ambush_limit_; // How far we can go before encountering hidden units, ignoring allied units.
393  route_iterator obstructed_; // Points to either full_end_ or a hex we cannot enter. This is used so that exit_hex can fire before we decide we cannot enter this hex.
394  route_iterator real_end_; // How far we actually can move this turn.
395 
396  // The unit that is moving.
398 
399  // This data stores the state from before the move started.
400  const int orig_side_;
404 
405  // This data tracks the current state as the move is in progress.
407  team * current_team_; // Will default to the original team if the moving unit becomes invalid.
409  route_iterator move_loc_; // Will point to the last moved-to location (in case the moving unit disappears).
410  // Data accumulated while making the move.
412  map_location ambush_stop_; // Could be inaccurate if ambushed_ is false.
413  map_location blocked_loc_; // Location of a blocking, enemy, non-ambusher unit.
414  bool ambushed_;
419  bool event_mutated_mid_move_; // Cache of wml_removed_unit_ || wml_move_aborted_ from just before the end-of-move handling.
421  bool sighted_; // Records if sightings were made that could interrupt movement.
422  bool sighted_stop_; // Records if sightings were made that did interrupt movement (the same as sighted_ unless movement ended for another reason).
424  std::size_t enemy_count_;
425  std::size_t friend_count_;
426  std::string ambush_string_;
427  std::vector<map_location> ambushers_;
428  std::deque<int> moves_left_; // The front value is what the moving unit's remaining moves should be set to after the next step through the route.
429 
430  shroud_clearer clearer_;
431  };
432 
433 
434  /**
435  * This constructor assumes @a route is not empty, and it will assert() that
436  * there is a unit at route.front().
437  * Iterators into @a route must remain valid for the life of this object.
438  * It is assumed that move_spectator is only supplied for AI moves (only
439  * affects whether or not gotos are changed).
440  */
441  unit_mover::unit_mover(const std::vector<map_location> & route,
442  move_unit_spectator *move_spectator,
443  bool skip_sightings, bool skip_ally_sightings)
444  : spectator_(move_spectator)
445  , skip_sighting_(skip_sightings)
446  , skip_ally_sighting_(skip_ally_sightings)
447  , playing_team_is_viewing_(display::get_singleton()->viewing_team_is_playing() || display::get_singleton()->show_everything())
448  , route_(route)
449  , begin_(route.begin())
450  , full_end_(route.end())
454  , real_end_(begin_)
455  // Unit information:
456  , move_it_(resources::gameboard->units().find(*begin_))
457  , orig_side_(( static_cast<void>(assert(move_it_ != resources::gameboard->units().end())), move_it_->side() ))
458  , orig_moves_(move_it_->movement_left())
459  , orig_dir_(move_it_->facing())
460  , goto_( is_ai_move() ? move_it_->get_goto() : route.back() )
463  , current_uses_fog_(current_team_->fog_or_shroud() && current_team_->auto_shroud_updates())
464  , move_loc_(begin_)
465  // The remaining fields are set to some sort of "zero state".
466  , zoc_stop_(map_location::null_location())
467  , ambush_stop_(map_location::null_location())
468  , blocked_loc_(map_location::null_location())
469  , ambushed_(false)
470  , show_ambush_alert_(false)
471  , wml_removed_unit_(false)
472  , wml_undo_disabled_(false)
473  , wml_move_aborted_(false)
474  , event_mutated_mid_move_(false)
475  , fog_changed_(false)
476  , sighted_(false)
477  , sighted_stop_(false)
478  , teleport_failed_(false)
479  , enemy_count_(0)
480  , friend_count_(0)
481  , ambush_string_()
482  , ambushers_()
483  , moves_left_()
484  , clearer_()
485  {
486  if ( !is_ai_move() )
487  // Clear the "goto" instruction during movement.
488  // (It will be reset in the destructor if needed.)
490  }
491 
492 
493  unit_mover::~unit_mover()
494  {
495  // Set the "goto" order? (Not if WML set it.)
496  if ( !is_ai_move() && move_it_.valid() &&
497  move_it_->get_goto() == map_location::null_location() )
498  {
499  // Only set the goto if movement was not complete and was not
500  // interrupted.
501  if (real_end_ != full_end_ && !interrupted(false)) {
502  // End-of-move-events do not cancel a goto. (Use case: tutorial S2.)
503  move_it_->set_goto(goto_);
504  }
505  }
506  }
507 
508 
509  // Private inlines:
510 
511  /**
512  * Checks for ambushers around @a hex, setting flags as appropriate.
513  */
514  inline void unit_mover::check_for_ambushers(const map_location & hex)
515  {
516  const unit_map &units = resources::gameboard->units();
517 
518  // Need to check each adjacent hex for hidden enemies.
519  for(const map_location& loc : get_adjacent_tiles(hex)) {
520  const unit_map::const_iterator neighbor_it = units.find(loc);
521 
522  if ( neighbor_it != units.end() &&
523  current_team_->is_enemy(neighbor_it->side()) &&
524  neighbor_it->invisible(loc) )
525  {
526  // Ambushed!
527  ambushed_ = true;
528  ambush_stop_ = hex;
529  ambushers_.push_back(loc);
530  }
531  }
532  }
533 
534 
535  /**
536  * Makes sure the path is not obstructed by a unit.
537  * @param hex The hex to check.
538  * @param prev_hex The previous hex in the route (used to detect a teleport).
539  * @return true if @a hex is obstructed.
540  */
541  inline bool unit_mover::check_for_obstructing_unit(const map_location & hex,
542  const map_location & prev_hex)
543  {
544  const unit_map::const_iterator blocking_unit = resources::gameboard->units().find(hex);
545 
546  // If no unit, then the path is not obstructed.
547  if (blocking_unit == resources::gameboard->units().end()) {
548  return false;
549  }
550 
551  // Check for units blocking a teleport exit. This can now only happen
552  // if these units are not visible to the current side, as otherwise no
553  // valid path is found.
554  if ( !tiles_adjacent(hex, prev_hex) ) {
555  if ( current_team_->is_enemy(blocking_unit->side()) ) {
556  // Enemy units always block the tunnel.
557  teleport_failed_ = true;
558  return true;
559  } else {
560  // By contrast, allied units (of a side which does not share vision) only
561  // block the tunnel if pass_allied_units=true. Whether the teleport is possible
562  // is checked by getting the teleport map with the see_all flag set to true.
563  const pathfind::teleport_map teleports = pathfind::get_teleport_locations(*move_it_, *current_team_, true, false, false);
564  const auto allowed_teleports = teleports.get_adjacents(prev_hex);
565 
566  if(allowed_teleports.count(hex) == 0) {
567  teleport_failed_ = true;
568  return true;
569  }
570  }
571  }
572 
573  if ( current_team_->is_enemy(blocking_unit->side()) ) {
574  // Trying to go through an enemy.
575  blocked_loc_ = hex;
576  return true;
577  }
578 
579  // If we get here, the unit does not interfere with movement.
580  return false;
581  }
582 
583 
584  /**
585  * Moves the unit the next step.
586  * @a step_to is the hex being moved to.
587  * @a step_from is the hex before that in the route.
588  * (The unit is actually at *move_loc_.)
589  * @a animator is the unit_display::unit_mover_animation being used.
590  * @return whether or not we started a new animation.
591  */
592  inline bool unit_mover::do_move(const route_iterator & step_from,
593  const route_iterator & step_to,
595  {
597 
598  // Adjust the movement even if we cannot move yet.
599  // We will eventually be able to move if nothing unexpected
600  // happens, and if something does happen, this movement is the
601  // cost to discover it.
602  move_it_->set_movement(moves_left_.front(), true);
603  moves_left_.pop_front();
604 
605  // Invalidate before moving so we invalidate neighbor hexes if needed.
606  move_it_->anim_comp().invalidate(disp);
607 
608  // Attempt actually moving. Fails if *step_to is occupied.
609  auto [unit_it, success] = resources::gameboard->units().move(*move_loc_, *step_to);
610 
611  if(success) {
613  unit_it.get_shared_ptr(), move_loc_, step_to + 1, orig_moves_, unit_it->facing());
614  orig_moves_ = unit_it->movement_left();
615  // Update the moving unit.
616  move_it_ = unit_it;
617  move_it_->set_facing(step_from->get_relative_dir(*step_to));
618  // Disable bars. The expectation here is that the animation
619  // unit_mover::finish() will clean after us at a later point. Ugly,
620  // but it works.
621  move_it_->anim_comp().set_standing(false);
622  disp.invalidate_unit_after_move(*move_loc_, *step_to);
623  disp.invalidate(*step_to);
624  move_loc_ = step_to;
625 
626  // Show this move.
627  animator.proceed_to(move_it_.get_shared_ptr(), step_to - begin_,
628  move_it_->appearance_changed(), false);
629  move_it_->set_appearance_changed(false);
630  disp.redraw_minimap();
631  }
632 
633  return success;
634  }
635 
636  /**
637  * Teleports the unit to begin_ + 1.
638  * @a animator is the unit_display::unit_mover_animation being used.
639  * @return whether or not we started a new animation.
640  */
641  inline bool unit_mover::do_teleport(unit_display::unit_movement_animator& animator)
642  {
644  const route_iterator step_to = begin_ + 1;
645 
646  // Invalidate before moving so we invalidate neighbor hexes if needed.
647  move_it_->anim_comp().invalidate(disp);
648 
649  // Attempt actually moving. Fails if *step_to is occupied.
650  auto [unit_it, success] = resources::gameboard->units().move(*begin_, *step_to);
651 
652  if(success) {
653  // Update the moving unit.
654  move_it_ = unit_it;
655  move_it_->set_facing(begin_->get_relative_dir(*step_to));
656 
657  move_it_->anim_comp().set_standing(false);
658  disp.invalidate_unit_after_move(*begin_, *step_to);
659  disp.invalidate(*step_to);
660  move_loc_ = step_to;
661 
662  // Show this move.
663  animator.proceed_to(move_it_.get_shared_ptr(), step_to - begin_, move_it_->appearance_changed(), false);
664 
665  move_it_->set_appearance_changed(false);
666  disp.redraw_minimap();
667  }
668  return success;
669  }
670 
671  /**
672  * Clears fog/shroud and raises events for units being sighted.
673  * Only call this if the current team uses fog or shroud.
674  * @a hex is both the center of fog clearing and the filtered location of
675  * the moving unit when the sighted events will be fired.
676  */
677  inline void unit_mover::handle_fog(const map_location & hex,
678  bool new_animation)
679  {
680  // Clear the fog.
681  if ( clearer_.clear_unit(hex, *move_it_, *current_team_, nullptr,
683  !new_animation) )
684  {
685  clearer_.invalidate_after_clear();
686  fog_changed_ = true;
687  }
688 
689  // Check for sighted units?
690  if ( !skip_sighting_ ) {
691  sighted_ = enemy_count_ != 0 ;
692  }
694  {
695  sighted_ |= (friend_count_ != 0);
696  }
697  }
698 
699 
700  /**
701  * @return true if an unscheduled stop at @a hex is not likely to negatively
702  * impact the player's plans.
703  * (E.g. it would not kill movement by making an unintended village capture.)
704  */
705  inline bool unit_mover::is_reasonable_stop(const map_location & hex) const
706  {
707  // We cannot reasonably stop if move_it_ could not be moved to this
708  // hex (the hex was occupied by someone else).
709  if (*move_loc_ != hex) {
710  return false;
711  }
712 
713  // We can reasonably stop if the hex is not an unowned village.
714  return !resources::gameboard->map().is_village(hex) ||
716  }
717 
718 
719  /**
720  * Reveals the units stored in ambushers_ (and blocked_loc_).
721  * Also sets ambush_string_.
722  * May fire "sighted" events.
723  * Only call this if appropriate; this function does not itself check
724  * ambushed_ or blocked().
725  */
726  inline void unit_mover::reveal_ambushers()
727  {
728  // Reveal the blocking unit.
729  if (blocked()) {
730  reveal_ambusher(blocked_loc_, false);
731  }
732  // Reveal ambushers.
733  for(const map_location & reveal : ambushers_) {
734  reveal_ambusher(reveal, true);
735  }
736 
737  // Default "Ambushed!" message?
738  if (ambush_string_.empty()) {
739  ambush_string_ = _("Ambushed!");
740  }
741  }
742 
743 
744  /**
745  * Makes sure the units in ambushers_ still exist.
746  */
747  inline void unit_mover::validate_ambushers()
748  {
749  const unit_map &units = resources::gameboard->units();
750 
751  // Loop through the previously-detected ambushers.
752  std::size_t i = 0;
753  while ( i != ambushers_.size() ) {
754  if (units.count(ambushers_[i]) == 0) {
755  // Ambusher is gone.
756  ambushers_.erase(ambushers_.begin() + i);
757  }
758  else {
759  // Proceed to the next ambusher.
760  ++i;
761  }
762  }
763  }
764 
765 
766  // Private utilities:
767 
768  /**
769  * Checks the expected route for hidden units.
770  * This basically handles all the checks for surprises that can be done
771  * without visibly notifying a player. Thus this can be called at the
772  * start of movement and immediately after events, rather than tie up
773  * CPU time in the middle of animating a move.
774  *
775  * @param[in] start The beginning of the path to check.
776  * @param[in] stop The end of the path to check.
777  */
778  void unit_mover::cache_hidden_units(const route_iterator & start,
779  const route_iterator & stop)
780  {
781  // Clear the old cache.
784  teleport_failed_ = false;
785  // The ambush cache needs special treatment since we cannot re-detect
786  // an ambush if we are already at the ambushed location.
788  if ( ambushed_ ) {
789  validate_ambushers();
790  ambushed_ = !ambushers_.empty();
791  }
792  if ( !ambushed_ ) {
794  ambushers_.clear();
795  }
796 
797  // Update the shroud clearer.
798  clearer_.cache_units(current_uses_fog_ ? current_team_ : nullptr);
799 
800 
801  // Abort for null routes.
802  if ( start == stop ) {
804  return;
805  }
806 
807  // This loop will end with ambush_limit_ pointing one element beyond
808  // where the unit would be forced to stop by a hidden unit.
809  for ( ambush_limit_ = start+1; ambush_limit_ != stop; ++ambush_limit_ ) {
810  // Check if we need to stop in the previous hex.
811  if ( ambushed_ ) {
812  break;
813  }
814  // Check for being unable to enter this hex.
815  if ( check_for_obstructing_unit(*ambush_limit_, *(ambush_limit_-1)) ) {
816  // No replay check here? Makes some sense, I guess.
817  obstructed_ = ambush_limit_++; // The limit needs to be after obstructed_ in order for the latter to do anything.
818  break;
819  }
820 
821  // We can enter this hex.
822  // See if we are stopped in this hex.
823  check_for_ambushers(*ambush_limit_);
824  }
825  }
826 
827 
828  /**
829  * Fires the enter_hex or exit_hex event and updates our data as needed.
830  *
831  * @param[in] event_name The name of the event ("enter_hex" or "exit_hex").
832  * @param[in] current The currently occupied hex.
833  * @param[in] other The secondary hex to provide to the event.
834  *
835  */
836  void unit_mover::fire_hex_event(const std::string & event_name,
837  const route_iterator & current,
838  const route_iterator & other)
839  {
840 
841  const game_events::entity_location mover(*move_it_, *current);
842 
843  post_wml(resources::game_events->pump().fire(event_name, mover, *other), current);
844  }
845 
846 
847  /**
848  * Checks how far it appears we can move this turn.
849  *
850  * @param[in] start The beginning of the plotted path.
851  * @param[in] stop The end of the plotted path.
852  *
853  * @return An end iterator for the path that can be traversed this turn.
854  */
855  unit_mover::route_iterator unit_mover::plot_turn(const route_iterator & start,
856  const route_iterator & stop)
857  {
858  const gamemap &map = resources::gameboard->map();
859 
860  // Handle null routes.
861  if (start == stop) {
862  return start;
863  }
864 
865  int remaining_moves = move_it_->movement_left();
867  moves_left_.clear();
868 
869  if ( start != begin_ ) {
870  // Check for being unable to leave the current hex.
871  if ( !move_it_->get_ability_bool("skirmisher", *start) &&
873  zoc_stop_ = *start;
874  }
875 
876  // This loop will end with end pointing one element beyond where the
877  // unit thinks it will stop (the usual notion of "end" for iterators).
878  route_iterator end = start + 1;
879  for ( ; end != stop; ++end )
880  {
881  // Break out of the loop if we cannot leave the previous hex.
883  break;
884  }
885  if(!map.on_board(*end)) {
886  wml_move_aborted_ = true;
887  break;
888  }
889  remaining_moves -= move_it_->movement_cost(map[*end]);
890  if ( remaining_moves < 0 ) {
891  break;
892  }
893 
894  // We can enter this hex. Record the cost.
895  moves_left_.push_back(remaining_moves);
896 
897  // Check for being unable to leave this hex.
898  if (!move_it_->get_ability_bool("skirmisher", *end) &&
900  {
901  zoc_stop_ = *end;
902  }
903  }
904 
905  route_iterator min_end = start == begin_ ? start : start + 1;
906  while (end != min_end && resources::gameboard->has_visible_unit(*(end - 1), *current_team_)) {
907  // Backtrack.
908  --end;
909  }
910 
911  return end;
912  }
913 
914 
915  /**
916  * Updates our stored info after a WML event might have changed something.
917  *
918  * @param step Indicates the position in the path where we might need to start recalculating movement.
919  * Set this to full_end_ (or do not supply it) to skip such recalculations (because movement has finished).
920  *
921  * @returns false if continuing is impossible (i.e. we lost the moving unit).
922  */
923  void unit_mover::post_wml(game_events::pump_result_t pump_res, const route_iterator & step)
924  {
925  wml_move_aborted_ |= std::get<1>(pump_res);
926  wml_undo_disabled_ |= std::get<0>(pump_res);
927 
928  // Re-find the moving unit.
930  const bool found = move_it_ != resources::gameboard->units().end();
931 
932  // Update the current unit data.
933  current_side_ = found ? move_it_->side() : orig_side_;
936  ( current_side_ != orig_side_ ||
938 
939  // Update the path.
940  if ( found && step != full_end_ ) {
941  const route_iterator new_limit = plot_turn(step, expected_end_);
942  cache_hidden_units(step, new_limit);
943  // Just in case: length 0 paths become length 1 paths.
944  if (ambush_limit_ == step) {
945  ++ambush_limit_;
946  }
947  }
948 
949  wml_removed_unit_ |= !found;
950  }
951 
952 
953  /**
954  * Fires the sighted events that were raised earlier.
955  *
956  * @param[in] from Points to the hex the sighting unit currently occupies.
957  *
958  * @return sets event_mutated_ || wml_move_aborted_ to true if this event should interrupt movement.
959  */
960  void unit_mover::pump_sighted(const route_iterator & from)
961  {
962  game_events::pump_result_t pump_res = clearer_.fire_events();
963  post_wml(pump_res, from);
964  }
965 
966 
967  /**
968  * Returns the ambush alert (if any) for the given unit.
969  */
970  std::string unit_mover::read_ambush_string(const unit & ambusher)
971  {
972  for(const active_ability &hide : ambusher.get_abilities("hides"))
973  {
974  const std::string & ambush_string = hide.ability_cfg()["alert"].str();
975  if (!ambush_string.empty()) {
976  return ambush_string;
977  }
978  }
979 
980  // No string found.
981  return std::string();
982  }
983 
984 
985  /**
986  * Reveals the unit at the indicated location.
987  * Can also update the current ambushed alert.
988  * May fire "sighted" events.
989  */
990  void unit_mover::reveal_ambusher(const map_location & hex, bool update_alert)
991  {
992  // Convenient alias:
993  unit_map &units = resources::gameboard->units();
995 
996  // Find the unit at the indicated location.
997  unit_map::iterator ambusher = units.find(hex);
998  if ( ambusher != units.end() ) {
999  // Prepare for sighted events.
1000  std::vector<int> sight_cache(get_sides_not_seeing(*ambusher));
1001  // Make sure the unit is visible (during sighted events, and in case
1002  // we had to backtrack).
1003  ambusher->set_state(unit::STATE_UNCOVERED, true);
1004 
1005  // Record this in the move spectator.
1006  if (spectator_) {
1007  spectator_->set_ambusher(ambusher);
1008  }
1009  // Override the default ambushed message?
1010  if ( update_alert ) {
1011  // Observers don't get extra information.
1012  if ( playing_team_is_viewing_ || !disp.fogged(hex) ) {
1013  show_ambush_alert_ = true;
1014  // We only support one custom ambush message; use the first one.
1015  if (ambush_string_.empty()) {
1016  ambush_string_ = read_ambush_string(*ambusher);
1017  }
1018  }
1019  }
1020 
1021  // Make sure this hex is drawn correctly.
1022  disp.invalidate(hex);
1023  // Fire sighted events.
1024  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*ambusher, &sight_cache);
1025  // TODO: should we call post_wml ?
1026  wml_move_aborted_ |= wml_move_aborted;
1027  wml_undo_disabled_ |= wml_undo_blocked;
1028  }
1029  }
1030 
1031 
1032  // Public interface:
1033 
1034  /**
1035  * Determines how far along the route the unit can expect to move this turn.
1036  * This is based solely on data known to the player, and will not plot a move
1037  * that ends on another (known) unit.
1038  * (For example, this prevents a player from plotting a multi-turn move that
1039  * has this turn's movement ending on a (slower) unit, and thereby clearing
1040  * fog as if the moving unit actually made it on top of that other unit.)
1041  *
1042  * @returns false if the expectation is to not move at all.
1043  */
1044  bool unit_mover::check_expected_movement()
1045  {
1046  expected_end_ = plot_turn(begin_, full_end_);
1047  return expected_end_ != begin_;
1048  }
1049 
1050 
1051  /**
1052  * Attempts to move the unit along the expected path.
1053  * (This will do nothing unless check_expected_movement() was called first.)
1054  *
1055  * @param[in] show Set to false to suppress animations.
1056  */
1057  void unit_mover::try_actual_movement(bool show)
1058  {
1059  static const std::string enter_hex_str("enter hex");
1060  static const std::string exit_hex_str("exit hex");
1061 
1062 
1063  bool obstructed_stop = false;
1064 
1065 
1066  // Check for hidden units along the expected path before we start
1067  // animating and firing events.
1068  cache_hidden_units(begin_, expected_end_);
1069 
1070  if ( begin_ != ambush_limit_ ) {
1071  // Cache the moving unit's visibility.
1072  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1073 
1074  // Prepare to animate.
1076  animator.start(move_it_.get_shared_ptr());
1077 
1078  // Traverse the route to the hex where we need to stop.
1079  // Each iteration performs the move from real_end_-1 to real_end_.
1080  for ( real_end_ = begin_+1; real_end_ != ambush_limit_; ++real_end_ ) {
1081  const route_iterator step_from = real_end_ - 1;
1082 
1083  // See if we can leave *step_from.
1084  // Already accounted for: ambusher
1086  break;
1087  }
1088  if ( sighted_ && is_reasonable_stop(*step_from) ) {
1089  sighted_stop_ = true;
1090  break;
1091  }
1092  // Already accounted for: ZoC
1093  // Already accounted for: movement cost
1094  fire_hex_event(exit_hex_str, step_from, real_end_);
1096  break;
1097  }
1098  if ( real_end_ == obstructed_ ) {
1099  // We did not check for being a replay when checking for an
1100  // obstructed hex, so we do not check can_break here.
1101  obstructed_stop = true;
1102  break;
1103  }
1104 
1105  // We can leave *step_from. Make the move to *real_end_.
1106  bool new_animation = do_move(step_from, real_end_, animator);
1107  // Update the fog.
1108  if ( current_uses_fog_ )
1109  handle_fog(*real_end_, new_animation);
1110  animator.wait_for_anims();
1111 
1112  // Fire the events for this step.
1113  // (These return values are not checked since real_end_ still
1114  // needs to be incremented. The wml_move_aborted_ check will break
1115  // us out of the loop if needed.)
1116  fire_hex_event(enter_hex_str, real_end_, step_from);
1117  // Sighted events only fire if we could stop due to sighting.
1118  if (is_reasonable_stop(*real_end_)) {
1119  pump_sighted(real_end_);
1120  }
1121  }//for
1122  // Make sure any remaining sighted events get fired.
1123  pump_sighted(real_end_-1);
1124 
1125  if ( move_it_.valid() ) {
1126  // Finish animating.
1127  animator.finish(move_it_.get_shared_ptr());
1128  // Check for the moving unit being seen.
1129  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1130  // TODO: should we call post_wml ?
1131  wml_move_aborted_ |= wml_move_aborted;
1132  wml_undo_disabled_ |= wml_undo_blocked;
1133  }
1134  }//if
1135 
1136  // Some flags were set to indicate why we might stop.
1137  // Update those to reflect whether or not we got to them.
1139  if (!obstructed_stop) {
1141  }
1142  teleport_failed_ = teleport_failed_ && obstructed_stop;
1143  // event_mutated_ does not get unset, regardless of other reasons
1144  // for stopping, but we do save its current value.
1146  }
1147 
1148  /**
1149  * Attempts to teleport the unit to a map_location.
1150  *
1151  * @param[in] show Set to false to suppress animations.
1152  */
1153  void unit_mover::try_teleport(bool show)
1154  {
1155  const route_iterator step_from = real_end_ - 1;
1156 
1157  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1158 
1159  // Prepare to animate.
1161  animator.start(move_it_.get_shared_ptr());
1162  fire_hex_event("exit hex", step_from, begin_);
1163 
1164  bool new_animation = do_teleport(animator);
1165 
1166  if(current_uses_fog_)
1167  handle_fog(*(begin_ + 1), new_animation);
1168 
1169  animator.wait_for_anims();
1170 
1171  fire_hex_event("enter hex", begin_, step_from);
1172 
1173  if(is_reasonable_stop(*step_from)) {
1174  pump_sighted(step_from);
1175  }
1176 
1177  pump_sighted(step_from);
1178 
1179  if(move_it_.valid()) {
1180  // Finish animating.
1181  animator.finish(move_it_.get_shared_ptr());
1182 
1183  // Check for the moving unit being seen.
1184  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1185 
1186  wml_move_aborted_ |= wml_move_aborted;
1187  wml_undo_disabled_ |= wml_undo_blocked;
1188  }
1189 
1190  post_move();
1191  }
1192 
1193  /**
1194  * Does some bookkeeping and event firing, for use after movement.
1195  * This includes village capturing and the undo stack.
1196  */
1197  void unit_mover::post_move()
1198  {
1199  const map_location & final_loc = final_hex();
1200 
1201  int orig_village_owner = 0;
1202  bool action_time_bonus = false;
1203 
1204  // Reveal ambushers?
1205  if (ambushed_ || blocked()) {
1206  reveal_ambushers();
1207  }
1208  else if (teleport_failed_ && spectator_) {
1209  spectator_->set_failed_teleport(resources::gameboard->units().find(*obstructed_));
1210  }
1212 
1213  if ( move_it_.valid() ) {
1214  // Update the moving unit.
1215  move_it_->set_interrupted_move(
1216  sighted_stop_ && !resources::whiteboard->is_executing_actions() ?
1217  *(full_end_-1) :
1219  if (ambushed_ || final_loc == zoc_stop_) {
1220  move_it_->set_movement(0, true);
1221  }
1222 
1223  // Village capturing.
1224  if ( resources::gameboard->map().is_village(final_loc) ) {
1225  // Is this a capture?
1226  orig_village_owner = resources::gameboard->village_owner(final_loc);
1227  if ( orig_village_owner != current_side_) {
1228  // Captured. Zap movement and take over the village.
1229  move_it_->set_movement(0, true);
1230  post_wml(get_village(final_loc, current_side_, &action_time_bonus));
1231  }
1232  }
1233  }
1234 
1235  // Finally, the moveto event.
1236  post_wml(resources::game_events->pump().fire("moveto", final_loc, *begin_));
1237 
1238  // Record keeping.
1239  if (spectator_) {
1240  spectator_->set_unit(move_it_);
1241  spectator_->set_interrupted(interrupted());
1242  spectator_->set_tiles_entered(steps_travelled());
1243  }
1244 
1245  const bool mover_valid = move_it_.valid();
1246 
1247 
1248  if(!mover_valid || undo_blocked()) {
1250  }
1251 
1252  // TODO: this looks wrong, whiteboard shouldn't effect the undo stack during a synced action.
1253  if(resources::whiteboard->is_active() && resources::whiteboard->should_clear_undo()) {
1255  }
1256 
1257  // Update the screen.
1259  }
1260 
1261 
1262  /**
1263  * Shows the various on-screen messages, for use after movement.
1264  */
1265  void unit_mover::feedback() const
1266  {
1267  // Alias some resources.
1269 
1270  // Multiple messages may be displayed simultaneously
1271  // this variable is used to keep them from overlapping
1272  std::string message_prefix = "";
1273 
1274  // Ambush feedback?
1275  if ( ambushed_ && show_ambush_alert_ ) {
1276  disp.announce(message_prefix + ambush_string_, font::BAD_COLOR);
1277  message_prefix += " \n";
1278  }
1279 
1280  display::announce_options announce_options;
1281  announce_options.discard_previous = false;
1282 
1283  // Failed teleport feedback?
1285  std::string teleport_string = _("Failed teleport! Exit not empty");
1286  disp.announce(message_prefix + teleport_string, font::BAD_COLOR, announce_options);
1287  message_prefix += " \n";
1288  }
1289 
1290  // Sighted units feedback?
1291  if ( playing_team_is_viewing_ && (enemy_count_ != 0 || friend_count_ != 0) ) {
1292  // Create the message to display (depends on whether friends,
1293  // enemies, or both were sighted, and on how many of each).
1294  utils::string_map symbols;
1295  symbols["enemies"] = std::to_string(enemy_count_);
1296  symbols["friends"] = std::to_string(friend_count_);
1297  std::string message;
1298  color_t msg_color;
1299  if ( friend_count_ != 0 && enemy_count_ != 0 ) {
1300  // TRANSLATORS: This becomes the "friendphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1301  symbols["friendphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 friendly", "$friends friendly", friend_count_, symbols);
1302  // TRANSLATORS: This becomes the "enemyphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1303  symbols["enemyphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 enemy", "$enemies enemy", enemy_count_, symbols);
1304  // TRANSLATORS: Both friends and enemies sighted -- neutral message.
1305  // This is shown when a move is interrupted because units were revealed from the fog of war.
1306  message = VGETTEXT("Units sighted! ($friendphrase, $enemyphrase)", symbols);
1307  msg_color = font::NORMAL_COLOR;
1308  } else if ( enemy_count_ != 0 ) {
1309  // TRANSLATORS: Only enemies sighted -- bad message.
1310  // This is shown when a move is interrupted because units were revealed from the fog of war.
1311  message = VNGETTEXT("Enemy unit sighted!", "$enemies enemy units sighted!", enemy_count_, symbols);
1312  msg_color = font::BAD_COLOR;
1313  } else if ( friend_count_ != 0 ) {
1314  // TRANSLATORS: Only friends sighted -- good message.
1315  // This is shown when a move is interrupted because units were revealed from the fog of war.
1316  message = VNGETTEXT("Friendly unit sighted", "$friends friendly units sighted", friend_count_, symbols);
1317  msg_color = font::GOOD_COLOR;
1318  }
1319 
1320  disp.announce(message_prefix + message, msg_color, announce_options);
1321  message_prefix += " \n";
1322  }
1323 
1324  // Suggest "continue move"?
1325  if ( playing_team_is_viewing_ && sighted_stop_ && !resources::whiteboard->is_executing_actions() ) {
1326  // See if the "Continue Move" action has an associated hotkey
1328  if ( !name.empty() ) {
1329  utils::string_map symbols;
1330  symbols["hotkey"] = name;
1331  std::string message = VGETTEXT("(press $hotkey to keep moving)", symbols);
1332  disp.announce(message_prefix + message, font::NORMAL_COLOR, announce_options);
1333  message_prefix += " \n";
1334  }
1335  }
1336  }
1337 
1338 }//end anonymous namespace
1339 
1340 
1341 static void move_unit_internal(unit_mover& mover)
1342 {
1344  const events::command_disabler disable_commands;
1345 
1346  // Attempt moving.
1347  mover.try_actual_movement(show_move);
1348 
1349  config co;
1350  config cn {
1351  "stopped_early", mover.stopped_early(),
1352  "final_hex_x", mover.final_hex().wml_x(),
1353  "final_hex_y", mover.final_hex().wml_y(),
1354  };
1355  bool matches_replay = checkup_instance->local_checkup(cn,co);
1356  if(!matches_replay)
1357  {
1358  replay::process_error("calculated movement destination (x="+ cn["final_hex_x"].str() + " y=" + cn["final_hex_y"].str() +
1359  ") didn't match the original destination(x="+ co["final_hex_x"].str() + " y=" + co["final_hex_y"].str() + ")\n");
1360 
1361  //TODO: move the unit by force to the desired destination with something like mover.reset_final_hex(co["x"], co["y"]);
1362  }
1363 
1364  // Bookkeeping, etc.
1365  // also fires the moveto event
1366  mover.post_move();
1367  if (show_move) {
1368  mover.feedback();
1369  }
1370 }
1371 
1372 /**
1373  * Moves a unit across the board.
1374  *
1375  * This function handles actual movement, checking terrain costs as well as
1376  * things that might interrupt movement (e.g. ambushes). If the full path
1377  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1378  * instruction. (The unit itself is whatever unit is at the beginning of the
1379  * supplied path.)
1380  *
1381  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1382  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1383  * interrupted should units be spotted).
1384  * @param[in] skip_ally_sighted If set to true, movement is not interrupted should allied units be spotted.
1385  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the
1386  * move
1387  */
1388 void execute_move_unit(const std::vector<map_location>& steps,
1389  bool continued_move,
1390  bool skip_ally_sighted,
1391  move_unit_spectator* move_spectator)
1392 {
1393  // Evaluate this move.
1394  unit_mover mover(steps, move_spectator, continued_move, skip_ally_sighted);
1395  if(!mover.check_expected_movement()) {
1396  DBG_NG << "found expected empty move, aborting";
1397  return;
1398  }
1399 
1400  move_unit_internal(mover);
1401 }
1402 
1403 void teleport_unit_and_record(const map_location& teleport_from,
1404  const map_location& teleport_to,
1405  move_unit_spectator* /* move_spectator */)
1406 {
1407  synced_context::run_and_throw("debug_teleport",
1408  config{"teleport_from_x", teleport_from.wml_x(), "teleport_from_y", teleport_from.wml_y(), "teleport_to_x",
1409  teleport_to.wml_x(), "teleport_to_y", teleport_to.wml_y()});
1410 }
1411 
1412 void teleport_unit_from_replay(const std::vector<map_location> &steps,
1413  bool continued_move, bool skip_ally_sighted, bool show_move)
1414 {
1415  unit_mover mover(steps, nullptr, continued_move, skip_ally_sighted);
1416  mover.try_teleport(show_move);
1417 }
1418 
1419 
1420 
1421 /**
1422  * Wrapper around the other overload.
1423  *
1424  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1425  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1426  * interrupted should units be spotted).
1427  * @param[out] interrupted If supplied, then this is set to true if information was uncovered that warrants
1428  * interrupting a chain of actions (and set to false otherwise).
1429  *
1430  * @returns The number of hexes entered. This can safely be used as an index
1431  * into @a steps to get the location where movement ended, provided
1432  * @a steps is not empty (the return value is guaranteed to be less
1433  * than steps.size() ).
1434  */
1436  const std::vector<map_location>& steps, bool continued_move, bool* interrupted)
1437 {
1438  auto move_spectator = move_unit_spectator();
1439  move_unit_and_record(steps, continued_move, move_spectator);
1440  if(interrupted) {
1441  *interrupted = move_spectator.get_interrupted();
1442  }
1443  return move_spectator.get_tiles_entered();
1444 }
1445 
1446 /**
1447  * Moves a unit across the board.
1448  *
1449  * This function handles actual movement, checking terrain costs as well as
1450  * things that might interrupt movement (e.g. ambushes). If the full path
1451  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1452  * instruction. (The unit itself is whatever unit is at the beginning of the
1453  * supplied path.)
1454  *
1455  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1456  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not interrupted should units be spotted).
1457  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the move
1458  */
1460  const std::vector<map_location>& steps, bool continued_move, move_unit_spectator& move_spectator)
1461 {
1462 
1463  // Avoid some silliness.
1464  if ( steps.size() < 2 || (steps.size() == 2 && steps.front() == steps.back()) ) {
1465  DBG_NG << "Ignoring a unit trying to jump on its hex at " <<
1466  ( steps.empty() ? map_location::null_location() : steps.front() ) << ".";
1467  return;
1468  }
1469  //if we have no fog activated then we always skip sighted
1470  if(resources::gameboard->units().find(steps.front()) != resources::gameboard->units().end())
1471  {
1472  const team &current_team = resources::gameboard->teams()[
1473  resources::gameboard->units().find(steps.front())->side() - 1];
1474  continued_move |= !current_team.fog_or_shroud();
1475  }
1476  const bool skip_ally_sighted = !prefs::get().ally_sighted_interrupts();
1477 
1479  "move", replay_helper::get_movement(steps, continued_move, skip_ally_sighted), move_spectator);
1480 
1481 }
1482 
1483 
1484 }//namespace actions
bool sighted_
Definition: move.cpp:421
std::string ambush_string_
Definition: move.cpp:426
route_iterator real_end_
Definition: move.cpp:394
bool wml_removed_unit_
Definition: move.cpp:416
int current_side_
Definition: move.cpp:406
bool sighted_stop_
Definition: move.cpp:422
bool event_mutated_mid_move_
Definition: move.cpp:419
bool fog_changed_
Definition: move.cpp:420
std::vector< map_location > ambushers_
Definition: move.cpp:427
const map_location goto_
Definition: move.cpp:403
std::size_t friend_count_
Definition: move.cpp:425
unit_map::iterator move_it_
Definition: move.cpp:397
bool ambushed_
Definition: move.cpp:414
route_iterator expected_end_
Definition: move.cpp:391
bool current_uses_fog_
Definition: move.cpp:408
map_location loc
Definition: move.cpp:172
map_location blocked_loc_
Definition: move.cpp:413
int original_village_owner
Definition: move.cpp:170
team * current_team_
Definition: move.cpp:407
static lg::log_domain log_engine("engine")
const int orig_side_
Definition: move.cpp:400
#define ERR_NG
Definition: move.cpp:53
const std::vector< map_location > & route_
Definition: move.cpp:386
map_location ambush_stop_
Definition: move.cpp:412
bool take_village_timebonus
Definition: move.cpp:171
const bool playing_team_is_viewing_
Definition: move.cpp:384
bool teleport_failed_
Definition: move.cpp:423
route_iterator obstructed_
Definition: move.cpp:393
const map_location::direction orig_dir_
Definition: move.cpp:402
std::deque< int > moves_left_
Definition: move.cpp:428
route_iterator ambush_limit_
Definition: move.cpp:392
move_unit_spectator *const spectator_
Definition: move.cpp:381
int orig_moves_
Definition: move.cpp:401
shroud_clearer clearer_
Definition: move.cpp:430
#define DBG_NG
Definition: move.cpp:54
bool wml_undo_disabled_
Definition: move.cpp:417
const route_iterator begin_
Definition: move.cpp:389
map_location zoc_stop_
Definition: move.cpp:411
const bool skip_sighting_
Definition: move.cpp:382
bool show_ambush_alert_
Definition: move.cpp:415
route_iterator move_loc_
Definition: move.cpp:409
std::size_t enemy_count_
Definition: move.cpp:424
bool wml_move_aborted_
Definition: move.cpp:418
const route_iterator full_end_
Definition: move.cpp:390
const bool skip_ally_sighting_
Definition: move.cpp:383
Various functions related to moving units.
double t
Definition: astarsearch.cpp:63
void set_ambusher(const unit_map::const_iterator &u)
set the location of an ambusher
Definition: move.cpp:145
void add_seen_friend(const unit_map::const_iterator &u)
add a location of a seen friend
Definition: move.cpp:60
const std::vector< unit_map::const_iterator > & get_seen_enemies() const
get the locations of seen enemies
Definition: move.cpp:84
const unit_map::const_iterator & get_ambusher() const
get the location of an ambusher
Definition: move.cpp:72
void error(const std::string &message) override
ingerited from action_spectator
Definition: move.cpp:140
void add_seen_enemy(const unit_map::const_iterator &u)
add the location of new seen enemy
Definition: move.cpp:66
const unit_map::const_iterator & get_unit() const
get new location of moved unit
Definition: move.cpp:96
unit_map::const_iterator ambusher_
Definition: move.hpp:103
unit_map::const_iterator unit_
Definition: move.hpp:107
void reset(const unit_map &units)
reset all locations to empty values
Definition: move.cpp:129
void set_unit(const unit_map::const_iterator &u)
set the iterator to moved unit
Definition: move.cpp:157
std::vector< unit_map::const_iterator > seen_enemies_
Definition: move.hpp:105
void set_failed_teleport(const unit_map::const_iterator &u)
set the location of a failed teleport
Definition: move.cpp:151
std::vector< unit_map::const_iterator > seen_friends_
Definition: move.hpp:106
virtual ~move_unit_spectator()
destructor
Definition: move.cpp:125
const std::vector< unit_map::const_iterator > & get_seen_friends() const
get the locations of seen friends
Definition: move.cpp:90
std::size_t tiles_entered_
Definition: move.hpp:108
const unit_map::const_iterator & get_failed_teleport() const
get the location of a failed teleport
Definition: move.cpp:78
unit_map::const_iterator failed_teleport_
Definition: move.hpp:104
void add_custom(Args &&... args)
Definition: undo.hpp:67
void add_move(const unit_const_ptr &u, const std::vector< map_location >::const_iterator &begin, const std::vector< map_location >::const_iterator &end, int start_moves, const map_location::direction dir=map_location::direction::indeterminate)
Adds a move to the undo stack.
Definition: undo.cpp:96
virtual bool local_checkup(const config &expected_data, config &real_data)=0
Compares data to the results calculated during the original game.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
int village_owner(const map_location &loc) const
Given the location of a village, will return the 1-based number of the team that currently owns it,...
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition: display.hpp:88
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1486
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2927
void announce(const std::string &msg, const color_t &color=font::GOOD_COLOR, const announce_options &options=announce_options())
Announce a message prominently.
Definition: display.cpp:1449
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:669
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:102
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:79
team & get_team(int i)
Definition: game_board.hpp:91
bool team_is_defeated(const team &t) const
Calculates whether a team is defeated.
Definition: game_board.cpp:244
virtual const unit_map & units() const override
Definition: game_board.hpp:106
virtual const gamemap & map() const override
Definition: game_board.hpp:96
void invalidate_unit_after_move(const map_location &src, const map_location &dst)
Same as invalidate_unit() if moving the displayed unit.
static game_display * get_singleton()
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
Encapsulates the map of the game.
Definition: map.hpp:176
bool is_village(const map_location &loc) const
Definition: map.cpp:60
const std::unordered_set< map_location > & get_adjacents(map_location loc) const
Definition: teleport.cpp:231
bool is_skipping_actions() const
bool is_skipping_replay() const
static prefs & get()
static config get_movement(const std::vector< map_location > &steps, bool skip_sighted, bool skip_ally_sighted)
Records a move that follows the provided steps.
static void process_error(const std::string &msg)
Definition: replay.cpp:183
static void block_undo(bool do_block=true, bool clear_undo=true)
set this to false to prevent clearing the undo stack, this is important when we cannot change the gam...
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
bool fog_or_shroud() const
Definition: team.hpp:346
bool auto_shroud_updates() const
Definition: team.hpp:366
void decrement_action_bonus_count()
Definition: team.hpp:241
bool is_enemy(int n) const
Definition: team.hpp:270
bool owns_village(const map_location &loc) const
Definition: team.hpp:179
A class to encapsulate the steps of drawing a unit's move.
Definition: udisplay.hpp:45
void proceed_to(const unit_ptr &u, std::size_t path_index, bool update=false, bool wait=true)
Visually moves a unit from the last hex we drew to the one specified by path_index.
Definition: udisplay.cpp:315
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:394
void finish(const unit_ptr &u, map_location::direction dir=map_location::direction::indeterminate)
Finishes the display of movement for the supplied unit.
Definition: udisplay.cpp:434
void start(const unit_ptr &u)
Initiates the display of movement for the supplied unit.
Definition: udisplay.cpp:257
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
std::size_t count(const map_location &loc) const
Definition: map.hpp:413
unit_iterator find(std::size_t id)
Definition: map.cpp:302
umap_retval_pair_t move(const map_location &src, const map_location &dst)
Moves a unit from location src to location dst.
Definition: map.cpp:92
This class represents a single unit of a specific type.
Definition: unit.hpp:39
static void clear_status_caches()
Clear this unit status cache for all units.
Definition: unit.cpp:790
const config * cfg
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1031
static std::string _(const char *str)
Definition: gettext.hpp:100
active_ability_list get_abilities(const std::string &tag_name, const map_location &loc) const
Gets the unit's active abilities of a particular type if it were on a specified location.
Definition: abilities.cpp:768
@ STATE_UNCOVERED
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:778
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:541
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
void teleport_unit_from_replay(const std::vector< map_location > &steps, bool continued_move, bool skip_ally_sighted, bool show_move)
Teleports a unit across the board.
Definition: move.cpp:1412
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 teleport_unit_and_record(const map_location &teleport_from, const map_location &teleport_to, move_unit_spectator *)
Teleports a unit across the board and enters the synced context.
Definition: move.cpp:1403
void move_unit_and_record(const std::vector< map_location > &steps, bool continued_move, move_unit_spectator &move_spectator)
Moves a unit across the board.
Definition: move.cpp:1459
game_events::pump_result_t get_village(const map_location &loc, int side, bool *action_timebonus, bool fire_event)
Makes it so the village at the given location is owned by the given side.
Definition: move.cpp:221
static void move_unit_internal(unit_mover &mover)
Definition: move.cpp:1341
void execute_move_unit(const std::vector< map_location > &steps, bool continued_move, bool skip_ally_sighted, move_unit_spectator *move_spectator)
Moves a unit across the board.
Definition: move.cpp:1388
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
void pump()
Process all events currently in the queue.
Definition: events.cpp:480
const color_t GOOD_COLOR
const color_t BAD_COLOR
const color_t NORMAL_COLOR
std::tuple< bool, bool > pump_result_t
Definition: fwd.hpp:29
void hide()
Hides the fps report popup.
Definition: fps_report.cpp:147
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
static bool is_active(const widget *wgt)
Definition: window.cpp:1265
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
const hotkey_command & get_hotkey_command(std::string_view command)
Returns the hotkey_command with the given id.
@ HOTKEY_CONTINUE_MOVE
void write(std::ostream &out, const configr_of &cfg, unsigned int level, bool strong_quotes)
Definition: parser.cpp:748
bool enemy_zoc(const team &current_team, const map_location &loc, const team &viewing_team, bool see_all)
Determines if a given location is in an enemy zone of control.
Definition: pathfind.cpp:134
const teleport_map get_teleport_locations(const unit &u, const team &viewing_team, bool see_all, bool ignore_units, bool check_vision)
Definition: teleport.cpp:251
game_board * gameboard
Definition: resources.cpp:20
game_data * gamedata
Definition: resources.cpp:22
game_events::manager * game_events
Definition: resources.cpp:24
actions::undo_list * undo_stack
Definition: resources.cpp:32
play_controller * controller
Definition: resources.cpp:21
std::shared_ptr< wb::manager > whiteboard
Definition: resources.cpp:33
std::map< std::string, t_string > string_map
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
Definition: general.hpp:142
std::string to_string(const Range &range, const Func &op)
bool show(std::string title, std::string message)
Displays a tray notification.
This module contains various pathfinding functions and utilities.
Define the game's event mechanism.
Replay control code.
Records information to be able to undo an action.
Definition: undo_action.hpp:85
virtual void write(config &cfg) const
Writes this into the provided config.
Definition: undo_action.hpp:96
Data typedef for active_ability_list.
Definition: abilities.hpp:165
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:602
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:611
Encapsulates the map of the game.
Definition: location.hpp:46
int wml_y() const
Definition: location.hpp:187
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
static const map_location & null_location()
Definition: location.hpp:103
int wml_x() const
Definition: location.hpp:186
void write(config &cfg) const
Definition: location.cpp:223
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
checkup * checkup_instance
Display units performing various actions: moving, attacking, and dying.
Various functions that implement the undoing (and redoing) of in-game commands.
Various functions implementing vision (through fog of war and shroud).