The Battle for Wesnoth  1.19.24+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); // <-- modify x and y
404  fx = x;
405  fy = y;
406 
408  return;
409  }
410 
411  // Don't process other motion events while scrolling
412  if(scroll_started_) {
413  return;
414  }
415 
416  if(new_hex == map_location::null_location()) {
417  new_hex = gui().hex_clicked_on(x, y);
418  }
419 
420  if(new_hex != last_hex_) {
421  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
422  lk->mouse_over_hex_callback(new_hex);
423  }
424 
425  update = true;
426 
427  if(pc_.get_map().on_board(last_hex_)) {
428  // we store the previous hexes used to propose attack direction
430 
431  // the hex of the selected unit is also "free"
432  { // start planned unit map scope
436  }
437  } // end planned unit map scope
438  }
439 
440  last_hex_ = new_hex;
441  }
442 
443  if(reachmap_invalid_) {
444  update = true;
445  }
446 
447  if(!update) {
448  return;
449  }
450 
451  if(reachmap_invalid_) {
452  reachmap_invalid_ = false;
453 
455  bool selected_hex_has_unit;
456  { // start planned unit map scope
457  wb::future_map_if_active planned_unit_map;
458  selected_hex_has_unit = find_unit(selected_hex_).valid();
459  } // end planned unit map scope
460 
461  if(selected_hex_.valid() && selected_hex_has_unit) {
462  // reselect the unit without firing events (updates current_paths_)
463  select_hex(selected_hex_, true);
464  }
465 
466  // we do never deselect here, mainly because of canceled attack-move
467  }
468  }
469 
470  // reset current_route_ and current_paths if not valid anymore
471  // we do it before cursor selection, because it uses current_paths_
472  if(!pc_.get_map().on_board(new_hex)) {
473  current_route_.steps.clear();
474  gui().set_route(nullptr);
475  pc_.get_whiteboard()->erase_temp_move();
476  }
477 
478  if(unselected_paths_) {
479  unselected_paths_ = false;
482  } else if(over_route_) {
483  over_route_ = false;
484  current_route_.steps.clear();
485  gui().set_route(nullptr);
486  pc_.get_whiteboard()->erase_temp_move();
487  }
488 
489  gui().highlight_hex(new_hex);
490  pc_.get_whiteboard()->on_mouseover_change(new_hex);
491 
493  unit_map::iterator mouseover_unit;
494  map_location attack_from;
495 
496  { // start planned unit map scope
497  wb::future_map_if_active planned_unit_map;
499  mouseover_unit = find_unit(new_hex);
500 
501  // we search if there is an attack possibility and where
502  attack_from = current_unit_attacks_from(new_hex);
503 
504  // see if we should show the normal cursor, the movement cursor, or
505  // the attack cursor
506  // If the cursor is on WAIT, we don't change it and let the setter
507  // of this state end it
508  if(cursor::get() != cursor::WAIT) {
509  if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
510  if(attack_from.valid()) {
512  } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
514  } else {
515  // selected unit can't attack or move there
517  }
518  } else {
519  // no selected unit or we can't move it
520 
521  if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
522  // empty hex field selected and unit on our site under the cursor
524  } else {
526  }
527  }
528  }
529  } // end planned unit map scope
530 
531  // show (or cancel) the attack direction indicator
532  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
533  gui().set_attack_indicator(attack_from, new_hex);
534  } else {
536  }
537 
538  unit_ptr un; // will later point to unit at mouseover_hex_
539 
540  // the destination is the pointed hex or the adjacent hex
541  // used to attack it
542  map_location dest;
543  unit_map::const_iterator dest_un;
544  /* start planned unit map scope*/
545  {
547  if(attack_from.valid()) {
548  dest = attack_from;
549  dest_un = find_unit(dest);
550  } else {
551  dest = new_hex;
552  dest_un = find_unit(new_hex);
553  }
554 
555  if(dest == selected_hex_ || dest_un) {
556  current_route_.steps.clear();
557  gui().set_route(nullptr);
558  pc_.get_whiteboard()->erase_temp_move();
559  } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
560  if(selected_unit && !selected_unit->incapacitated()) {
561  // Show the route from selected unit to mouseover hex
562  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
563 
564  pc_.get_whiteboard()->create_temp_move();
565 
566  if(!browse) {
568  }
569  }
570  }
571 
572  if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
573  // Show the route from selected hex to mouseover unit
574  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
575 
576  pc_.get_whiteboard()->create_temp_move();
577 
578  if(!browse) {
580  }
581  } else if(!selected_unit) {
582  current_route_.steps.clear();
583  gui().set_route(nullptr);
584  pc_.get_whiteboard()->erase_temp_move();
585  }
586 
587  if(mouseover_unit) {
588  un = mouseover_unit.get_shared_ptr();
589  } else {
590  un.reset();
591  }
592  } /*end planned unit map scope*/
593 
594  /*
595  * Only highlight unit's reach if toggler not preventing normal unit
596  * processing. This can happen e.g. if, after activating 'show
597  * [best possible] enemy movements' through the UI menu, the
598  * mouse cursor lands on a hex with unit in it.
599  */
602  }
603 
604  if(!un && preventing_units_highlight_) {
605  // Cursor on empty hex, turn unit highlighting back on.
607  }
608 }
609 
610 // Hook for notifying lua game kernel of mouse button events. We pass button as
611 // a separate argument than the original SDL event in order to manage touch
612 // emulation (e.g., long touch = right click) and such.
613 bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
614  map_location loc, bool click)
615 {
616  static const std::array<const std::string, 6> buttons = {
617  "",
618  "left", // SDL_BUTTON_LEFT
619  "middle", // SDL_BUTTON_MIDDLE
620  "right", // SDL_BUTTON_RIGHT
621  "mouse4", // SDL_BUTTON_X1
622  "mouse5" // SDL_BUTTON_X2
623  };
624 
625  if (gui().view_locked() || button < SDL_BUTTON_LEFT || button >= buttons.size()) {
626  return false;
627  } else if (!pc_.get_map().on_board(loc)) {
628  return false;
629  }
630 
631  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
632  lk->mouse_button_callback(loc, buttons[button], (event.down ? "down" : "up"));
633 
634  // Are we being asked to send a click event?
635  if (click) {
636  // Was both the up and down on the same map tile?
637  if (loc != drag_from_hex_) {
638  return false;
639  }
640  // We allow this event to be consumed, but not up/down
641  return lk->mouse_button_callback(loc, buttons[button], "click");
642  }
643  }
644  return false;
645 }
646 
648 {
650  if(res) {
651  return res;
652  }
653 
654  return find_unit(last_hex_);
655 }
656 
658 {
659  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
660  if(it.valid()) {
661  return it;
662  }
663 
664  return pc_.get_units().end();
665 }
666 
668 {
669  return pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
670 }
671 
673 {
674  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
675  return it.valid() ? &*it : nullptr;
676 }
677 
679 {
680  unit_map::const_iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
681  return it.valid() ? &*it : nullptr;
682 }
683 
685 {
686  float x = -1;
687  float y = -1;
688  sdl::get_mouse_state(&x, &y);
689  return gui_->hex_clicked_on(x, y);
690 }
691 
693 {
694  return find_unit(hex).valid();
695 }
696 
697 // 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
699 {
700  if(loc == selected_hex_) {
701  return map_location();
702  }
703 
704  bool wb_active = pc_.get_whiteboard()->is_active();
705  std::set<int> attackable_distances;
706 
707  {
708  // Check the unit SOURCE of the attack
709 
710  // Check that there's a selected unit
711  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
712 
713  bool source_eligible = source_unit.valid();
714  if(!source_eligible) {
715  return map_location();
716  }
717 
718  // The selected unit must at least belong to the player currently controlling this client.
719  source_eligible &= source_unit->side() == gui_->viewing_team().side();
720  if(!source_eligible) {
721  return map_location();
722  }
723 
724  // In addition:
725  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
726  // - If whiteboard is disabled, it must be the turn of the player controlling this client
727  if(!wb_active) {
728  source_eligible &= gui_->viewing_team().side() == pc_.current_side();
729  if(!source_eligible) {
730  return map_location();
731  }
732  }
733 
734  // Unit must have attacks left
735  source_eligible &= source_unit->attacks_left() != 0;
736  if(!source_eligible) {
737  return map_location();
738  }
739 
740  // Check the unit TARGET of the attack
741 
742  const team& viewer = gui().viewing_team();
743 
744  // Check that there's a unit at the target location
745  const unit_map::const_iterator target_unit = find_unit(loc);
746 
747  bool target_eligible = target_unit.valid();
748  if(!target_eligible) {
749  return map_location();
750  }
751 
752  // The player controlling this client must be an enemy of the target unit's side
753  target_eligible &= viewer.is_enemy(target_unit->side());
754  if(!target_eligible) {
755  return map_location();
756  }
757 
758  // Sanity check: source and target of the attack shouldn't be on the same team
759  assert(source_unit->side() != target_unit->side());
760 
761  target_eligible &= !target_unit->incapacitated();
762  if(!target_eligible) {
763  return map_location();
764  }
765 
766  for (const auto& attack : source_unit->attacks()) {
767  for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
768  attackable_distances.insert(i);
769  }
770  }
771  }
772 
773  //invalid attack ranges
774  if(attackable_distances.empty()) {
775  return map_location{};
776  }
777 
778  //ranged attack
779  if(*attackable_distances.rbegin() > 1){
780  if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))) {
781  return selected_hex_;
782  }
783  map_location res;
784  int best_move = -1;
786  map_location dst = step.curr;
787  if(utils::contains(attackable_distances, distance_between(loc, dst))) {
788  if (step.move_left > best_move){
789  best_move = step.move_left;
790  res=dst;
791  }
792  }
793  }
794  return res;
795  }
796 
797  //no ranged attack
800 
801  int best_rating = 100; // smaller is better
802 
803  map_location res;
804  const auto adj = get_adjacent_tiles(loc);
805 
806  for(std::size_t n = 0; n < adj.size(); ++n) {
807  if(pc_.get_map().on_board(adj[n]) == false) {
808  continue;
809  }
810 
811  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
812  continue;
813  }
814 
816  static const std::size_t ndirections = static_cast<int>(map_location::direction::indeterminate);
817 
818  unsigned int difference = std::abs(static_cast<int>(static_cast<int>(preferred) - n));
819  if(difference > ndirections / 2) {
820  difference = ndirections - difference;
821  }
822 
823  unsigned int second_difference = std::abs(static_cast<int>(static_cast<int>(second_preferred) - n));
824  if(second_difference > ndirections / 2) {
825  second_difference = ndirections - second_difference;
826  }
827 
828  const int rating = difference * 2 + (second_difference > difference);
829  if(rating < best_rating || res.valid() == false) {
830  best_rating = rating;
831  res = adj[n];
832  }
833  }
834  }
835 
836  return res;
837 }
838 
840 {
841  game_board& board = pc_.gamestate().board_;
842 
843  // The pathfinder will check unit visibility (fogged/stealthy).
844  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
845 
846  pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, gui().viewing_team());
847 
848  pathfind::plain_route route;
849 
850  route = pathfind::a_star_search(
851  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
852 
853  return mark_route(route);
854 }
855 
856 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
857 {
859  unselected_reach_ = false;
860  return false;
861  }
862 
863  return gui().map_area().contains(x, y);
864 }
865 
867 {
868  // Load whiteboard partial moves
869  //wb::future_map_if_active planned_unit_map;
870 
871  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
872  lk->select_hex_callback(last_hex_);
873  }
874 
877 
878  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
879  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
880  ) {
882  teleport_selected_ = true;
884  gui().set_route(nullptr);
885  }
886 }
887 
889 {
890  // Set the teleport to active so that we can use existing functions
891  // for teleport
892  teleport_selected_ = false;
893 
897  gui().invalidate_all();
899  gui().set_route(nullptr);
900 
901  // Select and deselect the units hex to prompt updates for hover
902  select_hex(last_hex_, false);
903  deselect_hex();
904  current_route_.steps.clear();
905 }
906 
908 {
909  if(!pc_.get_map().on_board(last_hex_)) {
911  return;
912  }
913 
914  // Load whiteboard partial moves
915  wb::future_map_if_active planned_unit_map;
916 
917  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
918  lk->select_hex_callback(last_hex_);
919  }
920 
923 
924  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
925  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
926  ) {
927  select_hex(last_hex_, false);
928  }
929 #ifdef __ANDROID__
930  else if (clicked_u && clicked_u->side() != side_num_) {
931  select_hex(last_hex_, false);
932  }
933 #endif
934  else {
935  move_action(browse);
936  }
937  teleport_selected_ = false;
938 }
939 
940 void mouse_handler::move_action(bool browse)
941 {
942  // Lock whiteboard activation state to avoid problems due to
943  // its changing while an animation takes place.
944  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
945 
946  // we use the last registered highlighted hex
947  // since it's what update our global state
948  map_location hex = last_hex_;
949 
950  // TODO
951  // // Clicks on border hexes mean to deselect.
952  // // (Check this before doing processing that might not be needed.)
953  // if ( !pc_.get_map().on_board(hex) ) {
954  // deselect_hex();
955  // return false;
956  // }
957 
958  unit* u = nullptr;
959  const unit* clicked_u = nullptr;
960 
962  pathfind::paths orig_paths;
963  map_location attack_from;
964 
965  { // start planned unit map scope
966  wb::future_map_if_active planned_unit_map;
968 
969  // if the unit is selected and then itself clicked on,
970  // any goto command is canceled
971  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
972  u->set_goto(map_location());
973  }
974 
975  clicked_u = find_unit_nonowning(hex);
976 
977  src = selected_hex_;
978  orig_paths = current_paths_;
979  attack_from = current_unit_attacks_from(hex);
980  } // end planned unit map scope
981 
982  // See if the teleport option is toggled
983  if(teleport_selected_) {
984  teleport_action();
985  }
986  // see if we're trying to do a attack or move-and-attack
987  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
988  // Ignore this command if commands are disabled.
989  if(commands_disabled) {
990  return;
991  }
992 
993  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
994  if(attack_from == selected_hex_) { // no move needed
995  int choice = -1;
996  {
997  wb::future_map_if_active planned_unit_map; // start planned unit map scope
998  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
999  } // end planned unit map scope
1000 
1001  if(choice >= 0) {
1002  if(pc_.get_whiteboard()->is_active()) {
1003  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
1004  } else {
1005  // clear current unit selection so that any other unit selected
1006  // triggers a new selection
1008 
1009  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
1010  }
1011  }
1012 
1013  return;
1014  } else {
1015  int choice = -1; // for the attack dialog
1016 
1017  {
1018  wb::future_map_if_active planned_unit_map; // start planned unit map scope
1019  // we will now temporary move next to the enemy
1020  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
1021  if(itor == current_paths_.destinations.end()) {
1022  // can't reach the attacking location
1023  // not supposed to happen, so abort
1024  return;
1025  }
1026 
1027  // block where we temporary move the unit
1028  {
1029  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1030  }
1031 
1032  if(choice < 0) {
1033  // user hit cancel or attack is invalid, don't start move&attack
1034  return;
1035  }
1036  } // end planned unit map scope
1037 
1038  if(pc_.get_whiteboard()->is_active()) {
1039  save_whiteboard_attack(attack_from, hex, choice);
1040  } else {
1041  bool not_interrupted = move_unit_along_current_route();
1042  bool alt_unit_selected = (selected_hex_ != src);
1043  src = selected_hex_;
1044  // clear current unit selection so that any other unit selected
1045  // triggers a new selection
1047 
1048  if(not_interrupted)
1049  attack_enemy(attack_from, hex, choice); // Fight !!
1050 
1051  // TODO: Maybe store the attack choice so "press t to continue"
1052  // can also continue the attack?
1053 
1054  if(alt_unit_selected && !selected_hex_.valid()) {
1055  // reselect other unit if selected during movement animation
1056  select_hex(src, browse);
1057  }
1058  }
1059 
1060  return;
1061  }
1062  }
1063  }
1064  // otherwise we're trying to move to a hex
1065  else if(
1066  // The old use case: move selected unit to mouse hex field.
1067  (
1068  (!browse || pc_.get_whiteboard()->is_active())
1069  && selected_hex_.valid()
1070  && selected_hex_ != hex
1071  && u != nullptr
1072  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1073  && !clicked_u
1074  && !current_route_.steps.empty()
1075  && current_route_.steps.front() == selected_hex_
1076  )
1077  || // The new use case: move mouse unit to selected hex field.
1078  (
1079  (!browse || pc_.get_whiteboard()->is_active())
1080  && selected_hex_.valid()
1081  && selected_hex_ != hex
1082  && clicked_u
1083  && !current_route_.steps.empty()
1084  && current_route_.steps.back() == selected_hex_
1085  && !u
1086  && clicked_u->side() == side_num_
1087  )
1088  ) {
1089  // Ignore this command if commands are disabled.
1090  if(commands_disabled) {
1091  return;
1092  }
1093 
1094  // If the whiteboard is active, it intercepts any unit movement.
1095  if(pc_.get_whiteboard()->is_active()) {
1096  // Deselect the current hex, and create planned move for whiteboard.
1098 
1101  gui().set_route(nullptr);
1102 
1103  show_partial_move_ = false;
1104 
1105  gui().unhighlight_reach();
1106 
1108  current_route_.steps.clear();
1109 
1110  pc_.get_whiteboard()->save_temp_move();
1111 
1112  // Otherwise proceed to normal unit movement
1113  } else {
1114  // Don't move if the unit already has actions
1115  // from the whiteboard.
1116  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1117  return;
1118  }
1119 
1121 
1122  // During the move, we may have selected another unit
1123  // (but without triggering a select event (command was disabled)
1124  // in that case reselect it now to fire the event (+ anim & sound)
1125  if(selected_hex_ != src) {
1126  select_hex(selected_hex_, browse);
1127  }
1128  }
1129 
1130  return;
1131  }
1132 }
1133 
1134 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1135 {
1136  unit_map::iterator unit = find_unit(touched_hex);
1137 #ifdef __ANDROID__
1138  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1139  if(touched_hex == selected_hex_) {
1140  deselect_hex();
1141  } else {
1142  select_or_action(browse);
1143  }
1144  }
1145 #else
1146  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1147  select_or_action(browse);
1148  }
1149 #endif
1150 }
1151 
1152 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1153 {
1154  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1155 
1156  selected_hex_ = hex;
1157 
1160  gui().set_route(nullptr);
1161 
1162  show_partial_move_ = false;
1163 
1164  wb::future_map_if_active planned_unit_map; // lasts for whole method
1165 
1167 
1168  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1170 
1171  {
1172  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1173  }
1174 
1175  if(highlight) {
1177  }
1178 
1179  // The highlight now comes from selection
1180  // and not from the mouseover on an enemy
1181  unselected_paths_ = false;
1182  gui().set_route(nullptr);
1183 
1184  // Selection have impact only if we are not observing and it's our unit
1185  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1186  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1187  sound::play_UI_sound("select-unit.wav");
1188 
1190 
1191  if(fire_event) {
1192  // Ensure unit map is back to normal while event is fired
1193  wb::real_map srum;
1194  pc_.pump().fire("select", hex);
1195  // end forced real unit map
1196  }
1197  }
1198  }
1199 
1200  return;
1201  }
1202 
1203  if(selected_hex_.valid() && !unit) {
1204  // When no unit is selected, compute unit in range of the empty selected_hex field
1205 
1207 
1208  pathfind::paths reaching_unit_locations;
1209 
1210  pathfind::paths clicked_location;
1211  clicked_location.destinations.insert(hex);
1212 
1213  for(const ::unit& u : pc_.get_units()) {
1214  bool invisible = u.invisible(u.get_location());
1215 
1216  if(!gui_->fogged(u.get_location()) && !u.incapacitated() && !invisible) {
1217  const pathfind::paths& path =
1218  pathfind::paths(u, false, true, gui().viewing_team(), path_turns_, false, false);
1219 
1220  if(path.destinations.find(hex) != path.destinations.end()) {
1221  reaching_unit_locations.destinations.insert(u.get_location());
1222  gui_->highlight_another_reach(clicked_location);
1223  }
1224  }
1225  }
1226 
1227  gui_->highlight_another_reach(reaching_unit_locations);
1228  } else {
1229  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1230  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1232  }
1233 
1235  current_route_.steps.clear();
1236 
1237  pc_.get_whiteboard()->on_deselect_hex();
1238  }
1239 }
1240 
1242 {
1243  select_hex(map_location(), true);
1244 }
1245 
1246 /**
1247  * Moves a unit along the currently cached route.
1248  *
1249  * @returns true if the end of the route was reached and no information was
1250  * uncovered that would warrant interrupting a chain of actions;
1251  * false otherwise.
1252  */
1254 {
1255  // Copy the current route to ensure it remains valid throughout the animation.
1256  const std::vector<map_location> steps = current_route_.steps;
1257 
1258  // do not show footsteps during movement
1259  gui().set_route(nullptr);
1260  gui().unhighlight_reach();
1261 
1262  // do not keep the hex highlighted that we started from
1265 
1266  bool interrupted = false;
1267  if(steps.size() > 1) {
1268  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1269 
1270  interrupted = interrupted || num_moves + 1 < steps.size();
1271  next_unit_ = steps[num_moves];
1272  }
1273 
1274  // invalid after the move
1276  current_route_.steps.clear();
1277 
1278  return !interrupted;
1279 }
1280 
1281 /**
1282  * Moves a unit across the board for a player.
1283  * This is specifically for movement at the time it is initiated by a player,
1284  * whether via a mouse click or executing whiteboard actions. Continued moves
1285  * (including goto execution) can bypass this and call actions::move_unit() directly.
1286  * This function call may include time for an animation, so make sure the
1287  * provided route will remain unchanged (the caller should probably make a local
1288  * copy).
1289  *
1290  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1291  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1292  * actions (and set to false otherwise).
1293  *
1294  * @returns The number of hexes entered. This can safely be used as an index
1295  * into steps to get the location where movement ended, provided
1296  * steps is not empty (the return value is guaranteed to be less
1297  * than steps.size() ).
1298  */
1299 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1300 {
1301  if(steps.empty()) {
1302  interrupted = false;
1303  return 0;
1304  }
1305 
1306  // Default return value.
1307  interrupted = true;
1308 
1309  // If this is a leader on a keep, ask permission to the whiteboard to move it
1310  // since otherwise it may cause planned recruits to be erased.
1311  if(pc_.get_map().is_keep(steps.front())) {
1312  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1313 
1314  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1315  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1317  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1318  return 0;
1319  }
1320  }
1321 
1322  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1323  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1324 
1327 
1328  if(moves == 0)
1329  return 0;
1330 
1331  if(interrupted && moves + 1 < steps.size()) {
1332  // reselect the unit (for "press t to continue")
1333  select_hex(steps[moves], false, false, false);
1334  // the new discovery is more important than the new movement range
1335  show_partial_move_ = true;
1336  }
1337 
1338  return moves;
1339 }
1340 
1342  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1343 {
1344  {
1345  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1346  // This method should do the cleanup of highlights and selection but it doesn't work properly
1347 
1348  // gui().highlight_hex(map_location());
1349 
1350  gui().unhighlight_reach();
1352 
1353  // remove footsteps if any - useless for whiteboard as of now
1354  gui().set_route(nullptr);
1355 
1356  // do not keep the hex that we started from highlighted
1359  show_partial_move_ = false;
1360 
1361  // invalid after saving the move
1363  current_route_.steps.clear();
1364  }
1365 
1366  // create planned attack for whiteboard
1367  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1368 }
1369 
1371  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1372 {
1373  int best = 0;
1374  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1375  // skip weapons with attack_weight=0
1376  if(attacker->attacks()[i].attack_weight() > 0) {
1377  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1378 
1379  // Don't include if the attacker's weapon has at least one active "disable" special.
1380  if(bc.get_attacker_stats().disable) {
1381  continue;
1382  }
1383 
1384  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1385  // as some weapons can be hidden, i is not a valid index into the resulting vector
1386  best = bc_vector.size();
1387  }
1388 
1389  bc_vector.emplace_back(std::move(bc));
1390  }
1391  }
1392 
1393  return best;
1394 }
1395 
1396 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1397 {
1398  game_board& board = pc_.gamestate().board_;
1399  unit_map::iterator attacker;
1400  unit_map::iterator defender;
1401  std::vector<battle_context> bc_vector;
1402  std::vector<gui2::widget_data> bc_widget_data_vector;
1403  int best;
1404  {
1405  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1406  temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1407 
1408  attacker = board.units().find(attacker_loc);
1409  defender = board.units().find(defender_loc);
1410 
1411  if(!attacker || !defender) {
1412  if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1413  if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1414  return -1; // abort, click will do nothing
1415  }
1416 
1417  best = fill_weapon_choices(bc_vector, attacker, defender);
1418 
1419  if (bc_vector.empty()) {
1420  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1421 
1422  return -1;
1423  }
1424 
1425  static const config empty;
1426  static const_attack_ptr no_weapon(new attack_type(empty));
1427 
1428  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1429  for(const auto& weapon : bc_vector) {
1430  const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1431  const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1432 
1433  const attack_type& attacker_weapon = *attacker_stats.weapon;
1434  const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1435 
1436  const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1437  const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1438 
1439  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1440  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1441 
1442  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1443  if(!range.empty()) {
1444  range = string_table["range_" + range];
1445  }
1446 
1447  auto ctx = specials_context_t::make(
1448  { attacker.get_shared_ptr(), attacker->get_location(), attacker_stats.weapon },
1449  { defender.get_shared_ptr(), defender->get_location(), defender_stats.weapon },
1450  true);
1451 
1452  std::string types = attacker_weapon.effective_damage_type().first;
1453  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1454  if(!attw_type.empty()) {
1455  attw_type = string_table["type_" + attw_type];
1456  }
1457  std::string def_types = defender_weapon.effective_damage_type().first;
1458  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1459  if(!defw_type.empty()) {
1460  defw_type = string_table["type_" + defw_type];
1461  }
1462 
1463  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains",
1464  "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1465  std::string attw_specials = ctx.describe_weapon_specials(attacker_weapon);
1466  std::string attw_specials_dmg = ctx.describe_weapon_specials_value(attacker_weapon, {"leadership", "damage"});
1467  std::string attw_specials_atk = ctx.describe_weapon_specials_value(attacker_weapon, {"attacks", "swarm"});
1468  std::string attw_specials_cth = ctx.describe_weapon_specials_value(attacker_weapon, {"chance_to_hit"});
1469  std::string attw_specials_others = ctx.describe_weapon_specials_value(attacker_weapon, checking_tags_other);
1470  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1471  && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1472  std::string defw_specials = defender_attack ? ctx.describe_weapon_specials(defender_weapon) : "";
1473  std::string defw_specials_dmg
1474  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"leadership", "damage"}) : "";
1475  std::string defw_specials_atk
1476  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"attacks", "swarm"}) : "";
1477  std::string defw_specials_cth
1478  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"chance_to_hit"}) : "";
1479  std::string defw_specials_others
1480  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, checking_tags_other) : "";
1481 
1482  if(!attw_specials.empty()) {
1483  attw_specials = " " + attw_specials;
1484  }
1485  if(!attw_specials_dmg.empty()) {
1486  attw_specials_dmg = " " + attw_specials_dmg;
1487  }
1488  if(!attw_specials_atk.empty()) {
1489  attw_specials_atk = " " + attw_specials_atk;
1490  }
1491  if(!attw_specials_cth.empty()) {
1492  attw_specials_cth = " " + attw_specials_cth;
1493  }
1494  if(!attw_specials_others.empty()) {
1495  attw_specials_others
1496  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1497  }
1498  if(!defw_specials.empty()) {
1499  defw_specials = " " + defw_specials;
1500  }
1501  if(!defw_specials_dmg.empty()) {
1502  defw_specials_dmg = " " + defw_specials_dmg;
1503  }
1504  if(!defw_specials_atk.empty()) {
1505  defw_specials_atk = " " + defw_specials_atk;
1506  }
1507  if(!defw_specials_cth.empty()) {
1508  defw_specials_cth = " " + defw_specials_cth;
1509  }
1510  if(!defw_specials_others.empty()) {
1511  defw_specials_others
1512  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1513  }
1514 
1515  std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1516 
1517  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1518  // not consider the swarm weapon special
1519  attacker_stats_str << markup::bold(attw_name) << "\n"
1520  << attw_type << "\n"
1521  << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1522  << attw_specials << "\n"
1523  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1524 
1525  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1526  << _("Type: ") << attw_type << "\n"
1527  << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1528  << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1529  << _("Chance to hit: ")
1530  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1531  << markup::italic(attw_specials_cth) << attw_specials_others;
1532 
1533  defender_stats_str << markup::bold(defw_name) << "\n"
1534  << defw_type << "\n"
1535  << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1536  << defw_specials << "\n"
1537  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1538 
1539  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1540  << _("Type: ") << defw_type << "\n"
1541  << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1542  << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1543  << _("Chance to hit: ")
1544  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1545  << markup::italic(defw_specials_cth) << defw_specials_others;
1546 
1548  gui2::widget_item item;
1549 
1550  item["use_markup"] = "true";
1551 
1552  item["label"] = attacker_weapon.icon();
1553  data.emplace("attacker_weapon_icon", item);
1554 
1555  item["tooltip"] = attacker_tooltip.str();
1556  item["label"] = attacker_stats_str.str();
1557  data.emplace("attacker_weapon", item);
1558  item["tooltip"] = "";
1559 
1560  item["label"]
1561  = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1562  data.emplace("range", item);
1563 
1564  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1565  item["label"] = defender_stats_str.str();
1566  data.emplace("defender_weapon", item);
1567 
1568  item["tooltip"] = "";
1569  item["label"] = defender_weapon.icon();
1570  data.emplace("defender_weapon_icon", item);
1571 
1572  bc_widget_data_vector.emplace_back(data);
1573  }
1574  }
1575  // bc_widget_data_vector won't be empty when it reaches here.
1576  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector);
1577 
1578  if(dlg.show()) {
1579  return dlg.get_selected_weapon();
1580  }
1581 
1582  return -1;
1583 }
1584 
1585 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1586 {
1587  try {
1588  attack_enemy_(attacker_loc, defender_loc, choice);
1589  } catch(const std::bad_alloc&) {
1590  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1591  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1592  }
1593 }
1594 
1595 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1596 {
1597  // NOTE: copy the values because the const reference may change!
1598  // (WML events and mouse inputs during animations may modify
1599  // the data of the caller)
1600  const map_location attacker_loc = att_loc;
1601  const map_location defender_loc = def_loc;
1602 
1603  unit* attacker = nullptr;
1604  const unit* defender = nullptr;
1605  std::vector<battle_context> bc_vector;
1606 
1607  {
1608  unit_map::iterator attacker_it = find_unit(attacker_loc);
1609  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1610  return;
1611  }
1612 
1613  unit_map::iterator defender_it = find_unit(defender_loc);
1614  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1615  return;
1616  }
1617 
1618  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1619 
1620  attacker = &*attacker_it;
1621  defender = &*defender_it;
1622  }
1623 
1624  if(std::size_t(choice) >= bc_vector.size()) {
1625  return;
1626  }
1627 
1628  events::command_disabler disabler;
1629  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1630  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1631 
1632  attacker->set_goto(map_location());
1633 
1635 
1636  // make the attacker's stats appear during the attack
1637  gui().display_unit_hex(attacker_loc);
1638 
1639  // remove highlighted hexes etc..
1643  gui().unhighlight_reach();
1644 
1646  // TODO: change ToD to be location specific for the defender
1647 
1648  const tod_manager& tod_man = pc_.get_tod_manager();
1649 
1652  attacker_loc,
1653  defender_loc,
1654  att.attack_num,
1655  def.attack_num,
1656  attacker->type_id(),
1657  defender->type_id(),
1658  att.level,
1659  def.level,
1660  tod_man.turn(),
1661  tod_man.get_time_of_day()
1662  )
1663  );
1664 }
1665 
1667 {
1668  game_board& board = pc_.gamestate().board_;
1669 
1670  if(!it) {
1671  return false;
1672  }
1673 
1674  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1675  return false;
1676  }
1677 
1678  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1679  return false;
1680  }
1681 
1682  if(it->get_hidden()) {
1683  return false;
1684  }
1685 
1686  return true;
1687 }
1688 
1689 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1690 {
1691  unit_map& units = pc_.get_units();
1692 
1693  if(units.begin() == units.end()) {
1694  return;
1695  }
1696 
1698  if(!it) {
1699  it = units.begin();
1700  }
1701 
1702  const unit_map::const_iterator itx = it;
1703 
1704  do {
1705  if(reverse) {
1706  if(it == units.begin()) {
1707  it = units.end();
1708  }
1709 
1710  --it;
1711  } else {
1712  if(it == units.end()) {
1713  it = units.begin();
1714  } else {
1715  ++it;
1716  }
1717  }
1718  } while(it != itx && !unit_in_cycle(it));
1719 
1720  if(unit_in_cycle(it)) {
1721  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1722 
1723  select_hex(it->get_location(), browse);
1724  // mouse_update(browse);
1725  }
1726 }
1727 
1729 {
1730  gui().unhighlight_reach();
1731 
1732  current_paths_ = new_paths;
1733  current_route_.steps.clear();
1734 
1735  gui().set_route(nullptr);
1736 
1737  pc_.get_whiteboard()->erase_temp_move();
1738 }
1739 
1741 {
1742  return pc_.get_teams()[side_num_ - 1];
1743 }
1744 
1746 
1748 {
1750 }
1751 
1753 {
1755 }
1756 
1757 } // end namespace events
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1038
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:331
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:1857
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:663
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2962
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:685
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:484
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:527
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1582
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:80
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:185
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
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:270
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:179
bool is_enemy(int n) const
Definition: team.hpp:267
void increment_action_bonus_count()
Definition: team.hpp:237
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:97
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:206
@ 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:172
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:948
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:223
bool valid() const
Definition: map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217
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.