The Battle for Wesnoth  1.19.26+dev
mouse_events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
3  by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 #include "mouse_events.hpp"
18 
19 #include "actions/attack.hpp" // for battle_context, etc
20 #include "actions/move.hpp" // for move_and_record
21 #include "config.hpp" // for config
22 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
23 #include "game_board.hpp" // for game_board, etc
24 #include "game_events/pump.hpp" // for fire
25 #include "gettext.hpp" // for _
26 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
27 #include "gui/dialogs/unit_attack.hpp" // for unit_attack
28 #include "gui/widgets/settings.hpp" // for new_widgets
29 #include "log.hpp" // for LOG_STREAM, logger, etc
30 #include "map/map.hpp" // for gamemap
31 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
32 #include "play_controller.hpp" // for playing_side, set_button_state
33 #include "replay_helper.hpp"
35 #include "sound.hpp"
36 #include "synced_context.hpp"
37 #include "team.hpp" // for team
38 #include "tod_manager.hpp"
39 #include "map/location.hpp"
41 #include "units/ptr.hpp" // for unit_const_ptr
42 #include "units/unit.hpp" // for unit
43 #include "whiteboard/manager.hpp" // for manager, etc
44 #include "whiteboard/typedefs.hpp" // for whiteboard_lock
45 #include "sdl/input.hpp" // for get_mouse_state
46 #include "language.hpp"
47 #include "serialization/markup.hpp"
48 
49 #include <cassert> // for assert
50 #include <new> // for bad_alloc
51 #include <string> // for string, operator<<, etc
52 
53 static lg::log_domain log_engine("engine");
54 #define ERR_NG LOG_STREAM(err, log_engine)
55 #define LOG_NG LOG_STREAM(info, log_engine)
56 
57 static lg::log_domain log_wml("wml");
58 #define ERR_WML LOG_STREAM(err, log_wml)
59 
60 namespace events
61 {
64  , gui_(nullptr)
65  , pc_(pc)
66  , previous_hex_()
67  , previous_free_hex_()
68  , selected_hex_(map_location::null_location())
69  , next_unit_()
70  , current_route_()
71  , current_paths_()
72  , unselected_paths_(false)
73  , unselected_reach_(false)
74  , path_turns_(0)
75  , side_num_(1)
76  , over_route_(false)
77  , reachmap_invalid_(false)
78  , show_partial_move_(false)
79  , teleport_selected_(false)
80  , preventing_units_highlight_(false)
81 {
82  singleton_ = this;
83 }
84 
86 {
87  singleton_ = nullptr;
88 }
89 
91 {
93 }
94 
96 {
97  // Function uses window resolution as an estimate of users perception of distance
98  // Tune this variable if necessary:
99  const unsigned threshold_1080p = 14; // threshold number of pixels for 1080p
100  double screen_diagonal = std::hypot(gui2::settings::screen_width,gui2::settings::screen_height);
101  const double scale_factor = threshold_1080p / std::hypot(1080,1920);
102  return static_cast<int>(screen_diagonal * scale_factor);
103 }
104 
105 void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, map_location new_hex)
106 {
107  // Frankensteining from mouse_motion(), as it has a lot in common, but a lot of differences too.
108  // Copy-pasted from everywhere. TODO: generalize the two.
109  float fx;
110  float fy;
111  sdl::get_mouse_state(&fx, &fy);
112  x = fx;
113  y = fy;
114 
115  // This is from mouse_handler_base::mouse_motion_default()
116  tooltips::process(x, y);
117 
118  if(simple_warp_) {
119  return;
120  }
121 
122  if(minimap_scrolling_) {
123  const map_location& mini_loc = gui().minimap_location_on(x,y);
124  if(mini_loc.valid()) {
125  if(mini_loc != last_hex_) {
126  last_hex_ = mini_loc;
127  gui().scroll_to_tile(mini_loc,display::WARP,false);
128  }
129  return;
130  } else {
131  // clicking outside of the minimap will end minimap scrolling
132  minimap_scrolling_ = false;
133  }
134  }
135 
136  // Fire the drag & drop only after minimal drag distance
137  // While we check the mouse buttons state, we also grab fresh position data.
138 
139  if(is_dragging() && !dragging_started_) {
140  if(dragging_touch_) {
142  const double drag_distance =
143  std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
144  std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
145 
146  if(drag_distance > drag_threshold()*drag_threshold()) {
147  dragging_started_ = true;
148  }
149  }
150  }
151 
152  // Not-so-smooth panning
153  const auto found_unit = find_unit(selected_hex_);
154  bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
155  if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
156 
157  if(gui().map_area().contains(x, y)) {
159  gui().scroll(drag_from_ - pos);
160  drag_from_ = pos;
161  }
162  return;
163  }
164 
165  // now copy-pasting mouse_handler::mouse_motion()
166 
167  // Note for anyone reconciling this code with the version in mouse_handler::mouse_motion:
168  // commit 27a40a82aeea removed the game_board& board from mouse_motion, but didn't update
169  // the corresponding code here in touch_motion.
170  game_board & board = pc_.gamestate().board_;
171 
172  if(new_hex == map_location::null_location())
173  new_hex = gui().hex_clicked_on(x,y);
174 
175  if(new_hex != last_hex_) {
176  update = true;
177  if( pc_.get_map().on_board(last_hex_) ) {
178  // we store the previous hexes used to propose attack direction
180  // the hex of the selected unit is also "free"
181  { // start planned unit map scope
185  }
186  } // end planned unit map scope
187  }
188  last_hex_ = new_hex;
189  }
190 
191  if(reachmap_invalid_) update = true;
192 
193  if(!update) return;
194 
195  if(reachmap_invalid_) {
196  reachmap_invalid_ = false;
198  { // start planned unit map scope
199  wb::future_map_if_active planned_unit_map;
200  selected_hex_has_my_unit = found_unit.valid();
201  } // end planned unit map scope
202  if(selected_hex_.valid() && selected_hex_has_my_unit) {
203  // FIXME: vic: why doesn't this trigger when touch-dragging an unselected unit?
204  // reselect the unit without firing events (updates current_paths_)
205  select_hex(selected_hex_, true);
206  }
207  // we do never deselect here, mainly because of canceled attack-move
208  }
209  }
210 
211  // reset current_route_ and current_paths if not valid anymore
212  // we do it before cursor selection, because it uses current_paths_
213  if( !pc_.get_map().on_board(new_hex) ) {
214  current_route_.steps.clear();
215  gui().set_route(nullptr);
216  pc_.get_whiteboard()->erase_temp_move();
217  }
218 
219  if(unselected_paths_) {
220  unselected_paths_ = false;
223  } else if(over_route_) {
224  over_route_ = false;
225  current_route_.steps.clear();
226  gui().set_route(nullptr);
227  pc_.get_whiteboard()->erase_temp_move();
228  }
229 
230  gui().highlight_hex(new_hex);
231  pc_.get_whiteboard()->on_mouseover_change(new_hex);
232 
234  unit_map::iterator mouseover_unit;
235  map_location attack_from;
236 
237  { // start planned unit map scope
238  wb::future_map_if_active planned_unit_map;
239  selected_unit = found_unit;
240  mouseover_unit = find_unit(new_hex);
241 
242  // we search if there is an attack possibility and where
243  attack_from = current_unit_attacks_from(new_hex);
244 
245  //see if we should show the normal cursor, the movement cursor, or
246  //the attack cursor
247  //If the cursor is on WAIT, we don't change it and let the setter
248  //of this state end it
249  if (cursor::get() != cursor::WAIT) {
250  if (selected_unit &&
251  selected_unit->side() == side_num_ &&
252  !selected_unit->incapacitated() && !browse)
253  {
254  if (attack_from.valid()) {
256  }
257  else if (!mouseover_unit &&
259  {
260  // Is this where left-drag cursor changes? Test.
262  } else {
263  // selected unit can't attack or move there
265  }
266  } else {
267  // no selected unit or we can't move it
268 
269  if ( selected_hex_.valid() && mouseover_unit
270  && mouseover_unit->side() == side_num_ ) {
271  // empty hex field selected and unit on our site under the cursor
273  } else {
275  }
276  }
277  }
278  } // end planned unit map scope
279 
280  // show (or cancel) the attack direction indicator
281  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
282  gui().set_attack_indicator(attack_from, new_hex);
283  } else {
285  }
286 
287  unit_ptr un; //will later point to unit at mouseover_hex_
288 
289  // the destination is the pointed hex or the adjacent hex
290  // used to attack it
291  map_location dest;
292  unit_map::const_iterator dest_un;
293  { // start planned unit map scope
295  if (attack_from.valid()) {
296  dest = attack_from;
297  dest_un = find_unit(dest);
298  } else {
299  dest = new_hex;
300  dest_un = find_unit(new_hex);
301  }
302 
303  if(dest == selected_hex_ || dest_un) {
304  current_route_.steps.clear();
305  gui().set_route(nullptr);
306  pc_.get_whiteboard()->erase_temp_move();
307  }
308  else if (!current_paths_.destinations.empty() &&
309  board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
310  {
311  if (selected_unit && !selected_unit->incapacitated()) {
312  // Show the route from selected unit to mouseover hex
313  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
314 
315  pc_.get_whiteboard()->create_temp_move();
316 
317  if(!browse) {
319  }
320  }
321  }
322 
323  if(board.map().on_board(selected_hex_)
324  && !selected_unit
325  && mouseover_unit.valid()
326  && mouseover_unit) {
327  // Show the route from selected hex to mouseover unit
328  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
329 
330  pc_.get_whiteboard()->create_temp_move();
331 
332  if(!browse) {
334  }
335  } else if (!selected_unit) {
336  current_route_.steps.clear();
337  gui().set_route(nullptr);
338  pc_.get_whiteboard()->erase_temp_move();
339  }
340 
341  unit_map::iterator iter = mouseover_unit;
342  if (iter)
343  un = iter.get_shared_ptr();
344  else
345  un.reset();
346  } //end planned unit map scope
347 }
348 
350 {
351  if( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
352  !gui().fogged(un->get_location()))
353  {
354  // If the unit has a path set and is either ours or allied then show the path.
355  //
356  // Exception: allied AI sides' moves are still hidden, on the assumption that
357  // campaign authors won't want to leak goto_x,goto_y tricks to the player.
358  if(!gui().viewing_team().is_enemy(un->side()) && !pc_.get_teams()[un->side() - 1].is_ai()) {
359  //unit is on our team or an allied team, show path if the unit has one
360  const map_location go_to = un->get_goto();
361  if(pc_.get_map().on_board(go_to)) {
363  { // start planned unit map scope
365  route = get_route(un.get(), go_to, current_team());
366  } // end planned unit map scope
367  gui().set_route(&route);
368  }
369  over_route_ = true;
370  }
371 
372  // Scope for the unit_movement_resetter and future_map_if_active.
373  {
374  // Making this non-null will show the unit's max moves instead of current moves.
375  // Because movement is reset to max in the side's refresh phase, use the max if
376  // that refresh will happen before the unit's side can move again.
377  std::unique_ptr<unit_movement_resetter> move_reset;
378  if(un->side() != side_num_) {
379  move_reset = std::make_unique<unit_movement_resetter>(*un);
380  }
381 
382  // Handle whiteboard. Any move_reset must be done before this, since the future
383  // state includes changes to units' movement.
385 
386  current_paths_ = pathfind::paths(*un, false, true, gui().viewing_team(), path_turns_);
387  }
388 
389  unselected_paths_ = true;
391  }
392 }
393 
394 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
395 {
396  // we ignore the position coming from event handler
397  // because it's always a little obsolete and we don't need
398  // to highlight all the hexes where the mouse passed.
399  // Also, sometimes it seems to have one *very* obsolete
400  // and isolated mouse motion event when using drag&drop
401  float fx;
402  float fy;
403  sdl::get_mouse_state(&fx, &fy);
404  // modify x and y
405  x = fx;
406  y = fy;
407 
409  return;
410  }
411 
412  // Don't process other motion events while scrolling
413  if(scroll_started_) {
414  return;
415  }
416 
417  if(new_hex == map_location::null_location()) {
418  new_hex = gui().hex_clicked_on(x, y);
419  }
420 
421  if(new_hex != last_hex_) {
422  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
423  lk->mouse_over_hex_callback(new_hex);
424  }
425 
426  update = true;
427 
428  if(pc_.get_map().on_board(last_hex_)) {
429  // we store the previous hexes used to propose attack direction
431 
432  // the hex of the selected unit is also "free"
433  { // start planned unit map scope
437  }
438  } // end planned unit map scope
439  }
440 
441  last_hex_ = new_hex;
442  }
443 
444  if(reachmap_invalid_) {
445  update = true;
446  }
447 
448  if(!update) {
449  return;
450  }
451 
452  if(reachmap_invalid_) {
453  reachmap_invalid_ = false;
454 
456  bool selected_hex_has_unit;
457  { // start planned unit map scope
458  wb::future_map_if_active planned_unit_map;
459  selected_hex_has_unit = find_unit(selected_hex_).valid();
460  } // end planned unit map scope
461 
462  if(selected_hex_.valid() && selected_hex_has_unit) {
463  // reselect the unit without firing events (updates current_paths_)
464  select_hex(selected_hex_, true);
465  }
466 
467  // we do never deselect here, mainly because of canceled attack-move
468  }
469  }
470 
471  // reset current_route_ and current_paths if not valid anymore
472  // we do it before cursor selection, because it uses current_paths_
473  if(!pc_.get_map().on_board(new_hex)) {
474  current_route_.steps.clear();
475  gui().set_route(nullptr);
476  pc_.get_whiteboard()->erase_temp_move();
477  }
478 
479  if(unselected_paths_) {
480  unselected_paths_ = false;
483  } else if(over_route_) {
484  over_route_ = false;
485  current_route_.steps.clear();
486  gui().set_route(nullptr);
487  pc_.get_whiteboard()->erase_temp_move();
488  }
489 
490  gui().highlight_hex(new_hex);
491  pc_.get_whiteboard()->on_mouseover_change(new_hex);
492 
494  unit_map::iterator mouseover_unit;
495  map_location attack_from;
496 
497  { // start planned unit map scope
498  wb::future_map_if_active planned_unit_map;
500  mouseover_unit = find_unit(new_hex);
501 
502  // we search if there is an attack possibility and where
503  attack_from = current_unit_attacks_from(new_hex);
504 
505  // see if we should show the normal cursor, the movement cursor, or
506  // the attack cursor
507  // If the cursor is on WAIT, we don't change it and let the setter
508  // of this state end it
509  if(cursor::get() != cursor::WAIT) {
510  if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
511  if(attack_from.valid()) {
513  } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
515  } else {
516  // selected unit can't attack or move there
518  }
519  } else {
520  // no selected unit or we can't move it
521 
522  if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
523  // empty hex field selected and unit on our site under the cursor
525  } else {
527  }
528  }
529  }
530  } // end planned unit map scope
531 
532  // show (or cancel) the attack direction indicator
533  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
534  gui().set_attack_indicator(attack_from, new_hex);
535  } else {
537  }
538 
539  unit_ptr un; // will later point to unit at mouseover_hex_
540 
541  // the destination is the pointed hex or the adjacent hex
542  // used to attack it
543  map_location dest;
544  unit_map::const_iterator dest_un;
545  /* start planned unit map scope*/
546  {
548  if(attack_from.valid()) {
549  dest = attack_from;
550  dest_un = find_unit(dest);
551  } else {
552  dest = new_hex;
553  dest_un = find_unit(new_hex);
554  }
555 
556  if(dest == selected_hex_ || dest_un) {
557  current_route_.steps.clear();
558  gui().set_route(nullptr);
559  pc_.get_whiteboard()->erase_temp_move();
560  } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
561  if(selected_unit && !selected_unit->incapacitated()) {
562  // Show the route from selected unit to mouseover hex
563  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
564 
565  pc_.get_whiteboard()->create_temp_move();
566 
567  if(!browse) {
569  }
570  }
571  }
572 
573  if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
574  // Show the route from selected hex to mouseover unit
575  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
576 
577  pc_.get_whiteboard()->create_temp_move();
578 
579  if(!browse) {
581  }
582  } else if(!selected_unit) {
583  current_route_.steps.clear();
584  gui().set_route(nullptr);
585  pc_.get_whiteboard()->erase_temp_move();
586  }
587 
588  if(mouseover_unit) {
589  un = mouseover_unit.get_shared_ptr();
590  } else {
591  un.reset();
592  }
593  } /*end planned unit map scope*/
594 
595  /*
596  * Only highlight unit's reach if toggler not preventing normal unit
597  * processing. This can happen e.g. if, after activating 'show
598  * [best possible] enemy movements' through the UI menu, the
599  * mouse cursor lands on a hex with unit in it.
600  */
603  }
604 
605  if(!un && preventing_units_highlight_) {
606  // Cursor on empty hex, turn unit highlighting back on.
608  }
609 }
610 
611 // Hook for notifying lua game kernel of mouse button events. We pass button as
612 // a separate argument than the original SDL event in order to manage touch
613 // emulation (e.g., long touch = right click) and such.
614 bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
615  map_location loc, bool click)
616 {
617  static const std::array<const std::string, 6> buttons = {
618  "",
619  "left", // SDL_BUTTON_LEFT
620  "middle", // SDL_BUTTON_MIDDLE
621  "right", // SDL_BUTTON_RIGHT
622  "mouse4", // SDL_BUTTON_X1
623  "mouse5" // SDL_BUTTON_X2
624  };
625 
626  if (gui().view_locked() || button < SDL_BUTTON_LEFT || button >= buttons.size()) {
627  return false;
628  } else if (!pc_.get_map().on_board(loc)) {
629  return false;
630  }
631 
632  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
633  lk->mouse_button_callback(loc, buttons[button], (event.down ? "down" : "up"));
634 
635  // Are we being asked to send a click event?
636  if (click) {
637  // Was both the up and down on the same map tile?
638  if (loc != drag_from_hex_) {
639  return false;
640  }
641  // We allow this event to be consumed, but not up/down
642  return lk->mouse_button_callback(loc, buttons[button], "click");
643  }
644  }
645  return false;
646 }
647 
649 {
651  if(res) {
652  return res;
653  }
654 
655  return find_unit(last_hex_);
656 }
657 
659 {
660  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
661  if(it.valid()) {
662  return it;
663  }
664 
665  return pc_.get_units().end();
666 }
667 
669 {
670  return pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
671 }
672 
674 {
675  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
676  return it.valid() ? &*it : nullptr;
677 }
678 
680 {
681  unit_map::const_iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
682  return it.valid() ? &*it : nullptr;
683 }
684 
686 {
687  float x = -1;
688  float y = -1;
689  sdl::get_mouse_state(&x, &y);
690  return gui_->hex_clicked_on(x, y);
691 }
692 
694 {
695  return find_unit(hex).valid();
696 }
697 
698 // the attack dialog is shown as if the unit is standing at the hex, this may be not the best approach for units possessing weapons of different ranges
700 {
701  if(loc == selected_hex_) {
702  return map_location();
703  }
704 
705  bool wb_active = pc_.get_whiteboard()->is_active();
706  std::set<int> attackable_distances;
707 
708  {
709  // Check the unit SOURCE of the attack
710 
711  // Check that there's a selected unit
712  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
713 
714  bool source_eligible = source_unit.valid();
715  if(!source_eligible) {
716  return map_location();
717  }
718 
719  // The selected unit must at least belong to the player currently controlling this client.
720  source_eligible &= source_unit->side() == gui_->viewing_team().side();
721  if(!source_eligible) {
722  return map_location();
723  }
724 
725  // In addition:
726  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
727  // - If whiteboard is disabled, it must be the turn of the player controlling this client
728  if(!wb_active) {
729  source_eligible &= gui_->viewing_team().side() == pc_.current_side();
730  if(!source_eligible) {
731  return map_location();
732  }
733  }
734 
735  // Unit must have attacks left
736  source_eligible &= source_unit->attacks_left() != 0;
737  if(!source_eligible) {
738  return map_location();
739  }
740 
741  // Check the unit TARGET of the attack
742 
743  const team& viewer = gui().viewing_team();
744 
745  // Check that there's a unit at the target location
746  const unit_map::const_iterator target_unit = find_unit(loc);
747 
748  bool target_eligible = target_unit.valid();
749  if(!target_eligible) {
750  return map_location();
751  }
752 
753  // The player controlling this client must be an enemy of the target unit's side
754  target_eligible &= viewer.is_enemy(target_unit->side());
755  if(!target_eligible) {
756  return map_location();
757  }
758 
759  // Sanity check: source and target of the attack shouldn't be on the same team
760  assert(source_unit->side() != target_unit->side());
761 
762  target_eligible &= !target_unit->incapacitated();
763  if(!target_eligible) {
764  return map_location();
765  }
766 
767  for (const auto& attack : source_unit->attacks()) {
768  for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
769  attackable_distances.insert(i);
770  }
771  }
772  }
773 
774  //invalid attack ranges
775  if(attackable_distances.empty()) {
776  return map_location{};
777  }
778 
779  //ranged attack
780  if(*attackable_distances.rbegin() > 1){
781  if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))) {
782  return selected_hex_;
783  }
784  map_location res;
785  int best_move = -1;
787  map_location dst = step.curr;
788  if(utils::contains(attackable_distances, distance_between(loc, dst))) {
789  if (step.move_left > best_move){
790  best_move = step.move_left;
791  res=dst;
792  }
793  }
794  }
795  return res;
796  }
797 
798  //no ranged attack
801 
802  int best_rating = 100; // smaller is better
803 
804  map_location res;
805  const auto adj = get_adjacent_tiles(loc);
806 
807  for(std::size_t n = 0; n < adj.size(); ++n) {
808  if(pc_.get_map().on_board(adj[n]) == false) {
809  continue;
810  }
811 
812  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
813  continue;
814  }
815 
817  static const std::size_t ndirections = static_cast<int>(map_location::direction::indeterminate);
818 
819  unsigned int difference = std::abs(static_cast<int>(static_cast<int>(preferred) - n));
820  if(difference > ndirections / 2) {
821  difference = ndirections - difference;
822  }
823 
824  unsigned int second_difference = std::abs(static_cast<int>(static_cast<int>(second_preferred) - n));
825  if(second_difference > ndirections / 2) {
826  second_difference = ndirections - second_difference;
827  }
828 
829  const int rating = difference * 2 + (second_difference > difference);
830  if(rating < best_rating || res.valid() == false) {
831  best_rating = rating;
832  res = adj[n];
833  }
834  }
835  }
836 
837  return res;
838 }
839 
841 {
842  game_board& board = pc_.gamestate().board_;
843 
844  // The pathfinder will check unit visibility (fogged/stealthy).
845  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
846 
847  pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, gui().viewing_team());
848 
849  pathfind::plain_route route;
850 
851  route = pathfind::a_star_search(
852  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
853 
854  return mark_route(route);
855 }
856 
857 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
858 {
860  unselected_reach_ = false;
861  return false;
862  }
863 
864  return gui().map_area().contains(x, y);
865 }
866 
868 {
869  // Load whiteboard partial moves
870  //wb::future_map_if_active planned_unit_map;
871 
872  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
873  lk->select_hex_callback(last_hex_);
874  }
875 
878 
879  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
880  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
881  ) {
883  teleport_selected_ = true;
885  gui().set_route(nullptr);
886  }
887 }
888 
890 {
891  // Set the teleport to active so that we can use existing functions
892  // for teleport
893  teleport_selected_ = false;
894 
898  gui().invalidate_all();
900  gui().set_route(nullptr);
901 
902  // Select and deselect the units hex to prompt updates for hover
903  select_hex(last_hex_, false);
904  deselect_hex();
905  current_route_.steps.clear();
906 }
907 
909 {
910  if(!pc_.get_map().on_board(last_hex_)) {
912  return;
913  }
914 
915  // Load whiteboard partial moves
916  wb::future_map_if_active planned_unit_map;
917 
918  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
919  lk->select_hex_callback(last_hex_);
920  }
921 
924 
925  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
926  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
927  ) {
928  select_hex(last_hex_, false);
929  }
930 #ifdef __ANDROID__
931  else if (clicked_u && clicked_u->side() != side_num_) {
932  select_hex(last_hex_, false);
933  }
934 #endif
935  else {
936  move_action(browse);
937  }
938  teleport_selected_ = false;
939 }
940 
941 void mouse_handler::move_action(bool browse)
942 {
943  // Lock whiteboard activation state to avoid problems due to
944  // its changing while an animation takes place.
945  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
946 
947  // we use the last registered highlighted hex
948  // since it's what update our global state
949  map_location hex = last_hex_;
950 
951  // TODO
952  // // Clicks on border hexes mean to deselect.
953  // // (Check this before doing processing that might not be needed.)
954  // if ( !pc_.get_map().on_board(hex) ) {
955  // deselect_hex();
956  // return false;
957  // }
958 
959  unit* u = nullptr;
960  const unit* clicked_u = nullptr;
961 
963  pathfind::paths orig_paths;
964  map_location attack_from;
965 
966  { // start planned unit map scope
967  wb::future_map_if_active planned_unit_map;
969 
970  // if the unit is selected and then itself clicked on,
971  // any goto command is canceled
972  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
973  u->set_goto(map_location());
974  }
975 
976  clicked_u = find_unit_nonowning(hex);
977 
978  src = selected_hex_;
979  orig_paths = current_paths_;
980  attack_from = current_unit_attacks_from(hex);
981  } // end planned unit map scope
982 
983  // See if the teleport option is toggled
984  if(teleport_selected_) {
985  teleport_action();
986  }
987  // see if we're trying to do a attack or move-and-attack
988  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
989  // Ignore this command if commands are disabled.
990  if(commands_disabled) {
991  return;
992  }
993 
994  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
995  if(attack_from == selected_hex_) { // no move needed
996  int choice = -1;
997  {
998  wb::future_map_if_active planned_unit_map; // start planned unit map scope
999  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1000  } // end planned unit map scope
1001 
1002  if(choice >= 0) {
1003  if(pc_.get_whiteboard()->is_active()) {
1004  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
1005  } else {
1006  // clear current unit selection so that any other unit selected
1007  // triggers a new selection
1009 
1010  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
1011  }
1012  }
1013 
1014  return;
1015  } else {
1016  int choice = -1; // for the attack dialog
1017 
1018  {
1019  wb::future_map_if_active planned_unit_map; // start planned unit map scope
1020  // we will now temporary move next to the enemy
1021  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
1022  if(itor == current_paths_.destinations.end()) {
1023  // can't reach the attacking location
1024  // not supposed to happen, so abort
1025  return;
1026  }
1027 
1028  // block where we temporary move the unit
1029  {
1030  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1031  }
1032 
1033  if(choice < 0) {
1034  // user hit cancel or attack is invalid, don't start move&attack
1035  return;
1036  }
1037  } // end planned unit map scope
1038 
1039  if(pc_.get_whiteboard()->is_active()) {
1040  save_whiteboard_attack(attack_from, hex, choice);
1041  } else {
1042  bool not_interrupted = move_unit_along_current_route();
1043  bool alt_unit_selected = (selected_hex_ != src);
1044  src = selected_hex_;
1045  // clear current unit selection so that any other unit selected
1046  // triggers a new selection
1048 
1049  if(not_interrupted)
1050  attack_enemy(attack_from, hex, choice); // Fight !!
1051 
1052  // TODO: Maybe store the attack choice so "press t to continue"
1053  // can also continue the attack?
1054 
1055  if(alt_unit_selected && !selected_hex_.valid()) {
1056  // reselect other unit if selected during movement animation
1057  select_hex(src, browse);
1058  }
1059  }
1060 
1061  return;
1062  }
1063  }
1064  }
1065  // otherwise we're trying to move to a hex
1066  else if(
1067  // The old use case: move selected unit to mouse hex field.
1068  (
1069  (!browse || pc_.get_whiteboard()->is_active())
1070  && selected_hex_.valid()
1071  && selected_hex_ != hex
1072  && u != nullptr
1073  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1074  && !clicked_u
1075  && !current_route_.steps.empty()
1076  && current_route_.steps.front() == selected_hex_
1077  )
1078  || // The new use case: move mouse unit to selected hex field.
1079  (
1080  (!browse || pc_.get_whiteboard()->is_active())
1081  && selected_hex_.valid()
1082  && selected_hex_ != hex
1083  && clicked_u
1084  && !current_route_.steps.empty()
1085  && current_route_.steps.back() == selected_hex_
1086  && !u
1087  && clicked_u->side() == side_num_
1088  )
1089  ) {
1090  // Ignore this command if commands are disabled.
1091  if(commands_disabled) {
1092  return;
1093  }
1094 
1095  // If the whiteboard is active, it intercepts any unit movement.
1096  if(pc_.get_whiteboard()->is_active()) {
1097  // Deselect the current hex, and create planned move for whiteboard.
1099 
1102  gui().set_route(nullptr);
1103 
1104  show_partial_move_ = false;
1105 
1106  gui().unhighlight_reach();
1107 
1109  current_route_.steps.clear();
1110 
1111  pc_.get_whiteboard()->save_temp_move();
1112 
1113  // Otherwise proceed to normal unit movement
1114  } else {
1115  // Don't move if the unit already has actions
1116  // from the whiteboard.
1117  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1118  return;
1119  }
1120 
1122 
1123  // During the move, we may have selected another unit
1124  // (but without triggering a select event (command was disabled)
1125  // in that case reselect it now to fire the event (+ anim & sound)
1126  if(selected_hex_ != src) {
1127  select_hex(selected_hex_, browse);
1128  }
1129  }
1130 
1131  return;
1132  }
1133 }
1134 
1135 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1136 {
1137  unit_map::iterator unit = find_unit(touched_hex);
1138 #ifdef __ANDROID__
1139  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1140  if(touched_hex == selected_hex_) {
1141  deselect_hex();
1142  gui().unhighlight_reach();
1144  } else {
1146  select_or_action(browse);
1147  }
1148  }
1149 #else
1150  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1151  select_or_action(browse);
1152  }
1153 #endif
1154 }
1155 
1156 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1157 {
1158  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1159 
1160  selected_hex_ = hex;
1161 
1164  gui().set_route(nullptr);
1165 
1166  show_partial_move_ = false;
1167 
1168  wb::future_map_if_active planned_unit_map; // lasts for whole method
1169 
1171 
1172  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1174 
1175  {
1176  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1177  }
1178 
1179  if(highlight) {
1181  }
1182 
1183  // The highlight now comes from selection
1184  // and not from the mouseover on an enemy
1185  unselected_paths_ = false;
1186  gui().set_route(nullptr);
1187 
1188  // Selection have impact only if we are not observing and it's our unit
1189  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1190  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1191  sound::play_UI_sound("select-unit.wav");
1192 
1194 
1195  if(fire_event) {
1196  // Ensure unit map is back to normal while event is fired
1197  wb::real_map srum;
1198  pc_.pump().fire("select", hex);
1199  // end forced real unit map
1200  }
1201  }
1202  }
1203 
1204  return;
1205  }
1206 
1207  if(selected_hex_.valid() && !unit) {
1208  // When no unit is selected, compute unit in range of the empty selected_hex field
1209 
1211 
1212  pathfind::paths reaching_unit_locations;
1213 
1214  pathfind::paths clicked_location;
1215  clicked_location.destinations.insert(hex);
1216 
1217  for(const ::unit& u : pc_.get_units()) {
1218  bool invisible = u.invisible(u.get_location());
1219 
1220  if(!gui_->fogged(u.get_location()) && !u.incapacitated() && !invisible) {
1221  const pathfind::paths& path =
1222  pathfind::paths(u, false, true, gui().viewing_team(), path_turns_, false, false);
1223 
1224  if(path.destinations.find(hex) != path.destinations.end()) {
1225  reaching_unit_locations.destinations.insert(u.get_location());
1226  gui_->highlight_another_reach(clicked_location);
1227  }
1228  }
1229  }
1230 
1231  gui_->highlight_another_reach(reaching_unit_locations);
1232  } else {
1233  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1234  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1236  }
1237 
1239  current_route_.steps.clear();
1240 
1241  pc_.get_whiteboard()->on_deselect_hex();
1242  }
1243 }
1244 
1246 {
1247  select_hex(map_location(), true);
1248 }
1249 
1250 /**
1251  * Moves a unit along the currently cached route.
1252  *
1253  * @returns true if the end of the route was reached and no information was
1254  * uncovered that would warrant interrupting a chain of actions;
1255  * false otherwise.
1256  */
1258 {
1259  // Capture the full intended route before movement modifies or clears it.
1260  const pathfind::marked_route intended_route = current_route_;
1261 
1262  // do not show footsteps during movement
1263  gui().set_route(nullptr);
1264  gui().unhighlight_reach();
1265 
1266  // do not keep the hex highlighted that we started from
1269 
1270  bool interrupted = false;
1271  std::size_t num_moves = 0;
1272 
1273  if(intended_route.steps.size() > 1) {
1274  num_moves = move_unit_along_route(intended_route.steps, interrupted);
1275 
1276  interrupted = interrupted || num_moves + 1 < intended_route.steps.size();
1277  next_unit_ = intended_route.steps[num_moves];
1278  }
1279 
1280  // TRIGGER FLASH-FADE:
1281  // If the unit didn't finish the path (0 MP or interruption), visualize the remainder.
1282  if(intended_route.steps.size() > num_moves + 1) {
1283  pathfind::marked_route remaining_route;
1284  for(auto i = intended_route.steps.begin() + num_moves; i != intended_route.steps.end(); ++i) {
1285  remaining_route.steps.push_back(*i);
1286  if(auto it = intended_route.marks.find(*i); it != intended_route.marks.end()) {
1287  remaining_route.marks[*i] = it->second;
1288  }
1289  }
1290 
1291  gui().flash_fade_route(remaining_route);
1292  }
1293 
1294  // Cleanup state
1296  current_route_.steps.clear();
1297 
1298  return !interrupted;
1299 }
1300 
1301 /**
1302  * Moves a unit across the board for a player.
1303  * This is specifically for movement at the time it is initiated by a player,
1304  * whether via a mouse click or executing whiteboard actions. Continued moves
1305  * (including goto execution) can bypass this and call actions::move_unit() directly.
1306  * This function call may include time for an animation, so make sure the
1307  * provided route will remain unchanged (the caller should probably make a local
1308  * copy).
1309  *
1310  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1311  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1312  * actions (and set to false otherwise).
1313  *
1314  * @returns The number of hexes entered. This can safely be used as an index
1315  * into steps to get the location where movement ended, provided
1316  * steps is not empty (the return value is guaranteed to be less
1317  * than steps.size() ).
1318  */
1319 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1320 {
1321  if(steps.empty()) {
1322  interrupted = false;
1323  return 0;
1324  }
1325 
1326  // Default return value.
1327  interrupted = true;
1328 
1329  // If this is a leader on a keep, ask permission to the whiteboard to move it
1330  // since otherwise it may cause planned recruits to be erased.
1331  if(pc_.get_map().is_keep(steps.front())) {
1332  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1333 
1334  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1335  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1337  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1338  return 0;
1339  }
1340  }
1341 
1342  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1343  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1344 
1347 
1348  if(moves == 0)
1349  return 0;
1350 
1351  if(interrupted && moves + 1 < steps.size()) {
1352  // reselect the unit (for "press t to continue")
1353  select_hex(steps[moves], false, false, false);
1354  // the new discovery is more important than the new movement range
1355  show_partial_move_ = true;
1356  }
1357 
1358  return moves;
1359 }
1360 
1362  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1363 {
1364  {
1365  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1366  // This method should do the cleanup of highlights and selection but it doesn't work properly
1367 
1368  // gui().highlight_hex(map_location());
1369 
1370  gui().unhighlight_reach();
1372 
1373  // remove footsteps if any - useless for whiteboard as of now
1374  gui().set_route(nullptr);
1375 
1376  // do not keep the hex that we started from highlighted
1379  show_partial_move_ = false;
1380 
1381  // invalid after saving the move
1383  current_route_.steps.clear();
1384  }
1385 
1386  // create planned attack for whiteboard
1387  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1388 }
1389 
1391  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1392 {
1393  int best = 0;
1394  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1395  // skip weapons with attack_weight=0
1396  if(attacker->attacks()[i].attack_weight() > 0) {
1397  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1398 
1399  // Don't include if the attacker's weapon has at least one active "disable" special.
1400  if(bc.get_attacker_stats().disable) {
1401  continue;
1402  }
1403 
1404  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1405  // as some weapons can be hidden, i is not a valid index into the resulting vector
1406  best = bc_vector.size();
1407  }
1408 
1409  bc_vector.emplace_back(std::move(bc));
1410  }
1411  }
1412 
1413  return best;
1414 }
1415 
1416 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1417 {
1418  game_board& board = pc_.gamestate().board_;
1419  unit_map::iterator attacker;
1420  unit_map::iterator defender;
1421  std::vector<battle_context> bc_vector;
1422  std::vector<gui2::widget_data> bc_widget_data_vector;
1423  int best;
1424  {
1425  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1426  temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1427 
1428  attacker = board.units().find(attacker_loc);
1429  defender = board.units().find(defender_loc);
1430 
1431  if(!attacker || !defender) {
1432  if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1433  if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1434  return -1; // abort, click will do nothing
1435  }
1436 
1437  best = fill_weapon_choices(bc_vector, attacker, defender);
1438 
1439  if (bc_vector.empty()) {
1440  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1441 
1442  return -1;
1443  }
1444 
1445  static const config empty;
1446  static const_attack_ptr no_weapon(new attack_type(empty));
1447 
1448  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1449  for(const auto& weapon : bc_vector) {
1450  const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1451  const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1452 
1453  const attack_type& attacker_weapon = *attacker_stats.weapon;
1454  const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1455 
1456  const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1457  const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1458 
1459  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1460  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1461 
1462  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1463  if(!range.empty()) {
1464  range = string_table["range_" + range];
1465  }
1466 
1467  auto ctx = specials_context_t::make(
1468  { attacker.get_shared_ptr(), attacker->get_location(), attacker_stats.weapon },
1469  { defender.get_shared_ptr(), defender->get_location(), defender_stats.weapon },
1470  true);
1471 
1472  std::string types = attacker_weapon.effective_damage_type().first;
1473  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1474  if(!attw_type.empty()) {
1475  attw_type = string_table["type_" + attw_type];
1476  }
1477  std::string def_types = defender_weapon.effective_damage_type().first;
1478  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1479  if(!defw_type.empty()) {
1480  defw_type = string_table["type_" + defw_type];
1481  }
1482 
1483  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains",
1484  "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1485  std::string attw_specials = ctx.describe_weapon_specials(attacker_weapon);
1486  std::string attw_specials_dmg = ctx.describe_weapon_specials_value(attacker_weapon, {"leadership", "damage"});
1487  std::string attw_specials_atk = ctx.describe_weapon_specials_value(attacker_weapon, {"attacks", "swarm"});
1488  std::string attw_specials_cth = ctx.describe_weapon_specials_value(attacker_weapon, {"chance_to_hit"});
1489  std::string attw_specials_others = ctx.describe_weapon_specials_value(attacker_weapon, checking_tags_other);
1490  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1491  && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1492  std::string defw_specials = defender_attack ? ctx.describe_weapon_specials(defender_weapon) : "";
1493  std::string defw_specials_dmg
1494  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"leadership", "damage"}) : "";
1495  std::string defw_specials_atk
1496  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"attacks", "swarm"}) : "";
1497  std::string defw_specials_cth
1498  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"chance_to_hit"}) : "";
1499  std::string defw_specials_others
1500  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, checking_tags_other) : "";
1501 
1502  if(!attw_specials.empty()) {
1503  attw_specials = " " + attw_specials;
1504  }
1505  if(!attw_specials_dmg.empty()) {
1506  attw_specials_dmg = " " + attw_specials_dmg;
1507  }
1508  if(!attw_specials_atk.empty()) {
1509  attw_specials_atk = " " + attw_specials_atk;
1510  }
1511  if(!attw_specials_cth.empty()) {
1512  attw_specials_cth = " " + attw_specials_cth;
1513  }
1514  if(!attw_specials_others.empty()) {
1515  attw_specials_others
1516  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1517  }
1518  if(!defw_specials.empty()) {
1519  defw_specials = " " + defw_specials;
1520  }
1521  if(!defw_specials_dmg.empty()) {
1522  defw_specials_dmg = " " + defw_specials_dmg;
1523  }
1524  if(!defw_specials_atk.empty()) {
1525  defw_specials_atk = " " + defw_specials_atk;
1526  }
1527  if(!defw_specials_cth.empty()) {
1528  defw_specials_cth = " " + defw_specials_cth;
1529  }
1530  if(!defw_specials_others.empty()) {
1531  defw_specials_others
1532  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1533  }
1534 
1535  std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1536 
1537  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1538  // not consider the swarm weapon special
1539  attacker_stats_str << markup::bold(attw_name) << "\n"
1540  << attw_type << "\n"
1541  << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1542  << attw_specials << "\n"
1543  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1544 
1545  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1546  << _("Type: ") << attw_type << "\n"
1547  << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1548  << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1549  << _("Chance to hit: ")
1550  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1551  << markup::italic(attw_specials_cth) << attw_specials_others;
1552 
1553  defender_stats_str << markup::bold(defw_name) << "\n"
1554  << defw_type << "\n"
1555  << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1556  << defw_specials << "\n"
1557  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1558 
1559  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1560  << _("Type: ") << defw_type << "\n"
1561  << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1562  << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1563  << _("Chance to hit: ")
1564  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1565  << markup::italic(defw_specials_cth) << defw_specials_others;
1566 
1568  gui2::widget_item item;
1569 
1570  item["use_markup"] = "true";
1571 
1572  item["label"] = attacker_weapon.icon();
1573  data.emplace("attacker_weapon_icon", item);
1574 
1575  item["tooltip"] = attacker_tooltip.str();
1576  item["label"] = attacker_stats_str.str();
1577  data.emplace("attacker_weapon", item);
1578  item["tooltip"] = "";
1579 
1580  item["label"]
1581  = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1582  data.emplace("range", item);
1583 
1584  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1585  item["label"] = defender_stats_str.str();
1586  data.emplace("defender_weapon", item);
1587 
1588  item["tooltip"] = "";
1589  item["label"] = defender_weapon.icon();
1590  data.emplace("defender_weapon_icon", item);
1591 
1592  bc_widget_data_vector.emplace_back(data);
1593  }
1594  }
1595  // bc_widget_data_vector won't be empty when it reaches here.
1596  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector);
1597 
1598  if(dlg.show()) {
1599  return dlg.get_selected_weapon();
1600  }
1601 
1602  return -1;
1603 }
1604 
1605 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1606 {
1607  try {
1608  attack_enemy_(attacker_loc, defender_loc, choice);
1609  } catch(const std::bad_alloc&) {
1610  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1611  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1612  }
1613 }
1614 
1615 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1616 {
1617  // NOTE: copy the values because the const reference may change!
1618  // (WML events and mouse inputs during animations may modify
1619  // the data of the caller)
1620  const map_location attacker_loc = att_loc;
1621  const map_location defender_loc = def_loc;
1622 
1623  unit* attacker = nullptr;
1624  const unit* defender = nullptr;
1625  std::vector<battle_context> bc_vector;
1626 
1627  {
1628  unit_map::iterator attacker_it = find_unit(attacker_loc);
1629  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1630  return;
1631  }
1632 
1633  unit_map::iterator defender_it = find_unit(defender_loc);
1634  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1635  return;
1636  }
1637 
1638  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1639 
1640  attacker = &*attacker_it;
1641  defender = &*defender_it;
1642  }
1643 
1644  if(std::size_t(choice) >= bc_vector.size()) {
1645  return;
1646  }
1647 
1648  events::command_disabler disabler;
1649  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1650  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1651 
1652  attacker->set_goto(map_location());
1653 
1655 
1656  // make the attacker's stats appear during the attack
1657  gui().display_unit_hex(attacker_loc);
1658 
1659  // remove highlighted hexes etc..
1663  gui().unhighlight_reach();
1664 
1666  // TODO: change ToD to be location specific for the defender
1667 
1668  const tod_manager& tod_man = pc_.get_tod_manager();
1669 
1672  attacker_loc,
1673  defender_loc,
1674  att.attack_num,
1675  def.attack_num,
1676  attacker->type_id(),
1677  defender->type_id(),
1678  att.level,
1679  def.level,
1680  tod_man.turn(),
1681  tod_man.get_time_of_day()
1682  )
1683  );
1684 }
1685 
1687 {
1688  game_board& board = pc_.gamestate().board_;
1689 
1690  if(!it) {
1691  return false;
1692  }
1693 
1694  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1695  return false;
1696  }
1697 
1698  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1699  return false;
1700  }
1701 
1702  if(it->get_hidden()) {
1703  return false;
1704  }
1705 
1706  return true;
1707 }
1708 
1709 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1710 {
1711  unit_map& units = pc_.get_units();
1712 
1713  if(units.begin() == units.end()) {
1714  return;
1715  }
1716 
1718  if(!it) {
1719  it = units.begin();
1720  }
1721 
1722  const unit_map::const_iterator itx = it;
1723 
1724  do {
1725  if(reverse) {
1726  if(it == units.begin()) {
1727  it = units.end();
1728  }
1729 
1730  --it;
1731  } else {
1732  if(it == units.end()) {
1733  it = units.begin();
1734  } else {
1735  ++it;
1736  }
1737  }
1738  } while(it != itx && !unit_in_cycle(it));
1739 
1740  if(unit_in_cycle(it)) {
1741  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1742 
1743  select_hex(it->get_location(), browse);
1744  // mouse_update(browse);
1745  }
1746 }
1747 
1749 {
1750  gui().unhighlight_reach();
1751 
1752  current_paths_ = new_paths;
1753  current_route_.steps.clear();
1754 
1755  gui().set_route(nullptr);
1756 
1757  pc_.get_whiteboard()->erase_temp_move();
1758 }
1759 
1761 {
1762  return pc_.get_teams()[side_num_ - 1];
1763 }
1764 
1766 
1768 {
1770 }
1771 
1773 {
1775 }
1776 
1777 } // end namespace events
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1063
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
Various functions related to moving units.
const std::string & range() const
Definition: attack_type.hpp:50
const std::string & type() const
Definition: attack_type.hpp:48
int num_attacks() const
Definition: attack_type.hpp:58
const t_string & name() const
Definition: attack_type.hpp:46
const std::string & icon() const
Definition: attack_type.hpp:49
int damage() const
Definition: attack_type.hpp:57
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:165
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition: attack.hpp:191
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:344
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
can_move_result unit_can_move(const unit &u) const
Work out what u can do - this does not check which player's turn is currently active,...
const team & viewing_team() const
Definition: display.cpp:332
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:305
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1858
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:664
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2963
map_location minimap_location_on(int x, int y)
given x,y co-ordinates of the mouse, will return the location of the hex in the minimap that the mous...
Definition: display.cpp:686
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:485
map_location hex_clicked_on(int x, int y) const
given x,y co-ordinates of an onscreen pixel, will return the location of the hex that this pixel corr...
Definition: display.cpp:528
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1583
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
map_location last_hex_
last highlighted hex
bool scroll_started_
Scroll start flag.
map_location drag_from_hex_
Drag start or mouse-down map location.
bool simple_warp_
MMB click (on game map) state flag.
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
bool dragging_started_
Actual drag flag.
bool dragging_touch_
Finger drag init flag.
point drag_from_
Drag start position.
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
int fill_weapon_choices(std::vector< battle_context > &bc_vector, const unit_map::iterator &attacker, const unit_map::iterator &defender)
bool move_unit_along_current_route()
Moves a unit along the currently cached route.
bool mouse_button_event(const SDL_MouseButtonEvent &event, uint8_t button, map_location loc, bool click=false) override
map_location previous_hex_
void show_reach_for_unit(const unit_ptr &un)
Highlight the hexes that a unit can move to.
game_display & gui() override
Due to the way this class is constructed we can assume that the display* gui_ member actually points ...
void select_or_action(bool browse)
void attack_enemy_(const map_location &attacker_loc, const map_location &defender_loc, int choice)
void save_whiteboard_attack(const map_location &attacker_loc, const map_location &defender_loc, int weapon_choice)
int show_attack_dialog(const map_location &attacker_loc, const map_location &defender_loc, const map_location &attacker_src)
void set_current_paths(const pathfind::paths &new_paths)
pathfind::marked_route get_route(const unit *un, map_location go_to, const team &team) const
play_controller & pc_
unit_map::iterator selected_unit()
static mouse_handler * singleton_
void enable_units_highlight()
When unit highlighting is disabled, call this when the mouse no longer hovers any unit to enable high...
mouse_handler(play_controller &pc)
void touch_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
void attack_enemy(const map_location &attacker_loc, const map_location &defender_loc, int choice)
map_location previous_free_hex_
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true, const bool force_unhighlight=false)
unit_map::const_iterator find_unit(const map_location &hex) const
map_location current_unit_attacks_from(const map_location &loc) const
bool right_click_show_menu(int x, int y, const bool browse) override
Called in the default right_click when the context menu is about to be shown, can be used for preproc...
map_location selected_hex_
pathfind::marked_route current_route_
const map_location hovered_hex() const
Uses SDL and game_display::hex_clicked_on to fetch the hex the mouse is hovering, if applicable.
bool unit_in_cycle(const unit_map::const_iterator &it)
void touch_action(const map_location hex, bool browse) override
void set_side(int side_number)
void move_action(bool browse) override
Overridden in derived class.
pathfind::paths current_paths_
If non-empty, current_paths_.destinations contains a cache of highlighted hexes, likely the movement ...
bool hex_hosts_unit(const map_location &hex) const
Unit exists on the hex, no matter if friend or foe.
int drag_threshold() const override
Minimum dragging distance to fire the drag&drop.
unit * find_unit_nonowning(const map_location &hex)
void cycle_units(const bool browse, const bool reverse=false)
std::size_t move_unit_along_route(const std::vector< map_location > &steps, bool &interrupted)
Moves a unit across the board for a player.
void mouse_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
Use update to force an update of the mouse state.
Game board class.
Definition: game_board.hpp:47
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:79
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:172
virtual const unit_map & units() const override
Definition: game_board.hpp:106
virtual const gamemap & map() const override
Definition: game_board.hpp:96
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
void flash_fade_route(const pathfind::marked_route &route)
Visualizes a path by flashing it white then fade out over 2 seconds.
virtual void highlight_hex(map_location hex) override
Function to highlight a location.
void set_attack_indicator(const map_location &src, const map_location &dst)
Set the attack direction indicator.
void set_route(const pathfind::marked_route *route)
Sets the route along which footsteps are drawn to show movement of a unit.
virtual void select_hex(map_location hex) override
Function to display a location as selected.
void highlight_reach(const pathfind::paths &paths_list)
Sets the paths that are currently displayed as available for the unit to move along.
void clear_attack_indicator()
void highlight_another_reach(const pathfind::paths &paths_list, const map_location &goal=map_location::null_location())
Add more paths to highlight.
bool unhighlight_reach()
Reset highlighting of paths.
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:48
game_board board_
Definition: game_state.hpp:44
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
bool is_keep(const map_location &loc) const
Definition: map.cpp:66
bool show(const unsigned auto_close_time=0)
Shows the window.
std::vector< team > & get_teams()
const unit_map & get_units() const
game_state & gamestate()
const tod_manager & get_tod_manager() const
int current_side() const
Returns the number of the side whose turn it is.
std::shared_ptr< wb::manager > get_whiteboard() const
const gamemap & get_map() const
game_events::wml_event_pump & pump()
static config get_attack(const map_location &a, const map_location &b, int att_weapon, int def_weapon, const std::string &attacker_type_id, const std::string &defender_type_id, int attacker_lvl, int defender_lvl, const std::size_t turn, const time_of_day &t)
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
Definition: abilities.hpp:275
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition: tstring.hpp:200
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
int side() const
Definition: team.hpp:182
bool is_enemy(int n) const
Definition: team.hpp:270
void increment_action_bonus_count()
Definition: team.hpp:240
int turn() const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:57
void set_selecting()
Sets the animation state to that when the unit is selected.
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator begin()
Definition: map.hpp:418
This class represents a single unit of a specific type.
Definition: unit.hpp:39
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1031
static std::string _(const char *str)
Definition: gettext.hpp:100
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1954
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition: unit.hpp:642
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
unit_animation_component & anim_comp() const
Definition: unit.hpp:1537
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1364
Contains functions for cleanly handling SDL input.
auto string_table
Definition: language.hpp:70
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
#define ERR_WML
static lg::log_domain log_wml("wml")
#define LOG_NG
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
std::size_t move_unit_and_record(const std::vector< map_location > &steps, bool continued_move, bool *interrupted)
Wrapper around the other overload.
Definition: move.cpp:1431
int side_number
Definition: game_info.hpp:40
CURSOR_TYPE get()
Definition: cursor.cpp:205
@ WAIT
Definition: cursor.hpp:28
@ ATTACK
Definition: cursor.hpp:28
@ MOVE_DRAG
Definition: cursor.hpp:28
@ NORMAL
Definition: cursor.hpp:28
@ ATTACK_DRAG
Definition: cursor.hpp:28
@ MOVE
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:171
static void update()
Handling of system events.
const std::string unicode_em_dash
Definition: constants.cpp:44
const std::string weapon_numbers_sep
Definition: constants.cpp:49
std::string path
Definition: filesystem.cpp:106
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
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.
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
unsigned screen_height
Definition: settings.cpp:28
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
General purpose widgets.
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:161
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
marked_route mark_route(const plain_route &rt, bool update_move_cost)
Add marks on a route rt assuming that the unit located at the first hex of rt travels along it.
Definition: pathfind.cpp:658
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
uint32_t get_mouse_state(float *x, float *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition: input.cpp:27
point get_mouse_location()
Returns the current mouse location in draw space.
Definition: input.cpp:54
void play_UI_sound(const std::string &files)
Definition: sound.cpp:997
bool click(int mousex, int mousey)
Definition: tooltips.cpp:350
void process(int mousex, int mousey)
Definition: tooltips.cpp:334
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
constexpr auto reverse
Definition: ranges.hpp:44
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:87
std::shared_ptr< bool > whiteboard_lock
Definition: typedefs.hpp:56
std::string_view data
Definition: picture.cpp:188
std::shared_ptr< const attack_type > const_attack_ptr
Definition: ptr.hpp:34
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
Define the game's event mechanism.
This file contains the settings handling of the widget library.
rect dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:54
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:81
unsigned int level
Definition: attack.hpp:71
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:55
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:77
bool disable
Attack has disable special.
Definition: attack.hpp:67
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:76
int attack_num
Index into unit->attacks() or -1 for none.
Definition: attack.hpp:56
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
Encapsulates the map of the game.
Definition: location.hpp:46
bool valid() const
Definition: location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:238
static const map_location & null_location()
Definition: location.hpp:103
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
void insert(const map_location &)
Definition: pathfind.cpp:485
bool contains(const map_location &) const
Definition: pathfind.cpp:514
const_iterator find(const map_location &) const
Definition: pathfind.cpp:478
map_location curr
Definition: pathfind.hpp:89
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:73
dest_vect destinations
Definition: pathfind.hpp:101
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:133
Holds a 2D point.
Definition: point.hpp:25
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:54
This object is used to temporary move a unit in the unit map, swapping out any unit that is already t...
Definition: game_board.hpp:220
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
ONLY IF whiteboard is currently active, applies the planned unit map for the duration of the struct's...
Definition: manager.hpp:274
Ensures that the real unit map is active for the duration of the struct's life.
Definition: manager.hpp:284
static map_location::direction n
Contains typedefs for the whiteboard.