The Battle for Wesnoth  1.19.7+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_(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) {
1120  }
1121 
1122  // The highlight now comes from selection
1123  // and not from the mouseover on an enemy
1124  unselected_paths_ = false;
1125  gui().set_route(nullptr);
1126 
1127  // Selection have impact only if we are not observing and it's our unit
1128  if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1129  if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1130  sound::play_UI_sound("select-unit.wav");
1131 
1133 
1134  if(fire_event) {
1135  // Ensure unit map is back to normal while event is fired
1136  wb::real_map srum;
1137  pc_.pump().fire("select", hex);
1138  // end forced real unit map
1139  }
1140  }
1141  }
1142 
1143  return;
1144  }
1145 
1146  if(selected_hex_.valid() && !unit) {
1147  // Compute unit in range of the empty selected_hex field
1148 
1150 
1151  pathfind::paths reaching_unit_locations;
1152 
1153  pathfind::paths clicked_location;
1154  clicked_location.destinations.insert(hex);
1155 
1156  for(unit_map::iterator u = pc_.get_units().begin(); u != pc_.get_units().end();
1157  ++u) {
1158  bool invisible = u->invisible(u->get_location());
1159 
1160  if(!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible) {
1161  const pathfind::paths& path =
1162  pathfind::paths(*u, false, true, gui().viewing_team(), path_turns_, false, false);
1163 
1164  if(path.destinations.find(hex) != path.destinations.end()) {
1165  reaching_unit_locations.destinations.insert(u->get_location());
1166  gui_->highlight_another_reach(clicked_location);
1167  }
1168  }
1169  }
1170 
1171  gui_->highlight_another_reach(reaching_unit_locations);
1172  } else {
1173  // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1174  if(!pc_.get_units().find(last_hex_) || unhighlight) {
1176  }
1177 
1179  current_route_.steps.clear();
1180 
1181  pc_.get_whiteboard()->on_deselect_hex();
1182  }
1183 }
1184 
1186 {
1187  select_hex(map_location(), true);
1188 }
1189 
1190 /**
1191  * Moves a unit along the currently cached route.
1192  *
1193  * @returns true if the end of the route was reached and no information was
1194  * uncovered that would warrant interrupting a chain of actions;
1195  * false otherwise.
1196  */
1198 {
1199  // Copy the current route to ensure it remains valid throughout the animation.
1200  const std::vector<map_location> steps = current_route_.steps;
1201 
1202  // do not show footsteps during movement
1203  gui().set_route(nullptr);
1204  gui().unhighlight_reach();
1205 
1206  // do not keep the hex highlighted that we started from
1209 
1210  bool interrupted = false;
1211  if(steps.size() > 1) {
1212  std::size_t num_moves = move_unit_along_route(steps, interrupted);
1213 
1214  interrupted = interrupted || num_moves + 1 < steps.size();
1215  next_unit_ = steps[num_moves];
1216  }
1217 
1218  // invalid after the move
1220  current_route_.steps.clear();
1221 
1222  return !interrupted;
1223 }
1224 
1225 /**
1226  * Moves a unit across the board for a player.
1227  * This is specifically for movement at the time it is initiated by a player,
1228  * whether via a mouse click or executing whiteboard actions. Continued moves
1229  * (including goto execution) can bypass this and call actions::move_unit() directly.
1230  * This function call may include time for an animation, so make sure the
1231  * provided route will remain unchanged (the caller should probably make a local
1232  * copy).
1233  *
1234  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1235  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1236  * actions (and set to false otherwise).
1237  *
1238  * @returns The number of hexes entered. This can safely be used as an index
1239  * into steps to get the location where movement ended, provided
1240  * steps is not empty (the return value is guaranteed to be less
1241  * than steps.size() ).
1242  */
1243 std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1244 {
1245  if(steps.empty()) {
1246  interrupted = false;
1247  return 0;
1248  }
1249 
1250  // Default return value.
1251  interrupted = true;
1252 
1253  // If this is a leader on a keep, ask permission to the whiteboard to move it
1254  // since otherwise it may cause planned recruits to be erased.
1255  if(pc_.get_map().is_keep(steps.front())) {
1256  unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1257 
1258  if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1259  && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1261  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1262  return 0;
1263  }
1264  }
1265 
1266  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1267  std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1268 
1271 
1272  if(moves == 0)
1273  return 0;
1274 
1275  if(interrupted && moves + 1 < steps.size()) {
1276  // reselect the unit (for "press t to continue")
1277  select_hex(steps[moves], false, false, false);
1278  // the new discovery is more important than the new movement range
1279  show_partial_move_ = true;
1280  }
1281 
1282  return moves;
1283 }
1284 
1286  const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1287 {
1288  {
1289  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1290  // This method should do the cleanup of highlights and selection but it doesn't work properly
1291 
1292  // gui().highlight_hex(map_location());
1293 
1294  gui().unhighlight_reach();
1296 
1297  // remove footsteps if any - useless for whiteboard as of now
1298  gui().set_route(nullptr);
1299 
1300  // do not keep the hex that we started from highlighted
1303  show_partial_move_ = false;
1304 
1305  // invalid after saving the move
1307  current_route_.steps.clear();
1308  }
1309 
1310  // create planned attack for whiteboard
1311  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1312 }
1313 
1315  std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1316 {
1317  int best = 0;
1318  for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1319  // skip weapons with attack_weight=0
1320  if(attacker->attacks()[i].attack_weight() > 0) {
1321  battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i);
1322 
1323  // Don't include if the attacker's weapon has at least one active "disable" special.
1324  if(bc.get_attacker_stats().disable) {
1325  continue;
1326  }
1327 
1328  if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1329  // as some weapons can be hidden, i is not a valid index into the resulting vector
1330  best = bc_vector.size();
1331  }
1332 
1333  bc_vector.emplace_back(std::move(bc));
1334  }
1335  }
1336 
1337  return best;
1338 }
1339 
1340 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
1341 {
1342  game_board& board = pc_.gamestate().board_;
1343 
1344  unit_map::iterator attacker = board.units().find(attacker_loc);
1345  unit_map::iterator defender = board.units().find(defender_loc);
1346 
1347  if(!attacker || !defender) {
1348  ERR_NG << "One fighter is missing, can't attack";
1349  return -1; // abort, click will do nothing
1350  }
1351 
1352  std::vector<battle_context> bc_vector;
1353  const int best = fill_weapon_choices(bc_vector, attacker, defender);
1354 
1355  if(bc_vector.empty()) {
1356  gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1357 
1358  return -1;
1359  }
1360 
1361  gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best);
1362 
1363  if(dlg.show()) {
1364  return dlg.get_selected_weapon();
1365  }
1366 
1367  return -1;
1368 }
1369 
1370 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1371 {
1372  try {
1373  attack_enemy_(attacker_loc, defender_loc, choice);
1374  } catch(const std::bad_alloc&) {
1375  lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1376  ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1377  }
1378 }
1379 
1380 void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1381 {
1382  // NOTE: copy the values because the const reference may change!
1383  // (WML events and mouse inputs during animations may modify
1384  // the data of the caller)
1385  const map_location attacker_loc = att_loc;
1386  const map_location defender_loc = def_loc;
1387 
1388  unit* attacker = nullptr;
1389  const unit* defender = nullptr;
1390  std::vector<battle_context> bc_vector;
1391 
1392  {
1393  unit_map::iterator attacker_it = find_unit(attacker_loc);
1394  if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1395  return;
1396  }
1397 
1398  unit_map::iterator defender_it = find_unit(defender_loc);
1399  if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1400  return;
1401  }
1402 
1403  fill_weapon_choices(bc_vector, attacker_it, defender_it);
1404 
1405  attacker = &*attacker_it;
1406  defender = &*defender_it;
1407  }
1408 
1409  if(std::size_t(choice) >= bc_vector.size()) {
1410  return;
1411  }
1412 
1413  events::command_disabler disabler;
1414  const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1415  const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1416 
1417  attacker->set_goto(map_location());
1418 
1420 
1421  // make the attacker's stats appear during the attack
1422  gui().display_unit_hex(attacker_loc);
1423 
1424  // remove highlighted hexes etc..
1428  gui().unhighlight_reach();
1429 
1430  current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
1431  // TODO: change ToD to be location specific for the defender
1432 
1433  const tod_manager& tod_man = pc_.get_tod_manager();
1434 
1437  attacker_loc,
1438  defender_loc,
1439  att.attack_num,
1440  def.attack_num,
1441  attacker->type_id(),
1442  defender->type_id(),
1443  att.level,
1444  def.level,
1445  tod_man.turn(),
1446  tod_man.get_time_of_day()
1447  )
1448  );
1449 }
1450 
1451 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1452 {
1453  std::set<map_location> res;
1454 
1455  const team& uteam = pc_.get_teams()[side - 1];
1456 
1457  for(const map_location& aloc : get_adjacent_tiles(loc)) {
1459 
1460  if(i && uteam.is_enemy(i->side())) {
1461  res.insert(aloc);
1462  }
1463  }
1464 
1465  return res;
1466 }
1467 
1468 /**
1469  * Causes attackable hexes to be highlighted.
1470  *
1471  * This checks the hexes that the provided unit can attack. If there is a valid
1472  * target there, that location is inserted into current_paths_.destinations.
1473  */
1475 {
1476  // Cannot attack if no attacks are left.
1477  if(u->attacks_left() == 0) {
1478  return;
1479  }
1480 
1481  // Get the teams involved.
1482  const team& cur_team = current_team();
1483  const team& u_team = pc_.get_teams()[u->side() - 1];
1484 
1485  // Check each adjacent hex.
1486  for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
1487  // No attack option shown if no visible unit present.
1488  // (Visible to current team, not necessarily the unit's team.)
1489  if(!pc_.get_map().on_board(loc)) {
1490  continue;
1491  }
1492 
1494  if(!i || !i->is_visible_to_team(cur_team, false)) {
1495  continue;
1496  }
1497 
1498  const unit& target = *i;
1499 
1500  // Can only attack non-petrified enemies.
1501  if(u_team.is_enemy(target.side()) && !target.incapacitated()) {
1503  }
1504  }
1505 }
1506 
1508 {
1509  game_board& board = pc_.gamestate().board_;
1510 
1511  if(!it) {
1512  return false;
1513  }
1514 
1515  if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1516  return false;
1517  }
1518 
1519  if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1520  return false;
1521  }
1522 
1523  if(it->get_hidden()) {
1524  return false;
1525  }
1526 
1527  return true;
1528 }
1529 
1530 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1531 {
1532  unit_map& units = pc_.get_units();
1533 
1534  if(units.begin() == units.end()) {
1535  return;
1536  }
1537 
1539  if(!it) {
1540  it = units.begin();
1541  }
1542 
1543  const unit_map::const_iterator itx = it;
1544 
1545  do {
1546  if(reverse) {
1547  if(it == units.begin()) {
1548  it = units.end();
1549  }
1550 
1551  --it;
1552  } else {
1553  if(it == units.end()) {
1554  it = units.begin();
1555  } else {
1556  ++it;
1557  }
1558  }
1559  } while(it != itx && !unit_in_cycle(it));
1560 
1561  if(unit_in_cycle(it)) {
1562  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1563 
1564  select_hex(it->get_location(), browse);
1565  // mouse_update(browse);
1566  }
1567 }
1568 
1570 {
1571  gui().unhighlight_reach();
1572 
1573  current_paths_ = new_paths;
1574  current_route_.steps.clear();
1575 
1576  gui().set_route(nullptr);
1577 
1578  pc_.get_whiteboard()->erase_temp_move();
1579 }
1580 
1582 {
1583  return pc_.get_teams()[side_num_ - 1];
1584 }
1585 
1587 
1589 {
1591 }
1592 
1594 {
1596 }
1597 
1598 } // 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:342
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:1975
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.cpp:674
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3080
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:693
rect map_area() const
Returns the area used for the map.
Definition: display.cpp:495
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:538
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition: display.cpp:1700
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 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.
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:200
int side() const
Definition: team.hpp:175
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
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
bool incapacitated() const
Check if the unit has been petrified.
Definition: unit.hpp:911
const std::string & type_id() const
The id of this unit's type.
Definition: unit.cpp:1933
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:1614
const map_location & get_location() const
The current map location this unit is at.
Definition: unit.hpp:1404
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition: unit.hpp:1441
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:1397
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:1429
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:91
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
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:53
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:225
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.