The Battle for Wesnoth  1.19.7+dev
move.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 }
219 game_events::pump_result_t get_village(const map_location& loc, int side, bool *action_timebonus, bool fire_event)
220 {
221  std::vector<team> &teams = resources::gameboard->teams();
222  team *t = static_cast<unsigned>(side - 1) < teams.size() ? &teams[side - 1] : nullptr;
223  if (t && t->owns_village(loc)) {
225  }
226 
227  bool not_defeated = t && !resources::gameboard->team_is_defeated(*t);
228 
229  bool grants_timebonus = false;
230 
231  int old_owner_side = 0;
232  // We strip the village off all other sides, unless it is held by an ally
233  // and our side is already defeated (and thus we can't occupy it)
234  for(team& tm : teams) {
235  int i_side = tm.side();
236  if (!t || not_defeated || t->is_enemy(i_side)) {
237  if(tm.owns_village(loc)) {
238  old_owner_side = i_side;
239  tm.lose_village(loc);
240  }
241  if (side != i_side && action_timebonus) {
242  grants_timebonus = true;
243  }
244  }
245  }
246 
247  if (!t) {
249  }
250 
251  if(grants_timebonus) {
252  t->set_action_bonus_count(1 + t->action_bonus_count());
253  *action_timebonus = true;
254  }
255 
256  if(not_defeated) {
257  if (display::get_singleton() != nullptr) {
259  }
260  resources::undo_stack->add_custom<take_village_step>(old_owner_side, grants_timebonus, loc);
261  return t->get_village(loc, old_owner_side, fire_event ? resources::gamedata : nullptr);
262  }
263 
265 }
266 
267 
268 namespace { // Private helpers for move_unit()
269 
270  /** Helper class for move_unit(). */
271  class unit_mover {
272  typedef std::vector<map_location>::const_iterator route_iterator;
273 
274  public:
275  unit_mover(const unit_mover&) = delete;
276  unit_mover& operator=(const unit_mover&) = delete;
277 
278  unit_mover(const std::vector<map_location> & route,
279  move_unit_spectator *move_spectator,
280  bool skip_sightings, bool skip_ally_sightings);
281  ~unit_mover();
282 
283  /** Determines how far along the route the unit can expect to move this turn. */
284  bool check_expected_movement();
285  /** Attempts to move the unit along the expected path. */
286  void try_actual_movement(bool show);
287  /** Does some bookkeeping and event firing, for use after movement. */
288  void post_move();
289  /** Shows the various on-screen messages, for use after movement. */
290  void feedback() const;
291  /** Attempts to teleport the unit to a map_location. */
292  void try_teleport(bool show);
293 
294  /** After checking expected movement, this is the expected path. */
295  std::vector<map_location> expected_path() const
296  { return std::vector<map_location>(begin_, expected_end_); }
297  /** After moving, this is the final hex reached. */
298  const map_location & final_hex() const
299  { return *move_loc_; }
300  /** The number of hexes actually entered. */
301  std::size_t steps_travelled() const
302  { return move_loc_ - begin_; }
303  /** After moving, use this to detect if movement was less than expected. */
304  bool stopped_early() const { return expected_end_ != real_end_; }
305  /**
306  * After moving, use this to detect if something happened that would
307  * interrupt movement (even if movement ended for a different reason).
308  */
309  bool interrupted(bool include_end_of_move_events=true) const
310  {
311  return ambushed_ || blocked() || sighted_ || teleport_failed_ ||
312  (include_end_of_move_events ? (wml_removed_unit_ || wml_move_aborted_): event_mutated_mid_move_ ) ||
313  !move_it_.valid();
314  }
315 
316  private: // functions
317  /** Returns whether or not movement was blocked by a non-ambushing enemy. */
318  bool blocked() const { return blocked_loc_ != map_location::null_location(); }
319  /** Checks the expected route for hidden units. */
320  void cache_hidden_units(const route_iterator & start,
321  const route_iterator & stop);
322  /** Fires the enter_hex or exit_hex event and updates our data as needed. */
323  void fire_hex_event(const std::string & event_name,
324  const route_iterator & current,
325  const route_iterator & other);
326  /** AI moves are supposed to not change the "goto" order. */
327  bool is_ai_move() const
328  {
329  return spectator_ && spectator_->is_ai_move();
330  }
331  /** Checks how far it appears we can move this turn. */
332  route_iterator plot_turn(const route_iterator & start,
333  const route_iterator & stop);
334  /** Updates our stored info after a WML event might have changed something. */
335  void post_wml(game_events::pump_result_t pump_res, const route_iterator & step);
336  void post_wml(game_events::pump_result_t pump_res) { return post_wml(pump_res, full_end_); }
337  /** Fires the sighted events that were raised earlier. */
338  void pump_sighted(const route_iterator & from);
339  /** Returns the ambush alert (if any) for the given unit. */
340  static std::string read_ambush_string(const unit & ambusher);
341  /** Reveals the unit at the indicated location. */
342  void reveal_ambusher(const map_location & hex, bool update_alert=true);
343 
344  /** Returns whether or not undoing this move should be blocked. */
345  bool undo_blocked() const
346  { return ambushed_ || blocked() || wml_removed_unit_ || wml_undo_disabled_ || fog_changed_ ||
348 
349  // The remaining private functions are suggested to be inlined because
350  // each is used in only one place. (They are separate functions to ease
351  // code reading.)
352 
353  /** Checks for ambushers around @a hex, setting flags as appropriate. */
354  inline void check_for_ambushers(const map_location & hex);
355  /** Makes sure the path is not obstructed by a unit. */
356  inline bool check_for_obstructing_unit(const map_location & hex,
357  const map_location & prev_hex);
358  /** Moves the unit the next step. */
359  inline bool do_move(const route_iterator & step_from,
360  const route_iterator & step_to,
361  unit_display::unit_mover & animator);
362 
363  /** Teleports the unit. */
364  inline bool do_teleport(unit_display::unit_mover& animator);
365 
366  /** Clears fog/shroud and handles units being sighted. */
367  inline void handle_fog(const map_location & hex, bool new_animation);
368  inline bool is_reasonable_stop(const map_location & hex) const;
369  /** Reveals the units stored in ambushers_ (and blocked_loc_). */
370  inline void reveal_ambushers();
371  /** Makes sure the units in ambushers_ still exist. */
372  inline void validate_ambushers();
373 
374  private: // data
375  // (The order of the fields is somewhat important for the constructor.)
376 
377  // Movement parameters (these decrease the number of parameters needed
378  // for individual functions).
379  move_unit_spectator * const spectator_;
380  const bool skip_sighting_;
383  // Needed to interface with unit_display::unit_mover.
384  const std::vector<map_location> & route_;
385 
386  // The route to traverse.
387  const route_iterator begin_;
388  const route_iterator full_end_; // The end of the plotted route.
389  route_iterator expected_end_; // The end of this turn's portion of the plotted route.
390  route_iterator ambush_limit_; // How far we can go before encountering hidden units, ignoring allied units.
391  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.
392  route_iterator real_end_; // How far we actually can move this turn.
393 
394  // The unit that is moving.
396 
397  // This data stores the state from before the move started.
398  const int orig_side_;
402 
403  // This data tracks the current state as the move is in progress.
405  team * current_team_; // Will default to the original team if the moving unit becomes invalid.
407  route_iterator move_loc_; // Will point to the last moved-to location (in case the moving unit disappears).
408  // Data accumulated while making the move.
410  map_location ambush_stop_; // Could be inaccurate if ambushed_ is false.
411  map_location blocked_loc_; // Location of a blocking, enemy, non-ambusher unit.
412  bool ambushed_;
417  bool event_mutated_mid_move_; // Cache of wml_removed_unit_ || wml_move_aborted_ from just before the end-of-move handling.
419  bool sighted_; // Records if sightings were made that could interrupt movement.
420  bool sighted_stop_; // Records if sightings were made that did interrupt movement (the same as sighted_ unless movement ended for another reason).
422  std::size_t enemy_count_;
423  std::size_t friend_count_;
424  std::string ambush_string_;
425  std::vector<map_location> ambushers_;
426  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.
427 
428  shroud_clearer clearer_;
429  };
430 
431 
432  /**
433  * This constructor assumes @a route is not empty, and it will assert() that
434  * there is a unit at route.front().
435  * Iterators into @a route must remain valid for the life of this object.
436  * It is assumed that move_spectator is only supplied for AI moves (only
437  * affects whether or not gotos are changed).
438  */
439  unit_mover::unit_mover(const std::vector<map_location> & route,
440  move_unit_spectator *move_spectator,
441  bool skip_sightings, bool skip_ally_sightings)
442  : spectator_(move_spectator)
443  , skip_sighting_(skip_sightings)
444  , skip_ally_sighting_(skip_ally_sightings)
445  , playing_team_is_viewing_(display::get_singleton()->viewing_team_is_playing() || display::get_singleton()->show_everything())
446  , route_(route)
447  , begin_(route.begin())
448  , full_end_(route.end())
452  , real_end_(begin_)
453  // Unit information:
454  , move_it_(resources::gameboard->units().find(*begin_))
455  , orig_side_(( static_cast<void>(assert(move_it_ != resources::gameboard->units().end())), move_it_->side() ))
456  , orig_moves_(move_it_->movement_left())
457  , orig_dir_(move_it_->facing())
458  , goto_( is_ai_move() ? move_it_->get_goto() : route.back() )
461  , current_uses_fog_(current_team_->fog_or_shroud() && current_team_->auto_shroud_updates())
462  , move_loc_(begin_)
463  // The remaining fields are set to some sort of "zero state".
464  , zoc_stop_(map_location::null_location())
465  , ambush_stop_(map_location::null_location())
466  , blocked_loc_(map_location::null_location())
467  , ambushed_(false)
468  , show_ambush_alert_(false)
469  , wml_removed_unit_(false)
470  , wml_undo_disabled_(false)
471  , wml_move_aborted_(false)
472  , event_mutated_mid_move_(false)
473  , fog_changed_(false)
474  , sighted_(false)
475  , sighted_stop_(false)
476  , teleport_failed_(false)
477  , enemy_count_(0)
478  , friend_count_(0)
479  , ambush_string_()
480  , ambushers_()
481  , moves_left_()
482  , clearer_()
483  {
484  if ( !is_ai_move() )
485  // Clear the "goto" instruction during movement.
486  // (It will be reset in the destructor if needed.)
488  }
489 
490 
491  unit_mover::~unit_mover()
492  {
493  // Set the "goto" order? (Not if WML set it.)
494  if ( !is_ai_move() && move_it_.valid() &&
495  move_it_->get_goto() == map_location::null_location() )
496  {
497  // Only set the goto if movement was not complete and was not
498  // interrupted.
499  if (real_end_ != full_end_ && !interrupted(false)) {
500  // End-of-move-events do not cancel a goto. (Use case: tutorial S2.)
501  move_it_->set_goto(goto_);
502  }
503  }
504  }
505 
506 
507  // Private inlines:
508 
509  /**
510  * Checks for ambushers around @a hex, setting flags as appropriate.
511  */
512  inline void unit_mover::check_for_ambushers(const map_location & hex)
513  {
514  const unit_map &units = resources::gameboard->units();
515 
516  // Need to check each adjacent hex for hidden enemies.
517  for(const map_location& loc : get_adjacent_tiles(hex)) {
518  const unit_map::const_iterator neighbor_it = units.find(loc);
519 
520  if ( neighbor_it != units.end() &&
521  current_team_->is_enemy(neighbor_it->side()) &&
522  neighbor_it->invisible(loc) )
523  {
524  // Ambushed!
525  ambushed_ = true;
526  ambush_stop_ = hex;
527  ambushers_.push_back(loc);
528  }
529  }
530  }
531 
532 
533  /**
534  * Makes sure the path is not obstructed by a unit.
535  * @param hex The hex to check.
536  * @param prev_hex The previous hex in the route (used to detect a teleport).
537  * @return true if @a hex is obstructed.
538  */
539  inline bool unit_mover::check_for_obstructing_unit(const map_location & hex,
540  const map_location & prev_hex)
541  {
542  const unit_map::const_iterator blocking_unit = resources::gameboard->units().find(hex);
543 
544  // If no unit, then the path is not obstructed.
545  if (blocking_unit == resources::gameboard->units().end()) {
546  return false;
547  }
548 
549  // Check for units blocking a teleport exit. This can now only happen
550  // if these units are not visible to the current side, as otherwise no
551  // valid path is found.
552  if ( !tiles_adjacent(hex, prev_hex) ) {
553  if ( current_team_->is_enemy(blocking_unit->side()) ) {
554  // Enemy units always block the tunnel.
555  teleport_failed_ = true;
556  return true;
557  } else {
558  // By contrast, allied units (of a side which does not share vision) only
559  // block the tunnel if pass_allied_units=true. Whether the teleport is possible
560  // is checked by getting the teleport map with the see_all flag set to true.
561  const pathfind::teleport_map teleports = pathfind::get_teleport_locations(*move_it_, *current_team_, true, false, false);
562  const auto allowed_teleports = teleports.get_adjacents(prev_hex);
563 
564  if(allowed_teleports.count(hex) == 0) {
565  teleport_failed_ = true;
566  return true;
567  }
568  }
569  }
570 
571  if ( current_team_->is_enemy(blocking_unit->side()) ) {
572  // Trying to go through an enemy.
573  blocked_loc_ = hex;
574  return true;
575  }
576 
577  // If we get here, the unit does not interfere with movement.
578  return false;
579  }
580 
581 
582  /**
583  * Moves the unit the next step.
584  * @a step_to is the hex being moved to.
585  * @a step_from is the hex before that in the route.
586  * (The unit is actually at *move_loc_.)
587  * @a animator is the unit_display::unit_mover being used.
588  * @return whether or not we started a new animation.
589  */
590  inline bool unit_mover::do_move(const route_iterator & step_from,
591  const route_iterator & step_to,
592  unit_display::unit_mover & animator)
593  {
595 
596  // Adjust the movement even if we cannot move yet.
597  // We will eventually be able to move if nothing unexpected
598  // happens, and if something does happen, this movement is the
599  // cost to discover it.
600  move_it_->set_movement(moves_left_.front(), true);
601  moves_left_.pop_front();
602 
603  // Invalidate before moving so we invalidate neighbor hexes if needed.
604  move_it_->anim_comp().invalidate(disp);
605 
606  // Attempt actually moving. Fails if *step_to is occupied.
607  auto [unit_it, success] = resources::gameboard->units().move(*move_loc_, *step_to);
608 
609  if(success) {
611  unit_it.get_shared_ptr(), move_loc_, step_to + 1, orig_moves_, unit_it->facing());
612  orig_moves_ = unit_it->movement_left();
613  // Update the moving unit.
614  move_it_ = unit_it;
615  move_it_->set_facing(step_from->get_relative_dir(*step_to));
616  // Disable bars. The expectation here is that the animation
617  // unit_mover::finish() will clean after us at a later point. Ugly,
618  // but it works.
619  move_it_->anim_comp().set_standing(false);
620  disp.invalidate_unit_after_move(*move_loc_, *step_to);
621  disp.invalidate(*step_to);
622  move_loc_ = step_to;
623 
624  // Show this move.
625  animator.proceed_to(move_it_.get_shared_ptr(), step_to - begin_,
626  move_it_->appearance_changed(), false);
627  move_it_->set_appearance_changed(false);
628  disp.redraw_minimap();
629  }
630 
631  return success;
632  }
633 
634  /**
635  * Teleports the unit to begin_ + 1.
636  * @a animator is the unit_display::unit_mover being used.
637  * @return whether or not we started a new animation.
638  */
639  inline bool unit_mover::do_teleport(unit_display::unit_mover& animator)
640  {
642  const route_iterator step_to = begin_ + 1;
643 
644  // Invalidate before moving so we invalidate neighbor hexes if needed.
645  move_it_->anim_comp().invalidate(disp);
646 
647  // Attempt actually moving. Fails if *step_to is occupied.
648  auto [unit_it, success] = resources::gameboard->units().move(*begin_, *step_to);
649 
650  if(success) {
651  // Update the moving unit.
652  move_it_ = unit_it;
653  move_it_->set_facing(begin_->get_relative_dir(*step_to));
654 
655  move_it_->anim_comp().set_standing(false);
656  disp.invalidate_unit_after_move(*begin_, *step_to);
657  disp.invalidate(*step_to);
658  move_loc_ = step_to;
659 
660  // Show this move.
661  animator.proceed_to(move_it_.get_shared_ptr(), step_to - begin_, move_it_->appearance_changed(), false);
662 
663  move_it_->set_appearance_changed(false);
664  disp.redraw_minimap();
665  }
666  return success;
667  }
668 
669  /**
670  * Clears fog/shroud and raises events for units being sighted.
671  * Only call this if the current team uses fog or shroud.
672  * @a hex is both the center of fog clearing and the filtered location of
673  * the moving unit when the sighted events will be fired.
674  */
675  inline void unit_mover::handle_fog(const map_location & hex,
676  bool new_animation)
677  {
678  // Clear the fog.
679  if ( clearer_.clear_unit(hex, *move_it_, *current_team_, nullptr,
681  !new_animation) )
682  {
683  clearer_.invalidate_after_clear();
684  fog_changed_ = true;
685  }
686 
687  // Check for sighted units?
688  if ( !skip_sighting_ ) {
689  sighted_ = enemy_count_ != 0 ;
690  }
692  {
693  sighted_ |= (friend_count_ != 0);
694  }
695  }
696 
697 
698  /**
699  * @return true if an unscheduled stop at @a hex is not likely to negatively
700  * impact the player's plans.
701  * (E.g. it would not kill movement by making an unintended village capture.)
702  */
703  inline bool unit_mover::is_reasonable_stop(const map_location & hex) const
704  {
705  // We cannot reasonably stop if move_it_ could not be moved to this
706  // hex (the hex was occupied by someone else).
707  if (*move_loc_ != hex) {
708  return false;
709  }
710 
711  // We can reasonably stop if the hex is not an unowned village.
712  return !resources::gameboard->map().is_village(hex) ||
714  }
715 
716 
717  /**
718  * Reveals the units stored in ambushers_ (and blocked_loc_).
719  * Also sets ambush_string_.
720  * May fire "sighted" events.
721  * Only call this if appropriate; this function does not itself check
722  * ambushed_ or blocked().
723  */
724  inline void unit_mover::reveal_ambushers()
725  {
726  // Reveal the blocking unit.
727  if (blocked()) {
728  reveal_ambusher(blocked_loc_, false);
729  }
730  // Reveal ambushers.
731  for(const map_location & reveal : ambushers_) {
732  reveal_ambusher(reveal, true);
733  }
734 
735  // Default "Ambushed!" message?
736  if (ambush_string_.empty()) {
737  ambush_string_ = _("Ambushed!");
738  }
739  }
740 
741 
742  /**
743  * Makes sure the units in ambushers_ still exist.
744  */
745  inline void unit_mover::validate_ambushers()
746  {
747  const unit_map &units = resources::gameboard->units();
748 
749  // Loop through the previously-detected ambushers.
750  std::size_t i = 0;
751  while ( i != ambushers_.size() ) {
752  if (units.count(ambushers_[i]) == 0) {
753  // Ambusher is gone.
754  ambushers_.erase(ambushers_.begin() + i);
755  }
756  else {
757  // Proceed to the next ambusher.
758  ++i;
759  }
760  }
761  }
762 
763 
764  // Private utilities:
765 
766  /**
767  * Checks the expected route for hidden units.
768  * This basically handles all the checks for surprises that can be done
769  * without visibly notifying a player. Thus this can be called at the
770  * start of movement and immediately after events, rather than tie up
771  * CPU time in the middle of animating a move.
772  *
773  * @param[in] start The beginning of the path to check.
774  * @param[in] stop The end of the path to check.
775  */
776  void unit_mover::cache_hidden_units(const route_iterator & start,
777  const route_iterator & stop)
778  {
779  // Clear the old cache.
782  teleport_failed_ = false;
783  // The ambush cache needs special treatment since we cannot re-detect
784  // an ambush if we are already at the ambushed location.
786  if ( ambushed_ ) {
787  validate_ambushers();
788  ambushed_ = !ambushers_.empty();
789  }
790  if ( !ambushed_ ) {
792  ambushers_.clear();
793  }
794 
795  // Update the shroud clearer.
796  clearer_.cache_units(current_uses_fog_ ? current_team_ : nullptr);
797 
798 
799  // Abort for null routes.
800  if ( start == stop ) {
802  return;
803  }
804 
805  // This loop will end with ambush_limit_ pointing one element beyond
806  // where the unit would be forced to stop by a hidden unit.
807  for ( ambush_limit_ = start+1; ambush_limit_ != stop; ++ambush_limit_ ) {
808  // Check if we need to stop in the previous hex.
809  if ( ambushed_ ) {
810  break;
811  }
812  // Check for being unable to enter this hex.
813  if ( check_for_obstructing_unit(*ambush_limit_, *(ambush_limit_-1)) ) {
814  // No replay check here? Makes some sense, I guess.
815  obstructed_ = ambush_limit_++; // The limit needs to be after obstructed_ in order for the latter to do anything.
816  break;
817  }
818 
819  // We can enter this hex.
820  // See if we are stopped in this hex.
821  check_for_ambushers(*ambush_limit_);
822  }
823  }
824 
825 
826  /**
827  * Fires the enter_hex or exit_hex event and updates our data as needed.
828  *
829  * @param[in] event_name The name of the event ("enter_hex" or "exit_hex").
830  * @param[in] current The currently occupied hex.
831  * @param[in] other The secondary hex to provide to the event.
832  *
833  */
834  void unit_mover::fire_hex_event(const std::string & event_name,
835  const route_iterator & current,
836  const route_iterator & other)
837  {
838 
839  const game_events::entity_location mover(*move_it_, *current);
840 
841  post_wml(resources::game_events->pump().fire(event_name, mover, *other), current);
842  }
843 
844 
845  /**
846  * Checks how far it appears we can move this turn.
847  *
848  * @param[in] start The beginning of the plotted path.
849  * @param[in] stop The end of the plotted path.
850  *
851  * @return An end iterator for the path that can be traversed this turn.
852  */
853  unit_mover::route_iterator unit_mover::plot_turn(const route_iterator & start,
854  const route_iterator & stop)
855  {
856  const gamemap &map = resources::gameboard->map();
857 
858  // Handle null routes.
859  if (start == stop) {
860  return start;
861  }
862 
863  int remaining_moves = move_it_->movement_left();
865  moves_left_.clear();
866 
867  if ( start != begin_ ) {
868  // Check for being unable to leave the current hex.
869  if ( !move_it_->get_ability_bool("skirmisher", *start) &&
871  zoc_stop_ = *start;
872  }
873 
874  // This loop will end with end pointing one element beyond where the
875  // unit thinks it will stop (the usual notion of "end" for iterators).
876  route_iterator end = start + 1;
877  for ( ; end != stop; ++end )
878  {
879  // Break out of the loop if we cannot leave the previous hex.
881  break;
882  }
883  remaining_moves -= move_it_->movement_cost(map[*end]);
884  if ( remaining_moves < 0 ) {
885  break;
886  }
887 
888  // We can enter this hex. Record the cost.
889  moves_left_.push_back(remaining_moves);
890 
891  // Check for being unable to leave this hex.
892  if (!move_it_->get_ability_bool("skirmisher", *end) &&
894  {
895  zoc_stop_ = *end;
896  }
897  }
898 
899  route_iterator min_end = start == begin_ ? start : start + 1;
900  while (end != min_end && resources::gameboard->has_visible_unit(*(end - 1), *current_team_)) {
901  // Backtrack.
902  --end;
903  }
904 
905  return end;
906  }
907 
908 
909  /**
910  * Updates our stored info after a WML event might have changed something.
911  *
912  * @param step Indicates the position in the path where we might need to start recalculating movement.
913  * Set this to full_end_ (or do not supply it) to skip such recalculations (because movement has finished).
914  *
915  * @returns false if continuing is impossible (i.e. we lost the moving unit).
916  */
917  void unit_mover::post_wml(game_events::pump_result_t pump_res, const route_iterator & step)
918  {
919  wml_move_aborted_ |= std::get<1>(pump_res);
920  wml_undo_disabled_ |= std::get<0>(pump_res);
921 
922  // Re-find the moving unit.
924  const bool found = move_it_ != resources::gameboard->units().end();
925 
926  // Update the current unit data.
927  current_side_ = found ? move_it_->side() : orig_side_;
930  ( current_side_ != orig_side_ ||
932 
933  // Update the path.
934  if ( found && step != full_end_ ) {
935  const route_iterator new_limit = plot_turn(step, expected_end_);
936  cache_hidden_units(step, new_limit);
937  // Just in case: length 0 paths become length 1 paths.
938  if (ambush_limit_ == step) {
939  ++ambush_limit_;
940  }
941  }
942 
943  wml_removed_unit_ |= !found;
944  }
945 
946 
947  /**
948  * Fires the sighted events that were raised earlier.
949  *
950  * @param[in] from Points to the hex the sighting unit currently occupies.
951  *
952  * @return sets event_mutated_ || wml_move_aborted_ to true if this event should interrupt movement.
953  */
954  void unit_mover::pump_sighted(const route_iterator & from)
955  {
956  game_events::pump_result_t pump_res = clearer_.fire_events();
957  post_wml(pump_res, from);
958  }
959 
960 
961  /**
962  * Returns the ambush alert (if any) for the given unit.
963  */
964  std::string unit_mover::read_ambush_string(const unit & ambusher)
965  {
966  for(const unit_ability &hide : ambusher.get_abilities("hides"))
967  {
968  const std::string & ambush_string = (*hide.ability_cfg)["alert"].str();
969  if (!ambush_string.empty()) {
970  return ambush_string;
971  }
972  }
973 
974  // No string found.
975  return std::string();
976  }
977 
978 
979  /**
980  * Reveals the unit at the indicated location.
981  * Can also update the current ambushed alert.
982  * May fire "sighted" events.
983  */
984  void unit_mover::reveal_ambusher(const map_location & hex, bool update_alert)
985  {
986  // Convenient alias:
987  unit_map &units = resources::gameboard->units();
989 
990  // Find the unit at the indicated location.
991  unit_map::iterator ambusher = units.find(hex);
992  if ( ambusher != units.end() ) {
993  // Prepare for sighted events.
994  std::vector<int> sight_cache(get_sides_not_seeing(*ambusher));
995  // Make sure the unit is visible (during sighted events, and in case
996  // we had to backtrack).
997  ambusher->set_state(unit::STATE_UNCOVERED, true);
998 
999  // Record this in the move spectator.
1000  if (spectator_) {
1001  spectator_->set_ambusher(ambusher);
1002  }
1003  // Override the default ambushed message?
1004  if ( update_alert ) {
1005  // Observers don't get extra information.
1006  if ( playing_team_is_viewing_ || !disp.fogged(hex) ) {
1007  show_ambush_alert_ = true;
1008  // We only support one custom ambush message; use the first one.
1009  if (ambush_string_.empty()) {
1010  ambush_string_ = read_ambush_string(*ambusher);
1011  }
1012  }
1013  }
1014 
1015  // Make sure this hex is drawn correctly.
1016  disp.invalidate(hex);
1017  // Fire sighted events.
1018  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*ambusher, &sight_cache);
1019  // TODO: should we call post_wml ?
1020  wml_move_aborted_ |= wml_move_aborted;
1021  wml_undo_disabled_ |= wml_undo_blocked;
1022  }
1023  }
1024 
1025 
1026  // Public interface:
1027 
1028  /**
1029  * Determines how far along the route the unit can expect to move this turn.
1030  * This is based solely on data known to the player, and will not plot a move
1031  * that ends on another (known) unit.
1032  * (For example, this prevents a player from plotting a multi-turn move that
1033  * has this turn's movement ending on a (slower) unit, and thereby clearing
1034  * fog as if the moving unit actually made it on top of that other unit.)
1035  *
1036  * @returns false if the expectation is to not move at all.
1037  */
1038  bool unit_mover::check_expected_movement()
1039  {
1040  expected_end_ = plot_turn(begin_, full_end_);
1041  return expected_end_ != begin_;
1042  }
1043 
1044 
1045  /**
1046  * Attempts to move the unit along the expected path.
1047  * (This will do nothing unless check_expected_movement() was called first.)
1048  *
1049  * @param[in] show Set to false to suppress animations.
1050  */
1051  void unit_mover::try_actual_movement(bool show)
1052  {
1053  static const std::string enter_hex_str("enter hex");
1054  static const std::string exit_hex_str("exit hex");
1055 
1056 
1057  bool obstructed_stop = false;
1058 
1059 
1060  // Check for hidden units along the expected path before we start
1061  // animating and firing events.
1062  cache_hidden_units(begin_, expected_end_);
1063 
1064  if ( begin_ != ambush_limit_ ) {
1065  // Cache the moving unit's visibility.
1066  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1067 
1068  // Prepare to animate.
1070  animator.start(move_it_.get_shared_ptr());
1071 
1072  // Traverse the route to the hex where we need to stop.
1073  // Each iteration performs the move from real_end_-1 to real_end_.
1074  for ( real_end_ = begin_+1; real_end_ != ambush_limit_; ++real_end_ ) {
1075  const route_iterator step_from = real_end_ - 1;
1076 
1077  // See if we can leave *step_from.
1078  // Already accounted for: ambusher
1080  break;
1081  }
1082  if ( sighted_ && is_reasonable_stop(*step_from) ) {
1083  sighted_stop_ = true;
1084  break;
1085  }
1086  // Already accounted for: ZoC
1087  // Already accounted for: movement cost
1088  fire_hex_event(exit_hex_str, step_from, real_end_);
1090  break;
1091  }
1092  if ( real_end_ == obstructed_ ) {
1093  // We did not check for being a replay when checking for an
1094  // obstructed hex, so we do not check can_break here.
1095  obstructed_stop = true;
1096  break;
1097  }
1098 
1099  // We can leave *step_from. Make the move to *real_end_.
1100  bool new_animation = do_move(step_from, real_end_, animator);
1101  // Update the fog.
1102  if ( current_uses_fog_ )
1103  handle_fog(*real_end_, new_animation);
1104  animator.wait_for_anims();
1105 
1106  // Fire the events for this step.
1107  // (These return values are not checked since real_end_ still
1108  // needs to be incremented. The wml_move_aborted_ check will break
1109  // us out of the loop if needed.)
1110  fire_hex_event(enter_hex_str, real_end_, step_from);
1111  // Sighted events only fire if we could stop due to sighting.
1112  if (is_reasonable_stop(*real_end_)) {
1113  pump_sighted(real_end_);
1114  }
1115  }//for
1116  // Make sure any remaining sighted events get fired.
1117  pump_sighted(real_end_-1);
1118 
1119  if ( move_it_.valid() ) {
1120  // Finish animating.
1121  animator.finish(move_it_.get_shared_ptr());
1122  // Check for the moving unit being seen.
1123  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1124  // TODO: should we call post_wml ?
1125  wml_move_aborted_ |= wml_move_aborted;
1126  wml_undo_disabled_ |= wml_undo_blocked;
1127  }
1128  }//if
1129 
1130  // Some flags were set to indicate why we might stop.
1131  // Update those to reflect whether or not we got to them.
1133  if (!obstructed_stop) {
1135  }
1136  teleport_failed_ = teleport_failed_ && obstructed_stop;
1137  // event_mutated_ does not get unset, regardless of other reasons
1138  // for stopping, but we do save its current value.
1140  }
1141 
1142  /**
1143  * Attempts to teleport the unit to a map_location.
1144  *
1145  * @param[in] show Set to false to suppress animations.
1146  */
1147  void unit_mover::try_teleport(bool show)
1148  {
1149  const route_iterator step_from = real_end_ - 1;
1150 
1151  std::vector<int> not_seeing = get_sides_not_seeing(*move_it_);
1152 
1153  // Prepare to animate.
1155  animator.start(move_it_.get_shared_ptr());
1156  fire_hex_event("exit hex", step_from, begin_);
1157 
1158  bool new_animation = do_teleport(animator);
1159 
1160  if(current_uses_fog_)
1161  handle_fog(*(begin_ + 1), new_animation);
1162 
1163  animator.wait_for_anims();
1164 
1165  fire_hex_event("enter hex", begin_, step_from);
1166 
1167  if(is_reasonable_stop(*step_from)) {
1168  pump_sighted(step_from);
1169  }
1170 
1171  pump_sighted(step_from);
1172 
1173  if(move_it_.valid()) {
1174  // Finish animating.
1175  animator.finish(move_it_.get_shared_ptr());
1176 
1177  // Check for the moving unit being seen.
1178  auto [wml_undo_blocked, wml_move_aborted] = actor_sighted(*move_it_, &not_seeing);
1179 
1180  wml_move_aborted_ |= wml_move_aborted;
1181  wml_undo_disabled_ |= wml_undo_blocked;
1182  }
1183 
1184  post_move();
1185  }
1186 
1187  /**
1188  * Does some bookkeeping and event firing, for use after movement.
1189  * This includes village capturing and the undo stack.
1190  */
1191  void unit_mover::post_move()
1192  {
1193  const map_location & final_loc = final_hex();
1194 
1195  int orig_village_owner = 0;
1196  bool action_time_bonus = false;
1197 
1198  // Reveal ambushers?
1199  if (ambushed_ || blocked()) {
1200  reveal_ambushers();
1201  }
1202  else if (teleport_failed_ && spectator_) {
1203  spectator_->set_failed_teleport(resources::gameboard->units().find(*obstructed_));
1204  }
1206 
1207  if ( move_it_.valid() ) {
1208  // Update the moving unit.
1209  move_it_->set_interrupted_move(
1210  sighted_stop_ && !resources::whiteboard->is_executing_actions() ?
1211  *(full_end_-1) :
1213  if (ambushed_ || final_loc == zoc_stop_) {
1214  move_it_->set_movement(0, true);
1215  }
1216 
1217  // Village capturing.
1218  if ( resources::gameboard->map().is_village(final_loc) ) {
1219  // Is this a capture?
1220  orig_village_owner = resources::gameboard->village_owner(final_loc);
1221  if ( orig_village_owner != current_side_) {
1222  // Captured. Zap movement and take over the village.
1223  move_it_->set_movement(0, true);
1224  post_wml(get_village(final_loc, current_side_, &action_time_bonus));
1225  }
1226  }
1227  }
1228 
1229  // Finally, the moveto event.
1230  post_wml(resources::game_events->pump().fire("moveto", final_loc, *begin_));
1231 
1232  // Record keeping.
1233  if (spectator_) {
1234  spectator_->set_unit(move_it_);
1235  spectator_->set_interrupted(interrupted());
1236  spectator_->set_tiles_entered(steps_travelled());
1237  }
1238 
1239  const bool mover_valid = move_it_.valid();
1240 
1241 
1242  if(!mover_valid || undo_blocked()) {
1244  }
1245 
1246  // TODO: this looks wrong, whiteboard shouldn't effect the undo stack during a synced action.
1247  if(resources::whiteboard->is_active() && resources::whiteboard->should_clear_undo()) {
1249  }
1250 
1251  // Update the screen.
1253  }
1254 
1255 
1256  /**
1257  * Shows the various on-screen messages, for use after movement.
1258  */
1259  void unit_mover::feedback() const
1260  {
1261  // Alias some resources.
1263 
1264  // Multiple messages may be displayed simultaneously
1265  // this variable is used to keep them from overlapping
1266  std::string message_prefix = "";
1267 
1268  // Ambush feedback?
1269  if ( ambushed_ && show_ambush_alert_ ) {
1270  disp.announce(message_prefix + ambush_string_, font::BAD_COLOR);
1271  message_prefix += " \n";
1272  }
1273 
1274  display::announce_options announce_options;
1275  announce_options.discard_previous = false;
1276 
1277  // Failed teleport feedback?
1279  std::string teleport_string = _("Failed teleport! Exit not empty");
1280  disp.announce(message_prefix + teleport_string, font::BAD_COLOR, announce_options);
1281  message_prefix += " \n";
1282  }
1283 
1284  // Sighted units feedback?
1285  if ( playing_team_is_viewing_ && (enemy_count_ != 0 || friend_count_ != 0) ) {
1286  // Create the message to display (depends on whether friends,
1287  // enemies, or both were sighted, and on how many of each).
1288  utils::string_map symbols;
1289  symbols["enemies"] = std::to_string(enemy_count_);
1290  symbols["friends"] = std::to_string(friend_count_);
1291  std::string message;
1292  color_t msg_color;
1293  if ( friend_count_ != 0 && enemy_count_ != 0 ) {
1294  // TRANSLATORS: This becomes the "friendphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1295  symbols["friendphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 friendly", "$friends friendly", friend_count_, symbols);
1296  // TRANSLATORS: This becomes the "enemyphrase" in "Units sighted! ($friendphrase, $enemyphrase)"
1297  symbols["enemyphrase"] = VNGETTEXT("Part of 'Units sighted! (...)' sentence^1 enemy", "$enemies enemy", enemy_count_, symbols);
1298  // TRANSLATORS: Both friends and enemies sighted -- neutral message.
1299  // This is shown when a move is interrupted because units were revealed from the fog of war.
1300  message = VGETTEXT("Units sighted! ($friendphrase, $enemyphrase)", symbols);
1301  msg_color = font::NORMAL_COLOR;
1302  } else if ( enemy_count_ != 0 ) {
1303  // TRANSLATORS: Only enemies sighted -- bad message.
1304  // This is shown when a move is interrupted because units were revealed from the fog of war.
1305  message = VNGETTEXT("Enemy unit sighted!", "$enemies enemy units sighted!", enemy_count_, symbols);
1306  msg_color = font::BAD_COLOR;
1307  } else if ( friend_count_ != 0 ) {
1308  // TRANSLATORS: Only friends sighted -- good message.
1309  // This is shown when a move is interrupted because units were revealed from the fog of war.
1310  message = VNGETTEXT("Friendly unit sighted", "$friends friendly units sighted", friend_count_, symbols);
1311  msg_color = font::GOOD_COLOR;
1312  }
1313 
1314  disp.announce(message_prefix + message, msg_color, announce_options);
1315  message_prefix += " \n";
1316  }
1317 
1318  // Suggest "continue move"?
1319  if ( playing_team_is_viewing_ && sighted_stop_ && !resources::whiteboard->is_executing_actions() ) {
1320  // See if the "Continue Move" action has an associated hotkey
1322  if ( !name.empty() ) {
1323  utils::string_map symbols;
1324  symbols["hotkey"] = name;
1325  std::string message = VGETTEXT("(press $hotkey to keep moving)", symbols);
1326  disp.announce(message_prefix + message, font::NORMAL_COLOR, announce_options);
1327  message_prefix += " \n";
1328  }
1329  }
1330  }
1331 
1332 }//end anonymous namespace
1333 
1334 
1335 static void move_unit_internal(unit_mover& mover)
1336 {
1338  const events::command_disabler disable_commands;
1339 
1340  // Attempt moving.
1341  mover.try_actual_movement(show_move);
1342 
1343  config co;
1344  config cn {
1345  "stopped_early", mover.stopped_early(),
1346  "final_hex_x", mover.final_hex().wml_x(),
1347  "final_hex_y", mover.final_hex().wml_y(),
1348  };
1349  bool matches_replay = checkup_instance->local_checkup(cn,co);
1350  if(!matches_replay)
1351  {
1352  replay::process_error("calculated movement destination (x="+ cn["final_hex_x"].str() + " y=" + cn["final_hex_y"].str() +
1353  ") didn't match the original destination(x="+ co["final_hex_x"].str() + " y=" + co["final_hex_y"].str() + ")\n");
1354 
1355  //TODO: move the unit by force to the desired destination with something like mover.reset_final_hex(co["x"], co["y"]);
1356  }
1357 
1358  // Bookkeeping, etc.
1359  // also fires the moveto event
1360  mover.post_move();
1361  if (show_move) {
1362  mover.feedback();
1363  }
1364 }
1365 
1366 /**
1367  * Moves a unit across the board.
1368  *
1369  * This function handles actual movement, checking terrain costs as well as
1370  * things that might interrupt movement (e.g. ambushes). If the full path
1371  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1372  * instruction. (The unit itself is whatever unit is at the beginning of the
1373  * supplied path.)
1374  *
1375  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1376  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1377  * interrupted should units be spotted).
1378  * @param[in] skip_ally_sighted If set to true, movement is not interrupted should allied units be spotted.
1379  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the
1380  * move
1381  */
1382 void execute_move_unit(const std::vector<map_location>& steps,
1383  bool continued_move,
1384  bool skip_ally_sighted,
1385  move_unit_spectator* move_spectator)
1386 {
1387  // Evaluate this move.
1388  unit_mover mover(steps, move_spectator, continued_move, skip_ally_sighted);
1389  if(!mover.check_expected_movement()) {
1390  DBG_NG << "found expected empty move, aborting";
1391  return;
1392  }
1393 
1394  move_unit_internal(mover);
1395 }
1396 
1397 void teleport_unit_and_record(const map_location& teleport_from,
1398  const map_location& teleport_to,
1399  move_unit_spectator* /* move_spectator */)
1400 {
1401  synced_context::run_and_throw("debug_teleport",
1402  config{"teleport_from_x", teleport_from.wml_x(), "teleport_from_y", teleport_from.wml_y(), "teleport_to_x",
1403  teleport_to.wml_x(), "teleport_to_y", teleport_to.wml_y()});
1404 }
1405 
1406 void teleport_unit_from_replay(const std::vector<map_location> &steps,
1407  bool continued_move, bool skip_ally_sighted, bool show_move)
1408 {
1409  unit_mover mover(steps, nullptr, continued_move, skip_ally_sighted);
1410  mover.try_teleport(show_move);
1411 }
1412 
1413 
1414 
1415 /**
1416  * Wrapper around the other overload.
1417  *
1418  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1419  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not
1420  * interrupted should units be spotted).
1421  * @param[out] interrupted If supplied, then this is set to true if information was uncovered that warrants
1422  * interrupting a chain of actions (and set to false otherwise).
1423  *
1424  * @returns The number of hexes entered. This can safely be used as an index
1425  * into @a steps to get the location where movement ended, provided
1426  * @a steps is not empty (the return value is guaranteed to be less
1427  * than steps.size() ).
1428  */
1430  const std::vector<map_location>& steps, bool continued_move, bool* interrupted)
1431 {
1432  auto move_spectator = move_unit_spectator();
1433  move_unit_and_record(steps, continued_move, move_spectator);
1434  if(interrupted) {
1435  *interrupted = move_spectator.get_interrupted();
1436  }
1437  return move_spectator.get_tiles_entered();
1438 }
1439 
1440 /**
1441  * Moves a unit across the board.
1442  *
1443  * This function handles actual movement, checking terrain costs as well as
1444  * things that might interrupt movement (e.g. ambushes). If the full path
1445  * cannot be reached this turn, the remainder is stored as the unit's "goto"
1446  * instruction. (The unit itself is whatever unit is at the beginning of the
1447  * supplied path.)
1448  *
1449  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1450  * @param[in] continued_move If set to true, this is a continuation of an earlier move (movement is not interrupted should units be spotted).
1451  * @param[out] move_spectator If supplied, this will be given the information uncovered by the move and about the move
1452  */
1454  const std::vector<map_location>& steps, bool continued_move, move_unit_spectator& move_spectator)
1455 {
1456 
1457  // Avoid some silliness.
1458  if ( steps.size() < 2 || (steps.size() == 2 && steps.front() == steps.back()) ) {
1459  DBG_NG << "Ignoring a unit trying to jump on its hex at " <<
1460  ( steps.empty() ? map_location::null_location() : steps.front() ) << ".";
1461  return;
1462  }
1463  //if we have no fog activated then we always skip sighted
1464  if(resources::gameboard->units().find(steps.front()) != resources::gameboard->units().end())
1465  {
1466  const team &current_team = resources::gameboard->teams()[
1467  resources::gameboard->units().find(steps.front())->side() - 1];
1468  continued_move |= !current_team.fog_or_shroud();
1469  }
1470  const bool skip_ally_sighted = !prefs::get().ally_sighted_interrupts();
1471 
1473  "move", replay_helper::get_movement(steps, continued_move, skip_ally_sighted), move_spectator);
1474 
1475 }
1476 
1477 
1478 }//namespace actions
bool sighted_
Definition: move.cpp:419
std::string ambush_string_
Definition: move.cpp:424
route_iterator real_end_
Definition: move.cpp:392
bool wml_removed_unit_
Definition: move.cpp:414
int current_side_
Definition: move.cpp:404
bool sighted_stop_
Definition: move.cpp:420
bool event_mutated_mid_move_
Definition: move.cpp:417
bool fog_changed_
Definition: move.cpp:418
std::vector< map_location > ambushers_
Definition: move.cpp:425
const map_location goto_
Definition: move.cpp:401
std::size_t friend_count_
Definition: move.cpp:423
unit_map::iterator move_it_
Definition: move.cpp:395
bool ambushed_
Definition: move.cpp:412
route_iterator expected_end_
Definition: move.cpp:389
bool current_uses_fog_
Definition: move.cpp:406
map_location loc
Definition: move.cpp:172
map_location blocked_loc_
Definition: move.cpp:411
int original_village_owner
Definition: move.cpp:170
team * current_team_
Definition: move.cpp:405
static lg::log_domain log_engine("engine")
const int orig_side_
Definition: move.cpp:398
#define ERR_NG
Definition: move.cpp:53
const std::vector< map_location > & route_
Definition: move.cpp:384
map_location ambush_stop_
Definition: move.cpp:410
bool take_village_timebonus
Definition: move.cpp:171
const bool playing_team_is_viewing_
Definition: move.cpp:382
bool teleport_failed_
Definition: move.cpp:421
route_iterator obstructed_
Definition: move.cpp:391
const map_location::direction orig_dir_
Definition: move.cpp:400
std::deque< int > moves_left_
Definition: move.cpp:426
route_iterator ambush_limit_
Definition: move.cpp:390
move_unit_spectator *const spectator_
Definition: move.cpp:379
int orig_moves_
Definition: move.cpp:399
shroud_clearer clearer_
Definition: move.cpp:428
#define DBG_NG
Definition: move.cpp:54
bool wml_undo_disabled_
Definition: move.cpp:415
const route_iterator begin_
Definition: move.cpp:387
map_location zoc_stop_
Definition: move.cpp:409
const bool skip_sighting_
Definition: move.cpp:380
bool show_ambush_alert_
Definition: move.cpp:413
route_iterator move_loc_
Definition: move.cpp:407
std::size_t enemy_count_
Definition: move.cpp:422
bool wml_move_aborted_
Definition: move.cpp:416
const route_iterator full_end_
Definition: move.cpp:388
const bool skip_ally_sighting_
Definition: move.cpp:381
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:97
void redraw_minimap()
Schedule the minimap to be redrawn.
Definition: display.cpp:1597
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:3087
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:1560
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:674
static display * get_singleton()
Returns the display object if a display object exists.
Definition: display.hpp:111
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:741
#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:1029
const unit * unit_
static std::string _(const char *str)
Definition: gettext.hpp:93
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:218
@ STATE_UNCOVERED
The unit is petrified - it cannot move or be attacked.
Definition: unit.hpp:863
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:1406
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:1397
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:1453
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:219
static void move_unit_internal(unit_mover &mover)
Definition: move.cpp:1335
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:1382
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:479
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 std::string &window_id, const t_string &message, const point &mouse, const SDL_Rect &source_rect)
Shows a tip.
Definition: tooltip.cpp:64
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:1267
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
@ HOTKEY_CONTINUE_MOVE
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.
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:766
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:616
bool discard_previous
An announcement according these options should replace the previous announce (typical of fast announc...
Definition: display.hpp:625
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:184
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:183
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).