The Battle for Wesnoth  1.19.15+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  }
919 #ifdef __ANDROID__
920  else if (clicked_u && clicked_u->side() != side_num_) {
921  select_hex(last_hex_, false);
922  }
923 #endif
924  else {
925  move_action(browse);
926  }
927  teleport_selected_ = false;
928 }
929 
930 void mouse_handler::move_action(bool browse)
931 {
932  // Lock whiteboard activation state to avoid problems due to
933  // its changing while an animation takes place.
934  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
935 
936  // we use the last registered highlighted hex
937  // since it's what update our global state
938  map_location hex = last_hex_;
939 
940  // TODO
941  // // Clicks on border hexes mean to deselect.
942  // // (Check this before doing processing that might not be needed.)
943  // if ( !pc_.get_map().on_board(hex) ) {
944  // deselect_hex();
945  // return false;
946  // }
947 
948  unit* u = nullptr;
949  const unit* clicked_u = nullptr;
950 
952  pathfind::paths orig_paths;
953  map_location attack_from;
954 
955  { // start planned unit map scope
956  wb::future_map_if_active planned_unit_map;
958 
959  // if the unit is selected and then itself clicked on,
960  // any goto command is canceled
961  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
962  u->set_goto(map_location());
963  }
964 
965  clicked_u = find_unit_nonowning(hex);
966 
967  src = selected_hex_;
968  orig_paths = current_paths_;
969  attack_from = current_unit_attacks_from(hex);
970  } // end planned unit map scope
971 
972  // See if the teleport option is toggled
973  if(teleport_selected_) {
974  teleport_action();
975  }
976  // see if we're trying to do a attack or move-and-attack
977  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
978  // Ignore this command if commands are disabled.
979  if(commands_disabled) {
980  return;
981  }
982 
983  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
984  if(attack_from == selected_hex_) { // no move needed
985  int choice = -1;
986  {
987  wb::future_map_if_active planned_unit_map; // start planned unit map scope
988  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
989  } // end planned unit map scope
990 
991  if(choice >= 0) {
992  if(pc_.get_whiteboard()->is_active()) {
993  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
994  } else {
995  // clear current unit selection so that any other unit selected
996  // triggers a new selection
998 
999  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
1000  }
1001  }
1002 
1003  return;
1004  } else {
1005  int choice = -1; // for the attack dialog
1006 
1007  {
1008  wb::future_map_if_active planned_unit_map; // start planned unit map scope
1009  // we will now temporary move next to the enemy
1010  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
1011  if(itor == current_paths_.destinations.end()) {
1012  // can't reach the attacking location
1013  // not supposed to happen, so abort
1014  return;
1015  }
1016 
1017  // block where we temporary move the unit
1018  {
1019  choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1020  }
1021 
1022  if(choice < 0) {
1023  // user hit cancel or attack is invalid, don't start move&attack
1024  return;
1025  }
1026  } // end planned unit map scope
1027 
1028  if(pc_.get_whiteboard()->is_active()) {
1029  save_whiteboard_attack(attack_from, hex, choice);
1030  } else {
1031  bool not_interrupted = move_unit_along_current_route();
1032  bool alt_unit_selected = (selected_hex_ != src);
1033  src = selected_hex_;
1034  // clear current unit selection so that any other unit selected
1035  // triggers a new selection
1037 
1038  if(not_interrupted)
1039  attack_enemy(attack_from, hex, choice); // Fight !!
1040 
1041  // TODO: Maybe store the attack choice so "press t to continue"
1042  // can also continue the attack?
1043 
1044  if(alt_unit_selected && !selected_hex_.valid()) {
1045  // reselect other unit if selected during movement animation
1046  select_hex(src, browse);
1047  }
1048  }
1049 
1050  return;
1051  }
1052  }
1053  }
1054  // otherwise we're trying to move to a hex
1055  else if(
1056  // The old use case: move selected unit to mouse hex field.
1057  (
1058  (!browse || pc_.get_whiteboard()->is_active())
1059  && selected_hex_.valid()
1060  && selected_hex_ != hex
1061  && u != nullptr
1062  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1063  && !clicked_u
1064  && !current_route_.steps.empty()
1065  && current_route_.steps.front() == selected_hex_
1066  )
1067  || // The new use case: move mouse unit to selected hex field.
1068  (
1069  (!browse || pc_.get_whiteboard()->is_active())
1070  && selected_hex_.valid()
1071  && selected_hex_ != hex
1072  && clicked_u
1073  && !current_route_.steps.empty()
1074  && current_route_.steps.back() == selected_hex_
1075  && !u
1076  && clicked_u->side() == side_num_
1077  )
1078  ) {
1079  // Ignore this command if commands are disabled.
1080  if(commands_disabled) {
1081  return;
1082  }
1083 
1084  // If the whiteboard is active, it intercepts any unit movement.
1085  if(pc_.get_whiteboard()->is_active()) {
1086  // Deselect the current hex, and create planned move for whiteboard.
1088 
1091  gui().set_route(nullptr);
1092 
1093  show_partial_move_ = false;
1094 
1095  gui().unhighlight_reach();
1096 
1098  current_route_.steps.clear();
1099 
1100  pc_.get_whiteboard()->save_temp_move();
1101 
1102  // Otherwise proceed to normal unit movement
1103  } else {
1104  // Don't move if the unit already has actions
1105  // from the whiteboard.
1106  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1107  return;
1108  }
1109 
1111 
1112  // During the move, we may have selected another unit
1113  // (but without triggering a select event (command was disabled)
1114  // in that case reselect it now to fire the event (+ anim & sound)
1115  if(selected_hex_ != src) {
1116  select_hex(selected_hex_, browse);
1117  }
1118  }
1119 
1120  return;
1121  }
1122 }
1123 
1124 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1125 {
1126  unit_map::iterator unit = find_unit(touched_hex);
1127 #ifdef __ANDROID__
1128  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1129  if(touched_hex == selected_hex_) {
1130  deselect_hex();
1131  } else {
1132  select_or_action(browse);
1133  }
1134  }
1135 #else
1136  if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1137  select_or_action(browse);
1138  }
1139 #endif
1140 }
1141 
1142 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1143 {
1144  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1145 
1146  selected_hex_ = hex;
1147 
1150  gui().set_route(nullptr);
1151 
1152  show_partial_move_ = false;
1153 
1154  wb::future_map_if_active planned_unit_map; // lasts for whole method
1155 
1157 
1158  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1160 
1161  {
1162  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1163  }
1164 
1165  if(highlight) {
1167  }
1168 
1169  // The highlight now comes from selection
1170  // and not from the mouseover on an enemy
1171  unselected_paths_ = false;
1172  gui().set_route(nullptr);
1173 
1174  // Selection have impact only if we are not observing and it's our unit
1175  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1176  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1177  sound::play_UI_sound("select-unit.wav");
1178 
1180 
1181  if(fire_event) {
1182  // Ensure unit map is back to normal while event is fired
1183  wb::real_map srum;
1184  pc_.pump().fire("select", hex);
1185  // end forced real unit map
1186  }
1187  }
1188  }
1189 
1190  return;
1191  }
1192 
1193  if(selected_hex_.valid() && !unit) {
1194  // When no unit is selected, compute unit in range of the empty selected_hex field
1195 
1197 
1198  pathfind::paths reaching_unit_locations;
1199 
1200  pathfind::paths clicked_location;
1201  clicked_location.destinations.insert(hex);
1202 
1203  for(const ::unit& u : pc_.get_units()) {
1204  bool invisible = u.invisible(u.get_location());
1205 
1206  if(!gui_->fogged(u.get_location()) && !u.incapacitated() && !invisible) {
1207  const pathfind::paths& path =
1208  pathfind::paths(u, false, true, gui().viewing_team(), path_turns_, false, false);
1209 
1210  if(path.destinations.find(hex) != path.destinations.end()) {
1211  reaching_unit_locations.destinations.insert(u.get_location());
1212  gui_->highlight_another_reach(clicked_location);
1213  }
1214  }
1215  }
1216 
1217  gui_->highlight_another_reach(reaching_unit_locations);
1218  } else {
1219  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1220  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1222  }
1223 
1225  current_route_.steps.clear();
1226 
1227  pc_.get_whiteboard()->on_deselect_hex();
1228  }
1229 }
1230 
1232 {
1233  select_hex(map_location(), true);
1234 }
1235 
1236 /**
1237  * Moves a unit along the currently cached route.
1238  *
1239  * @returns true if the end of the route was reached and no information was
1240  * uncovered that would warrant interrupting a chain of actions;
1241  * false otherwise.
1242  */
1244 {
1245  // Copy the current route to ensure it remains valid throughout the animation.
1246  const std::vector<map_location> steps = current_route_.steps;
1247 
1248  // do not show footsteps during movement
1249  gui().set_route(nullptr);
1250  gui().unhighlight_reach();
1251 
1252  // do not keep the hex highlighted that we started from
1255 
1256  bool interrupted = false;
1257  if(steps.size() > 1) {
1258  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1259 
1260  interrupted = interrupted || num_moves + 1 < steps.size();
1261  next_unit_ = steps[num_moves];
1262  }
1263 
1264  // invalid after the move
1266  current_route_.steps.clear();
1267 
1268  return !interrupted;
1269 }
1270 
1271 /**
1272  * Moves a unit across the board for a player.
1273  * This is specifically for movement at the time it is initiated by a player,
1274  * whether via a mouse click or executing whiteboard actions. Continued moves
1275  * (including goto execution) can bypass this and call actions::move_unit() directly.
1276  * This function call may include time for an animation, so make sure the
1277  * provided route will remain unchanged (the caller should probably make a local
1278  * copy).
1279  *
1280  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1281  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1282  * actions (and set to false otherwise).
1283  *
1284  * @returns The number of hexes entered. This can safely be used as an index
1285  * into steps to get the location where movement ended, provided
1286  * steps is not empty (the return value is guaranteed to be less
1287  * than steps.size() ).
1288  */
1289 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1290 {
1291  if(steps.empty()) {
1292  interrupted = false;
1293  return 0;
1294  }
1295 
1296  // Default return value.
1297  interrupted = true;
1298 
1299  // If this is a leader on a keep, ask permission to the whiteboard to move it
1300  // since otherwise it may cause planned recruits to be erased.
1301  if(pc_.get_map().is_keep(steps.front())) {
1302  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1303 
1304  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1305  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1307  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1308  return 0;
1309  }
1310  }
1311 
1312  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1313  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1314 
1317 
1318  if(moves == 0)
1319  return 0;
1320 
1321  if(interrupted && moves + 1 < steps.size()) {
1322  // reselect the unit (for "press t to continue")
1323  select_hex(steps[moves], false, false, false);
1324  // the new discovery is more important than the new movement range
1325  show_partial_move_ = true;
1326  }
1327 
1328  return moves;
1329 }
1330 
1332  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1333 {
1334  {
1335  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1336  // This method should do the cleanup of highlights and selection but it doesn't work properly
1337 
1338  // gui().highlight_hex(map_location());
1339 
1340  gui().unhighlight_reach();
1342 
1343  // remove footsteps if any - useless for whiteboard as of now
1344  gui().set_route(nullptr);
1345 
1346  // do not keep the hex that we started from highlighted
1349  show_partial_move_ = false;
1350 
1351  // invalid after saving the move
1353  current_route_.steps.clear();
1354  }
1355 
1356  // create planned attack for whiteboard
1357  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1358 }
1359 
1361  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1362 {
1363  int best = 0;
1364  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1365  // skip weapons with attack_weight=0
1366  if(attacker->attacks()[i].attack_weight() > 0) {
1367  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1368 
1369  // Don't include if the attacker's weapon has at least one active "disable" special.
1370  if(bc.get_attacker_stats().disable) {
1371  continue;
1372  }
1373 
1374  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1375  // as some weapons can be hidden, i is not a valid index into the resulting vector
1376  best = bc_vector.size();
1377  }
1378 
1379  bc_vector.emplace_back(std::move(bc));
1380  }
1381  }
1382 
1383  return best;
1384 }
1385 
1386 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1387 {
1388  game_board& board = pc_.gamestate().board_;
1389  unit_map::iterator attacker;
1390  unit_map::iterator defender;
1391  std::vector<battle_context> bc_vector;
1392  std::vector<gui2::widget_data> bc_widget_data_vector;
1393  int best;
1394  int leadership_bonus = 0;
1395  {
1396  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1397  temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1398 
1399  attacker = board.units().find(attacker_loc);
1400  defender = board.units().find(defender_loc);
1401 
1402  if(!attacker || !defender) {
1403  if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1404  if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1405  return -1; // abort, click will do nothing
1406  }
1407 
1408  best = fill_weapon_choices(bc_vector, attacker, defender);
1409 
1410  if (bc_vector.empty()) {
1411  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1412 
1413  return -1;
1414  }
1415 
1416  static const config empty;
1417  static const_attack_ptr no_weapon(new attack_type(empty));
1418 
1419  // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1420  for(const auto& weapon : bc_vector) {
1421  const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1422  const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1423 
1424  const attack_type& attacker_weapon = *attacker_stats.weapon;
1425  const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1426 
1427  if(leadership_bonus == 0) {
1428  leadership_bonus
1429  = under_leadership(*attacker, attacker_loc, attacker_stats.weapon, defender_stats.weapon);
1430  }
1431 
1432  const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1433  const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1434 
1435  const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1436  const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1437 
1438  std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1439  if(!range.empty()) {
1440  range = string_table["range_" + range];
1441  }
1442 
1443  auto a_ctx = attacker_weapon.specials_context(attacker.get_shared_ptr(), defender.get_shared_ptr(),
1444  attacker->get_location(), defender->get_location(), true, defender_stats.weapon);
1445 
1446  auto d_ctx = defender_weapon.specials_context(defender.get_shared_ptr(), attacker.get_shared_ptr(),
1447  defender->get_location(), attacker->get_location(), false, attacker_stats.weapon);
1448 
1449  std::string types = attacker_weapon.effective_damage_type().first;
1450  std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1451  if(!attw_type.empty()) {
1452  attw_type = string_table["type_" + attw_type];
1453  }
1454  std::string def_types = defender_weapon.effective_damage_type().first;
1455  std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1456  if(!defw_type.empty()) {
1457  defw_type = string_table["type_" + defw_type];
1458  }
1459 
1460  const std::set<std::string> checking_tags_other = {"defense", "damage_type", "disable", "berserk", "drains",
1461  "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1462  std::string attw_specials = attacker_weapon.weapon_specials();
1463  std::string attw_specials_dmg = attacker_weapon.weapon_specials_value({"leadership", "damage"});
1464  std::string attw_specials_atk = attacker_weapon.weapon_specials_value({"attacks", "swarm"});
1465  std::string attw_specials_cth = attacker_weapon.weapon_specials_value({"chance_to_hit"});
1466  std::string attw_specials_others = attacker_weapon.weapon_specials_value(checking_tags_other);
1467  bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1468  && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1469  std::string defw_specials = defender_attack ? defender_weapon.weapon_specials() : "";
1470  std::string defw_specials_dmg
1471  = defender_attack ? defender_weapon.weapon_specials_value({"leadership", "damage"}) : "";
1472  std::string defw_specials_atk
1473  = defender_attack ? defender_weapon.weapon_specials_value({"attacks", "swarm"}) : "";
1474  std::string defw_specials_cth
1475  = defender_attack ? defender_weapon.weapon_specials_value({"chance_to_hit"}) : "";
1476  std::string defw_specials_others
1477  = defender_attack ? defender_weapon.weapon_specials_value(checking_tags_other) : "";
1478 
1479  if(!attw_specials.empty()) {
1480  attw_specials = " " + attw_specials;
1481  }
1482  if(!attw_specials_dmg.empty()) {
1483  attw_specials_dmg = " " + attw_specials_dmg;
1484  }
1485  if(!attw_specials_atk.empty()) {
1486  attw_specials_atk = " " + attw_specials_atk;
1487  }
1488  if(!attw_specials_cth.empty()) {
1489  attw_specials_cth = " " + attw_specials_cth;
1490  }
1491  if(!attw_specials_others.empty()) {
1492  attw_specials_others
1493  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1494  }
1495  if(!defw_specials.empty()) {
1496  defw_specials = " " + defw_specials;
1497  }
1498  if(!defw_specials_dmg.empty()) {
1499  defw_specials_dmg = " " + defw_specials_dmg;
1500  }
1501  if(!defw_specials_atk.empty()) {
1502  defw_specials_atk = " " + defw_specials_atk;
1503  }
1504  if(!defw_specials_cth.empty()) {
1505  defw_specials_cth = " " + defw_specials_cth;
1506  }
1507  if(!defw_specials_others.empty()) {
1508  defw_specials_others
1509  = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1510  }
1511 
1512  std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1513 
1514  // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1515  // not consider the swarm weapon special
1516  attacker_stats_str << markup::bold(attw_name) << "\n"
1517  << attw_type << "\n"
1518  << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1519  << attw_specials << "\n"
1520  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1521 
1522  attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1523  << _("Type: ") << attw_type << "\n"
1524  << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1525  << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1526  << _("Chance to hit: ")
1527  << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1528  << markup::italic(attw_specials_cth) << attw_specials_others;
1529 
1530  defender_stats_str << markup::bold(defw_name) << "\n"
1531  << defw_type << "\n"
1532  << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1533  << defw_specials << "\n"
1534  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1535 
1536  defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1537  << _("Type: ") << defw_type << "\n"
1538  << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1539  << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1540  << _("Chance to hit: ")
1541  << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1542  << markup::italic(defw_specials_cth) << defw_specials_others;
1543 
1545  gui2::widget_item item;
1546 
1547  item["use_markup"] = "true";
1548 
1549  item["label"] = attacker_weapon.icon();
1550  data.emplace("attacker_weapon_icon", item);
1551 
1552  item["tooltip"] = attacker_tooltip.str();
1553  item["label"] = attacker_stats_str.str();
1554  data.emplace("attacker_weapon", item);
1555  item["tooltip"] = "";
1556 
1557  item["label"]
1558  = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1559  data.emplace("range", item);
1560 
1561  item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1562  item["label"] = defender_stats_str.str();
1563  data.emplace("defender_weapon", item);
1564 
1565  item["tooltip"] = "";
1566  item["label"] = defender_weapon.icon();
1567  data.emplace("defender_weapon_icon", item);
1568 
1569  bc_widget_data_vector.emplace_back(data);
1570  }
1571  }
1572  // bc_widget_data_vector won't be empty when it reaches here.
1573  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector, leadership_bonus);
1574 
1575  if(dlg.show()) {
1576  return dlg.get_selected_weapon();
1577  }
1578 
1579  return -1;
1580 }
1581 
1582 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1583 {
1584  try {
1585  attack_enemy_(attacker_loc, defender_loc, choice);
1586  } catch(const std::bad_alloc&) {
1587  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1588  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1589  }
1590 }
1591 
1592 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1593 {
1594  // NOTE: copy the values because the const reference may change!
1595  // (WML events and mouse inputs during animations may modify
1596  // the data of the caller)
1597  const map_location attacker_loc = att_loc;
1598  const map_location defender_loc = def_loc;
1599 
1600  unit* attacker = nullptr;
1601  const unit* defender = nullptr;
1602  std::vector<battle_context> bc_vector;
1603 
1604  {
1605  unit_map::iterator attacker_it = find_unit(attacker_loc);
1606  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1607  return;
1608  }
1609 
1610  unit_map::iterator defender_it = find_unit(defender_loc);
1611  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1612  return;
1613  }
1614 
1615  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1616 
1617  attacker = &*attacker_it;
1618  defender = &*defender_it;
1619  }
1620 
1621  if(std::size_t(choice) >= bc_vector.size()) {
1622  return;
1623  }
1624 
1625  events::command_disabler disabler;
1626  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1627  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1628 
1629  attacker->set_goto(map_location());
1630 
1632 
1633  // make the attacker's stats appear during the attack
1634  gui().display_unit_hex(attacker_loc);
1635 
1636  // remove highlighted hexes etc..
1640  gui().unhighlight_reach();
1641 
1643  // TODO: change ToD to be location specific for the defender
1644 
1645  const tod_manager& tod_man = pc_.get_tod_manager();
1646 
1649  attacker_loc,
1650  defender_loc,
1651  att.attack_num,
1652  def.attack_num,
1653  attacker->type_id(),
1654  defender->type_id(),
1655  att.level,
1656  def.level,
1657  tod_man.turn(),
1658  tod_man.get_time_of_day()
1659  )
1660  );
1661 }
1662 
1664 {
1665  game_board& board = pc_.gamestate().board_;
1666 
1667  if(!it) {
1668  return false;
1669  }
1670 
1671  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1672  return false;
1673  }
1674 
1675  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1676  return false;
1677  }
1678 
1679  if(it->get_hidden()) {
1680  return false;
1681  }
1682 
1683  return true;
1684 }
1685 
1686 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1687 {
1688  unit_map& units = pc_.get_units();
1689 
1690  if(units.begin() == units.end()) {
1691  return;
1692  }
1693 
1695  if(!it) {
1696  it = units.begin();
1697  }
1698 
1699  const unit_map::const_iterator itx = it;
1700 
1701  do {
1702  if(reverse) {
1703  if(it == units.begin()) {
1704  it = units.end();
1705  }
1706 
1707  --it;
1708  } else {
1709  if(it == units.end()) {
1710  it = units.begin();
1711  } else {
1712  ++it;
1713  }
1714  }
1715  } while(it != itx && !unit_in_cycle(it));
1716 
1717  if(unit_in_cycle(it)) {
1718  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1719 
1720  select_hex(it->get_location(), browse);
1721  // mouse_update(browse);
1722  }
1723 }
1724 
1726 {
1727  gui().unhighlight_reach();
1728 
1729  current_paths_ = new_paths;
1730  current_route_.steps.clear();
1731 
1732  gui().set_route(nullptr);
1733 
1734  pc_.get_whiteboard()->erase_temp_move();
1735 }
1736 
1738 {
1739  return pc_.get_teams()[side_num_ - 1];
1740 }
1741 
1743 
1745 {
1747 }
1748 
1750 {
1752 }
1753 
1754 } // 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:1588
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:995
std::string weapon_specials_value(const std::set< std::string > &checking_tags) const
Definition: abilities.cpp:1040
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:1321
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:447
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)
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: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: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:1956
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:1653
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1443
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1480
Contains functions for cleanly handling SDL input.
auto string_table
Definition: language.hpp:68
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
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:512
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:1051
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: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: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: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.