The Battle for Wesnoth  1.19.0-dev
mouse_events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2024
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 "actions/undo.hpp" // for undo_list
22 #include "config.hpp" // for config
23 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
24 #include "game_board.hpp" // for game_board, etc
25 #include "game_events/pump.hpp" // for fire
26 #include "gettext.hpp" // for _
27 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
28 #include "gui/dialogs/unit_attack.hpp" // for unit_attack
29 #include "gui/widgets/settings.hpp" // for new_widgets
30 #include "language.hpp" // for string_table, symbol_table
31 #include "log.hpp" // for LOG_STREAM, logger, etc
32 #include "map/map.hpp" // for gamemap
33 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
34 #include "play_controller.hpp" // for playing_side, set_button_state
35 #include "replay_helper.hpp"
37 #include "sound.hpp"
38 #include "synced_context.hpp"
39 #include "team.hpp" // for team
40 #include "tod_manager.hpp"
42 #include "units/ptr.hpp" // for unit_const_ptr
43 #include "units/unit.hpp" // for unit
44 #include "whiteboard/manager.hpp" // for manager, etc
45 #include "whiteboard/typedefs.hpp" // for whiteboard_lock
46 #include "sdl/input.hpp" // for get_mouse_state
47 
48 #include <cassert> // for assert
49 #include <new> // for bad_alloc
50 #include <ostream> // for operator<<, basic_ostream, etc
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_(gui)
65  , pc_(pc)
66  , previous_hex_()
67  , previous_free_hex_()
68  , selected_hex_()
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  , preventing_units_highlight_(false)
80 {
81  singleton_ = this;
82 }
83 
85 {
86  singleton_ = nullptr;
87 }
88 
90 {
92 }
93 
95 {
96  // Function uses window resolution as an estimate of users perception of distance
97  // Tune this variable if necessary:
98  const unsigned threshold_1080p = 14; // threshold number of pixels for 1080p
99  double screen_diagonal = std::hypot(gui2::settings::screen_width,gui2::settings::screen_height);
100  const double scale_factor = threshold_1080p / std::hypot(1080,1920);
101  return static_cast<int>(screen_diagonal * scale_factor);
102 }
103 
104 void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, map_location new_hex)
105 {
106  // Frankensteining from mouse_motion(), as it has a lot in common, but a lot of differences too.
107  // Copy-pasted from everywhere. TODO: generalize the two.
108  sdl::get_mouse_state(&x,&y);
109 
110  // This is from mouse_handler_base::mouse_motion_default()
111  tooltips::process(x, y);
112 
113  if(simple_warp_) {
114  return;
115  }
116 
117  if(minimap_scrolling_) {
118  const map_location& mini_loc = gui().minimap_location_on(x,y);
119  if(mini_loc.valid()) {
120  if(mini_loc != last_hex_) {
121  last_hex_ = mini_loc;
122  gui().scroll_to_tile(mini_loc,display::WARP,false);
123  }
124  return;
125  } else {
126  // clicking outside of the minimap will end minimap scrolling
127  minimap_scrolling_ = false;
128  }
129  }
130 
131  // Fire the drag & drop only after minimal drag distance
132  // While we check the mouse buttons state, we also grab fresh position data.
133  int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
134  int my = drag_from_y_;
135  if(is_dragging() && !dragging_started_) {
136  if(dragging_touch_) {
137  sdl::get_mouse_state(&mx, &my);
138  const double drag_distance = std::pow(static_cast<double>(drag_from_x_- mx), 2)
139  + std::pow(static_cast<double>(drag_from_y_- my), 2);
140  if(drag_distance > drag_threshold()*drag_threshold()) {
141  dragging_started_ = true;
142  }
143  }
144  }
145 
146  // Not-so-smooth panning
147  const auto found_unit = find_unit(selected_hex_);
148  bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
149  if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
150  sdl::get_mouse_state(&mx, &my);
151 
152  if(gui().map_area().contains(x, y)) {
153  int dx = drag_from_x_ - mx;
154  int dy = drag_from_y_ - my;
155 
156  gui().scroll(dx, dy);
157  drag_from_x_ = mx;
158  drag_from_y_ = my;
159  }
160  return;
161  }
162 
163  // now copy-pasting mouse_handler::mouse_motion()
164 
165  // Note for anyone reconciling this code with the version in mouse_handler::mouse_motion:
166  // commit 27a40a82aeea removed the game_board& board from mouse_motion, but didn't update
167  // the corresponding code here in touch_motion.
168  game_board & board = pc_.gamestate().board_;
169 
170  if(new_hex == map_location::null_location())
171  new_hex = gui().hex_clicked_on(x,y);
172 
173  if(new_hex != last_hex_) {
174  update = true;
175  if( pc_.get_map().on_board(last_hex_) ) {
176  // we store the previous hexes used to propose attack direction
178  // the hex of the selected unit is also "free"
179  { // start planned unit map scope
183  }
184  } // end planned unit map scope
185  }
186  last_hex_ = new_hex;
187  }
188 
189  if(reachmap_invalid_) update = true;
190 
191  if(!update) return;
192 
193  if(reachmap_invalid_) {
194  reachmap_invalid_ = false;
196  { // start planned unit map scope
197  wb::future_map_if_active planned_unit_map;
198  selected_hex_has_my_unit = found_unit.valid();
199  } // end planned unit map scope
200  if(selected_hex_.valid() && selected_hex_has_my_unit) {
201  // FIXME: vic: why doesn't this trigger when touch-dragging an unselected unit?
202  // reselect the unit without firing events (updates current_paths_)
203  select_hex(selected_hex_, true);
204  }
205  // we do never deselect here, mainly because of canceled attack-move
206  }
207  }
208 
209  // reset current_route_ and current_paths if not valid anymore
210  // we do it before cursor selection, because it uses current_paths_
211  if( !pc_.get_map().on_board(new_hex) ) {
212  current_route_.steps.clear();
213  gui().set_route(nullptr);
214  pc_.get_whiteboard()->erase_temp_move();
215  }
216 
217  if(unselected_paths_) {
218  unselected_paths_ = false;
221  } else if(over_route_) {
222  over_route_ = false;
223  current_route_.steps.clear();
224  gui().set_route(nullptr);
225  pc_.get_whiteboard()->erase_temp_move();
226  }
227 
228  gui().highlight_hex(new_hex);
229  pc_.get_whiteboard()->on_mouseover_change(new_hex);
230 
232  unit_map::iterator mouseover_unit;
233  map_location attack_from;
234 
235  { // start planned unit map scope
236  wb::future_map_if_active planned_unit_map;
237  selected_unit = found_unit;
238  mouseover_unit = find_unit(new_hex);
239 
240  // we search if there is an attack possibility and where
241  attack_from = current_unit_attacks_from(new_hex);
242 
243  //see if we should show the normal cursor, the movement cursor, or
244  //the attack cursor
245  //If the cursor is on WAIT, we don't change it and let the setter
246  //of this state end it
247  if (cursor::get() != cursor::WAIT) {
248  if (selected_unit &&
249  selected_unit->side() == side_num_ &&
250  !selected_unit->incapacitated() && !browse)
251  {
252  if (attack_from.valid()) {
254  }
255  else if (!mouseover_unit &&
257  {
258  // Is this where left-drag cursor changes? Test.
260  } else {
261  // selected unit can't attack or move there
263  }
264  } else {
265  // no selected unit or we can't move it
266 
267  if ( selected_hex_.valid() && mouseover_unit
268  && mouseover_unit->side() == side_num_ ) {
269  // empty hex field selected and unit on our site under the cursor
271  } else {
273  }
274  }
275  }
276  } // end planned unit map scope
277 
278  // show (or cancel) the attack direction indicator
279  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
280  gui().set_attack_indicator(attack_from, new_hex);
281  } else {
283  }
284 
285  unit_ptr un; //will later point to unit at mouseover_hex_
286 
287  // the destination is the pointed hex or the adjacent hex
288  // used to attack it
289  map_location dest;
290  unit_map::const_iterator dest_un;
291  { // start planned unit map scope
293  if (attack_from.valid()) {
294  dest = attack_from;
295  dest_un = find_unit(dest);
296  } else {
297  dest = new_hex;
298  dest_un = find_unit(new_hex);
299  }
300 
301  if(dest == selected_hex_ || dest_un) {
302  current_route_.steps.clear();
303  gui().set_route(nullptr);
304  pc_.get_whiteboard()->erase_temp_move();
305  }
306  else if (!current_paths_.destinations.empty() &&
307  board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
308  {
309  if (selected_unit && !selected_unit->incapacitated()) {
310  // Show the route from selected unit to mouseover hex
312 
313  pc_.get_whiteboard()->create_temp_move();
314 
315  if(!browse) {
317  }
318  }
319  }
320 
321  if(board.map().on_board(selected_hex_)
322  && !selected_unit
323  && mouseover_unit.valid()
324  && mouseover_unit) {
325  // Show the route from selected hex to mouseover unit
326  current_route_ = get_route(&*mouseover_unit, selected_hex_, viewing_team());
327 
328  pc_.get_whiteboard()->create_temp_move();
329 
330  if(!browse) {
332  }
333  } else if (!selected_unit) {
334  current_route_.steps.clear();
335  gui().set_route(nullptr);
336  pc_.get_whiteboard()->erase_temp_move();
337  }
338 
339  unit_map::iterator iter = mouseover_unit;
340  if (iter)
341  un = iter.get_shared_ptr();
342  else
343  un.reset();
344  } //end planned unit map scope
345 }
346 
348 {
349  if( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
350  !gui().fogged(un->get_location()))
351  {
352  // If the unit has a path set and is either ours or allied then show the path.
353  //
354  // Exception: allied AI sides' moves are still hidden, on the assumption that
355  // campaign authors won't want to leak goto_x,goto_y tricks to the player.
356  if(!viewing_team().is_enemy(un->side()) && !pc_.get_teams()[un->side() - 1].is_ai()) {
357  //unit is on our team or an allied team, show path if the unit has one
358  const map_location go_to = un->get_goto();
359  if(pc_.get_map().on_board(go_to)) {
361  { // start planned unit map scope
363  route = get_route(un.get(), go_to, current_team());
364  } // end planned unit map scope
365  gui().set_route(&route);
366  }
367  over_route_ = true;
368  }
369 
370  // Scope for the unit_movement_resetter and future_map_if_active.
371  {
372  // Making this non-null will show the unit's max moves instead of current moves.
373  // Because movement is reset to max in the side's refresh phase, use the max if
374  // that refresh will happen before the unit's side can move again.
375  std::unique_ptr<unit_movement_resetter> move_reset;
376  if(un->side() != side_num_) {
377  move_reset = std::make_unique<unit_movement_resetter>(*un);
378  }
379 
380  // Handle whiteboard. Any move_reset must be done before this, since the future
381  // state includes changes to units' movement.
383 
384  current_paths_ = pathfind::paths(*un, false, true, viewing_team(), path_turns_);
385  }
386 
387  unselected_paths_ = true;
389  }
390 }
391 
392 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
393 {
394  // we ignore the position coming from event handler
395  // because it's always a little obsolete and we don't need
396  // to highlight all the hexes where the mouse passed.
397  // Also, sometimes it seems to have one *very* obsolete
398  // and isolated mouse motion event when using drag&drop
399  sdl::get_mouse_state(&x, &y); // <-- modify x and y
400 
402  return;
403  }
404 
405  // Don't process other motion events while scrolling
406  if(scroll_started_) {
407  return;
408  }
409 
410  if(new_hex == map_location::null_location()) {
411  new_hex = gui().hex_clicked_on(x, y);
412  }
413 
414  if(new_hex != last_hex_) {
415  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
416  lk->mouse_over_hex_callback(new_hex);
417  }
418 
419  update = true;
420 
421  if(pc_.get_map().on_board(last_hex_)) {
422  // we store the previous hexes used to propose attack direction
424 
425  // the hex of the selected unit is also "free"
426  { // start planned unit map scope
430  }
431  } // end planned unit map scope
432  }
433 
434  last_hex_ = new_hex;
435  }
436 
437  if(reachmap_invalid_) {
438  update = true;
439  }
440 
441  if(!update) {
442  return;
443  }
444 
445  if(reachmap_invalid_) {
446  reachmap_invalid_ = false;
447 
449  bool selected_hex_has_unit;
450  { // start planned unit map scope
451  wb::future_map_if_active planned_unit_map;
452  selected_hex_has_unit = find_unit(selected_hex_).valid();
453  } // end planned unit map scope
454 
455  if(selected_hex_.valid() && selected_hex_has_unit) {
456  // reselect the unit without firing events (updates current_paths_)
457  select_hex(selected_hex_, true);
458  }
459 
460  // we do never deselect here, mainly because of canceled attack-move
461  }
462  }
463 
464  // reset current_route_ and current_paths if not valid anymore
465  // we do it before cursor selection, because it uses current_paths_
466  if(!pc_.get_map().on_board(new_hex)) {
467  current_route_.steps.clear();
468  gui().set_route(nullptr);
469  pc_.get_whiteboard()->erase_temp_move();
470  }
471 
472  if(unselected_paths_) {
473  unselected_paths_ = false;
476  } else if(over_route_) {
477  over_route_ = false;
478  current_route_.steps.clear();
479  gui().set_route(nullptr);
480  pc_.get_whiteboard()->erase_temp_move();
481  }
482 
483  gui().highlight_hex(new_hex);
484  pc_.get_whiteboard()->on_mouseover_change(new_hex);
485 
487  unit_map::iterator mouseover_unit;
488  map_location attack_from;
489 
490  { // start planned unit map scope
491  wb::future_map_if_active planned_unit_map;
493  mouseover_unit = find_unit(new_hex);
494 
495  // we search if there is an attack possibility and where
496  attack_from = current_unit_attacks_from(new_hex);
497 
498  // see if we should show the normal cursor, the movement cursor, or
499  // the attack cursor
500  // If the cursor is on WAIT, we don't change it and let the setter
501  // of this state end it
502  if(cursor::get() != cursor::WAIT) {
503  if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
504  if(attack_from.valid()) {
506  } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
508  } else {
509  // selected unit can't attack or move there
511  }
512  } else {
513  // no selected unit or we can't move it
514 
515  if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
516  // empty hex field selected and unit on our site under the cursor
518  } else {
520  }
521  }
522  }
523  } // end planned unit map scope
524 
525  // show (or cancel) the attack direction indicator
526  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
527  gui().set_attack_indicator(attack_from, new_hex);
528  } else {
530  }
531 
532  unit_ptr un; // will later point to unit at mouseover_hex_
533 
534  // the destination is the pointed hex or the adjacent hex
535  // used to attack it
536  map_location dest;
537  unit_map::const_iterator dest_un;
538  /* start planned unit map scope*/
539  {
541  if(attack_from.valid()) {
542  dest = attack_from;
543  dest_un = find_unit(dest);
544  } else {
545  dest = new_hex;
546  dest_un = find_unit(new_hex);
547  }
548 
549  if(dest == selected_hex_ || dest_un) {
550  current_route_.steps.clear();
551  gui().set_route(nullptr);
552  pc_.get_whiteboard()->erase_temp_move();
553  } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
554  if(selected_unit && !selected_unit->incapacitated()) {
555  // Show the route from selected unit to mouseover hex
557 
558  pc_.get_whiteboard()->create_temp_move();
559 
560  if(!browse) {
562  }
563  }
564  }
565 
566  if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
567  // Show the route from selected hex to mouseover unit
568  current_route_ = get_route(&*mouseover_unit, selected_hex_, viewing_team());
569 
570  pc_.get_whiteboard()->create_temp_move();
571 
572  if(!browse) {
574  }
575  } else if(!selected_unit) {
576  current_route_.steps.clear();
577  gui().set_route(nullptr);
578  pc_.get_whiteboard()->erase_temp_move();
579  }
580 
581  if(mouseover_unit) {
582  un = mouseover_unit.get_shared_ptr();
583  } else {
584  un.reset();
585  }
586  } /*end planned unit map scope*/
587 
588  /*
589  * Only highlight unit's reach if toggler not preventing normal unit
590  * processing. This can happen e.g. if, after activating 'show
591  * [best possible] enemy movements' through the UI menu, the
592  * mouse cursor lands on a hex with unit in it.
593  */
596  }
597 
598  if(!un && preventing_units_highlight_) {
599  // Cursor on empty hex, turn unit highlighting back on.
601  }
602 }
603 
604 // Hook for notifying lua game kernel of mouse button events. We pass button as
605 // a serpaate argument than the original SDL event in order to manage touch
606 // emulation (e.g., long touch = right click) and such.
607 bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
608  map_location loc, bool click)
609 {
610  static const std::array<const std::string, 6> buttons = {
611  "",
612  "left", // SDL_BUTTON_LEFT
613  "middle", // SDL_BUTTON_MIDDLE
614  "right", // SDL_BUTTON_RIGHT
615  "mouse4", // SDL_BUTTON_X1
616  "mouse5" // SDL_BUTTON_X2
617  };
618 
619  if (gui().view_locked() || button < SDL_BUTTON_LEFT || button > buttons.size()) {
620  return false;
621  } else if (event.state > SDL_PRESSED || !gui().get_map().on_board(loc)) {
622  return false;
623  }
624 
625  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
626  lk->mouse_button_callback(loc, buttons[button], (event.state == SDL_RELEASED ? "up" : "down"));
627 
628  // Are we being asked to send a click event?
629  if (click) {
630  // Was both the up and down on the same map tile?
631  if (loc != drag_from_hex_) {
632  return false;
633  }
634  // We allow this event to be consumed, but not up/down
635  return lk->mouse_button_callback(loc, buttons[button], "click");
636  }
637  }
638  return false;
639 }
640 
642 {
644  if(res) {
645  return res;
646  }
647 
648  return find_unit(last_hex_);
649 }
650 
652 {
654  if(it.valid()) {
655  return it;
656  }
657 
658  return pc_.get_units().end();
659 }
660 
662 {
664 }
665 
667 {
669  return it.valid() ? &*it : nullptr;
670 }
671 
673 {
675  return it.valid() ? &*it : nullptr;
676 }
677 
679 {
680  int x = -1;
681  int y = -1;
682  sdl::get_mouse_state(&x, &y);
683  return gui_->hex_clicked_on(x, y);
684 }
685 
687 {
688  return find_unit(hex).valid();
689 }
690 
692 {
693  if(loc == selected_hex_) {
694  return map_location();
695  }
696 
697  bool wb_active = pc_.get_whiteboard()->is_active();
698 
699  {
700  // Check the unit SOURCE of the attack
701 
702  // Check that there's a selected unit
703  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
704 
705  bool source_eligible = source_unit.valid();
706  if(!source_eligible) {
707  return map_location();
708  }
709 
710  // The selected unit must at least belong to the player currently controlling this client.
711  source_eligible &= source_unit->side() == gui_->viewing_side();
712  if(!source_eligible) {
713  return map_location();
714  }
715 
716  // In addition:
717  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
718  // - If whiteboard is disabled, it must be the turn of the player controlling this client
719  if(!wb_active) {
720  source_eligible &= gui_->viewing_side() == pc_.current_side();
721  if(!source_eligible) {
722  return map_location();
723  }
724  }
725 
726  // Unit must have attacks left
727  source_eligible &= source_unit->attacks_left() != 0;
728  if(!source_eligible) {
729  return map_location();
730  }
731 
732  // Check the unit TARGET of the attack
733 
734  const team& viewer = viewing_team();
735 
736  // Check that there's a unit at the target location
737  const unit_map::const_iterator target_unit = find_unit(loc);
738 
739  bool target_eligible = target_unit.valid();
740  if(!target_eligible) {
741  return map_location();
742  }
743 
744  // The player controlling this client must be an enemy of the target unit's side
745  target_eligible &= viewer.is_enemy(target_unit->side());
746  if(!target_eligible) {
747  return map_location();
748  }
749 
750  // Sanity check: source and target of the attack shouldn't be on the same team
751  assert(source_unit->side() != target_unit->side());
752 
753  target_eligible &= !target_unit->incapacitated();
754  if(!target_eligible) {
755  return map_location();
756  }
757  }
758 
760  const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
761 
762  int best_rating = 100; // smaller is better
763 
764  map_location res;
765  const auto adj = get_adjacent_tiles(loc);
766 
767  for(std::size_t n = 0; n < adj.size(); ++n) {
768  if(pc_.get_map().on_board(adj[n]) == false) {
769  continue;
770  }
771 
772  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
773  continue;
774  }
775 
777  static const std::size_t NDIRECTIONS = map_location::NDIRECTIONS;
778 
779  unsigned int difference = std::abs(static_cast<int>(preferred - n));
780  if(difference > NDIRECTIONS / 2) {
781  difference = NDIRECTIONS - difference;
782  }
783 
784  unsigned int second_difference = std::abs(static_cast<int>(second_preferred - n));
785  if(second_difference > NDIRECTIONS / 2) {
786  second_difference = NDIRECTIONS - second_difference;
787  }
788 
789  const int rating = difference * 2 + (second_difference > difference);
790  if(rating < best_rating || res.valid() == false) {
791  best_rating = rating;
792  res = adj[n];
793  }
794  }
795  }
796 
797  return res;
798 }
799 
801 {
802  game_board& board = pc_.gamestate().board_;
803 
804  // The pathfinder will check unit visibility (fogged/stealthy).
805  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
806 
808 
809  pathfind::plain_route route;
810 
811  route = pathfind::a_star_search(
812  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
813 
814  return mark_route(route);
815 }
816 
817 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
818 {
820  unselected_reach_ = false;
821  return false;
822  }
823 
824  return gui().map_area().contains(x, y);
825 }
826 
828 {
829  if(!pc_.get_map().on_board(last_hex_)) {
831  return;
832  }
833 
834  // Load whiteboard partial moves
835  wb::future_map_if_active planned_unit_map;
836 
837  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
838  lk->select_hex_callback(last_hex_);
839  }
840 
843 
844  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
845  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
846  ) {
847  select_hex(last_hex_, false);
848  } else {
849  move_action(browse);
850  }
851 }
852 
853 void mouse_handler::move_action(bool browse)
854 {
855  // Lock whiteboard activation state to avoid problems due to
856  // its changing while an animation takes place.
857  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
858 
859  // we use the last registered highlighted hex
860  // since it's what update our global state
861  map_location hex = last_hex_;
862 
863  // TODO
864  // // Clicks on border hexes mean to deselect.
865  // // (Check this before doing processing that might not be needed.)
866  // if ( !pc_.get_map().on_board(hex) ) {
867  // deselect_hex();
868  // return false;
869  // }
870 
871  unit* u = nullptr;
872  const unit* clicked_u = nullptr;
873 
874  map_location src;
875  pathfind::paths orig_paths;
876  map_location attack_from;
877 
878  { // start planned unit map scope
879  wb::future_map_if_active planned_unit_map;
881 
882  // if the unit is selected and then itself clicked on,
883  // any goto command is canceled
884  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
885  u->set_goto(map_location());
886  }
887 
888  clicked_u = find_unit_nonowning(hex);
889 
890  src = selected_hex_;
891  orig_paths = current_paths_;
892  attack_from = current_unit_attacks_from(hex);
893  } // end planned unit map scope
894 
895  // see if we're trying to do a attack or move-and-attack
896  if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
897  // Ignore this command if commands are disabled.
898  if(commands_disabled) {
899  return;
900  }
901 
902  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
903  if(attack_from == selected_hex_) { // no move needed
904  int choice = -1;
905  {
906  wb::future_map_if_active planned_unit_map; // start planned unit map scope
907  choice = show_attack_dialog(attack_from, clicked_u->get_location());
908  } // end planned unit map scope
909 
910  if(choice >= 0) {
911  if(pc_.get_whiteboard()->is_active()) {
912  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
913  } else {
914  // clear current unit selection so that any other unit selected
915  // triggers a new selection
917 
918  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
919  }
920  }
921 
922  return;
923  } else {
924  int choice = -1; // for the attack dialog
925 
926  {
927  wb::future_map_if_active planned_unit_map; // start planned unit map scope
928  // we will now temporary move next to the enemy
929  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
930  if(itor == current_paths_.destinations.end()) {
931  // can't reach the attacking location
932  // not supposed to happen, so abort
933  return;
934  }
935 
936  // block where we temporary move the unit
937  {
938  temporary_unit_mover temp_mover(pc_.get_units(), src, attack_from, itor->move_left, true);
939  choice = show_attack_dialog(attack_from, clicked_u->get_location());
940  }
941 
942  if(choice < 0) {
943  // user hit cancel, don't start move+attack
944  return;
945  }
946  } // end planned unit map scope
947 
948  if(pc_.get_whiteboard()->is_active()) {
949  save_whiteboard_attack(attack_from, hex, choice);
950  } else {
951  bool not_interrupted = move_unit_along_current_route();
952  bool alt_unit_selected = (selected_hex_ != src);
953  src = selected_hex_;
954  // clear current unit selection so that any other unit selected
955  // triggers a new selection
957 
958  if(not_interrupted)
959  attack_enemy(attack_from, hex, choice); // Fight !!
960 
961  // TODO: Maybe store the attack choice so "press t to continue"
962  // can also continue the attack?
963 
964  if(alt_unit_selected && !selected_hex_.valid()) {
965  // reselect other unit if selected during movement animation
966  select_hex(src, browse);
967  }
968  }
969 
970  return;
971  }
972  }
973  }
974  // otherwise we're trying to move to a hex
975  else if(
976  // The old use case: move selected unit to mouse hex field.
977  (
978  (!browse || pc_.get_whiteboard()->is_active())
979  && selected_hex_.valid()
980  && selected_hex_ != hex
981  && u != nullptr
982  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
983  && !clicked_u
984  && !current_route_.steps.empty()
985  && current_route_.steps.front() == selected_hex_
986  )
987  || // The new use case: move mouse unit to selected hex field.
988  (
989  (!browse || pc_.get_whiteboard()->is_active())
990  && selected_hex_.valid()
991  && selected_hex_ != hex
992  && clicked_u
993  && !current_route_.steps.empty()
994  && current_route_.steps.back() == selected_hex_
995  && !u
996  && clicked_u->side() == side_num_
997  )
998  ) {
999  // Ignore this command if commands are disabled.
1000  if(commands_disabled) {
1001  return;
1002  }
1003 
1004  // If the whiteboard is active, it intercepts any unit movement.
1005  if(pc_.get_whiteboard()->is_active()) {
1006  // Deselect the current hex, and create planned move for whiteboard.
1008 
1011  gui().set_route(nullptr);
1012 
1013  show_partial_move_ = false;
1014 
1015  gui().unhighlight_reach();
1016 
1018  current_route_.steps.clear();
1019 
1020  pc_.get_whiteboard()->save_temp_move();
1021 
1022  // Otherwise proceed to normal unit movement
1023  } else {
1024  // Don't move if the unit already has actions
1025  // from the whiteboard.
1026  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1027  return;
1028  }
1029 
1031 
1032  // During the move, we may have selected another unit
1033  // (but without triggering a select event (command was disabled)
1034  // in that case reselect it now to fire the event (+ anim & sound)
1035  if(selected_hex_ != src) {
1036  select_hex(selected_hex_, browse);
1037  }
1038  }
1039 
1040  return;
1041  }
1042 }
1043 
1044 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1045 {
1046  unit_map::iterator unit = find_unit(touched_hex);
1047 
1048  if (touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1049  select_or_action(browse);
1050  } else {
1051  deselect_hex();
1052  }
1053 }
1054 
1055 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event)
1056 {
1057  selected_hex_ = hex;
1058 
1061  gui().set_route(nullptr);
1062 
1063  show_partial_move_ = false;
1064 
1065  wb::future_map_if_active planned_unit_map; // lasts for whole method
1066 
1068 
1069  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1071 
1072  {
1074  }
1075 
1076  if(highlight) {
1079  }
1080 
1081  // The highlight now comes from selection
1082  // and not from the mouseover on an enemy
1083  unselected_paths_ = false;
1084  gui().set_route(nullptr);
1085 
1086  // Selection have impact only if we are not observing and it's our unit
1087  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_side()) {
1088  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1089  sound::play_UI_sound("select-unit.wav");
1090 
1092 
1093  if(fire_event) {
1094  // Ensure unit map is back to normal while event is fired
1095  wb::real_map srum;
1096  pc_.pump().fire("select", hex);
1097  // end forced real unit map
1098  }
1099  }
1100  }
1101 
1102  return;
1103  }
1104 
1105  if(selected_hex_.valid() && !unit) {
1106  // Compute unit in range of the empty selected_hex field
1107 
1109 
1110  pathfind::paths reaching_unit_locations;
1111 
1112  pathfind::paths clicked_location;
1113  clicked_location.destinations.insert(hex);
1114 
1115  for(unit_map::iterator u = pc_.get_units().begin(); u != pc_.get_units().end();
1116  ++u) {
1117  bool invisible = u->invisible(u->get_location());
1118 
1119  if(!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible) {
1120  const pathfind::paths& path =
1121  pathfind::paths(*u, false, true, viewing_team(), path_turns_, false, false);
1122 
1123  if(path.destinations.find(hex) != path.destinations.end()) {
1124  reaching_unit_locations.destinations.insert(u->get_location());
1125  gui_->highlight_another_reach(clicked_location);
1126  }
1127  }
1128  }
1129 
1130  gui_->highlight_another_reach(reaching_unit_locations);
1131  } else {
1132  if(!pc_.get_units().find(last_hex_)) {
1134  }
1135 
1137  current_route_.steps.clear();
1138 
1139  pc_.get_whiteboard()->on_deselect_hex();
1140  }
1141 }
1142 
1144 {
1145  select_hex(map_location(), true);
1146 }
1147 
1148 /**
1149  * Moves a unit along the currently cached route.
1150  *
1151  * @returns true if the end of the route was reached and no information was
1152  * uncovered that would warrant interrupting a chain of actions;
1153  * false otherwise.
1154  */
1156 {
1157  // Copy the current route to ensure it remains valid throughout the animation.
1158  const std::vector<map_location> steps = current_route_.steps;
1159 
1160  // do not show footsteps during movement
1161  gui().set_route(nullptr);
1162  gui().unhighlight_reach();
1163 
1164  // do not keep the hex highlighted that we started from
1167 
1168  bool interrupted = false;
1169  if(steps.size() > 1) {
1170  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1171 
1172  interrupted = interrupted || num_moves + 1 < steps.size();
1173  next_unit_ = steps[num_moves];
1174  }
1175 
1176  // invalid after the move
1178  current_route_.steps.clear();
1179 
1180  return !interrupted;
1181 }
1182 
1183 /**
1184  * Moves a unit across the board for a player.
1185  * This is specifically for movement at the time it is initiated by a player,
1186  * whether via a mouse click or executing whiteboard actions. Continued moves
1187  * (including goto execution) can bypass this and call actions::move_unit() directly.
1188  * This function call may include time for an animation, so make sure the
1189  * provided route will remain unchanged (the caller should probably make a local
1190  * copy).
1191  *
1192  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1193  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1194  * actions (and set to false otherwise).
1195  *
1196  * @returns The number of hexes entered. This can safely be used as an index
1197  * into steps to get the location where movement ended, provided
1198  * steps is not empty (the return value is guaranteed to be less
1199  * than steps.size() ).
1200  */
1201 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1202 {
1203  if(steps.empty()) {
1204  interrupted = false;
1205  return 0;
1206  }
1207 
1208  // Default return value.
1209  interrupted = true;
1210 
1211  // If this is a leader on a keep, ask permission to the whiteboard to move it
1212  // since otherwise it may cause planned recruits to be erased.
1213  if(pc_.get_map().is_keep(steps.front())) {
1214  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1215 
1216  if(u && u->can_recruit() && u->side() == gui().viewing_side()
1217  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1219  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1220  return 0;
1221  }
1222  }
1223 
1224  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1225  std::size_t moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), false, true, &interrupted);
1226 
1229 
1230  if(moves == 0)
1231  return 0;
1232 
1233  if(interrupted && moves + 1 < steps.size()) {
1234  // reselect the unit (for "press t to continue")
1235  select_hex(steps[moves], false, false, false);
1236  // the new discovery is more important than the new movement range
1237  show_partial_move_ = true;
1238  }
1239 
1240  return moves;
1241 }
1242 
1244  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1245 {
1246  {
1247  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1248  // This method should do the cleanup of highlights and selection but it doesn't work properly
1249 
1250  // gui().highlight_hex(map_location());
1251 
1252  gui().unhighlight_reach();
1254 
1255  // remove footsteps if any - useless for whiteboard as of now
1256  gui().set_route(nullptr);
1257 
1258  // do not keep the hex that we started from highlighted
1261  show_partial_move_ = false;
1262 
1263  // invalid after saving the move
1265  current_route_.steps.clear();
1266  }
1267 
1268  // create planned attack for whiteboard
1269  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1270 }
1271 
1273  std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
1274 {
1275  int best = 0;
1276  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1277  // skip weapons with attack_weight=0
1278  if(attacker->attacks()[i].attack_weight() > 0) {
1279  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1280 
1281  // Don't include if the attacker's weapon has at least one active "disable" special.
1282  if(bc.get_attacker_stats().disable) {
1283  continue;
1284  }
1285 
1286  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1287  // as some weapons can be hidden, i is not a valid index into the resulting vector
1288  best = bc_vector.size();
1289  }
1290 
1291  bc_vector.emplace_back(std::move(bc));
1292  }
1293  }
1294 
1295  return best;
1296 }
1297 
1298 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
1299 {
1300  game_board& board = pc_.gamestate().board_;
1301 
1302  unit_map::iterator attacker = board.units().find(attacker_loc);
1303  unit_map::iterator defender = board.units().find(defender_loc);
1304 
1305  if(!attacker || !defender) {
1306  ERR_NG << "One fighter is missing, can't attack";
1307  return -1; // abort, click will do nothing
1308  }
1309 
1310  std::vector<battle_context> bc_vector;
1311  const int best = fill_weapon_choices(bc_vector, attacker, defender);
1312 
1313  if(bc_vector.empty()) {
1314  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1315 
1316  return -1;
1317  }
1318 
1319  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best);
1320 
1321  if(dlg.show()) {
1322  return dlg.get_selected_weapon();
1323  }
1324 
1325  return -1;
1326 }
1327 
1328 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1329 {
1330  try {
1331  attack_enemy_(attacker_loc, defender_loc, choice);
1332  } catch(const std::bad_alloc&) {
1333  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1334  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1335  }
1336 }
1337 
1338 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1339 {
1340  // NOTE: copy the values because the const reference may change!
1341  // (WML events and mouse inputs during animations may modify
1342  // the data of the caller)
1343  const map_location attacker_loc = att_loc;
1344  const map_location defender_loc = def_loc;
1345 
1346  unit* attacker = nullptr;
1347  const unit* defender = nullptr;
1348  std::vector<battle_context> bc_vector;
1349 
1350  {
1351  unit_map::iterator attacker_it = find_unit(attacker_loc);
1352  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1353  return;
1354  }
1355 
1356  unit_map::iterator defender_it = find_unit(defender_loc);
1357  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1358  return;
1359  }
1360 
1361  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1362 
1363  attacker = &*attacker_it;
1364  defender = &*defender_it;
1365  }
1366 
1367  if(std::size_t(choice) >= bc_vector.size()) {
1368  return;
1369  }
1370 
1371  events::command_disabler disabler;
1372  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1373  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1374 
1375  attacker->set_goto(map_location());
1376 
1378 
1379  // make the attacker's stats appear during the attack
1380  gui().display_unit_hex(attacker_loc);
1381 
1382  // remove highlighted hexes etc..
1386  gui().unhighlight_reach();
1387 
1388  current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
1389  // TODO: change ToD to be location specific for the defender
1390 
1391  const tod_manager& tod_man = pc_.get_tod_manager();
1392 
1395  attacker_loc,
1396  defender_loc,
1397  att.attack_num,
1398  def.attack_num,
1399  attacker->type_id(),
1400  defender->type_id(),
1401  att.level,
1402  def.level,
1403  tod_man.turn(),
1404  tod_man.get_time_of_day()
1405  )
1406  );
1407 }
1408 
1409 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1410 {
1411  std::set<map_location> res;
1412 
1413  const team& uteam = pc_.get_teams()[side - 1];
1414 
1415  for(const map_location& aloc : get_adjacent_tiles(loc)) {
1417 
1418  if(i && uteam.is_enemy(i->side())) {
1419  res.insert(aloc);
1420  }
1421  }
1422 
1423  return res;
1424 }
1425 
1426 /**
1427  * Causes attackable hexes to be highlighted.
1428  *
1429  * This checks the hexes that the provided unit can attack. If there is a valid
1430  * target there, that location is inserted into current_paths_.destinations.
1431  */
1433 {
1434  // Cannot attack if no attacks are left.
1435  if(u->attacks_left() == 0) {
1436  return;
1437  }
1438 
1439  // Get the teams involved.
1440  const team& cur_team = current_team();
1441  const team& u_team = pc_.get_teams()[u->side() - 1];
1442 
1443  // Check each adjacent hex.
1444  for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
1445  // No attack option shown if no visible unit present.
1446  // (Visible to current team, not necessarily the unit's team.)
1447  if(!pc_.get_map().on_board(loc)) {
1448  continue;
1449  }
1450 
1452  if(!i || !i->is_visible_to_team(cur_team, false)) {
1453  continue;
1454  }
1455 
1456  const unit& target = *i;
1457 
1458  // Can only attack non-petrified enemies.
1459  if(u_team.is_enemy(target.side()) && !target.incapacitated()) {
1461  }
1462  }
1463 }
1464 
1466 {
1467  game_board& board = pc_.gamestate().board_;
1468 
1469  if(!it) {
1470  return false;
1471  }
1472 
1473  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1474  return false;
1475  }
1476 
1477  if(current_team().is_enemy(static_cast<int>(gui().viewing_team() + 1)) && it->invisible(it->get_location())) {
1478  return false;
1479  }
1480 
1481  if(it->get_hidden()) {
1482  return false;
1483  }
1484 
1485  return true;
1486 }
1487 
1488 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1489 {
1490  unit_map& units = pc_.get_units();
1491 
1492  if(units.begin() == units.end()) {
1493  return;
1494  }
1495 
1497  if(!it) {
1498  it = units.begin();
1499  }
1500 
1501  const unit_map::const_iterator itx = it;
1502 
1503  do {
1504  if(reverse) {
1505  if(it == units.begin()) {
1506  it = units.end();
1507  }
1508 
1509  --it;
1510  } else {
1511  if(it == units.end()) {
1512  it = units.begin();
1513  } else {
1514  ++it;
1515  }
1516  }
1517  } while(it != itx && !unit_in_cycle(it));
1518 
1519  if(unit_in_cycle(it)) {
1520  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1521 
1522  select_hex(it->get_location(), browse);
1523  // mouse_update(browse);
1524  }
1525 }
1526 
1528 {
1529  gui().unhighlight_reach();
1530 
1531  current_paths_ = new_paths;
1532  current_route_.steps.clear();
1533 
1534  gui().set_route(nullptr);
1535 
1536  pc_.get_whiteboard()->erase_temp_move();
1537 }
1538 
1540 {
1541  return pc_.get_teams()[gui().viewing_team()];
1542 }
1543 
1545 {
1546  return pc_.get_teams()[gui().viewing_team()];
1547 }
1548 
1550 {
1551  return pc_.get_teams()[side_num_ - 1];
1552 }
1553 
1555 
1557 {
1559 }
1560 
1562 {
1564 }
1565 
1566 } // end namespace events
Various functions that implement attacks and attack calculations.
Various functions related to moving units.
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:451
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,...
int viewing_side() const
The 1-based equivalent of the 0-based viewing_team() function.
Definition: display.hpp:129
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:121
const 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:563
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1783
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:312
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:2073
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:708
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:731
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:520
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.
int drag_from_y_
Drag start position y.
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.
int drag_from_x_
Drag start position x.
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
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
int show_attack_dialog(const map_location &attacker_loc, const map_location &defender_loc)
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)
void set_current_paths(const pathfind::paths &new_paths)
play_controller & pc_
unit_map::iterator selected_unit()
static mouse_handler * singleton_
int fill_weapon_choices(std::vector< battle_context > &bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
void enable_units_highlight()
When unit highlighting is disabled, call this when the mouse no longer hovers any unit to enable high...
void touch_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
void attack_enemy(const map_location &attacker_loc, const map_location &defender_loc, int choice)
std::set< map_location > get_adj_enemies(const map_location &loc, int side) const
map_location previous_free_hex_
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true)
unit_map::const_iterator find_unit(const map_location &hex) const
map_location current_unit_attacks_from(const map_location &loc) const
mouse_handler(game_display *gui, play_controller &pc)
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.
void touch_action(const map_location hex, bool browse) override
bool unit_in_cycle(unit_map::const_iterator it)
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 show_attack_options(const unit_map::const_iterator &u)
Causes attackable hexes to be highlighted.
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.
pathfind::marked_route get_route(const unit *un, map_location go_to, team &team) const
Game board class.
Definition: game_board.hpp:53
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:86
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:113
virtual const gamemap & map() const override
Definition: game_board.hpp:103
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:402
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:51
game_board board_
Definition: game_state.hpp:47
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:385
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
bool show(const unsigned auto_close_time=0)
Shows the window.
This shows the dialog for attacking units.
Definition: unit_attack.hpp:42
std::vector< team > & get_teams()
actions::undo_list & get_undo_stack()
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, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:76
void set_action_bonus_count(const int count)
Definition: team.hpp:202
bool is_enemy(int n) const
Definition: team.hpp:231
int turn() const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:56
void set_selecting()
Sets the animation state to that when the unit is selected.
Container associating units to locations.
Definition: map.hpp:99
unit_iterator end()
Definition: map.hpp:429
unit_iterator find(std::size_t id)
Definition: map.cpp:304
unit_iterator begin()
Definition: map.hpp:419
This class represents a single unit of a specific type.
Definition: unit.hpp:135
std::size_t i
Definition: function.cpp:968
static std::string _(const char *str)
Definition: gettext.hpp:93
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:907
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1957
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition: unit.hpp:722
int side() const
The side this unit belongs to.
Definition: unit.hpp:345
unit_animation_component & anim_comp() const
Definition: unit.hpp:1546
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1359
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1396
Contains functions for cleanly handling SDL input.
void get_adjacent_tiles(const map_location &a, map_location *res)
Function which, given a location, will place all adjacent locations in res.
Definition: location.cpp:475
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
std::size_t move_unit_and_record(const std::vector< map_location > &steps, undo_list *undo_stack, bool continued_move, bool show_move, bool *interrupted, move_unit_spectator *move_spectator)
Moves a unit across the board.
Definition: move.cpp:1229
int side_number
Definition: game_info.hpp:40
CURSOR_TYPE get()
Definition: cursor.cpp:216
@ WAIT
Definition: cursor.hpp:29
@ ATTACK
Definition: cursor.hpp:29
@ MOVE_DRAG
Definition: cursor.hpp:29
@ NORMAL
Definition: cursor.hpp:29
@ ATTACK_DRAG
Definition: cursor.hpp:29
@ MOVE
Definition: cursor.hpp:29
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:176
static void update()
Handling of system events.
Definition: manager.hpp:43
std::string path
Definition: filesystem.cpp:83
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
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:547
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:271
uint32_t get_mouse_state(int *x, int *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition: input.cpp:27
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
bool click(int mousex, int mousey)
Definition: tooltips.cpp:294
void process(int mousex, int mousey)
Definition: tooltips.cpp:278
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:84
std::shared_ptr< bool > whiteboard_lock
Definition: typedefs.hpp:56
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.
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:51
unsigned int level
Definition: attack.hpp:66
bool disable
Attack has disable special.
Definition: attack.hpp:64
int attack_num
Index into unit->attacks() or -1 for none.
Definition: attack.hpp:53
Encapsulates the map of the game.
Definition: location.hpp:38
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:227
bool valid() const
Definition: location.hpp:89
static const map_location & null_location()
Definition: location.hpp:81
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
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
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:231
bool valid() const
Definition: map.hpp:274
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:218
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.
Various functions that implement the undoing (and redoing) of in-game commands.