The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
mouse_events.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2006 - 2016 by Joerg Hinrichs <[email protected]>
3  wesnoth playturn Copyright (C) 2003 by David White <[email protected]>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 
17 #include "mouse_events.hpp"
18 #include "global.hpp"
19 
20 #include "actions/attack.hpp" // for battle_context, etc
21 #include "actions/move.hpp" // for move_and_record
22 #include "actions/undo.hpp" // for undo_list
23 #include "config.hpp" // for config
24 #include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
25 #include "dialogs.hpp" // for units_list_preview_pane, etc
26 #include "game_board.hpp" // for game_board, etc
27 #include "game_config.hpp" // for red_to_green
28 #include "game_events/manager.hpp"
29 #include "game_events/pump.hpp" // for fire
30 #include "gettext.hpp" // for _
31 #include "gui/dialogs/transient_message.hpp" // for show_transient_message
32 #include "gui/dialogs/unit_attack.hpp" // for tunit_attack
33 #include "gui/widgets/settings.hpp" // for new_widgets
34 #include "gui/widgets/window.hpp" // for enum
35 #include "language.hpp" // for string_table, symbol_table
36 #include "log.hpp" // for LOG_STREAM, logger, etc
37 #include "map/map.hpp" // for gamemap
38 #include "marked-up_text.hpp" // for color2markup, BOLD_TEXT, etc
39 #include "pathfind/teleport.hpp" // for get_teleport_locations, etc
40 #include "play_controller.hpp" // for playing_side, set_button_state
41 #include "replay_helper.hpp"
42 #include "sdl/utils.hpp" // for int_to_color
43 #include "serialization/string_utils.hpp" // for unicode_em_dash
44 #include "sound.hpp"
45 #include "synced_context.hpp"
46 #include "team.hpp" // for team
47 #include "tod_manager.hpp"
48 #include "tstring.hpp" // for t_string
49 #include "units/unit.hpp" // for unit, intrusive_ptr_add_ref
52 #include "units/ptr.hpp" // for unit_const_ptr
53 #include "whiteboard/manager.hpp" // for manager, etc
54 #include "whiteboard/typedefs.hpp" // for whiteboard_lock
55 #include "wml_separators.hpp" // for COLUMN_SEPARATOR, etc
56 
57 #include <boost/intrusive_ptr.hpp> // for intrusive_ptr
58 #include <boost/shared_ptr.hpp> // for shared_ptr
59 #include <cassert> // for assert
60 #include <new> // for bad_alloc
61 #include <ostream> // for operator<<, basic_ostream, etc
62 #include <string> // for string, operator<<, etc
63 #include <SDL_mouse.h> // for SDL_GetMouseState
64 #include <SDL_video.h> // for SDL_Color
65 
66 namespace gui { class slider; }
67 
68 static lg::log_domain log_engine("engine");
69 #define ERR_NG LOG_STREAM(err, log_engine)
70 #define LOG_NG LOG_STREAM(info, log_engine)
71 
72 namespace events{
73 
74 
77  gui_(gui),
78  pc_(pc),
79  previous_hex_(),
80  previous_free_hex_(),
81  selected_hex_(),
82  next_unit_(),
83  current_route_(),
84  current_paths_(),
85  unselected_paths_(false),
86  path_turns_(0),
87  side_num_(1),
88  over_route_(false),
89  reachmap_invalid_(false),
90  show_partial_move_(false)
91 {
92  singleton_ = this;
93 }
94 
96 {
97  singleton_ = nullptr;
98 }
99 
101 {
103 }
104 
106 {
107  return 14;
108 }
109 
110 void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
111 {
112  // we ignore the position coming from event handler
113  // because it's always a little obsolete and we don't need
114  // to highlight all the hexes where the mouse passed.
115  // Also, sometimes it seems to have one *very* obsolete
116  // and isolated mouse motion event when using drag&drop
117  SDL_GetMouseState(&x,&y); // <-- modify x and y
118 
119  if (mouse_handler_base::mouse_motion_default(x, y, update)) return;
120 
121  game_board & board = pc_.gamestate().board_;
122 
123  if (new_hex == map_location::null_location())
124  new_hex = gui().hex_clicked_on(x,y);
125 
126  if(new_hex != last_hex_) {
127  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
128  lk->mouse_over_hex_callback(new_hex);
129  }
130  update = true;
131  if ( pc_.get_map_const().on_board(last_hex_) ) {
132  // we store the previous hexes used to propose attack direction
134  // the hex of the selected unit is also "free"
135  { // start planned unit map scope
139  }
140  } // end planned unit map scope
141  }
142  last_hex_ = new_hex;
143  }
144 
145  if (reachmap_invalid_) update = true;
146 
147  if (!update) return;
148 
149  if (reachmap_invalid_) {
150  reachmap_invalid_ = false;
152  bool selected_hex_has_unit;
153  { // start planned unit map scope
154  wb::future_map_if_active planned_unit_map;
155  selected_hex_has_unit = find_unit(selected_hex_).valid();
156  } // end planned unit map scope
157  if(selected_hex_.valid() && selected_hex_has_unit ) {
158  // reselect the unit without firing events (updates current_paths_)
159  select_hex(selected_hex_, true);
160  }
161  // we do never deselect here, mainly because of canceled attack-move
162  }
163  }
164 
165  // reset current_route_ and current_paths if not valid anymore
166  // we do it before cursor selection, because it uses current_paths_
167  if( !pc_.get_map_const().on_board(new_hex) ) {
168  current_route_.steps.clear();
169  gui().set_route(nullptr);
170  pc_.get_whiteboard()->erase_temp_move();
171  }
172 
173  if(unselected_paths_) {
174  unselected_paths_ = false;
177  } else if(over_route_) {
178  over_route_ = false;
179  current_route_.steps.clear();
180  gui().set_route(nullptr);
181  pc_.get_whiteboard()->erase_temp_move();
182  }
183 
184  gui().highlight_hex(new_hex);
185  pc_.get_whiteboard()->on_mouseover_change(new_hex);
186 
188  unit_map::iterator mouseover_unit;
189  map_location attack_from;
190 
191  { // start planned unit map scope
192  wb::future_map_if_active planned_unit_map;
193  selected_unit = find_unit(selected_hex_);
194  mouseover_unit = find_unit(new_hex);
195 
196  // we search if there is an attack possibility and where
197  attack_from = current_unit_attacks_from(new_hex);
198 
199  //see if we should show the normal cursor, the movement cursor, or
200  //the attack cursor
201  //If the cursor is on WAIT, we don't change it and let the setter
202  //of this state end it
203  if (cursor::get() != cursor::WAIT) {
204  if (selected_unit &&
205  selected_unit->side() == side_num_ &&
206  !selected_unit->incapacitated() && !browse)
207  {
208  if (attack_from.valid()) {
210  }
211  else if (!mouseover_unit &&
213  {
215  } else {
216  // selected unit can't attack or move there
218  }
219  } else {
220  // no selected unit or we can't move it
221 
222  if ( selected_hex_.valid() && mouseover_unit
223  && mouseover_unit->side() == side_num_ ) {
224  // empty hex field selected and unit on our site under the cursor
226  } else {
228  }
229  }
230  }
231  } // end planned unit map scope
232 
233  // show (or cancel) the attack direction indicator
234  if (attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
235  gui().set_attack_indicator(attack_from, new_hex);
236  } else {
238  }
239 
240  unit_ptr un; //will later point to unit at mouseover_hex_
241 
242  // the destination is the pointed hex or the adjacent hex
243  // used to attack it
244  map_location dest;
245  unit_map::const_iterator dest_un;
246  { // start planned unit map scope
248  if (attack_from.valid()) {
249  dest = attack_from;
250  dest_un = find_unit(dest);
251  } else {
252  dest = new_hex;
253  dest_un = find_unit(new_hex);
254  }
255 
256  if(dest == selected_hex_ || dest_un) {
257  current_route_.steps.clear();
258  gui().set_route(nullptr);
259  pc_.get_whiteboard()->erase_temp_move();
260  }
261  else if (!current_paths_.destinations.empty() &&
262  board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
263  {
264  if (selected_unit && !selected_unit->incapacitated()) {
265  // Show the route from selected unit to mouseover hex
266  current_route_ = get_route(&*selected_unit, dest, viewing_team());
267 
268  pc_.get_whiteboard()->create_temp_move();
269 
270  if(!browse) {
272  }
273  }
274  }
275 
276  if(board.map().on_board(selected_hex_)
277  && !selected_unit
278  && mouseover_unit.valid()
279  && mouseover_unit) {
280  // Show the route from selected hex to mouseover unit
281  current_route_ = get_route(&*mouseover_unit, selected_hex_, viewing_team());
282 
283  pc_.get_whiteboard()->create_temp_move();
284 
285  if(!browse) {
287  }
288  } else if (!selected_unit) {
289  current_route_.steps.clear();
290  gui().set_route(nullptr);
291  pc_.get_whiteboard()->erase_temp_move();
292  }
293 
294  unit_map::iterator iter = mouseover_unit;
295  if (iter)
296  un = iter.get_shared_ptr();
297  else
298  un.reset();
299  } //end planned unit map scope
300 
301  if ( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
302  !gui().fogged(un->get_location()))
303  {
304  if (un->side() == side_num_) {
305  //unit is on our team, show path if the unit has one
306  const map_location go_to = un->get_goto();
307  if(board.map().on_board(go_to)) {
309  { // start planned unit map scope
311  route = get_route(un.get(), go_to, current_team());
312  } // end planned unit map scope
313  gui().set_route(&route);
314  }
315  over_route_ = true;
316 
318  current_paths_ = pathfind::paths(*un, false, true,
320  } else {
321  //unit under cursor is not on our team
322  //Note: planned unit map must be activated after this is done,
323  //since the future state includes changes to units' movement.
324  unit_movement_resetter move_reset(*un);
325 
327  current_paths_ = pathfind::paths(*un, false, true,
329  }
330 
331  unselected_paths_ = true;
333  }
334 
335 }
336 
338 {
340  if(res) {
341  return res;
342  } else {
343  return find_unit(last_hex_);
344  }
345 }
346 
348 {
350  if (it.valid())
351  return it;
352  else
353  return pc_.gamestate().board_.units_.end();
354 }
355 
357 {
359 }
360 
362 {
363  if(loc == selected_hex_)
364  return map_location();
365 
366  bool wb_active = pc_.get_whiteboard()->is_active();
367 
368  {
369  // Check the unit SOURCE of the attack
370 
371  // Check that there's a selected unit
372  const unit_map::const_iterator source_unit = find_unit(selected_hex_);
373  bool source_eligible = source_unit.valid();
374  if (!source_eligible) return map_location();
375 
376  // The selected unit must at least belong to the player currently controlling this client.
377  source_eligible &= source_unit->side() == gui_->viewing_side();
378  if (!source_eligible) return map_location();
379 
380  // In addition:
381  // - If whiteboard is enabled, we allow planning attacks outside of player's turn
382  // - If whiteboard is disabled, it must be the turn of the player controlling this client
383  if(!wb_active) {
384  source_eligible &= gui_->viewing_side() == pc_.current_side();
385  if (!source_eligible) return map_location();
386  }
387 
388  // Unit must have attacks left
389  source_eligible &= source_unit->attacks_left() != 0;
390  if (!source_eligible) return map_location();
391 
392 
393  // Check the unit TARGET of the attack
394 
395  team const& viewer = viewing_team();
396 
397  // Check that there's a unit at the target location
398  const unit_map::const_iterator target_unit = find_unit(loc);
399  bool target_eligible = target_unit.valid();
400  if (!target_eligible) return map_location();
401 
402  // The player controlling this client must be an enemy of the target unit's side
403  target_eligible &= viewer.is_enemy(target_unit->side());
404  if (!target_eligible) return map_location();
405 
406  // Sanity check: source and target of the attack shouldn't be on the same team
407  assert(source_unit->side() != target_unit->side());
408 
409  target_eligible &= !target_unit->incapacitated();
410  if (!target_eligible) return map_location();
411  }
412 
414  const map_location::DIRECTION second_preferred = loc.get_relative_dir(previous_free_hex_);
415 
416  int best_rating = 100;//smaller is better
418  map_location adj[6];
419  get_adjacent_tiles(loc,adj);
420 
421  for(size_t n = 0; n != 6; ++n) {
422  if(pc_.gamestate().board_.map().on_board(adj[n]) == false) {
423  continue;
424  }
425 
426  if(adj[n] != selected_hex_ && find_unit(adj[n])) {
427  continue;
428  }
429 
431  {
432  static const size_t NDIRECTIONS = map_location::NDIRECTIONS;
433  unsigned int difference = abs(int(preferred - n));
434  if(difference > NDIRECTIONS/2) {
435  difference = NDIRECTIONS - difference;
436  }
437  unsigned int second_difference = abs(int(second_preferred - n));
438  if(second_difference > NDIRECTIONS/2) {
439  second_difference = NDIRECTIONS - second_difference;
440  }
441  const int rating = difference * 2 + (second_difference > difference);
442  if(rating < best_rating || res.valid() == false) {
443  best_rating = rating;
444  res = adj[n];
445  }
446  }
447  }
448 
449  return res;
450 }
451 
453 {
454  game_board & board = pc_.gamestate().board_;
455 
456  // The pathfinder will check unit visibility (fogged/stealthy).
457  const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
458 
460 
461  pathfind::plain_route route;
462 
463  route = pathfind::a_star_search(un->get_location(), go_to, 10000.0, &calc, board.map().w(), board.map().h(), &allowed_teleports);
464 
465  return mark_route(route);
466 }
467 
468 void mouse_handler::mouse_press(const SDL_MouseButtonEvent& event, const bool browse)
469 {
470  mouse_handler_base::mouse_press(event, browse);
471 }
472 
473 bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
474 {
475  return ( selected_hex_.valid() ? false :
476  sdl::point_in_rect(x, y, gui().map_area()) );
477 }
478 
479 void mouse_handler::left_mouse_up(int /*x*/, int /*y*/, const bool /*browse*/)
480 {
481  std::shared_ptr<gui::slider> s = gui_->find_slider("map-zoom-slider");
482  if (s && s->value_change())
483  if (gui_->set_zoom(s->value(), true))
485 }
486 
487 void mouse_handler::mouse_wheel_up(int /*x*/, int /*y*/, const bool /*browse*/)
488 {
489  std::shared_ptr<gui::slider> s = gui_->find_slider("map-zoom-slider");
490  if (s && s->value_change())
491  if (gui_->set_zoom(s->value(), true))
493 }
494 
495 void mouse_handler::mouse_wheel_down(int /*x*/, int /*y*/, const bool /*browse*/)
496 {
497  std::shared_ptr<gui::slider> s = gui_->find_slider("map-zoom-slider");
498  if (s && s->value_change())
499  if (gui_->set_zoom(s->value(), true))
501 }
502 
503 void mouse_handler::mouse_wheel_left(int /*x*/, int /*y*/, const bool /*browse*/)
504 {
505  std::shared_ptr<gui::slider> s = gui_->find_slider("map-zoom-slider");
506  if (s && s->value_change())
507  if (gui_->set_zoom(s->value(), true))
509 }
510 
511 void mouse_handler::mouse_wheel_right(int /*x*/, int /*y*/, const bool /*browse*/)
512 {
513  std::shared_ptr<gui::slider> s = gui_->find_slider("map-zoom-slider");
514  if (s && s->value_change())
515  if (gui_->set_zoom(s->value(), true))
517 }
518 
520 {
522  return;
523 
524  // Load whiteboard partial moves
525  wb::future_map_if_active planned_unit_map;
526 
529  if ( clicked_u &&
530  (!selected_u ||
531  selected_u->side() != side_num_ ||
532  (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id())) )
533  {
534  select_hex(last_hex_, false);
535  }
536  else
537  {
538  move_action(browse);
539  }
540 }
541 
542 void mouse_handler::move_action(bool browse)
543 {
544  // Lock whiteboard activation state to avoid problems due to
545  // its changing while an animation takes place.
546  wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
547 
548  //we use the last registered highlighted hex
549  //since it's what update our global state
550  map_location hex = last_hex_;
551 
552  // TODO
553  // // Clicks on border hexes mean to deselect.
554  // // (Check this before doing processing that might not be needed.)
555  // if ( !pc_.get_map_const().on_board(hex) ) {
556  // deselect_hex();
557  // return false;
558  // }
559  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
560  lk->select_hex_callback(last_hex_);
561  }
563  unit_map::iterator clicked_u;
565  pathfind::paths orig_paths;
566  map_location attack_from;
567  { // start planned unit map scope
568  wb::future_map_if_active planned_unit_map;
570 
571  //if the unit is selected and then itself clicked on,
572  //any goto command is canceled
573  if (u && !browse && selected_hex_ == hex && u->side() == side_num_) {
574  u->set_goto(map_location());
575  }
576 
577  clicked_u = find_unit(hex);
578 
579  src = selected_hex_;
580  orig_paths = current_paths_;
581  attack_from = current_unit_attacks_from(hex);
582  } // end planned unit map scope
583 
584  //see if we're trying to do a attack or move-and-attack
585  if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
586 
587  // Ignore this command if commands are disabled.
588  if ( commands_disabled )
589  return;
590 
591  if (((u.valid() && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u.valid() ) {
592  if (attack_from == selected_hex_) { //no move needed
593  int choice = -1;
594  { wb::future_map_if_active planned_unit_map; //start planned unit map scope
595  choice = show_attack_dialog(attack_from, clicked_u->get_location());
596  } // end planned unit map scope
597  if (choice >=0 ) {
598  if (pc_.get_whiteboard()->is_active()) {
599  save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
600  } else {
601  // clear current unit selection so that any other unit selected
602  // triggers a new selection
604 
605  attack_enemy(u->get_location(), clicked_u->get_location(), choice);
606  }
607  }
608  return;
609  }
610  else {
611 
612  int choice = -1; //for the attack dialog
613 
614  { wb::future_map_if_active planned_unit_map; //start planned unit map scope
615  // we will now temporary move next to the enemy
616  pathfind::paths::dest_vect::const_iterator itor =
617  current_paths_.destinations.find(attack_from);
618  if(itor == current_paths_.destinations.end()) {
619  // can't reach the attacking location
620  // not supposed to happen, so abort
621  return;
622  }
623 
624  // block where we temporary move the unit
625  {
626  temporary_unit_mover temp_mover(pc_.gamestate().board_.units_, src, attack_from,
627  itor->move_left);
628  choice = show_attack_dialog(attack_from, clicked_u->get_location());
629  }
630 
631  if (choice < 0) {
632  // user hit cancel, don't start move+attack
633  return;
634  }
635  } // end planned unit map scope
636 
637  if (pc_.get_whiteboard()->is_active()) {
638  save_whiteboard_attack(attack_from, hex, choice);
639  }
640  else {
641  bool not_interrupted = move_unit_along_current_route();
642  bool alt_unit_selected = (selected_hex_ != src);
643  src = selected_hex_;
644  // clear current unit selection so that any other unit selected
645  // triggers a new selection
647 
648  if (not_interrupted)
649  attack_enemy(attack_from, hex, choice); // Fight !!
650 
651  //TODO: Maybe store the attack choice so "press t to continue"
652  // can also continue the attack?
653 
654  if (alt_unit_selected && !selected_hex_.valid()) {
655  //reselect other unit if selected during movement animation
656  select_hex(src, browse);
657  }
658  }
659 
660  return;
661  }
662  }
663  }
664  //otherwise we're trying to move to a hex
665  else if (
666  // the old use case: move selected unit to mouse hex field
667  ( (!browse || pc_.get_whiteboard()->is_active()) &&
668  selected_hex_.valid() && selected_hex_ != hex &&
669  u.valid() &&
670  (u->side() == side_num_ || pc_.get_whiteboard()->is_active()) &&
671  !clicked_u &&
672  !current_route_.steps.empty() &&
674  )
675  || // the new use case: move mouse unit to selected hex field
676  ( (!browse || pc_.get_whiteboard()->is_active()) &&
677  selected_hex_.valid() && selected_hex_ != hex &&
678  clicked_u &&
679  !current_route_.steps.empty() &&
681  && !u
682  && clicked_u->side() == side_num_
683  )
684  ) {
685 
686  // Ignore this command if commands are disabled.
687  if ( commands_disabled )
688  return;
689 
690  // If the whiteboard is active, it intercepts any unit movement
691  if (pc_.get_whiteboard()->is_active()) {
692  // deselect the current hex, and create planned move for whiteboard
696  gui().set_route(nullptr);
697  show_partial_move_ = false;
700  current_route_.steps.clear();
701 
702  pc_.get_whiteboard()->save_temp_move();
703 
704  // Otherwise proceed to normal unit movement
705  } else {
706  //Don't move if the unit already has actions
707  //from the whiteboard.
708  if (pc_.get_whiteboard()->unit_has_actions(
709  u ? &*u : &*clicked_u )) {
710  return;
711  }
712 
714  // during the move, we may have selected another unit
715  // (but without triggering a select event (command was disabled)
716  // in that case reselect it now to fire the event (+ anim & sound)
717  if (selected_hex_ != src) {
718  select_hex(selected_hex_, browse);
719  }
720  }
721  return;
722  }
723 }
724 
725 void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event) {
726 
727  selected_hex_ = hex;
728  if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
729  lk->select_hex_callback(last_hex_);
730  }
731 
734  gui().set_route(nullptr);
735  show_partial_move_ = false;
736 
737  wb::future_map_if_active planned_unit_map; //lasts for whole method
738 
740 
741  if (selected_hex_.valid() && u.valid() && !u->get_hidden()) {
742 
743  next_unit_ = u->get_location();
744 
745  {
747  }
748  if(highlight) {
751  }
752  // the highlight now comes from selection
753  // and not from the mouseover on an enemy
754  unselected_paths_ = false;
755  gui().set_route(nullptr);
756 
757  // selection have impact only if we are not observing and it's our unit
758  if ((!commands_disabled || pc_.get_whiteboard()->is_active()) && u->side() == gui().viewing_side()) {
759  if (!(browse || pc_.get_whiteboard()->unit_has_actions(&*u)))
760  {
761  sound::play_UI_sound("select-unit.wav");
762  u->anim_comp().set_selecting();
763  if(fire_event) {
764  // ensure unit map is back to normal while event is fired
765  wb::real_map srum;
766  pc_.pump().fire("select", hex);
767  //end forced real unit map
768  }
769  }
770  }
771  return;
772  }
773 
774  if (selected_hex_.valid() && !u) {
775  // compute unit in range of the empty selected_hex field
776 
778 
779  pathfind::paths reaching_unit_locations;
780 
781  pathfind::paths clicked_location;
782  clicked_location.destinations.insert(hex);
783 
785  bool invisible = u->invisible(u->get_location());
786 
787  if (!gui_->fogged(u->get_location()) && !u->incapacitated() && !invisible)
788  {
789 
790  const pathfind::paths& path = pathfind::paths(*u, false, true,
791  viewing_team(), path_turns_, false, false);
792 
793  if (path.destinations.find(hex) != path.destinations.end()) {
794  reaching_unit_locations.destinations.insert(u->get_location());
795  gui_->highlight_another_reach(clicked_location);
796  }
797  }
798  }
799  gui_->highlight_another_reach(reaching_unit_locations);
800  } else {
803  }
805  current_route_.steps.clear();
806 
807  pc_.get_whiteboard()->on_deselect_hex();
808  }
809 }
810 
812  select_hex(map_location(), true);
813 }
814 
815 /**
816  * Moves a unit along the currently cached route.
817  *
818  * @returns true if the end of the route was reached and no information was
819  * uncovered that would warrant interrupting a chain of actions;
820  * false otherwise.
821  */
823 {
824  // Copy the current route to ensure it remains valid throughout the animation.
825  const std::vector<map_location> steps = current_route_.steps;
826 
827  // do not show footsteps during movement
828  gui().set_route(nullptr);
830 
831  // do not keep the hex highlighted that we started from
834 
835  bool interrupted = false;
836  if ( steps.size() > 1 )
837  {
838  size_t num_moves = move_unit_along_route(steps, interrupted);
839 
840  interrupted = interrupted || num_moves + 1 < steps.size();
841  next_unit_ = steps[num_moves];
842  }
843 
844  // invalid after the move
846  current_route_.steps.clear();
847 
848  return !interrupted;
849 }
850 
851 /**
852  * Moves a unit across the board for a player.
853  * This is specifically for movement at the time it is initiated by a player,
854  * whether via a mouse click or executing whiteboard actions. Continued moves
855  * (including goto execution) can bypass this and call actions::move_unit() directly.
856  * This function call may include time for an animation, so make sure the
857  * provided route will remain unchanged (the caller should probably make a local
858  * copy).
859  *
860  * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
861  * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of actions (and set to false otherwise).
862  *
863  * @returns The number of hexes entered. This can safely be used as an index
864  * into steps to get the location where movement ended, provided
865  * steps is not empty (the return value is guaranteed to be less
866  * than steps.size() ).
867  */
868 size_t mouse_handler::move_unit_along_route(const std::vector<map_location> & steps,
869  bool & interrupted)
870 {
871  if(steps.empty()) {
872  interrupted = false;
873  return 0;
874  }
875 
876  // Default return value.
877  interrupted = true;
878 
879  //If this is a leader on a keep, ask permission to the whiteboard to move it
880  //since otherwise it may cause planned recruits to be erased.
881  if ( pc_.get_map_const().is_keep(steps.front()) )
882  {
883  unit_map::const_iterator const u = pc_.gamestate().board_.units().find(steps.front());
884 
885  if ( u && u->can_recruit() &&
886  u->side() == gui().viewing_side() &&
887  !pc_.get_whiteboard()->allow_leader_to_move(*u) )
888  {
890  _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
891  return 0;
892  }
893  }
894 
895  LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back() << "\n";
896  size_t moves = actions::move_unit_and_record(steps, &pc_.get_undo_stack(), false, true, &interrupted);
897 
900 
901  if(moves == 0)
902  return 0;
903 
904  if ( interrupted && moves + 1 < steps.size() ) {
905  // reselect the unit (for "press t to continue")
906  select_hex(steps[moves], false, false, false);
907  // the new discovery is more important than the new movement range
908  show_partial_move_ = true;
909  }
910 
911  return moves;
912 }
913 
914 void mouse_handler::save_whiteboard_attack(const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
915 {
916 
917  {
918  // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
919  // This method should do the cleanup of highlights and selection but it doesn't work properly
920 
921  // gui().highlight_hex(map_location());
922 
923  gui().draw();
926 
927  // remove footsteps if any - useless for whiteboard as of now
928  gui().set_route(nullptr);
929 
930  // do not keep the hex that we started from highlighted
933  show_partial_move_ = false;
934 
935  // invalid after saving the move
937  current_route_.steps.clear();
938  }
939 
940  //create planned attack for whiteboard
941  pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
942 
943 }
944 
945 int mouse_handler::fill_weapon_choices(std::vector<battle_context>& bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
946 {
947  int best = 0;
948  for (unsigned int i = 0; i < attacker->attacks().size(); i++) {
949  // skip weapons with attack_weight=0
950  if (attacker->attacks()[i].attack_weight() > 0) {
951  battle_context bc(pc_.gamestate().board_.units_, attacker->get_location(), defender->get_location(), i);
952  bc_vector.push_back(bc);
953  if (bc.better_attack(bc_vector[best], 0.5)) {
954  // as some weapons can be hidden, i is not a valid index into the resulting vector
955  best = bc_vector.size() - 1;
956  }
957  }
958  }
959  return best;
960 }
961 
962 int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc)
963 {
964  game_board & board = pc_.gamestate().board_;
965 
966  unit_map::iterator attacker = board.units_.find(attacker_loc);
967  unit_map::iterator defender = board.units_.find(defender_loc);
968  if(!attacker || !defender) {
969  ERR_NG << "One fighter is missing, can't attack";
970  return -1; // abort, click will do nothing
971  }
972 
973  if ((*attacker).attacks().empty()) {
974  gui2::show_transient_message(gui_->video(), "No Attacks",
975  _("This unit has no usable weapons."));
976 
977  return -1;
978  }
979 
980  std::vector<battle_context> bc_vector;
981  const int best = fill_weapon_choices(bc_vector, attacker, defender);
982 
983  gui2::tunit_attack dlg(
984  attacker
985  , defender
986  , bc_vector
987  , best);
988 
989  dlg.show(gui_->video());
990 
991  if(dlg.get_retval() == gui2::twindow::OK) {
992  return dlg.get_selected_weapon();
993  }
994 
995  return -1;
996 }
997 
998 void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
999 {
1000  try {
1001  attack_enemy_(attacker_loc, defender_loc, choice);
1002  } catch(std::bad_alloc) {
1003  lg::wml_error() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1004  }
1005 }
1006 
1008  , const map_location& def_loc
1009  , int choice)
1010 {
1011  //NOTE: copy the values because the const reference may change!
1012  //(WML events and mouse inputs during animations may modify
1013  // the data of the caller)
1014  const map_location attacker_loc = att_loc;
1015  const map_location defender_loc = def_loc;
1016 
1017  unit_map::iterator attacker = find_unit(attacker_loc);
1018  if(!attacker
1019  || attacker->side() != side_num_
1020  || attacker->incapacitated())
1021  return;
1022 
1023  unit_map::iterator defender = find_unit(defender_loc);
1024  if(!defender
1025  || current_team().is_enemy(defender->side()) == false
1026  || defender->incapacitated())
1027  return;
1028 
1029  std::vector<battle_context> bc_vector;
1030  fill_weapon_choices(bc_vector, attacker, defender);
1031 
1032  if(size_t(choice) >= bc_vector.size()) {
1033  return;
1034  }
1035 
1036  events::command_disabler disabler;
1037  const battle_context_unit_stats &att = bc_vector[choice].get_attacker_stats();
1038  const battle_context_unit_stats &def = bc_vector[choice].get_defender_stats();
1039 
1040  attacker->set_goto(map_location());
1041 
1043  // make the attacker's stats appear during the attack
1044  gui().display_unit_hex(attacker_loc);
1045  // remove highlighted hexes etc..
1049  gui().unhighlight_reach();
1050  gui().draw();
1051 
1052  current_team().set_action_bonus_count(1 + current_team().action_bonus_count());
1053  ///@todo change ToD to be location specific for the defender
1054 
1055  const tod_manager & tod_man = pc_.get_tod_manager_const();
1056 
1057  synced_context::run_and_throw("attack", replay_helper::get_attack(attacker_loc, defender_loc, att.attack_num, def.attack_num,
1058  attacker->type_id(), defender->type_id(), att.level,
1059  def.level, tod_man.turn(), tod_man.get_time_of_day()));
1060 }
1061 
1062 std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, int side) const
1063 {
1064  std::set<map_location> res;
1065 
1066  const team& uteam = pc_.gamestate().board_.teams_[side-1];
1067 
1068  map_location adj[6];
1069  get_adjacent_tiles(loc, adj);
1070  for(const map_location &aloc : adj) {
1072  if (i && uteam.is_enemy(i->side()))
1073  res.insert(aloc);
1074  }
1075  return res;
1076 }
1077 
1078 /**
1079  * Causes attackable hexes to be highlighted.
1080  *
1081  * This checks the hexes that the provided unit can attack. If there is a valid
1082  * target there, that location is inserted into current_paths_.destinations.
1083  */
1085 {
1086  // Cannot attack if no attacks are left.
1087  if (u->attacks_left() == 0)
1088  return;
1089 
1090  // Get the teams involved.
1091  const team & cur_team = current_team();
1092  const team & u_team = pc_.gamestate().board_.teams_[u->side()-1];
1093 
1094  // Check each adjacent hex.
1095  map_location adj[6];
1096  get_adjacent_tiles(u->get_location(), adj);
1097  for(const map_location &loc : adj)
1098  {
1099  // No attack option shown if no visible unit present.
1100  // (Visible to current team, not necessarily the unit's team.)
1101  if (!pc_.get_map_const().on_board(loc)) continue;
1103  if ( !i || !i->is_visible_to_team(cur_team, pc_.gamestate().board_.map(), false) )
1104  continue;
1105  const unit &target = *i;
1106  // Can only attack non-petrified enemies.
1107  if ( u_team.is_enemy(target.side()) && !target.incapacitated() )
1109  }
1110 }
1111 
1113 {
1114  game_board & board = pc_.gamestate().board_;
1115 
1116  if (!it) {
1117  return false;
1118  }
1119 
1120  if (it->side() != side_num_ || it->user_end_turn()
1121  || gui().fogged(it->get_location()) || !board.unit_can_move(*it))
1122  return false;
1123 
1124  if (current_team().is_enemy(int(gui().viewing_team()+1)) &&
1125  it->invisible(it->get_location()))
1126  return false;
1127 
1128  if (it->get_hidden())
1129  return false;
1130 
1131  return true;
1132 
1133 }
1134 
1135 void mouse_handler::cycle_units(const bool browse, const bool reverse)
1136 {
1137  game_board & board = pc_.gamestate().board_;
1138 
1139  if (board.units().begin() == board.units().end()) {
1140  return;
1141  }
1142 
1144  if (!it)
1145  it = board.units().begin();
1146  const unit_map::const_iterator itx = it;
1147 
1148  do {
1149  if (reverse) {
1150  if (it == board.units().begin())
1151  it = board.units().end();
1152  --it;
1153  } else {
1154  if (it == board.units().end())
1155  it = board.units().begin();
1156  else
1157  ++it;
1158  }
1159  } while (it != itx && !unit_in_cycle(it));
1160 
1161  if (unit_in_cycle(it)) {
1162  gui().scroll_to_tile(it->get_location(), game_display::WARP);
1163  select_hex(it->get_location(), browse);
1164 // mouse_update(browse);
1165  }
1166 }
1167 
1169  gui().unhighlight_reach();
1170  current_paths_ = new_paths;
1171  current_route_.steps.clear();
1172  gui().set_route(nullptr);
1173  pc_.get_whiteboard()->erase_temp_move();
1174 }
1175 
1177  return pc_.gamestate().board_.teams_[gui().viewing_team()];
1178 }
1179 
1181  return pc_.gamestate().board_.teams()[gui().viewing_team()];
1182 }
1183 
1185  return pc_.gamestate().board_.teams_[side_num_ - 1];
1186 }
1187 
1189 }
bool mouse_motion_default(int x, int y, bool update)
This handles minimap scrolling and click-drag.
virtual void highlight_hex(map_location hex)
Function to highlight a location.
void set_current_paths(const pathfind::paths &new_paths)
Game board class.
Definition: game_board.hpp:55
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition: display.hpp:353
plain_route a_star_search(const map_location &src, const map_location &dst, double stop_at, const cost_calculator *calc, const size_t width, const size_t height, const teleport_map *teleports, bool border)
virtual const unit_map & units() const
Definition: game_board.hpp:99
unit_iterator end()
Definition: map.hpp:311
const map_location & get_location() const
Definition: unit.hpp:286
marked_route mark_route(const plain_route &rt)
Add marks on a route rt assuming that the unit located at the first hex of rt travels along it...
Definition: pathfind.cpp:632
int fill_weapon_choices(std::vector< battle_context > &bc_vector, unit_map::iterator attacker, unit_map::iterator defender)
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition: display.hpp:299
std::shared_ptr< gui::zoom_slider > find_slider(const std::string &id)
Definition: display.cpp:846
void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
int get_retval() const
Definition: dialog.hpp:161
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition: cursor.cpp:154
size_t move_unit_and_record(const std::vector< map_location > &steps, undo_list *undo_stack, bool continued_move, bool show_move, bool *interrupted, move_unit_spectator *move_spectator)
Moves a unit across the board.
Definition: move.cpp:1225
Definition: unit.hpp:95
map_location selected_hex_
unit_map::const_iterator find_unit(const map_location &hex) const
bool dragging_started_
Actual drag flag.
Various functions that implement attacks and attack calculations.
bool show(CVideo &video, const unsigned auto_close_time=0)
Shows the window.
Definition: dialog.cpp:34
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.hpp:274
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
pathfind::marked_route get_route(const unit *un, map_location go_to, team &team) const
pathfind::paths current_paths_
bool is_enemy(int n) const
Definition: team.hpp:247
This file contains the window object, this object is a top level container which has the event manage...
General purpose widgets.
const_iterator find(const map_location &) const
Definition: pathfind.cpp:474
dest_vect destinations
Definition: pathfind.hpp:100
unit_iterator begin()
Definition: map.hpp:308
const map_location hex_clicked_on(int x, int y) const
given x,y co-ordinates of an onscreen pixel, will return the location of the hex that this pixel corr...
Definition: display.cpp:577
bool 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:471
virtual const std::vector< team > & teams() const
Definition: game_board.hpp:97
void show_transient_message(CVideo &video, const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup, const bool restore_background)
Shows a transient message to the user.
bool move_unit_along_current_route()
Moves a unit along the currently cached route.
bool unit_in_cycle(unit_map::const_iterator it)
DIRECTION get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition: location.cpp:220
int side() const
Definition: unit.hpp:201
GLint GLint GLint GLint GLint GLint y
Definition: glew.h:1220
GLenum src
Definition: glew.h:2392
virtual void draw()
Draws invalidated items.
Definition: display.cpp:2706
map_location previous_hex_
-file sdl_utils.hpp
void mouse_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location())
Use update to force an update of the mouse state.
bool unit_can_move(const unit &u) const
Will return true iff the unit u has any possible moves it can do (including attacking etc)...
void select_hex(const map_location &hex, const bool browse, const bool highlight=true, const bool fire_event=true)
Definitions for the interface to Wesnoth Markup Language (WML).
const gamemap & get_map_const() const
static mouse_handler * singleton_
boost::scoped_ptr< game_lua_kernel > lua_kernel_
Definition: game_state.hpp:53
virtual void mouse_press(const SDL_MouseButtonEvent &event, const bool browse)
bool contains(const map_location &) const
Definition: pathfind.cpp:510
unit_map units_
Definition: game_board.hpp:63
game_events::t_pump & pump()
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:50
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
bool incapacitated() const
Definition: unit.hpp:215
GLsizei const char ** path
Definition: glew.h:4654
Dialog is closed with ok button.
Definition: window.hpp:125
int current_side() const
Returns the number of the side whose turn it is.
This file contains the settings handling of the widget library.
unsigned int level
Definition: attack.hpp:69
std::vector< team > teams_
Definition: game_board.hpp:58
pointer get_shared_ptr() const
Definition: map.hpp:180
Structure which holds a single route between one location and another.
Definition: pathfind.hpp:132
bool valid() const
Definition: location.hpp:69
virtual void select_hex(map_location hex)
Function to display a location as selected.
map_location current_unit_attacks_from(const map_location &loc) const
Contains typedefs for the whiteboard.
int w() const
Effective map width.
Definition: map.hpp:105
Computes the statistics of a battle between an attacker and a defender unit.
Definition: attack.hpp:135
Object which temporarily resets a unit's movement.
Definition: unit.hpp:561
void highlight_another_reach(const pathfind::paths &paths_list)
Add more paths to highlight.
void highlight_reach(const pathfind::paths &paths_list)
Sets the paths that are currently displayed as available for the unit to move along.
int attack_num
Index into unit->attacks() or -1 for none.
Definition: attack.hpp:52
Structure describing the statistics of a unit involved in the battle.
Definition: attack.hpp:49
Structure which holds a single route and marks for special events.
Definition: pathfind.hpp:141
static const map_location & null_location()
Definition: location.hpp:195
int viewing_side() const
Definition: display.hpp:103
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:47
hotkey::command_executor * get_hotkey_command_executor()
Get (optionally) a command executor to handle context menu events.
void mouse_wheel_right(int x, int y, const bool)
Called when the mouse wheel is scrolled right.
bool right_click_show_menu(int x, int y, const bool browse)
Called in the default right_click when the context menu is about to be shown, can be used for preproc...
Encapsulates the map of the game.
Definition: location.hpp:38
Various functions related to moving units.
void left_mouse_up(int x, int y, const bool)
Called when the left mouse button is up.
actions::undo_list & get_undo_stack()
static lg::log_domain log_engine("engine")
GLuint res
Definition: glew.h:9258
std::map< std::string, tfilter >::iterator itor
Definition: filter.cpp:199
std::stringstream & wml_error()
Use this logger to send errors due to deprecated WML.
Definition: log.cpp:262
int h() const
Effective map height.
Definition: map.hpp:108
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:2434
void unhighlight_reach()
Reset highlighting of paths.
void attack_enemy(const map_location &attacker_loc, const map_location &defender_loc, int choice)
unit_map::iterator selected_unit()
void save_whiteboard_attack(const map_location &attacker_loc, const map_location &defender_loc, int weapon_choice)
int turn() const
Define the game's event mechanism.
map_location previous_free_hex_
size_t i
Definition: function.cpp:1057
ONLY IF whiteboard is currently active, applies the planned unit map for the duration of the struct's...
Definition: manager.hpp:269
CURSOR_TYPE get()
Definition: cursor.cpp:194
GLint GLint GLint GLint GLint x
Definition: glew.h:1220
DIRECTION
Valid directions which can be moved in our hexagonal world.
Definition: location.hpp:40
mouse_handler(game_display *gui, play_controller &pc)
const tod_manager & get_tod_manager_const() const
void cycle_units(const bool browse, const bool reverse=false)
bool fire_event(const tevent event, std::vector< std::pair< twidget *, tevent > > &event_chain, twidget *dispatcher, twidget *widget, F functor)
Helper function for fire_event.
#define LOG_NG
int get_selected_weapon() const
Definition: unit_attack.hpp:35
play_controller & pc_
Handling of system events.
Definition: manager.hpp:42
virtual const gamemap & map() const
Definition: game_board.hpp:98
size_t move_unit_along_route(const std::vector< map_location > &steps, bool &interrupted)
Moves a unit across the board for a player.
bool on_board(const map_location &loc) const
Tell if a location is on the map.
Definition: map.cpp:467
static bool run_and_throw(const std::string &commandname, const config &data, bool use_undo=true, bool show=true, synced_command::error_handler_function error_handler=default_error_function)
map_location last_hex_
last highlighted hex
int drag_threshold() const
GLclampd n
Definition: glew.h:5903
void show_attack_options(const unit_map::const_iterator &u)
Causes attackable hexes to be highlighted.
boost::shared_ptr< wb::manager > get_whiteboard()
game_state & gamestate()
cl_event event
Definition: glew.h:3070
void mouse_wheel_down(int x, int y, const bool)
Called when the mouse wheel is scrolled down.
game_board board_
Definition: game_state.hpp:49
Various functions that implement the undoing (and redoing) of in-game commands.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:842
void set_action_bonus_count(const int count)
Definition: team.hpp:219
Standard logging facilities (interface).
pathfind::marked_route current_route_
CVideo & video()
Gets the underlying screen object.
Definition: display.hpp:202
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition: pathfind.hpp:71
void mouse_wheel_left(int x, int y, const bool)
Called when the mouse wheel is scrolled left.
void mouse_wheel_up(int x, int y, const bool)
Called when the mouse wheel is scrolled up.
int side_number
Definition: game_info.hpp:44
size_t viewing_team() const
The viewing team is the team currently viewing the game.
Definition: display.hpp:102
unit_iterator find(size_t id)
Definition: map.cpp:285
bool valid() const
Definition: map.hpp:229
const teleport_map get_teleport_locations(const unit &u, const team &viewing_team, bool see_all, bool ignore_units)
Definition: teleport.cpp:233
void set_side(int side_number)
Ensures that the real unit map is active for the duration of the struct's life.
Definition: manager.hpp:279
void attack_enemy_(const map_location &attacker_loc, const map_location &defender_loc, int choice)
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 size_t turn, const time_of_day &t)
#define ERR_NG
void set_route(const pathfind::marked_route *route)
Sets the route along which footsteps are drawn to show movement of a unit.
GLdouble s
Definition: glew.h:1358
game_display & gui()
Due to the way this class is constructed we can assume that the display* gui_ member actually points ...
void clear_attack_indicator()
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
std::set< map_location > get_adj_enemies(const map_location &loc, int side) const
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
Definition: game_board.cpp:173
void set_attack_indicator(const map_location &src, const map_location &dst)
Set the attack direction indicator.
std::vector< map_location > & steps
Definition: pathfind.hpp:187
void insert(const map_location &)
Definition: pathfind.cpp:481
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:201
GLenum target
Definition: glew.h:5190
bool is_keep(const map_location &loc) const
Definition: map.cpp:74
int show_attack_dialog(const map_location &attacker_loc, const map_location &defender_loc)
bool set_zoom(int amount, bool absolute=false)
Zooms the display by the specified amount.
Definition: display.cpp:2285
void move_action(bool browse)
Overridden in derived class.
void select_or_action(bool browse)