The Battle for Wesnoth  1.19.10+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  {
197  undo_action::write(cfg);
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.set_action_bonus_count(current_team.action_bonus_count() - 1);
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->set_action_bonus_count(1 + t->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,
363  unit_display::unit_mover & animator);
364 
365  /** Teleports the unit. */
366  inline bool do_teleport(unit_display::unit_mover& 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.
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 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,
594  unit_display::unit_mover & animator)
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 being used.
639  * @return whether or not we started a new animation.
640  */
641  inline bool unit_mover::do_teleport(unit_display::unit_mover& 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  remaining_moves -= move_it_->movement_cost(map[*end]);
886  if ( remaining_moves < 0 ) {
887  break;
888  }
889 
890  // We can enter this hex. Record the cost.
891  moves_left_.push_back(remaining_moves);
892 
893  // Check for being unable to leave this hex.
894  if (!move_it_->get_ability_bool("skirmisher", *end) &&
896  {
897  zoc_stop_ = *end;
898  }
899  }
900 
901  route_iterator min_end = start == begin_ ? start : start + 1;
902  while (end != min_end && resources::gameboard->has_visible_unit(*(end - 1), *current_team_)) {
903  // Backtrack.
904  --end;
905  }
906 
907  return end;
908  }
909 
910 
911  /**
912  * Updates our stored info after a WML event might have changed something.
913  *
914  * @param step Indicates the position in the path where we might need to start recalculating movement.
915  * Set this to full_end_ (or do not supply it) to skip such recalculations (because movement has finished).
916  *
917  * @returns false if continuing is impossible (i.e. we lost the moving unit).
918  */
919  void unit_mover::post_wml(game_events::pump_result_t pump_res, const route_iterator & step)
920  {
921  wml_move_aborted_ |= std::get<1>(pump_res);
922  wml_undo_disabled_ |= std::get<0>(pump_res);
923 
924  // Re-find the moving unit.
926  const bool found = move_it_ != resources::gameboard->units().end();
927 
928  // Update the current unit data.
929  current_side_ = found ? move_it_->side() : orig_side_;
932  ( current_side_ != orig_side_ ||
934 
935  // Update the path.
936  if ( found && step != full_end_ ) {
937  const route_iterator new_limit = plot_turn(step, expected_end_);
938  cache_hidden_units(step, new_limit);
939  // Just in case: length 0 paths become length 1 paths.
940  if (ambush_limit_ == step) {
941  ++ambush_limit_;
942  }
943  }
944 
945  wml_removed_unit_ |= !found;
946  }
947 
948 
949  /**
950  * Fires the sighted events that were raised earlier.
951  *
952  * @param[in] from Points to the hex the sighting unit currently occupies.
953  *
954  * @return sets event_mutated_ || wml_move_aborted_ to true if this event should interrupt movement.
955  */
956  void unit_mover::pump_sighted(const route_iterator & from)
957  {
958  game_events::pump_result_t pump_res = clearer_.fire_events();
959  post_wml(pump_res, from);
960  }
961 
962 
963  /**
964  * Returns the ambush alert (if any) for the given unit.
965  */
966  std::string unit_mover::read_ambush_string(const unit & ambusher)
967  {
968  for(const unit_ability &hide : ambusher.get_abilities("hides"))
969  {
970  const std::string & ambush_string = (*hide.ability_cfg)["alert"].str();
971  if (!ambush_string.empty()) {
972  return ambush_string;
973  }
974  }
975 
976  // No string found.
977  return std::string();
978  }
979 
980 
981  /**
982  * Reveals the unit at the indicated location.
983  * Can also update the current ambushed alert.
984  * May fire "sighted" events.
985  */
986  void unit_mover::reveal_ambusher(const map_location & hex, bool update_alert)
987  {
988  // Convenient alias:
989  unit_map &units = resources::gameboard->units();
991 
992  // Find the unit at the indicated location.
993  unit_map::iterator ambusher = units.find(hex);
994  if ( ambusher != units.end() ) {
995  // Prepare for sighted events.
996  std::vector<int> sight_cache(get_sides_not_seeing(*ambusher));
997  // Make sure the unit is visible (during sighted events, and in case
998  // we had to backtrack).
999  ambusher->set_state(unit::STATE_UNCOVERED, true);
1000 
1001  // Record this in the move spectator.
1002  if (spectator_) {
1003  spectator_->set_ambusher(ambusher);
1004  }
1005  // Override the default ambushed message?
1006  if ( update_alert ) {
1007  // Observers don't get extra information.
1008  if ( playing_team_is_viewing_ || !disp.fogged(hex) ) {
1009  show_ambush_alert_ = true;
1010  // We only support one custom ambush message; use the first one.
1011  if (ambush_string_.empty()) {
1012  ambush_string_ = read_ambush_string(*ambusher);
1013  }
1014  }
1015  }
1016 
1017  // Make sure this hex is drawn correctly.
1018  disp.invalidate(hex);
1019  // Fire sighted events.
1020  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*ambusher, &sight_cache);
1021  // TODO: should we call post_wml ?
1022  wml_move_aborted_ |= wml_move_aborted;
1023  wml_undo_disabled_ |= wml_undo_blocked;
1024  }
1025  }
1026 
1027 
1028  // Public interface:
1029 
1030  /**
1031  * Determines how far along the route the unit can expect to move this turn.
1032  * This is based solely on data known to the player, and will not plot a move
1033  * that ends on another (known) unit.
1034  * (For example, this prevents a player from plotting a multi-turn move that
1035  * has this turn's movement ending on a (slower) unit, and thereby clearing
1036  * fog as if the moving unit actually made it on top of that other unit.)
1037  *
1038  * @returns false if the expectation is to not move at all.
1039  */
1040  bool unit_mover::check_expected_movement()
1041  {
1042  expected_end_ = plot_turn(begin_, full_end_);
1043  return expected_end_ != begin_;
1044  }
1045 
1046 
1047  /**
1048  * Attempts to move the unit along the expected path.
1049  * (This will do nothing unless check_expected_movement() was called first.)
1050  *
1051  * @param[in] show Set to false to suppress animations.
1052  */
1053  void unit_mover::try_actual_movement(bool show)
1054  {
1055  static const std::string enter_hex_str("enter hex");
1056  static const std::string exit_hex_str("exit hex");
1057 
1058 
1059  bool obstructed_stop = false;
1060 
1061 
1062  // Check for hidden units along the expected path before we start
1063  // animating and firing events.
1064  cache_hidden_units(begin_, expected_end_);
1065 
1066  if ( begin_ != ambush_limit_ ) {
1067  // Cache the moving unit's visibility.
1068  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1069 
1070  // Prepare to animate.
1072  animator.start(move_it_.get_shared_ptr());
1073 
1074  // Traverse the route to the hex where we need to stop.
1075  // Each iteration performs the move from real_end_-1 to real_end_.
1076  for ( real_end_ = begin_+1; real_end_ != ambush_limit_; ++real_end_ ) {
1077  const route_iterator step_from = real_end_ - 1;
1078 
1079  // See if we can leave *step_from.
1080  // Already accounted for: ambusher
1082  break;
1083  }
1084  if ( sighted_ && is_reasonable_stop(*step_from) ) {
1085  sighted_stop_ = true;
1086  break;
1087  }
1088  // Already accounted for: ZoC
1089  // Already accounted for: movement cost
1090  fire_hex_event(exit_hex_str, step_from, real_end_);
1092  break;
1093  }
1094  if ( real_end_ == obstructed_ ) {
1095  // We did not check for being a replay when checking for an
1096  // obstructed hex, so we do not check can_break here.
1097  obstructed_stop = true;
1098  break;
1099  }
1100 
1101  // We can leave *step_from. Make the move to *real_end_.
1102  bool new_animation = do_move(step_from, real_end_, animator);
1103  // Update the fog.
1104  if ( current_uses_fog_ )
1105  handle_fog(*real_end_, new_animation);
1106  animator.wait_for_anims();
1107 
1108  // Fire the events for this step.
1109  // (These return values are not checked since real_end_ still
1110  // needs to be incremented. The wml_move_aborted_ check will break
1111  // us out of the loop if needed.)
1112  fire_hex_event(enter_hex_str, real_end_, step_from);
1113  // Sighted events only fire if we could stop due to sighting.
1114  if (is_reasonable_stop(*real_end_)) {
1115  pump_sighted(real_end_);
1116  }
1117  }//for
1118  // Make sure any remaining sighted events get fired.
1119  pump_sighted(real_end_-1);
1120 
1121  if ( move_it_.valid() ) {
1122  // Finish animating.
1123  animator.finish(move_it_.get_shared_ptr());
1124  // Check for the moving unit being seen.
1125  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1126  // TODO: should we call post_wml ?
1127  wml_move_aborted_ |= wml_move_aborted;
1128  wml_undo_disabled_ |= wml_undo_blocked;
1129  }
1130  }//if
1131 
1132  // Some flags were set to indicate why we might stop.
1133  // Update those to reflect whether or not we got to them.
1135  if (!obstructed_stop) {
1137  }
1138  teleport_failed_ = teleport_failed_ && obstructed_stop;
1139  // event_mutated_ does not get unset, regardless of other reasons
1140  // for stopping, but we do save its current value.
1142  }
1143 
1144  /**
1145  * Attempts to teleport the unit to a map_location.
1146  *
1147  * @param[in] show Set to false to suppress animations.
1148  */
1149  void unit_mover::try_teleport(bool show)
1150  {
1151  const route_iterator step_from = real_end_ - 1;
1152 
1153  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1154 
1155  // Prepare to animate.
1157  animator.start(move_it_.get_shared_ptr());
1158  fire_hex_event("exit hex", step_from, begin_);
1159 
1160  bool new_animation = do_teleport(animator);
1161 
1162  if(current_uses_fog_)
1163  handle_fog(*(begin_ + 1), new_animation);
1164 
1165  animator.wait_for_anims();
1166 
1167  fire_hex_event("enter hex", begin_, step_from);
1168 
1169  if(is_reasonable_stop(*step_from)) {
1170  pump_sighted(step_from);
1171  }
1172 
1173  pump_sighted(step_from);
1174 
1175  if(move_it_.valid()) {
1176  // Finish animating.
1177  animator.finish(move_it_.get_shared_ptr());
1178 
1179  // Check for the moving unit being seen.
1180  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1181 
1182  wml_move_aborted_ |= wml_move_aborted;
1183  wml_undo_disabled_ |= wml_undo_blocked;
1184  }
1185 
1186  post_move();
1187  }
1188 
1189  /**
1190  * Does some bookkeeping and event firing, for use after movement.
1191  * This includes village capturing and the undo stack.
1192  */
1193  void unit_mover::post_move()
1194  {
1195  const map_location & final_loc = final_hex();
1196 
1197  int orig_village_owner = 0;
1198  bool action_time_bonus = false;
1199 
1200  // Reveal ambushers?
1201  if (ambushed_ || blocked()) {
1202  reveal_ambushers();
1203  }
1204  else if (teleport_failed_ && spectator_) {
1205  spectator_->set_failed_teleport(resources::gameboard->units().find(*obstructed_));
1206  }
1208 
1209  if ( move_it_.valid() ) {
1210  // Update the moving unit.
1211  move_it_->set_interrupted_move(
1212  sighted_stop_ && !resources::whiteboard->is_executing_actions() ?
1213  *(full_end_-1) :
1215  if (ambushed_ || final_loc == zoc_stop_) {
1216  move_it_->set_movement(0, true);
1217  }
1218 
1219  // Village capturing.
1220  if ( resources::gameboard->map().is_village(final_loc) ) {
1221  // Is this a capture?
1222  orig_village_owner = resources::gameboard->village_owner(final_loc);
1223  if ( orig_village_owner != current_side_) {
1224  // Captured. Zap movement and take over the village.
1225  move_it_->set_movement(0, true);
1226  post_wml(get_village(final_loc, current_side_, &action_time_bonus));
1227  }
1228  }
1229  }
1230 
1231  // Finally, the moveto event.
1232  post_wml(resources::game_events->pump().fire("moveto", final_loc, *begin_));
1233 
1234  // Record keeping.
1235  if (spectator_) {
1236  spectator_->set_unit(move_it_);
1237  spectator_->set_interrupted(interrupted());
1238  spectator_->set_tiles_entered(steps_travelled());
1239  }
1240 
1241  const bool mover_valid = move_it_.valid();
1242 
1243 
1244  if(!mover_valid || undo_blocked()) {
1246  }
1247 
1248  // TODO: this looks wrong, whiteboard shouldn't effect the undo stack during a synced action.
1249  if(resources::whiteboard->is_active() && resources::whiteboard->should_clear_undo()) {
1251  }
1252 
1253  // Update the screen.
1255  }
1256 
1257 
1258  /**
1259  * Shows the various on-screen messages, for use after movement.
1260  */
1261  void unit_mover::feedback() const
1262  {
1263  // Alias some resources.
1265 
1266  // Multiple messages may be displayed simultaneously
1267  // this variable is used to keep them from overlapping
1268  std::string message_prefix = "";
1269 
1270  // Ambush feedback?
1271  if ( ambushed_ && show_ambush_alert_ ) {
1272  disp.announce(message_prefix + ambush_string_, font::BAD_COLOR);
1273  message_prefix += " \n";
1274  }
1275 
1276  display::announce_options announce_options;
1277  announce_options.discard_previous = false;
1278 
1279  // Failed teleport feedback?
1281  std::string teleport_string = _("Failed teleport! Exit not empty");
1282  disp.announce(message_prefix + teleport_string, font::BAD_COLOR, announce_options);
1283  message_prefix += " \n";
1284  }
1285 
1286  // Sighted units feedback?
1287  if ( playing_team_is_viewing_ && (enemy_count_ != 0 || friend_count_ != 0) ) {
1288  // Create the message to display (depends on whether friends,
1289  // enemies, or both were sighted, and on how many of each).
1290  utils::string_map symbols;
1291  symbols["enemies"] = std::to_string(enemy_count_);
1292  symbols["friends"] = std::to_string(friend_count_);
1293  std::string message;
1294  color_t msg_color;
1295  if ( friend_count_ != 0 && enemy_count_ != 0 ) {
1296  // TRANSLATORS: This becomes the "friendphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1297  symbols["friendphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 friendly", "$friends friendly", friend_count_, symbols);
1298  // TRANSLATORS: This becomes the "enemyphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1299  symbols["enemyphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 enemy", "$enemies enemy", enemy_count_, symbols);
1300  // TRANSLATORS: Both friends and enemies sighted -- neutral message.
1301  // This is shown when a move is interrupted because units were revealed from the fog of war.
1302  message = VGETTEXT("Units sighted! ($friendphrase, $enemyphrase)", symbols);
1303  msg_color = font::NORMAL_COLOR;
1304  } else if ( enemy_count_ != 0 ) {
1305  // TRANSLATORS: Only enemies sighted -- bad message.
1306  // This is shown when a move is interrupted because units were revealed from the fog of war.
1307  message = VNGETTEXT("Enemy unit sighted!", "$enemies enemy units sighted!", enemy_count_, symbols);
1308  msg_color = font::BAD_COLOR;
1309  } else if ( friend_count_ != 0 ) {
1310  // TRANSLATORS: Only friends sighted -- good message.
1311  // This is shown when a move is interrupted because units were revealed from the fog of war.
1312  message = VNGETTEXT("Friendly unit sighted", "$friends friendly units sighted", friend_count_, symbols);
1313  msg_color = font::GOOD_COLOR;
1314  }
1315 
1316  disp.announce(message_prefix + message, msg_color, announce_options);
1317  message_prefix += " \n";
1318  }
1319 
1320  // Suggest "continue move"?
1321  if ( playing_team_is_viewing_ && sighted_stop_ && !resources::whiteboard->is_executing_actions() ) {
1322  // See if the "Continue Move" action has an associated hotkey
1324  if ( !name.empty() ) {
1325  utils::string_map symbols;
1326  symbols["hotkey"] = name;
1327  std::string message = VGETTEXT("(press $hotkey to keep moving)", symbols);
1328  disp.announce(message_prefix + message, font::NORMAL_COLOR, announce_options);
1329  message_prefix += " \n";
1330  }
1331  }
1332  }
1333 
1334 }//end anonymous namespace
1335 
1336 
1337 static void move_unit_internal(unit_mover& mover)
1338 {
1340  const events::command_disabler disable_commands;
1341 
1342  // Attempt moving.
1343  mover.try_actual_movement(show_move);
1344 
1345  config co;
1346  config cn {
1347  "stopped_early", mover.stopped_early(),
1348  "final_hex_x", mover.final_hex().wml_x(),
1349  "final_hex_y", mover.final_hex().wml_y(),
1350  };
1351  bool matches_replay = checkup_instance->local_checkup(cn,co);
1352  if(!matches_replay)
1353  {
1354  replay::process_error("calculated movement destination (x="+ cn["final_hex_x"].str() + " y=" + cn["final_hex_y"].str() +
1355  ") didn't match the original destination(x="+ co["final_hex_x"].str() + " y=" + co["final_hex_y"].str() + ")\n");
1356 
1357  //TODO: move the unit by force to the desired destination with something like mover.reset_final_hex(co["x"], co["y"]);
1358  }
1359 
1360  // Bookkeeping, etc.
1361  // also fires the moveto event
1362  mover.post_move();
1363  if (show_move) {
1364  mover.feedback();
1365  }
1366 }
1367 
1368 /**
1369  * Moves a unit across the board.
1370  *
1371  * This function handles actual movement, checking terrain costs as well as
1372  * things that might interrupt movement (e.g. ambushes). If the full path
1373  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1374  * instruction. (The unit itself is whatever unit is at the beginning of the
1375  * supplied path.)
1376  *
1377  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1378  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1379  * interrupted should units be spotted).
1380  * @param[in] skip_ally_sighted If set to true, movement is not interrupted should allied units be spotted.
1381  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the
1382  * move
1383  */
1384 void execute_move_unit(const std::vector<map_location>& steps,
1385  bool continued_move,
1386  bool skip_ally_sighted,
1387  move_unit_spectator* move_spectator)
1388 {
1389  // Evaluate this move.
1390  unit_mover mover(steps, move_spectator, continued_move, skip_ally_sighted);
1391  if(!mover.check_expected_movement()) {
1392  DBG_NG << "found expected empty move, aborting";
1393  return;
1394  }
1395 
1396  move_unit_internal(mover);
1397 }
1398 
1399 void teleport_unit_and_record(const map_location& teleport_from,
1400  const map_location& teleport_to,
1401  move_unit_spectator* /* move_spectator */)
1402 {
1403  synced_context::run_and_throw("debug_teleport",
1404  config{"teleport_from_x", teleport_from.wml_x(), "teleport_from_y", teleport_from.wml_y(), "teleport_to_x",
1405  teleport_to.wml_x(), "teleport_to_y", teleport_to.wml_y()});
1406 }
1407 
1408 void teleport_unit_from_replay(const std::vector<map_location> &steps,
1409  bool continued_move, bool skip_ally_sighted, bool show_move)
1410 {
1411  unit_mover mover(steps, nullptr, continued_move, skip_ally_sighted);
1412  mover.try_teleport(show_move);
1413 }
1414 
1415 
1416 
1417 /**
1418  * Wrapper around the other overload.
1419  *
1420  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1421  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1422  * interrupted should units be spotted).
1423  * @param[out] interrupted If supplied, then this is set to true if information was uncovered that warrants
1424  * interrupting a chain of actions (and set to false otherwise).
1425  *
1426  * @returns The number of hexes entered. This can safely be used as an index
1427  * into @a steps to get the location where movement ended, provided
1428  * @a steps is not empty (the return value is guaranteed to be less
1429  * than steps.size() ).
1430  */
1432  const std::vector<map_location>& steps, bool continued_move, bool* interrupted)
1433 {
1434  auto move_spectator = move_unit_spectator();
1435  move_unit_and_record(steps, continued_move, move_spectator);
1436  if(interrupted) {
1437  *interrupted = move_spectator.get_interrupted();
1438  }
1439  return move_spectator.get_tiles_entered();
1440 }
1441 
1442 /**
1443  * Moves a unit across the board.
1444  *
1445  * This function handles actual movement, checking terrain costs as well as
1446  * things that might interrupt movement (e.g. ambushes). If the full path
1447  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1448  * instruction. (The unit itself is whatever unit is at the beginning of the
1449  * supplied path.)
1450  *
1451  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1452  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not interrupted should units be spotted).
1453  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the move
1454  */
1456  const std::vector<map_location>& steps, bool continued_move, move_unit_spectator& move_spectator)
1457 {
1458 
1459  // Avoid some silliness.
1460  if ( steps.size() < 2 || (steps.size() == 2 && steps.front() == steps.back()) ) {
1461  DBG_NG << "Ignoring a unit trying to jump on its hex at " <<
1462  ( steps.empty() ? map_location::null_location() : steps.front() ) << ".";
1463  return;
1464  }
1465  //if we have no fog activated then we always skip sighted
1466  if(resources::gameboard->units().find(steps.front()) != resources::gameboard->units().end())
1467  {
1468  const team &current_team = resources::gameboard->teams()[
1469  resources::gameboard->units().find(steps.front())->side() - 1];
1470  continued_move |= !current_team.fog_or_shroud();
1471  }
1472  const bool skip_ally_sighted = !prefs::get().ally_sighted_interrupts();
1473 
1475  "move", replay_helper::get_movement(steps, continued_move, skip_ally_sighted), move_spectator);
1476 
1477 }
1478 
1479 
1480 }//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:158
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:96
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1495
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2979
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:1458
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:667
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:110
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
team & get_team(int i)
Definition: game_board.hpp:92
bool team_is_defeated(const team &t) const
Calculates whether a team is defeated.
Definition: game_board.cpp:257
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
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()
Encapsulates the map of the game.
Definition: map.hpp:172
bool is_village(const map_location &loc) const
Definition: map.cpp:66
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:184
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:75
void set_action_bonus_count(const int count)
Definition: team.hpp:205
bool fog_or_shroud() const
Definition: team.hpp:310
bool auto_shroud_updates() const
Definition: team.hpp:329
bool is_enemy(int n) const
Definition: team.hpp:234
int action_bonus_count() const
Definition: team.hpp:204
bool owns_village(const map_location &loc) const
Definition: team.hpp:177
A class to encapsulate the steps of drawing a unit's move.
Definition: udisplay.hpp:45
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:433
void wait_for_anims()
Waits for the final animation of the most recent proceed_to() to finish.
Definition: udisplay.cpp:393
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:314
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:133
static void clear_status_caches()
Clear this unit status cache for all units.
Definition: unit.cpp:750
#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:1030
const unit * unit_
static std::string _(const char *str)
Definition: gettext.hpp:97
unit_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:221
@ STATE_UNCOVERED
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:872
T end(const std::pair< T, T > &p)
T begin(const std::pair< T, T > &p)
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:479
bool tiles_adjacent(const map_location &a, const map_location &b)
Function which tells if two locations are adjacent.
Definition: location.cpp:507
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:1408
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:1399
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:1455
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:1337
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:1384
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:483
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 show(const gui2::tracked_drawable &target)
Displays the fps report popup for the given tracked_drawable.
Definition: fps_report.cpp:131
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:1261
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
@ HOTKEY_CONTINUE_MOVE
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:764
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:140
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:80
virtual void write(config &cfg) const
Writes this into the provided config.
Definition: undo_action.hpp:91
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:59
Holds options for calls to function 'announce' (announce).
Definition: display.hpp:611
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:620
static const hotkey_command & get_command_by_command(HOTKEY_COMMAND command)
the execute_command argument was changed from HOTKEY_COMMAND to hotkey_command, to be able to call it...
Encapsulates the map of the game.
Definition: location.hpp:45
int wml_y() const
Definition: location.hpp:218
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
static const map_location & null_location()
Definition: location.hpp:102
int wml_x() const
Definition: location.hpp:217
void write(config &cfg) const
Definition: location.cpp:225
Data typedef for unit_ability_list.
Definition: unit.hpp:38
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).