The Battle for Wesnoth  1.19.25+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  gui().unhighlight_reach();
1143  } else {
1145  select_or_action(browse);
1146  }
1147  }
1148 #else
1149  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1150  select_or_action(browse);
1151  }
1152 #endif
1153 }
1154 
1155 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1156 {
1157  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1158 
1159  selected_hex_ = hex;
1160 
1163  gui().set_route(nullptr);
1164 
1165  show_partial_move_ = false;
1166 
1167  wb::future_map_if_active planned_unit_map; // lasts for whole method
1168 
1170 
1171  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1173 
1174  {
1175  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1176  }
1177 
1178  if(highlight) {
1180  }
1181 
1182  // The highlight now comes from selection
1183  // and not from the mouseover on an enemy
1184  unselected_paths_ = false;
1185  gui().set_route(nullptr);
1186 
1187  // Selection have impact only if we are not observing and it's our unit
1188  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1189  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1190  sound::play_UI_sound("select-unit.wav");
1191 
1193 
1194  if(fire_event) {
1195  // Ensure unit map is back to normal while event is fired
1196  wb::real_map srum;
1197  pc_.pump().fire("select", hex);
1198  // end forced real unit map
1199  }
1200  }
1201  }
1202 
1203  return;
1204  }
1205 
1206  if(selected_hex_.valid() && !unit) {
1207  // When no unit is selected, compute unit in range of the empty selected_hex field
1208 
1210 
1211  pathfind::paths reaching_unit_locations;
1212 
1213  pathfind::paths clicked_location;
1214  clicked_location.destinations.insert(hex);
1215 
1216  for(const ::unit& u : pc_.get_units()) {
1217  bool invisible = u.invisible(u.get_location());
1218 
1219  if(!gui_->fogged(u.get_location()) && !u.incapacitated() && !invisible) {
1220  const pathfind::paths& path =
1221  pathfind::paths(u, false, true, gui().viewing_team(), path_turns_, false, false);
1222 
1223  if(path.destinations.find(hex) != path.destinations.end()) {
1224  reaching_unit_locations.destinations.insert(u.get_location());
1225  gui_->highlight_another_reach(clicked_location);
1226  }
1227  }
1228  }
1229 
1230  gui_->highlight_another_reach(reaching_unit_locations);
1231  } else {
1232  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1233  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1235  }
1236 
1238  current_route_.steps.clear();
1239 
1240  pc_.get_whiteboard()->on_deselect_hex();
1241  }
1242 }
1243 
1245 {
1246  select_hex(map_location(), true);
1247 }
1248 
1249 /**
1250  * Moves a unit along the currently cached route.
1251  *
1252  * @returns true if the end of the route was reached and no information was
1253  * uncovered that would warrant interrupting a chain of actions;
1254  * false otherwise.
1255  */
1257 {
1258  // Capture the full intended route before movement modifies or clears it.
1259  const pathfind::marked_route intended_route = current_route_;
1260 
1261  // do not show footsteps during movement
1262  gui().set_route(nullptr);
1263  gui().unhighlight_reach();
1264 
1265  // do not keep the hex highlighted that we started from
1268 
1269  bool interrupted = false;
1270  std::size_t num_moves = 0;
1271 
1272  if(intended_route.steps.size() > 1) {
1273  num_moves = move_unit_along_route(intended_route.steps, interrupted);
1274 
1275  interrupted = interrupted || num_moves + 1 < intended_route.steps.size();
1276  next_unit_ = intended_route.steps[num_moves];
1277  }
1278 
1279  // TRIGGER FLASH-FADE:
1280  // If the unit didn't finish the path (0 MP or interruption), visualize the remainder.
1281  if(intended_route.steps.size() > num_moves + 1) {
1282  pathfind::marked_route remaining_route;
1283  for(auto i = intended_route.steps.begin() + num_moves; i != intended_route.steps.end(); ++i) {
1284  remaining_route.steps.push_back(*i);
1285  if(auto it = intended_route.marks.find(*i); it != intended_route.marks.end()) {
1286  remaining_route.marks[*i] = it->second;
1287  }
1288  }
1289 
1290  gui().flash_fade_route(remaining_route);
1291  }
1292 
1293  // Cleanup state
1295  current_route_.steps.clear();
1296 
1297  return !interrupted;
1298 }
1299 
1300 /**
1301  * Moves a unit across the board for a player.
1302  * This is specifically for movement at the time it is initiated by a player,
1303  * whether via a mouse click or executing whiteboard actions. Continued moves
1304  * (including goto execution) can bypass this and call actions::move_unit() directly.
1305  * This function call may include time for an animation, so make sure the
1306  * provided route will remain unchanged (the caller should probably make a local
1307  * copy).
1308  *
1309  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1310  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1311  * actions (and set to false otherwise).
1312  *
1313  * @returns The number of hexes entered. This can safely be used as an index
1314  * into steps to get the location where movement ended, provided
1315  * steps is not empty (the return value is guaranteed to be less
1316  * than steps.size() ).
1317  */
1318 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1319 {
1320  if(steps.empty()) {
1321  interrupted = false;
1322  return 0;
1323  }
1324 
1325  // Default return value.
1326  interrupted = true;
1327 
1328  // If this is a leader on a keep, ask permission to the whiteboard to move it
1329  // since otherwise it may cause planned recruits to be erased.
1330  if(pc_.get_map().is_keep(steps.front())) {
1331  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1332 
1333  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1334  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1336  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1337  return 0;
1338  }
1339  }
1340 
1341  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1342  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1343 
1346 
1347  if(moves == 0)
1348  return 0;
1349 
1350  if(interrupted && moves + 1 < steps.size()) {
1351  // reselect the unit (for "press t to continue")
1352  select_hex(steps[moves], false, false, false);
1353  // the new discovery is more important than the new movement range
1354  show_partial_move_ = true;
1355  }
1356 
1357  return moves;
1358 }
1359 
1361  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1362 {
1363  {
1364  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1365  // This method should do the cleanup of highlights and selection but it doesn't work properly
1366 
1367  // gui().highlight_hex(map_location());
1368 
1369  gui().unhighlight_reach();
1371 
1372  // remove footsteps if any - useless for whiteboard as of now
1373  gui().set_route(nullptr);
1374 
1375  // do not keep the hex that we started from highlighted
1378  show_partial_move_ = false;
1379 
1380  // invalid after saving the move
1382  current_route_.steps.clear();
1383  }
1384 
1385  // create planned attack for whiteboard
1386  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1387 }
1388 
1390  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1391 {
1392  int best = 0;
1393  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1394  // skip weapons with attack_weight=0
1395  if(attacker->attacks()[i].attack_weight() > 0) {
1396  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1397 
1398  // Don't include if the attacker's weapon has at least one active "disable" special.
1399  if(bc.get_attacker_stats().disable) {
1400  continue;
1401  }
1402 
1403  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1404  // as some weapons can be hidden, i is not a valid index into the resulting vector
1405  best = bc_vector.size();
1406  }
1407 
1408  bc_vector.emplace_back(std::move(bc));
1409  }
1410  }
1411 
1412  return best;
1413 }
1414 
1415 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1416 {
1417  game_board& board = pc_.gamestate().board_;
1418  unit_map::iterator attacker;
1419  unit_map::iterator defender;
1420  std::vector<battle_context> bc_vector;
1421  std::vector<gui2::widget_data> bc_widget_data_vector;
1422  int best;
1423  {
1424  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1425  temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1426 
1427  attacker = board.units().find(attacker_loc);
1428  defender = board.units().find(defender_loc);
1429 
1430  if(!attacker || !defender) {
1431  if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1432  if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1433  return -1; // abort, click will do nothing
1434  }
1435 
1436  best = fill_weapon_choices(bc_vector, attacker, defender);
1437 
1438  if (bc_vector.empty()) {
1439  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1440 
1441  return -1;
1442  }
1443 
1444  static const config empty;
1445  static const_attack_ptr no_weapon(new attack_type(empty));
1446 
1447  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1448  for(const auto& weapon : bc_vector) {
1449  const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1450  const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1451 
1452  const attack_type& attacker_weapon = *attacker_stats.weapon;
1453  const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1454 
1455  const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1456  const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1457 
1458  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1459  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1460 
1461  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1462  if(!range.empty()) {
1463  range = string_table["range_" + range];
1464  }
1465 
1466  auto ctx = specials_context_t::make(
1467  { attacker.get_shared_ptr(), attacker->get_location(), attacker_stats.weapon },
1468  { defender.get_shared_ptr(), defender->get_location(), defender_stats.weapon },
1469  true);
1470 
1471  std::string types = attacker_weapon.effective_damage_type().first;
1472  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1473  if(!attw_type.empty()) {
1474  attw_type = string_table["type_" + attw_type];
1475  }
1476  std::string def_types = defender_weapon.effective_damage_type().first;
1477  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1478  if(!defw_type.empty()) {
1479  defw_type = string_table["type_" + defw_type];
1480  }
1481 
1482  const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains",
1483  "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1484  std::string attw_specials = ctx.describe_weapon_specials(attacker_weapon);
1485  std::string attw_specials_dmg = ctx.describe_weapon_specials_value(attacker_weapon, {"leadership", "damage"});
1486  std::string attw_specials_atk = ctx.describe_weapon_specials_value(attacker_weapon, {"attacks", "swarm"});
1487  std::string attw_specials_cth = ctx.describe_weapon_specials_value(attacker_weapon, {"chance_to_hit"});
1488  std::string attw_specials_others = ctx.describe_weapon_specials_value(attacker_weapon, checking_tags_other);
1489  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1490  && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1491  std::string defw_specials = defender_attack ? ctx.describe_weapon_specials(defender_weapon) : "";
1492  std::string defw_specials_dmg
1493  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"leadership", "damage"}) : "";
1494  std::string defw_specials_atk
1495  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"attacks", "swarm"}) : "";
1496  std::string defw_specials_cth
1497  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"chance_to_hit"}) : "";
1498  std::string defw_specials_others
1499  = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, checking_tags_other) : "";
1500 
1501  if(!attw_specials.empty()) {
1502  attw_specials = " " + attw_specials;
1503  }
1504  if(!attw_specials_dmg.empty()) {
1505  attw_specials_dmg = " " + attw_specials_dmg;
1506  }
1507  if(!attw_specials_atk.empty()) {
1508  attw_specials_atk = " " + attw_specials_atk;
1509  }
1510  if(!attw_specials_cth.empty()) {
1511  attw_specials_cth = " " + attw_specials_cth;
1512  }
1513  if(!attw_specials_others.empty()) {
1514  attw_specials_others
1515  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1516  }
1517  if(!defw_specials.empty()) {
1518  defw_specials = " " + defw_specials;
1519  }
1520  if(!defw_specials_dmg.empty()) {
1521  defw_specials_dmg = " " + defw_specials_dmg;
1522  }
1523  if(!defw_specials_atk.empty()) {
1524  defw_specials_atk = " " + defw_specials_atk;
1525  }
1526  if(!defw_specials_cth.empty()) {
1527  defw_specials_cth = " " + defw_specials_cth;
1528  }
1529  if(!defw_specials_others.empty()) {
1530  defw_specials_others
1531  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1532  }
1533 
1534  std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1535 
1536  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1537  // not consider the swarm weapon special
1538  attacker_stats_str << markup::bold(attw_name) << "\n"
1539  << attw_type << "\n"
1540  << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1541  << attw_specials << "\n"
1542  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1543 
1544  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1545  << _("Type: ") << attw_type << "\n"
1546  << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1547  << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1548  << _("Chance to hit: ")
1549  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1550  << markup::italic(attw_specials_cth) << attw_specials_others;
1551 
1552  defender_stats_str << markup::bold(defw_name) << "\n"
1553  << defw_type << "\n"
1554  << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1555  << defw_specials << "\n"
1556  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1557 
1558  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1559  << _("Type: ") << defw_type << "\n"
1560  << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1561  << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1562  << _("Chance to hit: ")
1563  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1564  << markup::italic(defw_specials_cth) << defw_specials_others;
1565 
1567  gui2::widget_item item;
1568 
1569  item["use_markup"] = "true";
1570 
1571  item["label"] = attacker_weapon.icon();
1572  data.emplace("attacker_weapon_icon", item);
1573 
1574  item["tooltip"] = attacker_tooltip.str();
1575  item["label"] = attacker_stats_str.str();
1576  data.emplace("attacker_weapon", item);
1577  item["tooltip"] = "";
1578 
1579  item["label"]
1580  = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1581  data.emplace("range", item);
1582 
1583  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1584  item["label"] = defender_stats_str.str();
1585  data.emplace("defender_weapon", item);
1586 
1587  item["tooltip"] = "";
1588  item["label"] = defender_weapon.icon();
1589  data.emplace("defender_weapon_icon", item);
1590 
1591  bc_widget_data_vector.emplace_back(data);
1592  }
1593  }
1594  // bc_widget_data_vector won't be empty when it reaches here.
1595  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector);
1596 
1597  if(dlg.show()) {
1598  return dlg.get_selected_weapon();
1599  }
1600 
1601  return -1;
1602 }
1603 
1604 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1605 {
1606  try {
1607  attack_enemy_(attacker_loc, defender_loc, choice);
1608  } catch(const std::bad_alloc&) {
1609  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1610  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1611  }
1612 }
1613 
1614 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1615 {
1616  // NOTE: copy the values because the const reference may change!
1617  // (WML events and mouse inputs during animations may modify
1618  // the data of the caller)
1619  const map_location attacker_loc = att_loc;
1620  const map_location defender_loc = def_loc;
1621 
1622  unit* attacker = nullptr;
1623  const unit* defender = nullptr;
1624  std::vector<battle_context> bc_vector;
1625 
1626  {
1627  unit_map::iterator attacker_it = find_unit(attacker_loc);
1628  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1629  return;
1630  }
1631 
1632  unit_map::iterator defender_it = find_unit(defender_loc);
1633  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1634  return;
1635  }
1636 
1637  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1638 
1639  attacker = &*attacker_it;
1640  defender = &*defender_it;
1641  }
1642 
1643  if(std::size_t(choice) >= bc_vector.size()) {
1644  return;
1645  }
1646 
1647  events::command_disabler disabler;
1648  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1649  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1650 
1651  attacker->set_goto(map_location());
1652 
1654 
1655  // make the attacker's stats appear during the attack
1656  gui().display_unit_hex(attacker_loc);
1657 
1658  // remove highlighted hexes etc..
1662  gui().unhighlight_reach();
1663 
1665  // TODO: change ToD to be location specific for the defender
1666 
1667  const tod_manager& tod_man = pc_.get_tod_manager();
1668 
1671  attacker_loc,
1672  defender_loc,
1673  att.attack_num,
1674  def.attack_num,
1675  attacker->type_id(),
1676  defender->type_id(),
1677  att.level,
1678  def.level,
1679  tod_man.turn(),
1680  tod_man.get_time_of_day()
1681  )
1682  );
1683 }
1684 
1686 {
1687  game_board& board = pc_.gamestate().board_;
1688 
1689  if(!it) {
1690  return false;
1691  }
1692 
1693  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1694  return false;
1695  }
1696 
1697  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1698  return false;
1699  }
1700 
1701  if(it->get_hidden()) {
1702  return false;
1703  }
1704 
1705  return true;
1706 }
1707 
1708 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1709 {
1710  unit_map& units = pc_.get_units();
1711 
1712  if(units.begin() == units.end()) {
1713  return;
1714  }
1715 
1717  if(!it) {
1718  it = units.begin();
1719  }
1720 
1721  const unit_map::const_iterator itx = it;
1722 
1723  do {
1724  if(reverse) {
1725  if(it == units.begin()) {
1726  it = units.end();
1727  }
1728 
1729  --it;
1730  } else {
1731  if(it == units.end()) {
1732  it = units.begin();
1733  } else {
1734  ++it;
1735  }
1736  }
1737  } while(it != itx && !unit_in_cycle(it));
1738 
1739  if(unit_in_cycle(it)) {
1740  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1741 
1742  select_hex(it->get_location(), browse);
1743  // mouse_update(browse);
1744  }
1745 }
1746 
1748 {
1749  gui().unhighlight_reach();
1750 
1751  current_paths_ = new_paths;
1752  current_route_.steps.clear();
1753 
1754  gui().set_route(nullptr);
1755 
1756  pc_.get_whiteboard()->erase_temp_move();
1757 }
1758 
1760 {
1761  return pc_.get_teams()[side_num_ - 1];
1762 }
1763 
1765 
1767 {
1769 }
1770 
1772 {
1774 }
1775 
1776 } // end namespace events
static bool is_enemy(std::size_t side, std::size_t other_side)
Definition: abilities.cpp:1063
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
Various functions related to moving units.
const std::string & range() const
Definition: attack_type.hpp:50
const std::string & type() const
Definition: attack_type.hpp:48
int num_attacks() const
Definition: attack_type.hpp:58
const t_string & name() const
Definition: attack_type.hpp:46
const std::string & icon() const
Definition: attack_type.hpp:49
int damage() const
Definition: attack_type.hpp:57
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:165
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition: attack.hpp:191
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition: attack.cpp:344
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
can_move_result unit_can_move(const unit &u) const
Work out what u can do - this does not check which player's turn is currently active,...
const team & viewing_team() const
Definition: display.cpp:332
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:305
void scroll_to_tile(const map_location &loc, SCROLL_TYPE scroll_type=ONSCREEN, bool check_fogged=true, bool force=true)
Scroll such that location loc is on-screen.
Definition: display.cpp:1858
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:664
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2963
map_location minimap_location_on(int x, int y)
given x,y co-ordinates of the mouse, will return the location of the hex in the minimap that the mous...
Definition: display.cpp:686
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:485
map_location hex_clicked_on(int x, int y) const
given x,y co-ordinates of an onscreen pixel, will return the location of the hex that this pixel corr...
Definition: display.cpp:528
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1583
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
map_location last_hex_
last highlighted hex
bool scroll_started_
Scroll start flag.
map_location drag_from_hex_
Drag start or mouse-down map location.
bool simple_warp_
MMB click (on game map) state flag.
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
bool dragging_started_
Actual drag flag.
bool dragging_touch_
Finger drag init flag.
point drag_from_
Drag start position.
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
int fill_weapon_choices(std::vector< battle_context > &bc_vector, const unit_map::iterator &attacker, const unit_map::iterator &defender)
bool move_unit_along_current_route()
Moves a unit along the currently cached route.
bool mouse_button_event(const SDL_MouseButtonEvent &event, uint8_t button, map_location loc, bool click=false) override
map_location previous_hex_
void show_reach_for_unit(const unit_ptr &un)
Highlight the hexes that a unit can move to.
game_display & gui() override
Due to the way this class is constructed we can assume that the display* gui_ member actually points ...
void select_or_action(bool browse)
void attack_enemy_(const map_location &attacker_loc, const map_location &defender_loc, int choice)
void save_whiteboard_attack(const map_location &attacker_loc, const map_location &defender_loc, int weapon_choice)
int show_attack_dialog(const map_location &attacker_loc, const map_location &defender_loc, const map_location &attacker_src)
void set_current_paths(const pathfind::paths &new_paths)
pathfind::marked_route get_route(const unit *un, map_location go_to, const team &team) const
play_controller & pc_
unit_map::iterator selected_unit()
static mouse_handler * singleton_
void enable_units_highlight()
When unit highlighting is disabled, call this when the mouse no longer hovers any unit to enable high...
mouse_handler(play_controller &pc)
void touch_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
void attack_enemy(const map_location &attacker_loc, const map_location &defender_loc, int choice)
map_location previous_free_hex_
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true, const bool force_unhighlight=false)
unit_map::const_iterator find_unit(const map_location &hex) const
map_location current_unit_attacks_from(const map_location &loc) const
bool right_click_show_menu(int x, int y, const bool browse) override
Called in the default right_click when the context menu is about to be shown, can be used for preproc...
map_location selected_hex_
pathfind::marked_route current_route_
const map_location hovered_hex() const
Uses SDL and game_display::hex_clicked_on to fetch the hex the mouse is hovering, if applicable.
bool unit_in_cycle(const unit_map::const_iterator &it)
void touch_action(const map_location hex, bool browse) override
void set_side(int side_number)
void move_action(bool browse) override
Overridden in derived class.
pathfind::paths current_paths_
If non-empty, current_paths_.destinations contains a cache of highlighted hexes, likely the movement ...
bool hex_hosts_unit(const map_location &hex) const
Unit exists on the hex, no matter if friend or foe.
int drag_threshold() const override
Minimum dragging distance to fire the drag&drop.
unit * find_unit_nonowning(const map_location &hex)
void cycle_units(const bool browse, const bool reverse=false)
std::size_t move_unit_along_route(const std::vector< map_location > &steps, bool &interrupted)
Moves a unit across the board for a player.
void mouse_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
Use update to force an update of the mouse state.
Game board class.
Definition: game_board.hpp:47
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp: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.
void flash_fade_route(const pathfind::marked_route &route)
Visualizes a path by flashing it white then fade out over 2 seconds.
virtual void highlight_hex(map_location hex) override
Function to highlight a location.
void set_attack_indicator(const map_location &src, const map_location &dst)
Set the attack direction indicator.
void set_route(const pathfind::marked_route *route)
Sets the route along which footsteps are drawn to show movement of a unit.
virtual void select_hex(map_location hex) override
Function to display a location as selected.
void highlight_reach(const pathfind::paths &paths_list)
Sets the paths that are currently displayed as available for the unit to move along.
void clear_attack_indicator()
void highlight_another_reach(const pathfind::paths &paths_list, const map_location &goal=map_location::null_location())
Add more paths to highlight.
bool unhighlight_reach()
Reset highlighting of paths.
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:48
game_board board_
Definition: game_state.hpp:44
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:347
bool is_keep(const map_location &loc) const
Definition: map.cpp:66
bool show(const unsigned auto_close_time=0)
Shows the window.
std::vector< team > & get_teams()
const unit_map & get_units() const
game_state & gamestate()
const tod_manager & get_tod_manager() const
int current_side() const
Returns the number of the side whose turn it is.
std::shared_ptr< wb::manager > get_whiteboard() const
const gamemap & get_map() const
game_events::wml_event_pump & pump()
static config get_attack(const map_location &a, const map_location &b, int att_weapon, int def_weapon, const std::string &attacker_type_id, const std::string &defender_type_id, int attacker_lvl, int defender_lvl, const std::size_t turn, const time_of_day &t)
static specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
Definition: abilities.hpp:275
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition: tstring.hpp:200
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:74
int side() const
Definition: team.hpp: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:100
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1954
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition: unit.hpp:642
int side() const
The side this unit belongs to.
Definition: unit.hpp:249
unit_animation_component & anim_comp() const
Definition: unit.hpp:1537
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1327
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1364
Contains functions for cleanly handling SDL input.
auto string_table
Definition: language.hpp:70
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition: location.cpp:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:513
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
#define ERR_WML
static lg::log_domain log_wml("wml")
#define LOG_NG
void teleport_unit_and_record(const map_location &teleport_from, const map_location &teleport_to, move_unit_spectator *)
Teleports a unit across the board and enters the synced context.
Definition: move.cpp:1399
std::size_t move_unit_and_record(const std::vector< map_location > &steps, bool continued_move, bool *interrupted)
Wrapper around the other overload.
Definition: move.cpp:1431
int side_number
Definition: game_info.hpp:40
CURSOR_TYPE get()
Definition: cursor.cpp:205
@ WAIT
Definition: cursor.hpp:28
@ ATTACK
Definition: cursor.hpp:28
@ MOVE_DRAG
Definition: cursor.hpp:28
@ NORMAL
Definition: cursor.hpp:28
@ ATTACK_DRAG
Definition: cursor.hpp:28
@ MOVE
Definition: cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:171
static void update()
Handling of system events.
const std::string unicode_em_dash
Definition: constants.cpp:44
const std::string weapon_numbers_sep
Definition: constants.cpp:49
std::string path
Definition: filesystem.cpp:106
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
bool fire_event(const ui_event event, const std::vector< std::pair< widget *, ui_event >> &event_chain, widget *dispatcher, widget *w, F &&... params)
Helper function for fire_event.
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
unsigned screen_height
Definition: settings.cpp:28
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
General purpose widgets.
std::stringstream & log_to_chat()
Use this to show WML errors in the ingame chat.
Definition: log.cpp:550
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition: markup.hpp:161
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
marked_route mark_route(const plain_route &rt, bool update_move_cost)
Add marks on a route rt assuming that the unit located at the first hex of rt travels along it.
Definition: pathfind.cpp:658
const teleport_map get_teleport_locations(const unit &u, const team &viewing_team, bool see_all, bool ignore_units, bool check_vision)
Definition: teleport.cpp:251
uint32_t get_mouse_state(float *x, float *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition: input.cpp:27
point get_mouse_location()
Returns the current mouse location in draw space.
Definition: input.cpp:54
void play_UI_sound(const std::string &files)
Definition: sound.cpp:952
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.