The Battle for Wesnoth  1.19.1+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 "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"
40 #include "units/ptr.hpp" // for unit_const_ptr
41 #include "units/unit.hpp" // for unit
42 #include "whiteboard/manager.hpp" // for manager, etc
43 #include "whiteboard/typedefs.hpp" // for whiteboard_lock
44 #include "sdl/input.hpp" // for get_mouse_state
45 
46 #include <cassert> // for assert
47 #include <new> // for bad_alloc
48 #include <string> // for string, operator<<, etc
49 
50 static lg::log_domain log_engine("engine");
51 #define ERR_NG LOG_STREAM(err, log_engine)
52 #define LOG_NG LOG_STREAM(info, log_engine)
53 
54 static lg::log_domain log_wml("wml");
55 #define ERR_WML LOG_STREAM(err, log_wml)
56 
57 namespace events
58 {
61  , gui_(gui)
62  , pc_(pc)
63  , previous_hex_()
64  , previous_free_hex_()
65  , selected_hex_()
66  , next_unit_()
67  , current_route_()
68  , current_paths_()
69  , unselected_paths_(false)
70  , unselected_reach_(false)
71  , path_turns_(0)
72  , side_num_(1)
73  , over_route_(false)
74  , reachmap_invalid_(false)
75  , show_partial_move_(false)
76  , teleport_selected_(false)
77  , preventing_units_highlight_(false)
78 {
79  singleton_ = this;
80 }
81 
83 {
84  singleton_ = nullptr;
85 }
86 
88 {
90 }
91 
93 {
94  // Function uses window resolution as an estimate of users perception of distance
95  // Tune this variable if necessary:
96  const unsigned threshold_1080p = 14; // threshold number of pixels for 1080p
97  double screen_diagonal = std::hypot(gui2::settings::screen_width,gui2::settings::screen_height);
98  const double scale_factor = threshold_1080p / std::hypot(1080,1920);
99  return static_cast<int>(screen_diagonal * scale_factor);
100 }
101 
102 void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, map_location new_hex)
103 {
104  // Frankensteining from mouse_motion(), as it has a lot in common, but a lot of differences too.
105  // Copy-pasted from everywhere. TODO: generalize the two.
106  sdl::get_mouse_state(&x,&y);
107 
108  // This is from mouse_handler_base::mouse_motion_default()
109  tooltips::process(x, y);
110 
111  if(simple_warp_) {
112  return;
113  }
114 
115  if(minimap_scrolling_) {
116  const map_location& mini_loc = gui().minimap_location_on(x,y);
117  if(mini_loc.valid()) {
118  if(mini_loc != last_hex_) {
119  last_hex_ = mini_loc;
120  gui().scroll_to_tile(mini_loc,display::WARP,false);
121  }
122  return;
123  } else {
124  // clicking outside of the minimap will end minimap scrolling
125  minimap_scrolling_ = false;
126  }
127  }
128 
129  // Fire the drag & drop only after minimal drag distance
130  // While we check the mouse buttons state, we also grab fresh position data.
131  int mx = drag_from_x_; // some default value to prevent unlikely SDL bug
132  int my = drag_from_y_;
133  if(is_dragging() && !dragging_started_) {
134  if(dragging_touch_) {
135  sdl::get_mouse_state(&mx, &my);
136  const double drag_distance = std::pow(static_cast<double>(drag_from_x_- mx), 2)
137  + std::pow(static_cast<double>(drag_from_y_- my), 2);
138  if(drag_distance > drag_threshold()*drag_threshold()) {
139  dragging_started_ = true;
140  }
141  }
142  }
143 
144  // Not-so-smooth panning
145  const auto found_unit = find_unit(selected_hex_);
146  bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
147  if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
148  sdl::get_mouse_state(&mx, &my);
149 
150  if(gui().map_area().contains(x, y)) {
151  int dx = drag_from_x_ - mx;
152  int dy = drag_from_y_ - my;
153 
154  gui().scroll(dx, dy);
155  drag_from_x_ = mx;
156  drag_from_y_ = my;
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
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_, 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(!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, 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
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_, 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 serpaate 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 || !gui().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 {
652  if(it.valid()) {
653  return it;
654  }
655 
656  return pc_.get_units().end();
657 }
658 
660 {
662 }
663 
665 {
667  return it.valid() ? &*it : nullptr;
668 }
669 
671 {
673  return it.valid() ? &*it : nullptr;
674 }
675 
677 {
678  int x = -1;
679  int y = -1;
680  sdl::get_mouse_state(&x, &y);
681  return gui_->hex_clicked_on(x, y);
682 }
683 
685 {
686  return find_unit(hex).valid();
687 }
688 
690 {
691  if(loc == selected_hex_) {
692  return map_location();
693  }
694 
695  bool wb_active = pc_.get_whiteboard()->is_active();
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_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_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 = 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 
758  const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
759 
760  int best_rating = 100; // smaller is better
761 
762  map_location res;
763  const auto adj = get_adjacent_tiles(loc);
764 
765  for(std::size_t n = 0; n < adj.size(); ++n) {
766  if(pc_.get_map().on_board(adj[n]) == false) {
767  continue;
768  }
769 
770  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
771  continue;
772  }
773 
775  static const std::size_t NDIRECTIONS = map_location::NDIRECTIONS;
776 
777  unsigned int difference = std::abs(static_cast<int>(preferred - n));
778  if(difference > NDIRECTIONS / 2) {
779  difference = NDIRECTIONS - difference;
780  }
781 
782  unsigned int second_difference = std::abs(static_cast<int>(second_preferred - n));
783  if(second_difference > NDIRECTIONS / 2) {
784  second_difference = NDIRECTIONS - second_difference;
785  }
786 
787  const int rating = difference * 2 + (second_difference > difference);
788  if(rating < best_rating || res.valid() == false) {
789  best_rating = rating;
790  res = adj[n];
791  }
792  }
793  }
794 
795  return res;
796 }
797 
799 {
800  game_board& board = pc_.gamestate().board_;
801 
802  // The pathfinder will check unit visibility (fogged/stealthy).
803  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
804 
806 
807  pathfind::plain_route route;
808 
809  route = pathfind::a_star_search(
810  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
811 
812  return mark_route(route);
813 }
814 
815 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
816 {
818  unselected_reach_ = false;
819  return false;
820  }
821 
822  return gui().map_area().contains(x, y);
823 }
824 
826 {
827  // Load whiteboard partial moves
828  //wb::future_map_if_active planned_unit_map;
829 
830  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
831  lk->select_hex_callback(last_hex_);
832  }
833 
836 
837  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
838  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
839  ) {
841  teleport_selected_ = true;
843  gui().set_route(nullptr);
844  }
845 }
846 
848 {
849  // Set the teleport to active so that we can use existing functions
850  // for teleport
851  teleport_selected_ = false;
852 
856  gui().invalidate_all();
858  gui().set_route(nullptr);
859 
860  // Select and deselect the units hex to prompt updates for hover
861  select_hex(last_hex_, false);
862  deselect_hex();
863  current_route_.steps.clear();
864 }
865 
867 {
868  if(!pc_.get_map().on_board(last_hex_)) {
870  return;
871  }
872 
873  // Load whiteboard partial moves
874  wb::future_map_if_active planned_unit_map;
875 
876  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
877  lk->select_hex_callback(last_hex_);
878  }
879 
882 
883  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
884  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
885  ) {
886  select_hex(last_hex_, false);
887  } else {
888  move_action(browse);
889  }
890  teleport_selected_ = false;
891 }
892 
893 void mouse_handler::move_action(bool browse)
894 {
895  // Lock whiteboard activation state to avoid problems due to
896  // its changing while an animation takes place.
897  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
898 
899  // we use the last registered highlighted hex
900  // since it's what update our global state
901  map_location hex = last_hex_;
902 
903  // TODO
904  // // Clicks on border hexes mean to deselect.
905  // // (Check this before doing processing that might not be needed.)
906  // if ( !pc_.get_map().on_board(hex) ) {
907  // deselect_hex();
908  // return false;
909  // }
910 
911  unit* u = nullptr;
912  const unit* clicked_u = nullptr;
913 
914  map_location src;
915  pathfind::paths orig_paths;
916  map_location attack_from;
917 
918  { // start planned unit map scope
919  wb::future_map_if_active planned_unit_map;
921 
922  // if the unit is selected and then itself clicked on,
923  // any goto command is canceled
924  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
925  u->set_goto(map_location());
926  }
927 
928  clicked_u = find_unit_nonowning(hex);
929 
930  src = selected_hex_;
931  orig_paths = current_paths_;
932  attack_from = current_unit_attacks_from(hex);
933  } // end planned unit map scope
934 
935  // See if the teleport option is toggled
936  if(teleport_selected_) {
937  teleport_action();
938  }
939  // see if we're trying to do a attack or move-and-attack
940  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
941  // Ignore this command if commands are disabled.
942  if(commands_disabled) {
943  return;
944  }
945 
946  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
947  if(attack_from == selected_hex_) { // no move needed
948  int choice = -1;
949  {
950  wb::future_map_if_active planned_unit_map; // start planned unit map scope
951  choice = show_attack_dialog(attack_from, clicked_u->get_location());
952  } // end planned unit map scope
953 
954  if(choice >= 0) {
955  if(pc_.get_whiteboard()->is_active()) {
956  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
957  } else {
958  // clear current unit selection so that any other unit selected
959  // triggers a new selection
961 
962  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
963  }
964  }
965 
966  return;
967  } else {
968  int choice = -1; // for the attack dialog
969 
970  {
971  wb::future_map_if_active planned_unit_map; // start planned unit map scope
972  // we will now temporary move next to the enemy
973  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
974  if(itor == current_paths_.destinations.end()) {
975  // can't reach the attacking location
976  // not supposed to happen, so abort
977  return;
978  }
979 
980  // block where we temporary move the unit
981  {
982  temporary_unit_mover temp_mover(pc_.get_units(), src, attack_from, itor->move_left, true);
983  choice = show_attack_dialog(attack_from, clicked_u->get_location());
984  }
985 
986  if(choice < 0) {
987  // user hit cancel, don't start move+attack
988  return;
989  }
990  } // end planned unit map scope
991 
992  if(pc_.get_whiteboard()->is_active()) {
993  save_whiteboard_attack(attack_from, hex, choice);
994  } else {
995  bool not_interrupted = move_unit_along_current_route();
996  bool alt_unit_selected = (selected_hex_ != src);
997  src = selected_hex_;
998  // clear current unit selection so that any other unit selected
999  // triggers a new selection
1001 
1002  if(not_interrupted)
1003  attack_enemy(attack_from, hex, choice); // Fight !!
1004 
1005  // TODO: Maybe store the attack choice so "press t to continue"
1006  // can also continue the attack?
1007 
1008  if(alt_unit_selected && !selected_hex_.valid()) {
1009  // reselect other unit if selected during movement animation
1010  select_hex(src, browse);
1011  }
1012  }
1013 
1014  return;
1015  }
1016  }
1017  }
1018  // otherwise we're trying to move to a hex
1019  else if(
1020  // The old use case: move selected unit to mouse hex field.
1021  (
1022  (!browse || pc_.get_whiteboard()->is_active())
1023  && selected_hex_.valid()
1024  && selected_hex_ != hex
1025  && u != nullptr
1026  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1027  && !clicked_u
1028  && !current_route_.steps.empty()
1029  && current_route_.steps.front() == selected_hex_
1030  )
1031  || // The new use case: move mouse unit to selected hex field.
1032  (
1033  (!browse || pc_.get_whiteboard()->is_active())
1034  && selected_hex_.valid()
1035  && selected_hex_ != hex
1036  && clicked_u
1037  && !current_route_.steps.empty()
1038  && current_route_.steps.back() == selected_hex_
1039  && !u
1040  && clicked_u->side() == side_num_
1041  )
1042  ) {
1043  // Ignore this command if commands are disabled.
1044  if(commands_disabled) {
1045  return;
1046  }
1047 
1048  // If the whiteboard is active, it intercepts any unit movement.
1049  if(pc_.get_whiteboard()->is_active()) {
1050  // Deselect the current hex, and create planned move for whiteboard.
1052 
1055  gui().set_route(nullptr);
1056 
1057  show_partial_move_ = false;
1058 
1059  gui().unhighlight_reach();
1060 
1062  current_route_.steps.clear();
1063 
1064  pc_.get_whiteboard()->save_temp_move();
1065 
1066  // Otherwise proceed to normal unit movement
1067  } else {
1068  // Don't move if the unit already has actions
1069  // from the whiteboard.
1070  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1071  return;
1072  }
1073 
1075 
1076  // During the move, we may have selected another unit
1077  // (but without triggering a select event (command was disabled)
1078  // in that case reselect it now to fire the event (+ anim & sound)
1079  if(selected_hex_ != src) {
1080  select_hex(selected_hex_, browse);
1081  }
1082  }
1083 
1084  return;
1085  }
1086 }
1087 
1088 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1089 {
1090  unit_map::iterator unit = find_unit(touched_hex);
1091 
1092  if (touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1093  select_or_action(browse);
1094  } else {
1095  deselect_hex();
1096  }
1097 }
1098 
1099 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event)
1100 {
1101  selected_hex_ = hex;
1102 
1105  gui().set_route(nullptr);
1106 
1107  show_partial_move_ = false;
1108 
1109  wb::future_map_if_active planned_unit_map; // lasts for whole method
1110 
1112 
1113  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1115 
1116  {
1118  }
1119 
1120  if(highlight) {
1123  }
1124 
1125  // The highlight now comes from selection
1126  // and not from the mouseover on an enemy
1127  unselected_paths_ = false;
1128  gui().set_route(nullptr);
1129 
1130  // Selection have impact only if we are not observing and it's our unit
1131  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_side()) {
1132  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1133  sound::play_UI_sound("select-unit.wav");
1134 
1136 
1137  if(fire_event) {
1138  // Ensure unit map is back to normal while event is fired
1139  wb::real_map srum;
1140  pc_.pump().fire("select", hex);
1141  // end forced real unit map
1142  }
1143  }
1144  }
1145 
1146  return;
1147  }
1148 
1149  if(selected_hex_.valid() && !unit) {
1150  // Compute unit in range of the empty selected_hex field
1151 
1153 
1154  pathfind::paths reaching_unit_locations;
1155 
1156  pathfind::paths clicked_location;
1157  clicked_location.destinations.insert(hex);
1158 
1159  for(unit_map::iterator u = pc_.get_units().begin(); u != pc_.get_units().end();
1160  ++u) {
1161  bool invisible = u->invisible(u->get_location());
1162 
1163  if(!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible) {
1164  const pathfind::paths& path =
1165  pathfind::paths(*u, false, true, viewing_team(), path_turns_, false, false);
1166 
1167  if(path.destinations.find(hex) != path.destinations.end()) {
1168  reaching_unit_locations.destinations.insert(u->get_location());
1169  gui_->highlight_another_reach(clicked_location);
1170  }
1171  }
1172  }
1173 
1174  gui_->highlight_another_reach(reaching_unit_locations);
1175  } else {
1176  if(!pc_.get_units().find(last_hex_)) {
1178  }
1179 
1181  current_route_.steps.clear();
1182 
1183  pc_.get_whiteboard()->on_deselect_hex();
1184  }
1185 }
1186 
1188 {
1189  select_hex(map_location(), true);
1190 }
1191 
1192 /**
1193  * Moves a unit along the currently cached route.
1194  *
1195  * @returns true if the end of the route was reached and no information was
1196  * uncovered that would warrant interrupting a chain of actions;
1197  * false otherwise.
1198  */
1200 {
1201  // Copy the current route to ensure it remains valid throughout the animation.
1202  const std::vector<map_location> steps = current_route_.steps;
1203 
1204  // do not show footsteps during movement
1205  gui().set_route(nullptr);
1206  gui().unhighlight_reach();
1207 
1208  // do not keep the hex highlighted that we started from
1211 
1212  bool interrupted = false;
1213  if(steps.size() > 1) {
1214  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1215 
1216  interrupted = interrupted || num_moves + 1 < steps.size();
1217  next_unit_ = steps[num_moves];
1218  }
1219 
1220  // invalid after the move
1222  current_route_.steps.clear();
1223 
1224  return !interrupted;
1225 }
1226 
1227 /**
1228  * Moves a unit across the board for a player.
1229  * This is specifically for movement at the time it is initiated by a player,
1230  * whether via a mouse click or executing whiteboard actions. Continued moves
1231  * (including goto execution) can bypass this and call actions::move_unit() directly.
1232  * This function call may include time for an animation, so make sure the
1233  * provided route will remain unchanged (the caller should probably make a local
1234  * copy).
1235  *
1236  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1237  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1238  * actions (and set to false otherwise).
1239  *
1240  * @returns The number of hexes entered. This can safely be used as an index
1241  * into steps to get the location where movement ended, provided
1242  * steps is not empty (the return value is guaranteed to be less
1243  * than steps.size() ).
1244  */
1245 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1246 {
1247  if(steps.empty()) {
1248  interrupted = false;
1249  return 0;
1250  }
1251 
1252  // Default return value.
1253  interrupted = true;
1254 
1255  // If this is a leader on a keep, ask permission to the whiteboard to move it
1256  // since otherwise it may cause planned recruits to be erased.
1257  if(pc_.get_map().is_keep(steps.front())) {
1258  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1259 
1260  if(u && u->can_recruit() && u->side() == gui().viewing_side()
1261  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1263  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1264  return 0;
1265  }
1266  }
1267 
1268  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1269  std::size_t moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), false, true, &interrupted);
1270 
1273 
1274  if(moves == 0)
1275  return 0;
1276 
1277  if(interrupted && moves + 1 < steps.size()) {
1278  // reselect the unit (for "press t to continue")
1279  select_hex(steps[moves], false, false, false);
1280  // the new discovery is more important than the new movement range
1281  show_partial_move_ = true;
1282  }
1283 
1284  return moves;
1285 }
1286 
1288  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1289 {
1290  {
1291  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1292  // This method should do the cleanup of highlights and selection but it doesn't work properly
1293 
1294  // gui().highlight_hex(map_location());
1295 
1296  gui().unhighlight_reach();
1298 
1299  // remove footsteps if any - useless for whiteboard as of now
1300  gui().set_route(nullptr);
1301 
1302  // do not keep the hex that we started from highlighted
1305  show_partial_move_ = false;
1306 
1307  // invalid after saving the move
1309  current_route_.steps.clear();
1310  }
1311 
1312  // create planned attack for whiteboard
1313  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1314 }
1315 
1317  std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
1318 {
1319  int best = 0;
1320  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1321  // skip weapons with attack_weight=0
1322  if(attacker->attacks()[i].attack_weight() > 0) {
1323  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1324 
1325  // Don't include if the attacker's weapon has at least one active "disable" special.
1326  if(bc.get_attacker_stats().disable) {
1327  continue;
1328  }
1329 
1330  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1331  // as some weapons can be hidden, i is not a valid index into the resulting vector
1332  best = bc_vector.size();
1333  }
1334 
1335  bc_vector.emplace_back(std::move(bc));
1336  }
1337  }
1338 
1339  return best;
1340 }
1341 
1342 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
1343 {
1344  game_board& board = pc_.gamestate().board_;
1345 
1346  unit_map::iterator attacker = board.units().find(attacker_loc);
1347  unit_map::iterator defender = board.units().find(defender_loc);
1348 
1349  if(!attacker || !defender) {
1350  ERR_NG << "One fighter is missing, can't attack";
1351  return -1; // abort, click will do nothing
1352  }
1353 
1354  std::vector<battle_context> bc_vector;
1355  const int best = fill_weapon_choices(bc_vector, attacker, defender);
1356 
1357  if(bc_vector.empty()) {
1358  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1359 
1360  return -1;
1361  }
1362 
1363  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best);
1364 
1365  if(dlg.show()) {
1366  return dlg.get_selected_weapon();
1367  }
1368 
1369  return -1;
1370 }
1371 
1372 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1373 {
1374  try {
1375  attack_enemy_(attacker_loc, defender_loc, choice);
1376  } catch(const std::bad_alloc&) {
1377  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1378  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1379  }
1380 }
1381 
1382 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1383 {
1384  // NOTE: copy the values because the const reference may change!
1385  // (WML events and mouse inputs during animations may modify
1386  // the data of the caller)
1387  const map_location attacker_loc = att_loc;
1388  const map_location defender_loc = def_loc;
1389 
1390  unit* attacker = nullptr;
1391  const unit* defender = nullptr;
1392  std::vector<battle_context> bc_vector;
1393 
1394  {
1395  unit_map::iterator attacker_it = find_unit(attacker_loc);
1396  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1397  return;
1398  }
1399 
1400  unit_map::iterator defender_it = find_unit(defender_loc);
1401  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1402  return;
1403  }
1404 
1405  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1406 
1407  attacker = &*attacker_it;
1408  defender = &*defender_it;
1409  }
1410 
1411  if(std::size_t(choice) >= bc_vector.size()) {
1412  return;
1413  }
1414 
1415  events::command_disabler disabler;
1416  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1417  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1418 
1419  attacker->set_goto(map_location());
1420 
1422 
1423  // make the attacker's stats appear during the attack
1424  gui().display_unit_hex(attacker_loc);
1425 
1426  // remove highlighted hexes etc..
1430  gui().unhighlight_reach();
1431 
1432  current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
1433  // TODO: change ToD to be location specific for the defender
1434 
1435  const tod_manager& tod_man = pc_.get_tod_manager();
1436 
1439  attacker_loc,
1440  defender_loc,
1441  att.attack_num,
1442  def.attack_num,
1443  attacker->type_id(),
1444  defender->type_id(),
1445  att.level,
1446  def.level,
1447  tod_man.turn(),
1448  tod_man.get_time_of_day()
1449  )
1450  );
1451 }
1452 
1453 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1454 {
1455  std::set<map_location> res;
1456 
1457  const team& uteam = pc_.get_teams()[side - 1];
1458 
1459  for(const map_location& aloc : get_adjacent_tiles(loc)) {
1461 
1462  if(i && uteam.is_enemy(i->side())) {
1463  res.insert(aloc);
1464  }
1465  }
1466 
1467  return res;
1468 }
1469 
1470 /**
1471  * Causes attackable hexes to be highlighted.
1472  *
1473  * This checks the hexes that the provided unit can attack. If there is a valid
1474  * target there, that location is inserted into current_paths_.destinations.
1475  */
1477 {
1478  // Cannot attack if no attacks are left.
1479  if(u->attacks_left() == 0) {
1480  return;
1481  }
1482 
1483  // Get the teams involved.
1484  const team& cur_team = current_team();
1485  const team& u_team = pc_.get_teams()[u->side() - 1];
1486 
1487  // Check each adjacent hex.
1488  for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
1489  // No attack option shown if no visible unit present.
1490  // (Visible to current team, not necessarily the unit's team.)
1491  if(!pc_.get_map().on_board(loc)) {
1492  continue;
1493  }
1494 
1496  if(!i || !i->is_visible_to_team(cur_team, false)) {
1497  continue;
1498  }
1499 
1500  const unit& target = *i;
1501 
1502  // Can only attack non-petrified enemies.
1503  if(u_team.is_enemy(target.side()) && !target.incapacitated()) {
1505  }
1506  }
1507 }
1508 
1510 {
1511  game_board& board = pc_.gamestate().board_;
1512 
1513  if(!it) {
1514  return false;
1515  }
1516 
1517  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1518  return false;
1519  }
1520 
1521  if(current_team().is_enemy(static_cast<int>(gui().viewing_team() + 1)) && it->invisible(it->get_location())) {
1522  return false;
1523  }
1524 
1525  if(it->get_hidden()) {
1526  return false;
1527  }
1528 
1529  return true;
1530 }
1531 
1532 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1533 {
1534  unit_map& units = pc_.get_units();
1535 
1536  if(units.begin() == units.end()) {
1537  return;
1538  }
1539 
1541  if(!it) {
1542  it = units.begin();
1543  }
1544 
1545  const unit_map::const_iterator itx = it;
1546 
1547  do {
1548  if(reverse) {
1549  if(it == units.begin()) {
1550  it = units.end();
1551  }
1552 
1553  --it;
1554  } else {
1555  if(it == units.end()) {
1556  it = units.begin();
1557  } else {
1558  ++it;
1559  }
1560  }
1561  } while(it != itx && !unit_in_cycle(it));
1562 
1563  if(unit_in_cycle(it)) {
1564  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1565 
1566  select_hex(it->get_location(), browse);
1567  // mouse_update(browse);
1568  }
1569 }
1570 
1572 {
1573  gui().unhighlight_reach();
1574 
1575  current_paths_ = new_paths;
1576  current_route_.steps.clear();
1577 
1578  gui().set_route(nullptr);
1579 
1580  pc_.get_whiteboard()->erase_temp_move();
1581 }
1582 
1584 {
1585  return pc_.get_teams()[gui().viewing_team()];
1586 }
1587 
1589 {
1590  return pc_.get_teams()[gui().viewing_team()];
1591 }
1592 
1594 {
1595  return pc_.get_teams()[side_num_ - 1];
1596 }
1597 
1599 
1601 {
1603 }
1604 
1606 {
1608 }
1609 
1610 } // 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:131
std::size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:123
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:557
bool scroll(int xmov, int ymov, bool force=false)
Scrolls the display by xmov,ymov pixels.
Definition: display.cpp:1735
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:314
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:2025
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:702
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3138
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:725
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:514
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:46
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:79
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:106
virtual const gamemap & map() const override
Definition: game_board.hpp:96
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:71
bool show(const unsigned auto_close_time=0)
Shows the window.
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:74
void set_action_bonus_count(const int count)
Definition: team.hpp:200
bool is_enemy(int n) const
Definition: team.hpp:229
int turn() const
const time_of_day & get_time_of_day(int for_turn=0) const
Returns global time of day for the passed turn.
Definition: tod_manager.hpp:56
void set_selecting()
Sets the animation state to that when the unit is selected.
Container associating units to locations.
Definition: map.hpp:98
unit_iterator end()
Definition: map.hpp:428
unit_iterator find(std::size_t id)
Definition: map.cpp:302
unit_iterator begin()
Definition: map.hpp:418
This class represents a single unit of a specific type.
Definition: unit.hpp:133
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:905
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:2000
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition: unit.hpp:720
int side() const
The side this unit belongs to.
Definition: unit.hpp:343
unit_animation_component & anim_comp() const
Definition: unit.hpp:1606
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1396
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1433
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:474
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:1348
void teleport_unit_and_record(const map_location &teleport_from, const map_location &teleport_to, bool show_move, move_unit_spectator *move_spectator)
Teleports a unit across the board and enters the synced context.
Definition: move.cpp:1292
int side_number
Definition: game_info.hpp:40
CURSOR_TYPE get()
Definition: cursor.cpp:216
@ 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:176
static void update()
Handling of system events.
std::string path
Definition: filesystem.cpp:84
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:544
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:270
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:1064
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:85
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:83
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:226
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:52
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:224
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.