The Battle for Wesnoth  1.19.13+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  sdl::get_mouse_state(&x,&y);
110 
111  // This is from mouse_handler_base::mouse_motion_default()
112  tooltips::process(x, y);
113 
114  if(simple_warp_) {
115  return;
116  }
117 
118  if(minimap_scrolling_) {
119  const map_location& mini_loc = gui().minimap_location_on(x,y);
120  if(mini_loc.valid()) {
121  if(mini_loc != last_hex_) {
122  last_hex_ = mini_loc;
123  gui().scroll_to_tile(mini_loc,display::WARP,false);
124  }
125  return;
126  } else {
127  // clicking outside of the minimap will end minimap scrolling
128  minimap_scrolling_ = false;
129  }
130  }
131 
132  // Fire the drag & drop only after minimal drag distance
133  // While we check the mouse buttons state, we also grab fresh position data.
134 
135  if(is_dragging() && !dragging_started_) {
136  if(dragging_touch_) {
138  const double drag_distance =
139  std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
140  std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
141 
142  if(drag_distance > drag_threshold()*drag_threshold()) {
143  dragging_started_ = true;
144  }
145  }
146  }
147 
148  // Not-so-smooth panning
149  const auto found_unit = find_unit(selected_hex_);
150  bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
151  if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
152 
153  if(gui().map_area().contains(x, y)) {
155  gui().scroll(drag_from_ - pos);
156  drag_from_ = pos;
157  }
158  return;
159  }
160 
161  // now copy-pasting mouse_handler::mouse_motion()
162 
163  // Note for anyone reconciling this code with the version in mouse_handler::mouse_motion:
164  // commit 27a40a82aeea removed the game_board& board from mouse_motion, but didn't update
165  // the corresponding code here in touch_motion.
166  game_board & board = pc_.gamestate().board_;
167 
168  if(new_hex == map_location::null_location())
169  new_hex = gui().hex_clicked_on(x,y);
170 
171  if(new_hex != last_hex_) {
172  update = true;
173  if( pc_.get_map().on_board(last_hex_) ) {
174  // we store the previous hexes used to propose attack direction
176  // the hex of the selected unit is also "free"
177  { // start planned unit map scope
181  }
182  } // end planned unit map scope
183  }
184  last_hex_ = new_hex;
185  }
186 
187  if(reachmap_invalid_) update = true;
188 
189  if(!update) return;
190 
191  if(reachmap_invalid_) {
192  reachmap_invalid_ = false;
194  { // start planned unit map scope
195  wb::future_map_if_active planned_unit_map;
196  selected_hex_has_my_unit = found_unit.valid();
197  } // end planned unit map scope
198  if(selected_hex_.valid() && selected_hex_has_my_unit) {
199  // FIXME: vic: why doesn't this trigger when touch-dragging an unselected unit?
200  // reselect the unit without firing events (updates current_paths_)
201  select_hex(selected_hex_, true);
202  }
203  // we do never deselect here, mainly because of canceled attack-move
204  }
205  }
206 
207  // reset current_route_ and current_paths if not valid anymore
208  // we do it before cursor selection, because it uses current_paths_
209  if( !pc_.get_map().on_board(new_hex) ) {
210  current_route_.steps.clear();
211  gui().set_route(nullptr);
212  pc_.get_whiteboard()->erase_temp_move();
213  }
214 
215  if(unselected_paths_) {
216  unselected_paths_ = false;
219  } else if(over_route_) {
220  over_route_ = false;
221  current_route_.steps.clear();
222  gui().set_route(nullptr);
223  pc_.get_whiteboard()->erase_temp_move();
224  }
225 
226  gui().highlight_hex(new_hex);
227  pc_.get_whiteboard()->on_mouseover_change(new_hex);
228 
230  unit_map::iterator mouseover_unit;
231  map_location attack_from;
232 
233  { // start planned unit map scope
234  wb::future_map_if_active planned_unit_map;
235  selected_unit = found_unit;
236  mouseover_unit = find_unit(new_hex);
237 
238  // we search if there is an attack possibility and where
239  attack_from = current_unit_attacks_from(new_hex);
240 
241  //see if we should show the normal cursor, the movement cursor, or
242  //the attack cursor
243  //If the cursor is on WAIT, we don't change it and let the setter
244  //of this state end it
245  if (cursor::get() != cursor::WAIT) {
246  if (selected_unit &&
247  selected_unit->side() == side_num_ &&
248  !selected_unit->incapacitated() && !browse)
249  {
250  if (attack_from.valid()) {
252  }
253  else if (!mouseover_unit &&
255  {
256  // Is this where left-drag cursor changes? Test.
258  } else {
259  // selected unit can't attack or move there
261  }
262  } else {
263  // no selected unit or we can't move it
264 
265  if ( selected_hex_.valid() && mouseover_unit
266  && mouseover_unit->side() == side_num_ ) {
267  // empty hex field selected and unit on our site under the cursor
269  } else {
271  }
272  }
273  }
274  } // end planned unit map scope
275 
276  // show (or cancel) the attack direction indicator
277  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
278  gui().set_attack_indicator(attack_from, new_hex);
279  } else {
281  }
282 
283  unit_ptr un; //will later point to unit at mouseover_hex_
284 
285  // the destination is the pointed hex or the adjacent hex
286  // used to attack it
287  map_location dest;
288  unit_map::const_iterator dest_un;
289  { // start planned unit map scope
291  if (attack_from.valid()) {
292  dest = attack_from;
293  dest_un = find_unit(dest);
294  } else {
295  dest = new_hex;
296  dest_un = find_unit(new_hex);
297  }
298 
299  if(dest == selected_hex_ || dest_un) {
300  current_route_.steps.clear();
301  gui().set_route(nullptr);
302  pc_.get_whiteboard()->erase_temp_move();
303  }
304  else if (!current_paths_.destinations.empty() &&
305  board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
306  {
307  if (selected_unit && !selected_unit->incapacitated()) {
308  // Show the route from selected unit to mouseover hex
309  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
310 
311  pc_.get_whiteboard()->create_temp_move();
312 
313  if(!browse) {
315  }
316  }
317  }
318 
319  if(board.map().on_board(selected_hex_)
320  && !selected_unit
321  && mouseover_unit.valid()
322  && mouseover_unit) {
323  // Show the route from selected hex to mouseover unit
324  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
325 
326  pc_.get_whiteboard()->create_temp_move();
327 
328  if(!browse) {
330  }
331  } else if (!selected_unit) {
332  current_route_.steps.clear();
333  gui().set_route(nullptr);
334  pc_.get_whiteboard()->erase_temp_move();
335  }
336 
337  unit_map::iterator iter = mouseover_unit;
338  if (iter)
339  un = iter.get_shared_ptr();
340  else
341  un.reset();
342  } //end planned unit map scope
343 }
344 
346 {
347  if( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
348  !gui().fogged(un->get_location()))
349  {
350  // If the unit has a path set and is either ours or allied then show the path.
351  //
352  // Exception: allied AI sides' moves are still hidden, on the assumption that
353  // campaign authors won't want to leak goto_x,goto_y tricks to the player.
354  if(!gui().viewing_team().is_enemy(un->side()) && !pc_.get_teams()[un->side() - 1].is_ai()) {
355  //unit is on our team or an allied team, show path if the unit has one
356  const map_location go_to = un->get_goto();
357  if(pc_.get_map().on_board(go_to)) {
359  { // start planned unit map scope
361  route = get_route(un.get(), go_to, current_team());
362  } // end planned unit map scope
363  gui().set_route(&route);
364  }
365  over_route_ = true;
366  }
367 
368  // Scope for the unit_movement_resetter and future_map_if_active.
369  {
370  // Making this non-null will show the unit's max moves instead of current moves.
371  // Because movement is reset to max in the side's refresh phase, use the max if
372  // that refresh will happen before the unit's side can move again.
373  std::unique_ptr<unit_movement_resetter> move_reset;
374  if(un->side() != side_num_) {
375  move_reset = std::make_unique<unit_movement_resetter>(*un);
376  }
377 
378  // Handle whiteboard. Any move_reset must be done before this, since the future
379  // state includes changes to units' movement.
381 
382  current_paths_ = pathfind::paths(*un, false, true, gui().viewing_team(), path_turns_);
383  }
384 
385  unselected_paths_ = true;
387  }
388 }
389 
390 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
391 {
392  // we ignore the position coming from event handler
393  // because it's always a little obsolete and we don't need
394  // to highlight all the hexes where the mouse passed.
395  // Also, sometimes it seems to have one *very* obsolete
396  // and isolated mouse motion event when using drag&drop
397  sdl::get_mouse_state(&x, &y); // <-- modify x and y
398 
400  return;
401  }
402 
403  // Don't process other motion events while scrolling
404  if(scroll_started_) {
405  return;
406  }
407 
408  if(new_hex == map_location::null_location()) {
409  new_hex = gui().hex_clicked_on(x, y);
410  }
411 
412  if(new_hex != last_hex_) {
413  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
414  lk->mouse_over_hex_callback(new_hex);
415  }
416 
417  update = true;
418 
419  if(pc_.get_map().on_board(last_hex_)) {
420  // we store the previous hexes used to propose attack direction
422 
423  // the hex of the selected unit is also "free"
424  { // start planned unit map scope
428  }
429  } // end planned unit map scope
430  }
431 
432  last_hex_ = new_hex;
433  }
434 
435  if(reachmap_invalid_) {
436  update = true;
437  }
438 
439  if(!update) {
440  return;
441  }
442 
443  if(reachmap_invalid_) {
444  reachmap_invalid_ = false;
445 
447  bool selected_hex_has_unit;
448  { // start planned unit map scope
449  wb::future_map_if_active planned_unit_map;
450  selected_hex_has_unit = find_unit(selected_hex_).valid();
451  } // end planned unit map scope
452 
453  if(selected_hex_.valid() && selected_hex_has_unit) {
454  // reselect the unit without firing events (updates current_paths_)
455  select_hex(selected_hex_, true);
456  }
457 
458  // we do never deselect here, mainly because of canceled attack-move
459  }
460  }
461 
462  // reset current_route_ and current_paths if not valid anymore
463  // we do it before cursor selection, because it uses current_paths_
464  if(!pc_.get_map().on_board(new_hex)) {
465  current_route_.steps.clear();
466  gui().set_route(nullptr);
467  pc_.get_whiteboard()->erase_temp_move();
468  }
469 
470  if(unselected_paths_) {
471  unselected_paths_ = false;
474  } else if(over_route_) {
475  over_route_ = false;
476  current_route_.steps.clear();
477  gui().set_route(nullptr);
478  pc_.get_whiteboard()->erase_temp_move();
479  }
480 
481  gui().highlight_hex(new_hex);
482  pc_.get_whiteboard()->on_mouseover_change(new_hex);
483 
485  unit_map::iterator mouseover_unit;
486  map_location attack_from;
487 
488  { // start planned unit map scope
489  wb::future_map_if_active planned_unit_map;
491  mouseover_unit = find_unit(new_hex);
492 
493  // we search if there is an attack possibility and where
494  attack_from = current_unit_attacks_from(new_hex);
495 
496  // see if we should show the normal cursor, the movement cursor, or
497  // the attack cursor
498  // If the cursor is on WAIT, we don't change it and let the setter
499  // of this state end it
500  if(cursor::get() != cursor::WAIT) {
501  if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
502  if(attack_from.valid()) {
504  } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
506  } else {
507  // selected unit can't attack or move there
509  }
510  } else {
511  // no selected unit or we can't move it
512 
513  if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
514  // empty hex field selected and unit on our site under the cursor
516  } else {
518  }
519  }
520  }
521  } // end planned unit map scope
522 
523  // show (or cancel) the attack direction indicator
524  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
525  gui().set_attack_indicator(attack_from, new_hex);
526  } else {
528  }
529 
530  unit_ptr un; // will later point to unit at mouseover_hex_
531 
532  // the destination is the pointed hex or the adjacent hex
533  // used to attack it
534  map_location dest;
535  unit_map::const_iterator dest_un;
536  /* start planned unit map scope*/
537  {
539  if(attack_from.valid()) {
540  dest = attack_from;
541  dest_un = find_unit(dest);
542  } else {
543  dest = new_hex;
544  dest_un = find_unit(new_hex);
545  }
546 
547  if(dest == selected_hex_ || dest_un) {
548  current_route_.steps.clear();
549  gui().set_route(nullptr);
550  pc_.get_whiteboard()->erase_temp_move();
551  } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
552  if(selected_unit && !selected_unit->incapacitated()) {
553  // Show the route from selected unit to mouseover hex
554  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
555 
556  pc_.get_whiteboard()->create_temp_move();
557 
558  if(!browse) {
560  }
561  }
562  }
563 
564  if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
565  // Show the route from selected hex to mouseover unit
566  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
567 
568  pc_.get_whiteboard()->create_temp_move();
569 
570  if(!browse) {
572  }
573  } else if(!selected_unit) {
574  current_route_.steps.clear();
575  gui().set_route(nullptr);
576  pc_.get_whiteboard()->erase_temp_move();
577  }
578 
579  if(mouseover_unit) {
580  un = mouseover_unit.get_shared_ptr();
581  } else {
582  un.reset();
583  }
584  } /*end planned unit map scope*/
585 
586  /*
587  * Only highlight unit's reach if toggler not preventing normal unit
588  * processing. This can happen e.g. if, after activating 'show
589  * [best possible] enemy movements' through the UI menu, the
590  * mouse cursor lands on a hex with unit in it.
591  */
594  }
595 
596  if(!un && preventing_units_highlight_) {
597  // Cursor on empty hex, turn unit highlighting back on.
599  }
600 }
601 
602 // Hook for notifying lua game kernel of mouse button events. We pass button as
603 // a separate argument than the original SDL event in order to manage touch
604 // emulation (e.g., long touch = right click) and such.
605 bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
606  map_location loc, bool click)
607 {
608  static const std::array<const std::string, 6> buttons = {
609  "",
610  "left", // SDL_BUTTON_LEFT
611  "middle", // SDL_BUTTON_MIDDLE
612  "right", // SDL_BUTTON_RIGHT
613  "mouse4", // SDL_BUTTON_X1
614  "mouse5" // SDL_BUTTON_X2
615  };
616 
617  if (gui().view_locked() || button < SDL_BUTTON_LEFT || button > buttons.size()) {
618  return false;
619  } else if (event.state > SDL_PRESSED || !pc_.get_map().on_board(loc)) {
620  return false;
621  }
622 
623  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
624  lk->mouse_button_callback(loc, buttons[button], (event.state == SDL_RELEASED ? "up" : "down"));
625 
626  // Are we being asked to send a click event?
627  if (click) {
628  // Was both the up and down on the same map tile?
629  if (loc != drag_from_hex_) {
630  return false;
631  }
632  // We allow this event to be consumed, but not up/down
633  return lk->mouse_button_callback(loc, buttons[button], "click");
634  }
635  }
636  return false;
637 }
638 
640 {
642  if(res) {
643  return res;
644  }
645 
646  return find_unit(last_hex_);
647 }
648 
650 {
651  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
652  if(it.valid()) {
653  return it;
654  }
655 
656  return pc_.get_units().end();
657 }
658 
660 {
661  return pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
662 }
663 
665 {
666  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
667  return it.valid() ? &*it : nullptr;
668 }
669 
671 {
672  unit_map::const_iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
673  return it.valid() ? &*it : nullptr;
674 }
675 
677 {
678  auto [x, y] = sdl::get_mouse_location();
679  return gui_->hex_clicked_on(x, y);
680 }
681 
683 {
684  return find_unit(hex).valid();
685 }
686 
687 // 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
689 {
690  if(loc == selected_hex_) {
691  return map_location();
692  }
693 
694  bool wb_active = pc_.get_whiteboard()->is_active();
695  std::set<int> attackable_distances;
696 
697  {
698  // Check the unit SOURCE of the attack
699 
700  // Check that there's a selected unit
701  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
702 
703  bool source_eligible = source_unit.valid();
704  if(!source_eligible) {
705  return map_location();
706  }
707 
708  // The selected unit must at least belong to the player currently controlling this client.
709  source_eligible &= source_unit->side() == gui_->viewing_team().side();
710  if(!source_eligible) {
711  return map_location();
712  }
713 
714  // In addition:
715  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
716  // - If whiteboard is disabled, it must be the turn of the player controlling this client
717  if(!wb_active) {
718  source_eligible &= gui_->viewing_team().side() == pc_.current_side();
719  if(!source_eligible) {
720  return map_location();
721  }
722  }
723 
724  // Unit must have attacks left
725  source_eligible &= source_unit->attacks_left() != 0;
726  if(!source_eligible) {
727  return map_location();
728  }
729 
730  // Check the unit TARGET of the attack
731 
732  const team& viewer = gui().viewing_team();
733 
734  // Check that there's a unit at the target location
735  const unit_map::const_iterator target_unit = find_unit(loc);
736 
737  bool target_eligible = target_unit.valid();
738  if(!target_eligible) {
739  return map_location();
740  }
741 
742  // The player controlling this client must be an enemy of the target unit's side
743  target_eligible &= viewer.is_enemy(target_unit->side());
744  if(!target_eligible) {
745  return map_location();
746  }
747 
748  // Sanity check: source and target of the attack shouldn't be on the same team
749  assert(source_unit->side() != target_unit->side());
750 
751  target_eligible &= !target_unit->incapacitated();
752  if(!target_eligible) {
753  return map_location();
754  }
755 
756  for (const auto& attack : source_unit->attacks()) {
757  for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
758  attackable_distances.insert(i);
759  }
760  }
761  }
762 
763  //invalid attack ranges
764  if(attackable_distances.empty()) {
765  return map_location{};
766  }
767 
768  //ranged attack
769  if(*attackable_distances.rbegin() > 1){
770  if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))) {
771  return selected_hex_;
772  }
773  map_location res;
774  int best_move = -1;
776  map_location dst = step.curr;
777  if(utils::contains(attackable_distances, distance_between(loc, dst))) {
778  if (step.move_left > best_move){
779  best_move = step.move_left;
780  res=dst;
781  }
782  }
783  }
784  return res;
785  }
786 
787  //no ranged attack
790 
791  int best_rating = 100; // smaller is better
792 
793  map_location res;
794  const auto adj = get_adjacent_tiles(loc);
795 
796  for(std::size_t n = 0; n < adj.size(); ++n) {
797  if(pc_.get_map().on_board(adj[n]) == false) {
798  continue;
799  }
800 
801  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
802  continue;
803  }
804 
806  static const std::size_t ndirections = static_cast<int>(map_location::direction::indeterminate);
807 
808  unsigned int difference = std::abs(static_cast<int>(static_cast<int>(preferred) - n));
809  if(difference > ndirections / 2) {
810  difference = ndirections - difference;
811  }
812 
813  unsigned int second_difference = std::abs(static_cast<int>(static_cast<int>(second_preferred) - n));
814  if(second_difference > ndirections / 2) {
815  second_difference = ndirections - second_difference;
816  }
817 
818  const int rating = difference * 2 + (second_difference > difference);
819  if(rating < best_rating || res.valid() == false) {
820  best_rating = rating;
821  res = adj[n];
822  }
823  }
824  }
825 
826  return res;
827 }
828 
830 {
831  game_board& board = pc_.gamestate().board_;
832 
833  // The pathfinder will check unit visibility (fogged/stealthy).
834  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
835 
836  pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, gui().viewing_team());
837 
838  pathfind::plain_route route;
839 
840  route = pathfind::a_star_search(
841  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
842 
843  return mark_route(route);
844 }
845 
846 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
847 {
849  unselected_reach_ = false;
850  return false;
851  }
852 
853  return gui().map_area().contains(x, y);
854 }
855 
857 {
858  // Load whiteboard partial moves
859  //wb::future_map_if_active planned_unit_map;
860 
861  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
862  lk->select_hex_callback(last_hex_);
863  }
864 
867 
868  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
869  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
870  ) {
872  teleport_selected_ = true;
874  gui().set_route(nullptr);
875  }
876 }
877 
879 {
880  // Set the teleport to active so that we can use existing functions
881  // for teleport
882  teleport_selected_ = false;
883 
887  gui().invalidate_all();
889  gui().set_route(nullptr);
890 
891  // Select and deselect the units hex to prompt updates for hover
892  select_hex(last_hex_, false);
893  deselect_hex();
894  current_route_.steps.clear();
895 }
896 
898 {
899  if(!pc_.get_map().on_board(last_hex_)) {
901  return;
902  }
903 
904  // Load whiteboard partial moves
905  wb::future_map_if_active planned_unit_map;
906 
907  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
908  lk->select_hex_callback(last_hex_);
909  }
910 
913 
914  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
915  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
916  ) {
917  select_hex(last_hex_, false);
918  } else {
919  move_action(browse);
920  }
921  teleport_selected_ = false;
922 }
923 
924 void mouse_handler::move_action(bool browse)
925 {
926  // Lock whiteboard activation state to avoid problems due to
927  // its changing while an animation takes place.
928  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
929 
930  // we use the last registered highlighted hex
931  // since it's what update our global state
932  map_location hex = last_hex_;
933 
934  // TODO
935  // // Clicks on border hexes mean to deselect.
936  // // (Check this before doing processing that might not be needed.)
937  // if ( !pc_.get_map().on_board(hex) ) {
938  // deselect_hex();
939  // return false;
940  // }
941 
942  unit* u = nullptr;
943  const unit* clicked_u = nullptr;
944 
946  pathfind::paths orig_paths;
947  map_location attack_from;
948 
949  { // start planned unit map scope
950  wb::future_map_if_active planned_unit_map;
952 
953  // if the unit is selected and then itself clicked on,
954  // any goto command is canceled
955  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
956  u->set_goto(map_location());
957  }
958 
959  clicked_u = find_unit_nonowning(hex);
960 
961  src = selected_hex_;
962  orig_paths = current_paths_;
963  attack_from = current_unit_attacks_from(hex);
964  } // end planned unit map scope
965 
966  // See if the teleport option is toggled
967  if(teleport_selected_) {
968  teleport_action();
969  }
970  // see if we're trying to do a attack or move-and-attack
971  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
972  // Ignore this command if commands are disabled.
973  if(commands_disabled) {
974  return;
975  }
976 
977  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
978  if(attack_from == selected_hex_) { // no move needed
979  int choice = -1;
980  {
981  wb::future_map_if_active planned_unit_map; // start planned unit map scope
982  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
983  } // end planned unit map scope
984 
985  if(choice >= 0) {
986  if(pc_.get_whiteboard()->is_active()) {
987  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
988  } else {
989  // clear current unit selection so that any other unit selected
990  // triggers a new selection
992 
993  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
994  }
995  }
996 
997  return;
998  } else {
999  int choice = -1; // for the attack dialog
1000 
1001  {
1002  wb::future_map_if_active planned_unit_map; // start planned unit map scope
1003  // we will now temporary move next to the enemy
1004  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
1005  if(itor == current_paths_.destinations.end()) {
1006  // can't reach the attacking location
1007  // not supposed to happen, so abort
1008  return;
1009  }
1010 
1011  // block where we temporary move the unit
1012  {
1013  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1014  }
1015 
1016  if(choice < 0) {
1017  // user hit cancel or attack is invalid, don't start move&attack
1018  return;
1019  }
1020  } // end planned unit map scope
1021 
1022  if(pc_.get_whiteboard()->is_active()) {
1023  save_whiteboard_attack(attack_from, hex, choice);
1024  } else {
1025  bool not_interrupted = move_unit_along_current_route();
1026  bool alt_unit_selected = (selected_hex_ != src);
1027  src = selected_hex_;
1028  // clear current unit selection so that any other unit selected
1029  // triggers a new selection
1031 
1032  if(not_interrupted)
1033  attack_enemy(attack_from, hex, choice); // Fight !!
1034 
1035  // TODO: Maybe store the attack choice so "press t to continue"
1036  // can also continue the attack?
1037 
1038  if(alt_unit_selected && !selected_hex_.valid()) {
1039  // reselect other unit if selected during movement animation
1040  select_hex(src, browse);
1041  }
1042  }
1043 
1044  return;
1045  }
1046  }
1047  }
1048  // otherwise we're trying to move to a hex
1049  else if(
1050  // The old use case: move selected unit to mouse hex field.
1051  (
1052  (!browse || pc_.get_whiteboard()->is_active())
1053  && selected_hex_.valid()
1054  && selected_hex_ != hex
1055  && u != nullptr
1056  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1057  && !clicked_u
1058  && !current_route_.steps.empty()
1059  && current_route_.steps.front() == selected_hex_
1060  )
1061  || // The new use case: move mouse unit to selected hex field.
1062  (
1063  (!browse || pc_.get_whiteboard()->is_active())
1064  && selected_hex_.valid()
1065  && selected_hex_ != hex
1066  && clicked_u
1067  && !current_route_.steps.empty()
1068  && current_route_.steps.back() == selected_hex_
1069  && !u
1070  && clicked_u->side() == side_num_
1071  )
1072  ) {
1073  // Ignore this command if commands are disabled.
1074  if(commands_disabled) {
1075  return;
1076  }
1077 
1078  // If the whiteboard is active, it intercepts any unit movement.
1079  if(pc_.get_whiteboard()->is_active()) {
1080  // Deselect the current hex, and create planned move for whiteboard.
1082 
1085  gui().set_route(nullptr);
1086 
1087  show_partial_move_ = false;
1088 
1089  gui().unhighlight_reach();
1090 
1092  current_route_.steps.clear();
1093 
1094  pc_.get_whiteboard()->save_temp_move();
1095 
1096  // Otherwise proceed to normal unit movement
1097  } else {
1098  // Don't move if the unit already has actions
1099  // from the whiteboard.
1100  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1101  return;
1102  }
1103 
1105 
1106  // During the move, we may have selected another unit
1107  // (but without triggering a select event (command was disabled)
1108  // in that case reselect it now to fire the event (+ anim & sound)
1109  if(selected_hex_ != src) {
1110  select_hex(selected_hex_, browse);
1111  }
1112  }
1113 
1114  return;
1115  }
1116 }
1117 
1118 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1119 {
1120  unit_map::iterator unit = find_unit(touched_hex);
1121 
1122  if (touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1123  select_or_action(browse);
1124  }
1125 }
1126 
1127 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1128 {
1129  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1130 
1131  selected_hex_ = hex;
1132 
1135  gui().set_route(nullptr);
1136 
1137  show_partial_move_ = false;
1138 
1139  wb::future_map_if_active planned_unit_map; // lasts for whole method
1140 
1142 
1143  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1145 
1146  {
1147  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1148  }
1149 
1150  if(highlight) {
1152  }
1153 
1154  // The highlight now comes from selection
1155  // and not from the mouseover on an enemy
1156  unselected_paths_ = false;
1157  gui().set_route(nullptr);
1158 
1159  // Selection have impact only if we are not observing and it's our unit
1160  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1161  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1162  sound::play_UI_sound("select-unit.wav");
1163 
1165 
1166  if(fire_event) {
1167  // Ensure unit map is back to normal while event is fired
1168  wb::real_map srum;
1169  pc_.pump().fire("select", hex);
1170  // end forced real unit map
1171  }
1172  }
1173  }
1174 
1175  return;
1176  }
1177 
1178  if(selected_hex_.valid() && !unit) {
1179  // When no unit is selected, compute unit in range of the empty selected_hex field
1180 
1182 
1183  pathfind::paths reaching_unit_locations;
1184 
1185  pathfind::paths clicked_location;
1186  clicked_location.destinations.insert(hex);
1187 
1188  for(unit_map::iterator u = pc_.get_units().begin(); u != pc_.get_units().end();
1189  ++u) {
1190  bool invisible = u->invisible(u->get_location());
1191 
1192  if(!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible) {
1193  const pathfind::paths& path =
1194  pathfind::paths(*u, false, true, gui().viewing_team(), path_turns_, false, false);
1195 
1196  if(path.destinations.find(hex) != path.destinations.end()) {
1197  reaching_unit_locations.destinations.insert(u->get_location());
1198  gui_->highlight_another_reach(clicked_location);
1199  }
1200  }
1201  }
1202 
1203  gui_->highlight_another_reach(reaching_unit_locations);
1204  } else {
1205  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1206  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1208  }
1209 
1211  current_route_.steps.clear();
1212 
1213  pc_.get_whiteboard()->on_deselect_hex();
1214  }
1215 }
1216 
1218 {
1219  select_hex(map_location(), true);
1220 }
1221 
1222 /**
1223  * Moves a unit along the currently cached route.
1224  *
1225  * @returns true if the end of the route was reached and no information was
1226  * uncovered that would warrant interrupting a chain of actions;
1227  * false otherwise.
1228  */
1230 {
1231  // Copy the current route to ensure it remains valid throughout the animation.
1232  const std::vector<map_location> steps = current_route_.steps;
1233 
1234  // do not show footsteps during movement
1235  gui().set_route(nullptr);
1236  gui().unhighlight_reach();
1237 
1238  // do not keep the hex highlighted that we started from
1241 
1242  bool interrupted = false;
1243  if(steps.size() > 1) {
1244  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1245 
1246  interrupted = interrupted || num_moves + 1 < steps.size();
1247  next_unit_ = steps[num_moves];
1248  }
1249 
1250  // invalid after the move
1252  current_route_.steps.clear();
1253 
1254  return !interrupted;
1255 }
1256 
1257 /**
1258  * Moves a unit across the board for a player.
1259  * This is specifically for movement at the time it is initiated by a player,
1260  * whether via a mouse click or executing whiteboard actions. Continued moves
1261  * (including goto execution) can bypass this and call actions::move_unit() directly.
1262  * This function call may include time for an animation, so make sure the
1263  * provided route will remain unchanged (the caller should probably make a local
1264  * copy).
1265  *
1266  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1267  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1268  * actions (and set to false otherwise).
1269  *
1270  * @returns The number of hexes entered. This can safely be used as an index
1271  * into steps to get the location where movement ended, provided
1272  * steps is not empty (the return value is guaranteed to be less
1273  * than steps.size() ).
1274  */
1275 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1276 {
1277  if(steps.empty()) {
1278  interrupted = false;
1279  return 0;
1280  }
1281 
1282  // Default return value.
1283  interrupted = true;
1284 
1285  // If this is a leader on a keep, ask permission to the whiteboard to move it
1286  // since otherwise it may cause planned recruits to be erased.
1287  if(pc_.get_map().is_keep(steps.front())) {
1288  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1289 
1290  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1291  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1293  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1294  return 0;
1295  }
1296  }
1297 
1298  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1299  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1300 
1303 
1304  if(moves == 0)
1305  return 0;
1306 
1307  if(interrupted && moves + 1 < steps.size()) {
1308  // reselect the unit (for "press t to continue")
1309  select_hex(steps[moves], false, false, false);
1310  // the new discovery is more important than the new movement range
1311  show_partial_move_ = true;
1312  }
1313 
1314  return moves;
1315 }
1316 
1318  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1319 {
1320  {
1321  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1322  // This method should do the cleanup of highlights and selection but it doesn't work properly
1323 
1324  // gui().highlight_hex(map_location());
1325 
1326  gui().unhighlight_reach();
1328 
1329  // remove footsteps if any - useless for whiteboard as of now
1330  gui().set_route(nullptr);
1331 
1332  // do not keep the hex that we started from highlighted
1335  show_partial_move_ = false;
1336 
1337  // invalid after saving the move
1339  current_route_.steps.clear();
1340  }
1341 
1342  // create planned attack for whiteboard
1343  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1344 }
1345 
1347  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1348 {
1349  int best = 0;
1350  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1351  // skip weapons with attack_weight=0
1352  if(attacker->attacks()[i].attack_weight() > 0) {
1353  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1354 
1355  // Don't include if the attacker's weapon has at least one active "disable" special.
1356  if(bc.get_attacker_stats().disable) {
1357  continue;
1358  }
1359 
1360  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1361  // as some weapons can be hidden, i is not a valid index into the resulting vector
1362  best = bc_vector.size();
1363  }
1364 
1365  bc_vector.emplace_back(std::move(bc));
1366  }
1367  }
1368 
1369  return best;
1370 }
1371 
1372 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1373 {
1374  game_board& board = pc_.gamestate().board_;
1375  unit_map::iterator attacker;
1376  unit_map::iterator defender;
1377  std::vector<battle_context> bc_vector;
1378  std::vector<gui2::widget_data> bc_widget_data_vector;
1379  int best;
1380  int leadership_bonus = 0;
1381  {
1382  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1383  temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1384 
1385  attacker = board.units().find(attacker_loc);
1386  defender = board.units().find(defender_loc);
1387 
1388  if(!attacker || !defender) {
1389  if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1390  if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1391  return -1; // abort, click will do nothing
1392  }
1393 
1394  best = fill_weapon_choices(bc_vector, attacker, defender);
1395 
1396  if (bc_vector.empty()) {
1397  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1398 
1399  return -1;
1400  }
1401 
1402  static const config empty;
1403  static const_attack_ptr no_weapon(new attack_type(empty));
1404 
1405  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1406  for(const auto& weapon : bc_vector) {
1407  const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1408  const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1409 
1410  const attack_type& attacker_weapon = *attacker_stats.weapon;
1411  const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1412 
1413  if(leadership_bonus == 0) {
1414  leadership_bonus
1415  = under_leadership(*attacker, attacker_loc, attacker_stats.weapon, defender_stats.weapon);
1416  }
1417 
1418  const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1419  const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1420 
1421  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1422  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1423 
1424  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1425  if(!range.empty()) {
1426  range = string_table["range_" + range];
1427  }
1428 
1429  auto a_ctx = attacker_weapon.specials_context(attacker.get_shared_ptr(), defender.get_shared_ptr(),
1430  attacker->get_location(), defender->get_location(), true, defender_stats.weapon);
1431 
1432  auto d_ctx = defender_weapon.specials_context(defender.get_shared_ptr(), attacker.get_shared_ptr(),
1433  defender->get_location(), attacker->get_location(), false, attacker_stats.weapon);
1434 
1435  std::string types = attacker_weapon.effective_damage_type().first;
1436  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1437  if(!attw_type.empty()) {
1438  attw_type = string_table["type_" + attw_type];
1439  }
1440  std::string def_types = defender_weapon.effective_damage_type().first;
1441  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1442  if(!defw_type.empty()) {
1443  defw_type = string_table["type_" + defw_type];
1444  }
1445 
1446  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains",
1447  "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1448  std::string attw_specials = attacker_weapon.weapon_specials();
1449  std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
1450  std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
1451  std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
1452  std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
1453  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1454  && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1455  std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
1456  std::string defw_specials_dmg
1457  = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
1458  std::string defw_specials_atk
1459  = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
1460  std::string defw_specials_cth
1461  = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
1462  std::string defw_specials_others
1463  = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
1464 
1465  if(!attw_specials.empty()) {
1466  attw_specials = " " + attw_specials;
1467  }
1468  if(!attw_specials_dmg.empty()) {
1469  attw_specials_dmg = " " + attw_specials_dmg;
1470  }
1471  if(!attw_specials_atk.empty()) {
1472  attw_specials_atk = " " + attw_specials_atk;
1473  }
1474  if(!attw_specials_cth.empty()) {
1475  attw_specials_cth = " " + attw_specials_cth;
1476  }
1477  if(!attw_specials_others.empty()) {
1478  attw_specials_others
1479  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1480  }
1481  if(!defw_specials.empty()) {
1482  defw_specials = " " + defw_specials;
1483  }
1484  if(!defw_specials_dmg.empty()) {
1485  defw_specials_dmg = " " + defw_specials_dmg;
1486  }
1487  if(!defw_specials_atk.empty()) {
1488  defw_specials_atk = " " + defw_specials_atk;
1489  }
1490  if(!defw_specials_cth.empty()) {
1491  defw_specials_cth = " " + defw_specials_cth;
1492  }
1493  if(!defw_specials_others.empty()) {
1494  defw_specials_others
1495  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1496  }
1497 
1498  std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1499 
1500  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1501  // not consider the swarm weapon special
1502  attacker_stats_str << markup::bold(attw_name) << "\n"
1503  << attw_type << "\n"
1504  << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1505  << attw_specials << "\n"
1506  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1507 
1508  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1509  << _("Type: ") << attw_type << "\n"
1510  << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1511  << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1512  << _("Chance to hit: ")
1513  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1514  << markup::italic(attw_specials_cth) << attw_specials_others;
1515 
1516  defender_stats_str << markup::bold(defw_name) << "\n"
1517  << defw_type << "\n"
1518  << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1519  << defw_specials << "\n"
1520  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1521 
1522  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1523  << _("Type: ") << defw_type << "\n"
1524  << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1525  << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1526  << _("Chance to hit: ")
1527  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1528  << markup::italic(defw_specials_cth) << defw_specials_others;
1529 
1531  gui2::widget_item item;
1532 
1533  item["use_markup"] = "true";
1534 
1535  item["label"] = attacker_weapon.icon();
1536  data.emplace("attacker_weapon_icon", item);
1537 
1538  item["tooltip"] = attacker_tooltip.str();
1539  item["label"] = attacker_stats_str.str();
1540  data.emplace("attacker_weapon", item);
1541  item["tooltip"] = "";
1542 
1543  item["label"]
1544  = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1545  data.emplace("range", item);
1546 
1547  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1548  item["label"] = defender_stats_str.str();
1549  data.emplace("defender_weapon", item);
1550 
1551  item["tooltip"] = "";
1552  item["label"] = defender_weapon.icon();
1553  data.emplace("defender_weapon_icon", item);
1554 
1555  bc_widget_data_vector.emplace_back(data);
1556  }
1557  }
1558  // bc_widget_data_vector won't be empty when it reaches here.
1559  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector, leadership_bonus);
1560 
1561  if(dlg.show()) {
1562  return dlg.get_selected_weapon();
1563  }
1564 
1565  return -1;
1566 }
1567 
1568 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1569 {
1570  try {
1571  attack_enemy_(attacker_loc, defender_loc, choice);
1572  } catch(const std::bad_alloc&) {
1573  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1574  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1575  }
1576 }
1577 
1578 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1579 {
1580  // NOTE: copy the values because the const reference may change!
1581  // (WML events and mouse inputs during animations may modify
1582  // the data of the caller)
1583  const map_location attacker_loc = att_loc;
1584  const map_location defender_loc = def_loc;
1585 
1586  unit* attacker = nullptr;
1587  const unit* defender = nullptr;
1588  std::vector<battle_context> bc_vector;
1589 
1590  {
1591  unit_map::iterator attacker_it = find_unit(attacker_loc);
1592  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1593  return;
1594  }
1595 
1596  unit_map::iterator defender_it = find_unit(defender_loc);
1597  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1598  return;
1599  }
1600 
1601  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1602 
1603  attacker = &*attacker_it;
1604  defender = &*defender_it;
1605  }
1606 
1607  if(std::size_t(choice) >= bc_vector.size()) {
1608  return;
1609  }
1610 
1611  events::command_disabler disabler;
1612  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1613  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1614 
1615  attacker->set_goto(map_location());
1616 
1618 
1619  // make the attacker's stats appear during the attack
1620  gui().display_unit_hex(attacker_loc);
1621 
1622  // remove highlighted hexes etc..
1626  gui().unhighlight_reach();
1627 
1629  // TODO: change ToD to be location specific for the defender
1630 
1631  const tod_manager& tod_man = pc_.get_tod_manager();
1632 
1635  attacker_loc,
1636  defender_loc,
1637  att.attack_num,
1638  def.attack_num,
1639  attacker->type_id(),
1640  defender->type_id(),
1641  att.level,
1642  def.level,
1643  tod_man.turn(),
1644  tod_man.get_time_of_day()
1645  )
1646  );
1647 }
1648 
1649 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1650 {
1651  std::set<map_location> res;
1652 
1653  const team& uteam = pc_.get_teams()[side - 1];
1654 
1655  for(const map_location& aloc : get_adjacent_tiles(loc)) {
1657 
1658  if(i && uteam.is_enemy(i->side())) {
1659  res.insert(aloc);
1660  }
1661  }
1662 
1663  return res;
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
int under_leadership(const unit &u, const map_location &loc, const_attack_ptr weapon, const_attack_ptr opp_weapon)
Tests if the unit at loc is currently affected by leadership.
Definition: attack.cpp:1597
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
Various functions related to moving units.
std::string weapon_specials() const
Returns a comma-separated string of active names for the specials of *this.
Definition: abilities.cpp:1004
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1049
const std::string & range() const
Definition: attack_type.hpp:46
const std::string & type() const
Definition: attack_type.hpp:44
int num_attacks() const
Definition: attack_type.hpp:54
const t_string & name() const
Definition: attack_type.hpp:42
specials_context_t specials_context(unit_const_ptr self, unit_const_ptr other, const map_location &unit_loc, const map_location &other_loc, bool attacking, const_attack_ptr other_attack) const
const std::string & icon() const
Definition: attack_type.hpp:45
int damage() const
Definition: attack_type.hpp:53
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Definition: abilities.cpp:1330
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:167
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition: attack.hpp:193
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:456
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
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:307
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:1854
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:2953
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:682
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:1579
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
map_location last_hex_
last highlighted hex
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)
std::set< map_location > get_adj_enemies(const map_location &loc, int side) const
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:384
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
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 bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition: tstring.hpp:197
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:56
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:132
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1032
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:1958
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition: unit.hpp:735
int side() const
The side this unit belongs to.
Definition: unit.hpp:342
unit_animation_component & anim_comp() const
Definition: unit.hpp:1641
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1431
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1468
Contains functions for cleanly handling SDL input.
auto string_table
Definition: language.hpp:68
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:512
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:583
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:218
@ 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:178
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(int *x, int *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:1074
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:85
constexpr auto reverse
Definition: ranges.hpp:40
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
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:51
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition: attack.hpp:76
unsigned int level
Definition: attack.hpp:66
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition: attack.hpp:52
int damage
Effective damage of the weapon (all factors accounted for).
Definition: attack.hpp:72
bool disable
Attack has disable special.
Definition: attack.hpp:64
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition: attack.hpp:71
int attack_num
Index into unit->attacks() or -1 for none.
Definition: attack.hpp:53
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:61
Encapsulates the map of the game.
Definition: location.hpp:45
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
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:102
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.