The Battle for Wesnoth  1.19.9+dev
mouse_events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2025
3  by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4  Copyright (C) 2003 by David White <dave@whitevine.net>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 #include "mouse_events.hpp"
18 
19 #include "actions/attack.hpp" // for battle_context, etc
20 #include "actions/move.hpp" // for move_and_record
21 #include "config.hpp" // for config
22 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
23 #include "game_board.hpp" // for game_board, etc
24 #include "game_events/pump.hpp" // for fire
25 #include "gettext.hpp" // for _
26 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
27 #include "gui/dialogs/unit_attack.hpp" // for unit_attack
28 #include "gui/widgets/settings.hpp" // for new_widgets
29 #include "log.hpp" // for LOG_STREAM, logger, etc
30 #include "map/map.hpp" // for gamemap
31 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
32 #include "play_controller.hpp" // for playing_side, set_button_state
33 #include "replay_helper.hpp"
35 #include "sound.hpp"
36 #include "synced_context.hpp"
37 #include "team.hpp" // for team
38 #include "tod_manager.hpp"
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_(map_location::null_location())
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 
132  if(is_dragging() && !dragging_started_) {
133  if(dragging_touch_) {
135  const double drag_distance =
136  std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
137  std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
138 
139  if(drag_distance > drag_threshold()*drag_threshold()) {
140  dragging_started_ = true;
141  }
142  }
143  }
144 
145  // Not-so-smooth panning
146  const auto found_unit = find_unit(selected_hex_);
147  bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
148  if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
149 
150  if(gui().map_area().contains(x, y)) {
152  gui().scroll(drag_from_ - pos);
153  drag_from_ = pos;
154  }
155  return;
156  }
157 
158  // now copy-pasting mouse_handler::mouse_motion()
159 
160  // Note for anyone reconciling this code with the version in mouse_handler::mouse_motion:
161  // commit 27a40a82aeea removed the game_board& board from mouse_motion, but didn't update
162  // the corresponding code here in touch_motion.
163  game_board & board = pc_.gamestate().board_;
164 
165  if(new_hex == map_location::null_location())
166  new_hex = gui().hex_clicked_on(x,y);
167 
168  if(new_hex != last_hex_) {
169  update = true;
170  if( pc_.get_map().on_board(last_hex_) ) {
171  // we store the previous hexes used to propose attack direction
173  // the hex of the selected unit is also "free"
174  { // start planned unit map scope
178  }
179  } // end planned unit map scope
180  }
181  last_hex_ = new_hex;
182  }
183 
184  if(reachmap_invalid_) update = true;
185 
186  if(!update) return;
187 
188  if(reachmap_invalid_) {
189  reachmap_invalid_ = false;
191  { // start planned unit map scope
192  wb::future_map_if_active planned_unit_map;
193  selected_hex_has_my_unit = found_unit.valid();
194  } // end planned unit map scope
195  if(selected_hex_.valid() && selected_hex_has_my_unit) {
196  // FIXME: vic: why doesn't this trigger when touch-dragging an unselected unit?
197  // reselect the unit without firing events (updates current_paths_)
198  select_hex(selected_hex_, true);
199  }
200  // we do never deselect here, mainly because of canceled attack-move
201  }
202  }
203 
204  // reset current_route_ and current_paths if not valid anymore
205  // we do it before cursor selection, because it uses current_paths_
206  if( !pc_.get_map().on_board(new_hex) ) {
207  current_route_.steps.clear();
208  gui().set_route(nullptr);
209  pc_.get_whiteboard()->erase_temp_move();
210  }
211 
212  if(unselected_paths_) {
213  unselected_paths_ = false;
216  } else if(over_route_) {
217  over_route_ = false;
218  current_route_.steps.clear();
219  gui().set_route(nullptr);
220  pc_.get_whiteboard()->erase_temp_move();
221  }
222 
223  gui().highlight_hex(new_hex);
224  pc_.get_whiteboard()->on_mouseover_change(new_hex);
225 
227  unit_map::iterator mouseover_unit;
228  map_location attack_from;
229 
230  { // start planned unit map scope
231  wb::future_map_if_active planned_unit_map;
232  selected_unit = found_unit;
233  mouseover_unit = find_unit(new_hex);
234 
235  // we search if there is an attack possibility and where
236  attack_from = current_unit_attacks_from(new_hex);
237 
238  //see if we should show the normal cursor, the movement cursor, or
239  //the attack cursor
240  //If the cursor is on WAIT, we don't change it and let the setter
241  //of this state end it
242  if (cursor::get() != cursor::WAIT) {
243  if (selected_unit &&
244  selected_unit->side() == side_num_ &&
245  !selected_unit->incapacitated() && !browse)
246  {
247  if (attack_from.valid()) {
249  }
250  else if (!mouseover_unit &&
252  {
253  // Is this where left-drag cursor changes? Test.
255  } else {
256  // selected unit can't attack or move there
258  }
259  } else {
260  // no selected unit or we can't move it
261 
262  if ( selected_hex_.valid() && mouseover_unit
263  && mouseover_unit->side() == side_num_ ) {
264  // empty hex field selected and unit on our site under the cursor
266  } else {
268  }
269  }
270  }
271  } // end planned unit map scope
272 
273  // show (or cancel) the attack direction indicator
274  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
275  gui().set_attack_indicator(attack_from, new_hex);
276  } else {
278  }
279 
280  unit_ptr un; //will later point to unit at mouseover_hex_
281 
282  // the destination is the pointed hex or the adjacent hex
283  // used to attack it
284  map_location dest;
285  unit_map::const_iterator dest_un;
286  { // start planned unit map scope
288  if (attack_from.valid()) {
289  dest = attack_from;
290  dest_un = find_unit(dest);
291  } else {
292  dest = new_hex;
293  dest_un = find_unit(new_hex);
294  }
295 
296  if(dest == selected_hex_ || dest_un) {
297  current_route_.steps.clear();
298  gui().set_route(nullptr);
299  pc_.get_whiteboard()->erase_temp_move();
300  }
301  else if (!current_paths_.destinations.empty() &&
302  board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
303  {
304  if (selected_unit && !selected_unit->incapacitated()) {
305  // Show the route from selected unit to mouseover hex
306  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
307 
308  pc_.get_whiteboard()->create_temp_move();
309 
310  if(!browse) {
312  }
313  }
314  }
315 
316  if(board.map().on_board(selected_hex_)
317  && !selected_unit
318  && mouseover_unit.valid()
319  && mouseover_unit) {
320  // Show the route from selected hex to mouseover unit
321  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
322 
323  pc_.get_whiteboard()->create_temp_move();
324 
325  if(!browse) {
327  }
328  } else if (!selected_unit) {
329  current_route_.steps.clear();
330  gui().set_route(nullptr);
331  pc_.get_whiteboard()->erase_temp_move();
332  }
333 
334  unit_map::iterator iter = mouseover_unit;
335  if (iter)
336  un = iter.get_shared_ptr();
337  else
338  un.reset();
339  } //end planned unit map scope
340 }
341 
343 {
344  if( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
345  !gui().fogged(un->get_location()))
346  {
347  // If the unit has a path set and is either ours or allied then show the path.
348  //
349  // Exception: allied AI sides' moves are still hidden, on the assumption that
350  // campaign authors won't want to leak goto_x,goto_y tricks to the player.
351  if(!gui().viewing_team().is_enemy(un->side()) && !pc_.get_teams()[un->side() - 1].is_ai()) {
352  //unit is on our team or an allied team, show path if the unit has one
353  const map_location go_to = un->get_goto();
354  if(pc_.get_map().on_board(go_to)) {
356  { // start planned unit map scope
358  route = get_route(un.get(), go_to, current_team());
359  } // end planned unit map scope
360  gui().set_route(&route);
361  }
362  over_route_ = true;
363  }
364 
365  // Scope for the unit_movement_resetter and future_map_if_active.
366  {
367  // Making this non-null will show the unit's max moves instead of current moves.
368  // Because movement is reset to max in the side's refresh phase, use the max if
369  // that refresh will happen before the unit's side can move again.
370  std::unique_ptr<unit_movement_resetter> move_reset;
371  if(un->side() != side_num_) {
372  move_reset = std::make_unique<unit_movement_resetter>(*un);
373  }
374 
375  // Handle whiteboard. Any move_reset must be done before this, since the future
376  // state includes changes to units' movement.
378 
379  current_paths_ = pathfind::paths(*un, false, true, gui().viewing_team(), path_turns_);
380  }
381 
382  unselected_paths_ = true;
384  }
385 }
386 
387 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
388 {
389  // we ignore the position coming from event handler
390  // because it's always a little obsolete and we don't need
391  // to highlight all the hexes where the mouse passed.
392  // Also, sometimes it seems to have one *very* obsolete
393  // and isolated mouse motion event when using drag&drop
394  sdl::get_mouse_state(&x, &y); // <-- modify x and y
395 
397  return;
398  }
399 
400  // Don't process other motion events while scrolling
401  if(scroll_started_) {
402  return;
403  }
404 
405  if(new_hex == map_location::null_location()) {
406  new_hex = gui().hex_clicked_on(x, y);
407  }
408 
409  if(new_hex != last_hex_) {
410  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
411  lk->mouse_over_hex_callback(new_hex);
412  }
413 
414  update = true;
415 
416  if(pc_.get_map().on_board(last_hex_)) {
417  // we store the previous hexes used to propose attack direction
419 
420  // the hex of the selected unit is also "free"
421  { // start planned unit map scope
425  }
426  } // end planned unit map scope
427  }
428 
429  last_hex_ = new_hex;
430  }
431 
432  if(reachmap_invalid_) {
433  update = true;
434  }
435 
436  if(!update) {
437  return;
438  }
439 
440  if(reachmap_invalid_) {
441  reachmap_invalid_ = false;
442 
444  bool selected_hex_has_unit;
445  { // start planned unit map scope
446  wb::future_map_if_active planned_unit_map;
447  selected_hex_has_unit = find_unit(selected_hex_).valid();
448  } // end planned unit map scope
449 
450  if(selected_hex_.valid() && selected_hex_has_unit) {
451  // reselect the unit without firing events (updates current_paths_)
452  select_hex(selected_hex_, true);
453  }
454 
455  // we do never deselect here, mainly because of canceled attack-move
456  }
457  }
458 
459  // reset current_route_ and current_paths if not valid anymore
460  // we do it before cursor selection, because it uses current_paths_
461  if(!pc_.get_map().on_board(new_hex)) {
462  current_route_.steps.clear();
463  gui().set_route(nullptr);
464  pc_.get_whiteboard()->erase_temp_move();
465  }
466 
467  if(unselected_paths_) {
468  unselected_paths_ = false;
471  } else if(over_route_) {
472  over_route_ = false;
473  current_route_.steps.clear();
474  gui().set_route(nullptr);
475  pc_.get_whiteboard()->erase_temp_move();
476  }
477 
478  gui().highlight_hex(new_hex);
479  pc_.get_whiteboard()->on_mouseover_change(new_hex);
480 
482  unit_map::iterator mouseover_unit;
483  map_location attack_from;
484 
485  { // start planned unit map scope
486  wb::future_map_if_active planned_unit_map;
488  mouseover_unit = find_unit(new_hex);
489 
490  // we search if there is an attack possibility and where
491  attack_from = current_unit_attacks_from(new_hex);
492 
493  // see if we should show the normal cursor, the movement cursor, or
494  // the attack cursor
495  // If the cursor is on WAIT, we don't change it and let the setter
496  // of this state end it
497  if(cursor::get() != cursor::WAIT) {
498  if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
499  if(attack_from.valid()) {
501  } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
503  } else {
504  // selected unit can't attack or move there
506  }
507  } else {
508  // no selected unit or we can't move it
509 
510  if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
511  // empty hex field selected and unit on our site under the cursor
513  } else {
515  }
516  }
517  }
518  } // end planned unit map scope
519 
520  // show (or cancel) the attack direction indicator
521  if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
522  gui().set_attack_indicator(attack_from, new_hex);
523  } else {
525  }
526 
527  unit_ptr un; // will later point to unit at mouseover_hex_
528 
529  // the destination is the pointed hex or the adjacent hex
530  // used to attack it
531  map_location dest;
532  unit_map::const_iterator dest_un;
533  /* start planned unit map scope*/
534  {
536  if(attack_from.valid()) {
537  dest = attack_from;
538  dest_un = find_unit(dest);
539  } else {
540  dest = new_hex;
541  dest_un = find_unit(new_hex);
542  }
543 
544  if(dest == selected_hex_ || dest_un) {
545  current_route_.steps.clear();
546  gui().set_route(nullptr);
547  pc_.get_whiteboard()->erase_temp_move();
548  } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
549  if(selected_unit && !selected_unit->incapacitated()) {
550  // Show the route from selected unit to mouseover hex
551  current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
552 
553  pc_.get_whiteboard()->create_temp_move();
554 
555  if(!browse) {
557  }
558  }
559  }
560 
561  if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
562  // Show the route from selected hex to mouseover unit
563  current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
564 
565  pc_.get_whiteboard()->create_temp_move();
566 
567  if(!browse) {
569  }
570  } else if(!selected_unit) {
571  current_route_.steps.clear();
572  gui().set_route(nullptr);
573  pc_.get_whiteboard()->erase_temp_move();
574  }
575 
576  if(mouseover_unit) {
577  un = mouseover_unit.get_shared_ptr();
578  } else {
579  un.reset();
580  }
581  } /*end planned unit map scope*/
582 
583  /*
584  * Only highlight unit's reach if toggler not preventing normal unit
585  * processing. This can happen e.g. if, after activating 'show
586  * [best possible] enemy movements' through the UI menu, the
587  * mouse cursor lands on a hex with unit in it.
588  */
591  }
592 
593  if(!un && preventing_units_highlight_) {
594  // Cursor on empty hex, turn unit highlighting back on.
596  }
597 }
598 
599 // Hook for notifying lua game kernel of mouse button events. We pass button as
600 // a serpaate argument than the original SDL event in order to manage touch
601 // emulation (e.g., long touch = right click) and such.
602 bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
603  map_location loc, bool click)
604 {
605  static const std::array<const std::string, 6> buttons = {
606  "",
607  "left", // SDL_BUTTON_LEFT
608  "middle", // SDL_BUTTON_MIDDLE
609  "right", // SDL_BUTTON_RIGHT
610  "mouse4", // SDL_BUTTON_X1
611  "mouse5" // SDL_BUTTON_X2
612  };
613 
614  if (gui().view_locked() || button < SDL_BUTTON_LEFT || button > buttons.size()) {
615  return false;
616  } else if (event.state > SDL_PRESSED || !pc_.get_map().on_board(loc)) {
617  return false;
618  }
619 
620  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
621  lk->mouse_button_callback(loc, buttons[button], (event.state == SDL_RELEASED ? "up" : "down"));
622 
623  // Are we being asked to send a click event?
624  if (click) {
625  // Was both the up and down on the same map tile?
626  if (loc != drag_from_hex_) {
627  return false;
628  }
629  // We allow this event to be consumed, but not up/down
630  return lk->mouse_button_callback(loc, buttons[button], "click");
631  }
632  }
633  return false;
634 }
635 
637 {
639  if(res) {
640  return res;
641  }
642 
643  return find_unit(last_hex_);
644 }
645 
647 {
648  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
649  if(it.valid()) {
650  return it;
651  }
652 
653  return pc_.get_units().end();
654 }
655 
657 {
658  return pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
659 }
660 
662 {
663  unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
664  return it.valid() ? &*it : nullptr;
665 }
666 
668 {
669  unit_map::const_iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
670  return it.valid() ? &*it : nullptr;
671 }
672 
674 {
675  auto [x, y] = sdl::get_mouse_location();
676  return gui_->hex_clicked_on(x, y);
677 }
678 
680 {
681  return find_unit(hex).valid();
682 }
683 
685 {
686  if(loc == selected_hex_) {
687  return map_location();
688  }
689 
690  bool wb_active = pc_.get_whiteboard()->is_active();
691 
692  {
693  // Check the unit SOURCE of the attack
694 
695  // Check that there's a selected unit
696  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
697 
698  bool source_eligible = source_unit.valid();
699  if(!source_eligible) {
700  return map_location();
701  }
702 
703  // The selected unit must at least belong to the player currently controlling this client.
704  source_eligible &= source_unit->side() == gui_->viewing_team().side();
705  if(!source_eligible) {
706  return map_location();
707  }
708 
709  // In addition:
710  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
711  // - If whiteboard is disabled, it must be the turn of the player controlling this client
712  if(!wb_active) {
713  source_eligible &= gui_->viewing_team().side() == pc_.current_side();
714  if(!source_eligible) {
715  return map_location();
716  }
717  }
718 
719  // Unit must have attacks left
720  source_eligible &= source_unit->attacks_left() != 0;
721  if(!source_eligible) {
722  return map_location();
723  }
724 
725  // Check the unit TARGET of the attack
726 
727  const team& viewer = gui().viewing_team();
728 
729  // Check that there's a unit at the target location
730  const unit_map::const_iterator target_unit = find_unit(loc);
731 
732  bool target_eligible = target_unit.valid();
733  if(!target_eligible) {
734  return map_location();
735  }
736 
737  // The player controlling this client must be an enemy of the target unit's side
738  target_eligible &= viewer.is_enemy(target_unit->side());
739  if(!target_eligible) {
740  return map_location();
741  }
742 
743  // Sanity check: source and target of the attack shouldn't be on the same team
744  assert(source_unit->side() != target_unit->side());
745 
746  target_eligible &= !target_unit->incapacitated();
747  if(!target_eligible) {
748  return map_location();
749  }
750  }
751 
754 
755  int best_rating = 100; // smaller is better
756 
757  map_location res;
758  const auto adj = get_adjacent_tiles(loc);
759 
760  for(std::size_t n = 0; n < adj.size(); ++n) {
761  if(pc_.get_map().on_board(adj[n]) == false) {
762  continue;
763  }
764 
765  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
766  continue;
767  }
768 
770  static const std::size_t ndirections = static_cast<int>(map_location::direction::indeterminate);
771 
772  unsigned int difference = std::abs(static_cast<int>(static_cast<int>(preferred) - n));
773  if(difference > ndirections / 2) {
774  difference = ndirections - difference;
775  }
776 
777  unsigned int second_difference = std::abs(static_cast<int>(static_cast<int>(second_preferred) - n));
778  if(second_difference > ndirections / 2) {
779  second_difference = ndirections - second_difference;
780  }
781 
782  const int rating = difference * 2 + (second_difference > difference);
783  if(rating < best_rating || res.valid() == false) {
784  best_rating = rating;
785  res = adj[n];
786  }
787  }
788  }
789 
790  return res;
791 }
792 
794 {
795  game_board& board = pc_.gamestate().board_;
796 
797  // The pathfinder will check unit visibility (fogged/stealthy).
798  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
799 
800  pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, gui().viewing_team());
801 
802  pathfind::plain_route route;
803 
804  route = pathfind::a_star_search(
805  un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
806 
807  return mark_route(route);
808 }
809 
810 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
811 {
813  unselected_reach_ = false;
814  return false;
815  }
816 
817  return gui().map_area().contains(x, y);
818 }
819 
821 {
822  // Load whiteboard partial moves
823  //wb::future_map_if_active planned_unit_map;
824 
825  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
826  lk->select_hex_callback(last_hex_);
827  }
828 
831 
832  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
833  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
834  ) {
836  teleport_selected_ = true;
838  gui().set_route(nullptr);
839  }
840 }
841 
843 {
844  // Set the teleport to active so that we can use existing functions
845  // for teleport
846  teleport_selected_ = false;
847 
851  gui().invalidate_all();
853  gui().set_route(nullptr);
854 
855  // Select and deselect the units hex to prompt updates for hover
856  select_hex(last_hex_, false);
857  deselect_hex();
858  current_route_.steps.clear();
859 }
860 
862 {
863  if(!pc_.get_map().on_board(last_hex_)) {
865  return;
866  }
867 
868  // Load whiteboard partial moves
869  wb::future_map_if_active planned_unit_map;
870 
871  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
872  lk->select_hex_callback(last_hex_);
873  }
874 
877 
878  if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
879  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
880  ) {
881  select_hex(last_hex_, false);
882  } else {
883  move_action(browse);
884  }
885  teleport_selected_ = false;
886 }
887 
888 void mouse_handler::move_action(bool browse)
889 {
890  // Lock whiteboard activation state to avoid problems due to
891  // its changing while an animation takes place.
892  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
893 
894  // we use the last registered highlighted hex
895  // since it's what update our global state
896  map_location hex = last_hex_;
897 
898  // TODO
899  // // Clicks on border hexes mean to deselect.
900  // // (Check this before doing processing that might not be needed.)
901  // if ( !pc_.get_map().on_board(hex) ) {
902  // deselect_hex();
903  // return false;
904  // }
905 
906  unit* u = nullptr;
907  const unit* clicked_u = nullptr;
908 
910  pathfind::paths orig_paths;
911  map_location attack_from;
912 
913  { // start planned unit map scope
914  wb::future_map_if_active planned_unit_map;
916 
917  // if the unit is selected and then itself clicked on,
918  // any goto command is canceled
919  if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
920  u->set_goto(map_location());
921  }
922 
923  clicked_u = find_unit_nonowning(hex);
924 
925  src = selected_hex_;
926  orig_paths = current_paths_;
927  attack_from = current_unit_attacks_from(hex);
928  } // end planned unit map scope
929 
930  // See if the teleport option is toggled
931  if(teleport_selected_) {
932  teleport_action();
933  }
934  // see if we're trying to do a attack or move-and-attack
935  else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
936  // Ignore this command if commands are disabled.
937  if(commands_disabled) {
938  return;
939  }
940 
941  if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
942  if(attack_from == selected_hex_) { // no move needed
943  int choice = -1;
944  {
945  wb::future_map_if_active planned_unit_map; // start planned unit map scope
946  choice = show_attack_dialog(attack_from, clicked_u->get_location());
947  } // end planned unit map scope
948 
949  if(choice >= 0) {
950  if(pc_.get_whiteboard()->is_active()) {
951  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
952  } else {
953  // clear current unit selection so that any other unit selected
954  // triggers a new selection
956 
957  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
958  }
959  }
960 
961  return;
962  } else {
963  int choice = -1; // for the attack dialog
964 
965  {
966  wb::future_map_if_active planned_unit_map; // start planned unit map scope
967  // we will now temporary move next to the enemy
968  pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
969  if(itor == current_paths_.destinations.end()) {
970  // can't reach the attacking location
971  // not supposed to happen, so abort
972  return;
973  }
974 
975  // block where we temporary move the unit
976  {
977  temporary_unit_mover temp_mover(pc_.get_units(), src, attack_from, itor->move_left, true);
978  choice = show_attack_dialog(attack_from, clicked_u->get_location());
979  }
980 
981  if(choice < 0) {
982  // user hit cancel, don't start move+attack
983  return;
984  }
985  } // end planned unit map scope
986 
987  if(pc_.get_whiteboard()->is_active()) {
988  save_whiteboard_attack(attack_from, hex, choice);
989  } else {
990  bool not_interrupted = move_unit_along_current_route();
991  bool alt_unit_selected = (selected_hex_ != src);
992  src = selected_hex_;
993  // clear current unit selection so that any other unit selected
994  // triggers a new selection
996 
997  if(not_interrupted)
998  attack_enemy(attack_from, hex, choice); // Fight !!
999 
1000  // TODO: Maybe store the attack choice so "press t to continue"
1001  // can also continue the attack?
1002 
1003  if(alt_unit_selected && !selected_hex_.valid()) {
1004  // reselect other unit if selected during movement animation
1005  select_hex(src, browse);
1006  }
1007  }
1008 
1009  return;
1010  }
1011  }
1012  }
1013  // otherwise we're trying to move to a hex
1014  else if(
1015  // The old use case: move selected unit to mouse hex field.
1016  (
1017  (!browse || pc_.get_whiteboard()->is_active())
1018  && selected_hex_.valid()
1019  && selected_hex_ != hex
1020  && u != nullptr
1021  && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1022  && !clicked_u
1023  && !current_route_.steps.empty()
1024  && current_route_.steps.front() == selected_hex_
1025  )
1026  || // The new use case: move mouse unit to selected hex field.
1027  (
1028  (!browse || pc_.get_whiteboard()->is_active())
1029  && selected_hex_.valid()
1030  && selected_hex_ != hex
1031  && clicked_u
1032  && !current_route_.steps.empty()
1033  && current_route_.steps.back() == selected_hex_
1034  && !u
1035  && clicked_u->side() == side_num_
1036  )
1037  ) {
1038  // Ignore this command if commands are disabled.
1039  if(commands_disabled) {
1040  return;
1041  }
1042 
1043  // If the whiteboard is active, it intercepts any unit movement.
1044  if(pc_.get_whiteboard()->is_active()) {
1045  // Deselect the current hex, and create planned move for whiteboard.
1047 
1050  gui().set_route(nullptr);
1051 
1052  show_partial_move_ = false;
1053 
1054  gui().unhighlight_reach();
1055 
1057  current_route_.steps.clear();
1058 
1059  pc_.get_whiteboard()->save_temp_move();
1060 
1061  // Otherwise proceed to normal unit movement
1062  } else {
1063  // Don't move if the unit already has actions
1064  // from the whiteboard.
1065  if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1066  return;
1067  }
1068 
1070 
1071  // During the move, we may have selected another unit
1072  // (but without triggering a select event (command was disabled)
1073  // in that case reselect it now to fire the event (+ anim & sound)
1074  if(selected_hex_ != src) {
1075  select_hex(selected_hex_, browse);
1076  }
1077  }
1078 
1079  return;
1080  }
1081 }
1082 
1083 void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1084 {
1085  unit_map::iterator unit = find_unit(touched_hex);
1086 
1087  if (touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1088  select_or_action(browse);
1089  } else {
1090  deselect_hex();
1091  }
1092 }
1093 
1094 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1095 {
1096  bool unhighlight = selected_hex_.valid() && force_unhighlight;
1097 
1098  selected_hex_ = hex;
1099 
1102  gui().set_route(nullptr);
1103 
1104  show_partial_move_ = false;
1105 
1106  wb::future_map_if_active planned_unit_map; // lasts for whole method
1107 
1109 
1110  if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1112 
1113  {
1114  current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1115  }
1116 
1117  if(highlight) {
1119  }
1120 
1121  // The highlight now comes from selection
1122  // and not from the mouseover on an enemy
1123  unselected_paths_ = false;
1124  gui().set_route(nullptr);
1125 
1126  // Selection have impact only if we are not observing and it's our unit
1127  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1128  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1129  sound::play_UI_sound("select-unit.wav");
1130 
1132 
1133  if(fire_event) {
1134  // Ensure unit map is back to normal while event is fired
1135  wb::real_map srum;
1136  pc_.pump().fire("select", hex);
1137  // end forced real unit map
1138  }
1139  }
1140  }
1141 
1142  return;
1143  }
1144 
1145  if(selected_hex_.valid() && !unit) {
1146  // Compute unit in range of the empty selected_hex field
1147 
1149 
1150  pathfind::paths reaching_unit_locations;
1151 
1152  pathfind::paths clicked_location;
1153  clicked_location.destinations.insert(hex);
1154 
1155  for(unit_map::iterator u = pc_.get_units().begin(); u != pc_.get_units().end();
1156  ++u) {
1157  bool invisible = u->invisible(u->get_location());
1158 
1159  if(!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible) {
1160  const pathfind::paths& path =
1161  pathfind::paths(*u, false, true, gui().viewing_team(), path_turns_, false, false);
1162 
1163  if(path.destinations.find(hex) != path.destinations.end()) {
1164  reaching_unit_locations.destinations.insert(u->get_location());
1165  gui_->highlight_another_reach(clicked_location);
1166  }
1167  }
1168  }
1169 
1170  gui_->highlight_another_reach(reaching_unit_locations);
1171  } else {
1172  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1173  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1175  }
1176 
1178  current_route_.steps.clear();
1179 
1180  pc_.get_whiteboard()->on_deselect_hex();
1181  }
1182 }
1183 
1185 {
1186  select_hex(map_location(), true);
1187 }
1188 
1189 /**
1190  * Moves a unit along the currently cached route.
1191  *
1192  * @returns true if the end of the route was reached and no information was
1193  * uncovered that would warrant interrupting a chain of actions;
1194  * false otherwise.
1195  */
1197 {
1198  // Copy the current route to ensure it remains valid throughout the animation.
1199  const std::vector<map_location> steps = current_route_.steps;
1200 
1201  // do not show footsteps during movement
1202  gui().set_route(nullptr);
1203  gui().unhighlight_reach();
1204 
1205  // do not keep the hex highlighted that we started from
1208 
1209  bool interrupted = false;
1210  if(steps.size() > 1) {
1211  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1212 
1213  interrupted = interrupted || num_moves + 1 < steps.size();
1214  next_unit_ = steps[num_moves];
1215  }
1216 
1217  // invalid after the move
1219  current_route_.steps.clear();
1220 
1221  return !interrupted;
1222 }
1223 
1224 /**
1225  * Moves a unit across the board for a player.
1226  * This is specifically for movement at the time it is initiated by a player,
1227  * whether via a mouse click or executing whiteboard actions. Continued moves
1228  * (including goto execution) can bypass this and call actions::move_unit() directly.
1229  * This function call may include time for an animation, so make sure the
1230  * provided route will remain unchanged (the caller should probably make a local
1231  * copy).
1232  *
1233  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1234  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1235  * actions (and set to false otherwise).
1236  *
1237  * @returns The number of hexes entered. This can safely be used as an index
1238  * into steps to get the location where movement ended, provided
1239  * steps is not empty (the return value is guaranteed to be less
1240  * than steps.size() ).
1241  */
1242 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1243 {
1244  if(steps.empty()) {
1245  interrupted = false;
1246  return 0;
1247  }
1248 
1249  // Default return value.
1250  interrupted = true;
1251 
1252  // If this is a leader on a keep, ask permission to the whiteboard to move it
1253  // since otherwise it may cause planned recruits to be erased.
1254  if(pc_.get_map().is_keep(steps.front())) {
1255  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1256 
1257  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1258  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1260  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1261  return 0;
1262  }
1263  }
1264 
1265  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1266  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1267 
1270 
1271  if(moves == 0)
1272  return 0;
1273 
1274  if(interrupted && moves + 1 < steps.size()) {
1275  // reselect the unit (for "press t to continue")
1276  select_hex(steps[moves], false, false, false);
1277  // the new discovery is more important than the new movement range
1278  show_partial_move_ = true;
1279  }
1280 
1281  return moves;
1282 }
1283 
1285  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1286 {
1287  {
1288  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1289  // This method should do the cleanup of highlights and selection but it doesn't work properly
1290 
1291  // gui().highlight_hex(map_location());
1292 
1293  gui().unhighlight_reach();
1295 
1296  // remove footsteps if any - useless for whiteboard as of now
1297  gui().set_route(nullptr);
1298 
1299  // do not keep the hex that we started from highlighted
1302  show_partial_move_ = false;
1303 
1304  // invalid after saving the move
1306  current_route_.steps.clear();
1307  }
1308 
1309  // create planned attack for whiteboard
1310  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1311 }
1312 
1314  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1315 {
1316  int best = 0;
1317  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1318  // skip weapons with attack_weight=0
1319  if(attacker->attacks()[i].attack_weight() > 0) {
1320  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1321 
1322  // Don't include if the attacker's weapon has at least one active "disable" special.
1323  if(bc.get_attacker_stats().disable) {
1324  continue;
1325  }
1326 
1327  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1328  // as some weapons can be hidden, i is not a valid index into the resulting vector
1329  best = bc_vector.size();
1330  }
1331 
1332  bc_vector.emplace_back(std::move(bc));
1333  }
1334  }
1335 
1336  return best;
1337 }
1338 
1339 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
1340 {
1341  game_board& board = pc_.gamestate().board_;
1342 
1343  unit_map::iterator attacker = board.units().find(attacker_loc);
1344  unit_map::iterator defender = board.units().find(defender_loc);
1345 
1346  if(!attacker || !defender) {
1347  ERR_NG << "One fighter is missing, can't attack";
1348  return -1; // abort, click will do nothing
1349  }
1350 
1351  std::vector<battle_context> bc_vector;
1352  const int best = fill_weapon_choices(bc_vector, attacker, defender);
1353 
1354  if(bc_vector.empty()) {
1355  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1356 
1357  return -1;
1358  }
1359 
1360  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best);
1361 
1362  if(dlg.show()) {
1363  return dlg.get_selected_weapon();
1364  }
1365 
1366  return -1;
1367 }
1368 
1369 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1370 {
1371  try {
1372  attack_enemy_(attacker_loc, defender_loc, choice);
1373  } catch(const std::bad_alloc&) {
1374  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1375  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1376  }
1377 }
1378 
1379 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1380 {
1381  // NOTE: copy the values because the const reference may change!
1382  // (WML events and mouse inputs during animations may modify
1383  // the data of the caller)
1384  const map_location attacker_loc = att_loc;
1385  const map_location defender_loc = def_loc;
1386 
1387  unit* attacker = nullptr;
1388  const unit* defender = nullptr;
1389  std::vector<battle_context> bc_vector;
1390 
1391  {
1392  unit_map::iterator attacker_it = find_unit(attacker_loc);
1393  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1394  return;
1395  }
1396 
1397  unit_map::iterator defender_it = find_unit(defender_loc);
1398  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1399  return;
1400  }
1401 
1402  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1403 
1404  attacker = &*attacker_it;
1405  defender = &*defender_it;
1406  }
1407 
1408  if(std::size_t(choice) >= bc_vector.size()) {
1409  return;
1410  }
1411 
1412  events::command_disabler disabler;
1413  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1414  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1415 
1416  attacker->set_goto(map_location());
1417 
1419 
1420  // make the attacker's stats appear during the attack
1421  gui().display_unit_hex(attacker_loc);
1422 
1423  // remove highlighted hexes etc..
1427  gui().unhighlight_reach();
1428 
1429  current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
1430  // TODO: change ToD to be location specific for the defender
1431 
1432  const tod_manager& tod_man = pc_.get_tod_manager();
1433 
1436  attacker_loc,
1437  defender_loc,
1438  att.attack_num,
1439  def.attack_num,
1440  attacker->type_id(),
1441  defender->type_id(),
1442  att.level,
1443  def.level,
1444  tod_man.turn(),
1445  tod_man.get_time_of_day()
1446  )
1447  );
1448 }
1449 
1450 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1451 {
1452  std::set<map_location> res;
1453 
1454  const team& uteam = pc_.get_teams()[side - 1];
1455 
1456  for(const map_location& aloc : get_adjacent_tiles(loc)) {
1458 
1459  if(i && uteam.is_enemy(i->side())) {
1460  res.insert(aloc);
1461  }
1462  }
1463 
1464  return res;
1465 }
1466 
1468 {
1469  game_board& board = pc_.gamestate().board_;
1470 
1471  if(!it) {
1472  return false;
1473  }
1474 
1475  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1476  return false;
1477  }
1478 
1479  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1480  return false;
1481  }
1482 
1483  if(it->get_hidden()) {
1484  return false;
1485  }
1486 
1487  return true;
1488 }
1489 
1490 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1491 {
1492  unit_map& units = pc_.get_units();
1493 
1494  if(units.begin() == units.end()) {
1495  return;
1496  }
1497 
1499  if(!it) {
1500  it = units.begin();
1501  }
1502 
1503  const unit_map::const_iterator itx = it;
1504 
1505  do {
1506  if(reverse) {
1507  if(it == units.begin()) {
1508  it = units.end();
1509  }
1510 
1511  --it;
1512  } else {
1513  if(it == units.end()) {
1514  it = units.begin();
1515  } else {
1516  ++it;
1517  }
1518  }
1519  } while(it != itx && !unit_in_cycle(it));
1520 
1521  if(unit_in_cycle(it)) {
1522  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1523 
1524  select_hex(it->get_location(), browse);
1525  // mouse_update(browse);
1526  }
1527 }
1528 
1530 {
1531  gui().unhighlight_reach();
1532 
1533  current_paths_ = new_paths;
1534  current_route_.steps.clear();
1535 
1536  gui().set_route(nullptr);
1537 
1538  pc_.get_whiteboard()->erase_temp_move();
1539 }
1540 
1542 {
1543  return pc_.get_teams()[side_num_ - 1];
1544 }
1545 
1547 
1549 {
1551 }
1552 
1554 {
1556 }
1557 
1558 } // end namespace events
Various functions that implement attacks and attack calculations.
map_location loc
Definition: move.cpp:172
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:456
can_move_result unit_can_move(const unit &u) const
Work out what u can do - this does not check which player's turn is currently active,...
const team & viewing_team() const
Definition: display.cpp:344
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:316
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:1985
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:676
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3088
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:695
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:497
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:540
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1710
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
map_location last_hex_
last highlighted hex
map_location drag_from_hex_
Drag start or mouse-down map location.
bool simple_warp_
MMB click (on game map) state flag.
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
bool dragging_started_
Actual drag flag.
bool dragging_touch_
Finger drag init flag.
point drag_from_
Drag start position.
void disable_units_highlight()
Use this to disable hovering an unit from highlighting its movement range.
int fill_weapon_choices(std::vector< battle_context > &bc_vector, const unit_map::iterator &attacker, const unit_map::iterator &defender)
bool move_unit_along_current_route()
Moves a unit along the currently cached route.
bool mouse_button_event(const SDL_MouseButtonEvent &event, uint8_t button, map_location loc, bool click=false) override
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)
pathfind::marked_route get_route(const unit *un, map_location go_to, const team &team) const
play_controller & pc_
unit_map::iterator selected_unit()
static mouse_handler * singleton_
void enable_units_highlight()
When unit highlighting is disabled, call this when the mouse no longer hovers any unit to enable high...
void touch_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
void attack_enemy(const map_location &attacker_loc, const map_location &defender_loc, int choice)
std::set< map_location > get_adj_enemies(const map_location &loc, int side) const
map_location previous_free_hex_
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true, const bool force_unhighlight=false)
unit_map::const_iterator find_unit(const map_location &hex) const
map_location current_unit_attacks_from(const map_location &loc) const
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.
bool unit_in_cycle(const unit_map::const_iterator &it)
void touch_action(const map_location hex, bool browse) override
void set_side(int side_number)
void move_action(bool browse) override
Overridden in derived class.
pathfind::paths current_paths_
If non-empty, current_paths_.destinations contains a cache of highlighted hexes, likely the movement ...
bool hex_hosts_unit(const map_location &hex) const
Unit exists on the hex, no matter if friend or foe.
int drag_threshold() const override
Minimum dragging distance to fire the drag&drop.
unit * find_unit_nonowning(const map_location &hex)
void cycle_units(const bool browse, const bool reverse=false)
std::size_t move_unit_along_route(const std::vector< map_location > &steps, bool &interrupted)
Moves a unit across the board for a player.
void mouse_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
Use update to force an update of the mouse state.
Game board class.
Definition: game_board.hpp:47
virtual const std::vector< team > & teams() const override
Definition: game_board.hpp:80
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:185
virtual const unit_map & units() const override
Definition: game_board.hpp:107
virtual const gamemap & map() const override
Definition: game_board.hpp:97
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
virtual void highlight_hex(map_location hex) override
Function to highlight a location.
void set_attack_indicator(const map_location &src, const map_location &dst)
Set the attack direction indicator.
void set_route(const pathfind::marked_route *route)
Sets the route along which footsteps are drawn to show movement of a unit.
virtual void select_hex(map_location hex) override
Function to display a location as selected.
void highlight_reach(const pathfind::paths &paths_list)
Sets the paths that are currently displayed as available for the unit to move along.
void clear_attack_indicator()
void highlight_another_reach(const pathfind::paths &paths_list, const map_location &goal=map_location::null_location())
Add more paths to highlight.
bool unhighlight_reach()
Reset highlighting of paths.
pump_result_t fire(const std::string &event, const entity_location &loc1=entity_location::null_entity, const entity_location &loc2=entity_location::null_entity, const config &data=config())
Function to fire an event.
Definition: pump.cpp:399
std::unique_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:48
game_board board_
Definition: game_state.hpp:44
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:385
bool is_keep(const map_location &loc) const
Definition: map.cpp:72
bool show(const unsigned auto_close_time=0)
Shows the window.
std::vector< team > & get_teams()
const unit_map & get_units() const
game_state & gamestate()
const tod_manager & get_tod_manager() const
int current_side() const
Returns the number of the side whose turn it is.
std::shared_ptr< wb::manager > get_whiteboard() const
const gamemap & get_map() const
game_events::wml_event_pump & pump()
static config get_attack(const map_location &a, const map_location &b, int att_weapon, int def_weapon, const std::string &attacker_type_id, const std::string &defender_type_id, int attacker_lvl, int defender_lvl, const std::size_t turn, const time_of_day &t)
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
void set_action_bonus_count(const int count)
Definition: team.hpp:205
int side() const
Definition: team.hpp:180
bool is_enemy(int n) const
Definition: team.hpp:234
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
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1029
static std::string _(const char *str)
Definition: gettext.hpp:93
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1913
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:1607
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1397
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1434
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:479
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
#define ERR_NG
#define ERR_WML
static lg::log_domain log_wml("wml")
#define LOG_NG
void teleport_unit_and_record(const map_location &teleport_from, const map_location &teleport_to, move_unit_spectator *)
Teleports a unit across the board and enters the synced context.
Definition: move.cpp:1399
std::size_t move_unit_and_record(const std::vector< map_location > &steps, bool continued_move, bool *interrupted)
Wrapper around the other overload.
Definition: move.cpp:1431
int side_number
Definition: game_info.hpp:40
CURSOR_TYPE get()
Definition: cursor.cpp: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:92
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:520
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator &calc, const std::size_t width, const std::size_t height, const teleport_map *teleports, bool border)
marked_route mark_route(const plain_route &rt, bool update_move_cost)
Add marks on a route rt assuming that the unit located at the first hex of rt travels along it.
Definition: pathfind.cpp:658
const teleport_map get_teleport_locations(const unit &u, const team &viewing_team, bool see_all, bool ignore_units, bool check_vision)
Definition: teleport.cpp:251
uint32_t get_mouse_state(int *x, int *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition: input.cpp:27
point get_mouse_location()
Returns the current mouse location in draw space.
Definition: input.cpp:54
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1078
bool click(int mousex, int mousey)
Definition: tooltips.cpp:356
void process(int mousex, int mousey)
Definition: tooltips.cpp:340
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
constexpr auto reverse
Definition: ranges.hpp:40
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
std::shared_ptr< bool > whiteboard_lock
Definition: typedefs.hpp:56
std::shared_ptr< unit > unit_ptr
Definition: ptr.hpp:26
Define the game's event mechanism.
This file contains the settings handling of the widget library.
rect src
Non-transparent portion of the surface to compose.
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:51
unsigned int 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:45
bool valid() const
Definition: location.hpp:110
direction
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:47
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:240
static const map_location & null_location()
Definition: location.hpp:102
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:142
std::vector< map_location > & steps
Definition: pathfind.hpp:187
void insert(const map_location &)
Definition: pathfind.cpp:485
bool contains(const map_location &) const
Definition: pathfind.cpp:514
const_iterator find(const map_location &) const
Definition: pathfind.cpp:478
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:73
dest_vect destinations
Definition: pathfind.hpp:101
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:133
Holds a 2D point.
Definition: point.hpp:25
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition: rect.cpp:54
This object is used to temporary move a unit in the unit map, swapping out any unit that is already t...
Definition: game_board.hpp:223
bool valid() const
Definition: map.hpp:273
pointer get_shared_ptr() const
This is exactly the same as operator-> but it's slightly more readable, and can replace &*iter syntax...
Definition: map.hpp:217
ONLY IF whiteboard is currently active, applies the planned unit map for the duration of the struct's...
Definition: manager.hpp:274
Ensures that the real unit map is active for the duration of the struct's life.
Definition: manager.hpp:284
static map_location::direction n
Contains typedefs for the whiteboard.