The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
mouse_events.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2006 - 2025
3 by Joerg Hinrichs <joerg.hinrichs@alice-dsl.de>
4 Copyright (C) 2003 by David White <dave@whitevine.net>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17#include "mouse_events.hpp"
18
19#include "actions/attack.hpp" // for battle_context, etc
20#include "actions/move.hpp" // for move_and_record
21#include "config.hpp" // for config
22#include "cursor.hpp" // for set, CURSOR_TYPE::NORMAL, etc
23#include "game_board.hpp" // for game_board, etc
24#include "game_events/pump.hpp" // for fire
25#include "gettext.hpp" // for _
26#include "gui/dialogs/transient_message.hpp" // for show_transient_message
27#include "gui/dialogs/unit_attack.hpp" // for unit_attack
28#include "gui/widgets/settings.hpp" // for new_widgets
29#include "log.hpp" // for LOG_STREAM, logger, etc
30#include "map/map.hpp" // for gamemap
31#include "pathfind/teleport.hpp" // for get_teleport_locations, etc
32#include "play_controller.hpp" // for playing_side, set_button_state
33#include "replay_helper.hpp"
35#include "sound.hpp"
36#include "synced_context.hpp"
37#include "team.hpp" // for team
38#include "tod_manager.hpp"
39#include "map/location.hpp"
41#include "units/ptr.hpp" // for unit_const_ptr
42#include "units/unit.hpp" // for unit
43#include "whiteboard/manager.hpp" // for manager, etc
44#include "whiteboard/typedefs.hpp" // for whiteboard_lock
45#include "sdl/input.hpp" // for get_mouse_state
46#include "language.hpp"
48
49#include <cassert> // for assert
50#include <new> // for bad_alloc
51#include <string> // for string, operator<<, etc
52
53static lg::log_domain log_engine("engine");
54#define ERR_NG LOG_STREAM(err, log_engine)
55#define LOG_NG LOG_STREAM(info, log_engine)
56
57static lg::log_domain log_wml("wml");
58#define ERR_WML LOG_STREAM(err, log_wml)
59
60namespace events
61{
64 , gui_(nullptr)
65 , pc_(pc)
66 , previous_hex_()
67 , previous_free_hex_()
68 , selected_hex_(map_location::null_location())
69 , next_unit_()
70 , current_route_()
71 , current_paths_()
72 , unselected_paths_(false)
73 , unselected_reach_(false)
74 , path_turns_(0)
75 , side_num_(1)
76 , over_route_(false)
77 , reachmap_invalid_(false)
78 , show_partial_move_(false)
79 , teleport_selected_(false)
80 , preventing_units_highlight_(false)
81{
82 singleton_ = this;
83}
84
89
90void mouse_handler::set_side(int side_number)
91{
92 side_num_ = side_number;
93}
94
96{
97 // Function uses window resolution as an estimate of users perception of distance
98 // Tune this variable if necessary:
99 const unsigned threshold_1080p = 14; // threshold number of pixels for 1080p
100 double screen_diagonal = std::hypot(gui2::settings::screen_width,gui2::settings::screen_height);
101 const double scale_factor = threshold_1080p / std::hypot(1080,1920);
102 return static_cast<int>(screen_diagonal * scale_factor);
103}
104
105void mouse_handler::touch_motion(int x, int y, const bool browse, bool update, map_location new_hex)
106{
107 // Frankensteining from mouse_motion(), as it has a lot in common, but a lot of differences too.
108 // Copy-pasted from everywhere. TODO: generalize the two.
109 float fx;
110 float fy;
111 sdl::get_mouse_state(&fx, &fy);
112 x = fx;
113 y = fy;
114
115 // This is from mouse_handler_base::mouse_motion_default()
116 tooltips::process(x, y);
117
118 if(simple_warp_) {
119 return;
120 }
121
123 const map_location& mini_loc = gui().minimap_location_on(x,y);
124 if(mini_loc.valid()) {
125 if(mini_loc != last_hex_) {
126 last_hex_ = mini_loc;
127 gui().scroll_to_tile(mini_loc,display::WARP,false);
128 }
129 return;
130 } else {
131 // clicking outside of the minimap will end minimap scrolling
132 minimap_scrolling_ = false;
133 }
134 }
135
136 // Fire the drag & drop only after minimal drag distance
137 // While we check the mouse buttons state, we also grab fresh position data.
138
140 if(dragging_touch_) {
142 const double drag_distance =
143 std::pow(static_cast<double>(drag_from_.x - pos.x), 2) +
144 std::pow(static_cast<double>(drag_from_.y - pos.y), 2);
145
146 if(drag_distance > drag_threshold()*drag_threshold()) {
147 dragging_started_ = true;
148 }
149 }
150 }
151
152 // Not-so-smooth panning
153 const auto found_unit = find_unit(selected_hex_);
154 bool selected_hex_has_my_unit = found_unit.valid() && found_unit.get_shared_ptr()->side() == side_num_;
155 if((browse || !found_unit.valid()) && is_dragging() && dragging_started_) {
156
157 if(gui().map_area().contains(x, y)) {
159 gui().scroll(drag_from_ - pos);
160 drag_from_ = pos;
161 }
162 return;
163 }
164
165 // now copy-pasting mouse_handler::mouse_motion()
166
167 // Note for anyone reconciling this code with the version in mouse_handler::mouse_motion:
168 // commit 27a40a82aeea removed the game_board& board from mouse_motion, but didn't update
169 // the corresponding code here in touch_motion.
170 game_board & board = pc_.gamestate().board_;
171
172 if(new_hex == map_location::null_location())
173 new_hex = gui().hex_clicked_on(x,y);
174
175 if(new_hex != last_hex_) {
176 update = true;
177 if( pc_.get_map().on_board(last_hex_) ) {
178 // we store the previous hexes used to propose attack direction
180 // the hex of the selected unit is also "free"
181 { // start planned unit map scope
185 }
186 } // end planned unit map scope
187 }
188 last_hex_ = new_hex;
189 }
190
191 if(reachmap_invalid_) update = true;
192
193 if(!update) return;
194
196 reachmap_invalid_ = false;
198 { // start planned unit map scope
199 wb::future_map_if_active planned_unit_map;
200 selected_hex_has_my_unit = found_unit.valid();
201 } // end planned unit map scope
202 if(selected_hex_.valid() && selected_hex_has_my_unit) {
203 // FIXME: vic: why doesn't this trigger when touch-dragging an unselected unit?
204 // reselect the unit without firing events (updates current_paths_)
206 }
207 // we do never deselect here, mainly because of canceled attack-move
208 }
209 }
210
211 // reset current_route_ and current_paths if not valid anymore
212 // we do it before cursor selection, because it uses current_paths_
213 if( !pc_.get_map().on_board(new_hex) ) {
214 current_route_.steps.clear();
215 gui().set_route(nullptr);
216 pc_.get_whiteboard()->erase_temp_move();
217 }
218
220 unselected_paths_ = false;
223 } else if(over_route_) {
224 over_route_ = false;
225 current_route_.steps.clear();
226 gui().set_route(nullptr);
227 pc_.get_whiteboard()->erase_temp_move();
228 }
229
230 gui().highlight_hex(new_hex);
231 pc_.get_whiteboard()->on_mouseover_change(new_hex);
232
234 unit_map::iterator mouseover_unit;
235 map_location attack_from;
236
237 { // start planned unit map scope
238 wb::future_map_if_active planned_unit_map;
239 selected_unit = found_unit;
240 mouseover_unit = find_unit(new_hex);
241
242 // we search if there is an attack possibility and where
243 attack_from = current_unit_attacks_from(new_hex);
244
245 //see if we should show the normal cursor, the movement cursor, or
246 //the attack cursor
247 //If the cursor is on WAIT, we don't change it and let the setter
248 //of this state end it
249 if (cursor::get() != cursor::WAIT) {
250 if (selected_unit &&
251 selected_unit->side() == side_num_ &&
252 !selected_unit->incapacitated() && !browse)
253 {
254 if (attack_from.valid()) {
256 }
257 else if (!mouseover_unit &&
259 {
260 // Is this where left-drag cursor changes? Test.
262 } else {
263 // selected unit can't attack or move there
265 }
266 } else {
267 // no selected unit or we can't move it
268
269 if ( selected_hex_.valid() && mouseover_unit
270 && mouseover_unit->side() == side_num_ ) {
271 // empty hex field selected and unit on our site under the cursor
273 } else {
275 }
276 }
277 }
278 } // end planned unit map scope
279
280 // show (or cancel) the attack direction indicator
281 if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
282 gui().set_attack_indicator(attack_from, new_hex);
283 } else {
285 }
286
287 unit_ptr un; //will later point to unit at mouseover_hex_
288
289 // the destination is the pointed hex or the adjacent hex
290 // used to attack it
291 map_location dest;
293 { // start planned unit map scope
295 if (attack_from.valid()) {
296 dest = attack_from;
297 dest_un = find_unit(dest);
298 } else {
299 dest = new_hex;
300 dest_un = find_unit(new_hex);
301 }
302
303 if(dest == selected_hex_ || dest_un) {
304 current_route_.steps.clear();
305 gui().set_route(nullptr);
306 pc_.get_whiteboard()->erase_temp_move();
307 }
308 else if (!current_paths_.destinations.empty() &&
309 board.map().on_board(selected_hex_) && board.map().on_board(new_hex))
310 {
311 if (selected_unit && !selected_unit->incapacitated()) {
312 // Show the route from selected unit to mouseover hex
313 current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
314
315 pc_.get_whiteboard()->create_temp_move();
316
317 if(!browse) {
319 }
320 }
321 }
322
323 if(board.map().on_board(selected_hex_)
324 && !selected_unit
325 && mouseover_unit.valid()
326 && mouseover_unit) {
327 // Show the route from selected hex to mouseover unit
328 current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
329
330 pc_.get_whiteboard()->create_temp_move();
331
332 if(!browse) {
334 }
335 } else if (!selected_unit) {
336 current_route_.steps.clear();
337 gui().set_route(nullptr);
338 pc_.get_whiteboard()->erase_temp_move();
339 }
340
341 unit_map::iterator iter = mouseover_unit;
342 if (iter)
343 un = iter.get_shared_ptr();
344 else
345 un.reset();
346 } //end planned unit map scope
347}
348
350{
351 if( (!selected_hex_.valid()) && un && current_paths_.destinations.empty() &&
352 !gui().fogged(un->get_location()))
353 {
354 // If the unit has a path set and is either ours or allied then show the path.
355 //
356 // Exception: allied AI sides' moves are still hidden, on the assumption that
357 // campaign authors won't want to leak goto_x,goto_y tricks to the player.
358 if(!gui().viewing_team().is_enemy(un->side()) && !pc_.get_teams()[un->side() - 1].is_ai()) {
359 //unit is on our team or an allied team, show path if the unit has one
360 const map_location go_to = un->get_goto();
361 if(pc_.get_map().on_board(go_to)) {
363 { // start planned unit map scope
365 route = get_route(un.get(), go_to, current_team());
366 } // end planned unit map scope
367 gui().set_route(&route);
368 }
369 over_route_ = true;
370 }
371
372 // Scope for the unit_movement_resetter and future_map_if_active.
373 {
374 // Making this non-null will show the unit's max moves instead of current moves.
375 // Because movement is reset to max in the side's refresh phase, use the max if
376 // that refresh will happen before the unit's side can move again.
377 std::unique_ptr<unit_movement_resetter> move_reset;
378 if(un->side() != side_num_) {
379 move_reset = std::make_unique<unit_movement_resetter>(*un);
380 }
381
382 // Handle whiteboard. Any move_reset must be done before this, since the future
383 // state includes changes to units' movement.
385
386 current_paths_ = pathfind::paths(*un, false, true, gui().viewing_team(), path_turns_);
387 }
388
389 unselected_paths_ = true;
391 }
392}
393
394void mouse_handler::mouse_motion(int x, int y, const bool browse, bool update, map_location new_hex)
395{
396 // we ignore the position coming from event handler
397 // because it's always a little obsolete and we don't need
398 // to highlight all the hexes where the mouse passed.
399 // Also, sometimes it seems to have one *very* obsolete
400 // and isolated mouse motion event when using drag&drop
401 float fx;
402 float fy;
403 sdl::get_mouse_state(&fx, &fy);
404 // modify x and y
405 x = fx;
406 y = fy;
407
409 return;
410 }
411
412 // Don't process other motion events while scrolling
413 if(scroll_started_) {
414 return;
415 }
416
417 if(new_hex == map_location::null_location()) {
418 new_hex = gui().hex_clicked_on(x, y);
419 }
420
421 if(new_hex != last_hex_) {
422 if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
423 lk->mouse_over_hex_callback(new_hex);
424 }
425
426 update = true;
427
429 // we store the previous hexes used to propose attack direction
431
432 // the hex of the selected unit is also "free"
433 { // start planned unit map scope
437 }
438 } // end planned unit map scope
439 }
440
441 last_hex_ = new_hex;
442 }
443
445 update = true;
446 }
447
448 if(!update) {
449 return;
450 }
451
453 reachmap_invalid_ = false;
454
456 bool selected_hex_has_unit;
457 { // start planned unit map scope
458 wb::future_map_if_active planned_unit_map;
459 selected_hex_has_unit = find_unit(selected_hex_).valid();
460 } // end planned unit map scope
461
462 if(selected_hex_.valid() && selected_hex_has_unit) {
463 // reselect the unit without firing events (updates current_paths_)
465 }
466
467 // we do never deselect here, mainly because of canceled attack-move
468 }
469 }
470
471 // reset current_route_ and current_paths if not valid anymore
472 // we do it before cursor selection, because it uses current_paths_
473 if(!pc_.get_map().on_board(new_hex)) {
474 current_route_.steps.clear();
475 gui().set_route(nullptr);
476 pc_.get_whiteboard()->erase_temp_move();
477 }
478
480 unselected_paths_ = false;
483 } else if(over_route_) {
484 over_route_ = false;
485 current_route_.steps.clear();
486 gui().set_route(nullptr);
487 pc_.get_whiteboard()->erase_temp_move();
488 }
489
490 gui().highlight_hex(new_hex);
491 pc_.get_whiteboard()->on_mouseover_change(new_hex);
492
494 unit_map::iterator mouseover_unit;
495 map_location attack_from;
496
497 { // start planned unit map scope
498 wb::future_map_if_active planned_unit_map;
500 mouseover_unit = find_unit(new_hex);
501
502 // we search if there is an attack possibility and where
503 attack_from = current_unit_attacks_from(new_hex);
504
505 // see if we should show the normal cursor, the movement cursor, or
506 // the attack cursor
507 // If the cursor is on WAIT, we don't change it and let the setter
508 // of this state end it
509 if(cursor::get() != cursor::WAIT) {
510 if(selected_unit && selected_unit->side() == side_num_ && !selected_unit->incapacitated() && !browse) {
511 if(attack_from.valid()) {
513 } else if(!mouseover_unit && current_paths_.destinations.contains(new_hex)) {
515 } else {
516 // selected unit can't attack or move there
518 }
519 } else {
520 // no selected unit or we can't move it
521
522 if(selected_hex_.valid() && mouseover_unit && mouseover_unit->side() == side_num_) {
523 // empty hex field selected and unit on our site under the cursor
525 } else {
527 }
528 }
529 }
530 } // end planned unit map scope
531
532 // show (or cancel) the attack direction indicator
533 if(attack_from.valid() && (!browse || pc_.get_whiteboard()->is_active())) {
534 gui().set_attack_indicator(attack_from, new_hex);
535 } else {
537 }
538
539 unit_ptr un; // will later point to unit at mouseover_hex_
540
541 // the destination is the pointed hex or the adjacent hex
542 // used to attack it
543 map_location dest;
545 /* start planned unit map scope*/
546 {
548 if(attack_from.valid()) {
549 dest = attack_from;
550 dest_un = find_unit(dest);
551 } else {
552 dest = new_hex;
553 dest_un = find_unit(new_hex);
554 }
555
556 if(dest == selected_hex_ || dest_un) {
557 current_route_.steps.clear();
558 gui().set_route(nullptr);
559 pc_.get_whiteboard()->erase_temp_move();
560 } else if(!current_paths_.destinations.empty() && pc_.get_map().on_board(selected_hex_) && pc_.get_map().on_board(new_hex)) {
561 if(selected_unit && !selected_unit->incapacitated()) {
562 // Show the route from selected unit to mouseover hex
563 current_route_ = get_route(&*selected_unit, dest, gui().viewing_team());
564
565 pc_.get_whiteboard()->create_temp_move();
566
567 if(!browse) {
569 }
570 }
571 }
572
573 if(pc_.get_map().on_board(selected_hex_) && !selected_unit && mouseover_unit.valid() && mouseover_unit) {
574 // Show the route from selected hex to mouseover unit
575 current_route_ = get_route(&*mouseover_unit, selected_hex_, gui().viewing_team());
576
577 pc_.get_whiteboard()->create_temp_move();
578
579 if(!browse) {
581 }
582 } else if(!selected_unit) {
583 current_route_.steps.clear();
584 gui().set_route(nullptr);
585 pc_.get_whiteboard()->erase_temp_move();
586 }
587
588 if(mouseover_unit) {
589 un = mouseover_unit.get_shared_ptr();
590 } else {
591 un.reset();
592 }
593 } /*end planned unit map scope*/
594
595 /*
596 * Only highlight unit's reach if toggler not preventing normal unit
597 * processing. This can happen e.g. if, after activating 'show
598 * [best possible] enemy movements' through the UI menu, the
599 * mouse cursor lands on a hex with unit in it.
600 */
603 }
604
605 if(!un && preventing_units_highlight_) {
606 // Cursor on empty hex, turn unit highlighting back on.
608 }
609}
610
611// Hook for notifying lua game kernel of mouse button events. We pass button as
612// a separate argument than the original SDL event in order to manage touch
613// emulation (e.g., long touch = right click) and such.
614bool mouse_handler::mouse_button_event(const SDL_MouseButtonEvent& event, uint8_t button,
615 map_location loc, bool click)
616{
617 static const std::array<const std::string, 6> buttons = {
618 "",
619 "left", // SDL_BUTTON_LEFT
620 "middle", // SDL_BUTTON_MIDDLE
621 "right", // SDL_BUTTON_RIGHT
622 "mouse4", // SDL_BUTTON_X1
623 "mouse5" // SDL_BUTTON_X2
624 };
625
626 if (gui().view_locked() || button < SDL_BUTTON_LEFT || button >= buttons.size()) {
627 return false;
628 } else if (!pc_.get_map().on_board(loc)) {
629 return false;
630 }
631
632 if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
633 lk->mouse_button_callback(loc, buttons[button], (event.down ? "down" : "up"));
634
635 // Are we being asked to send a click event?
636 if (click) {
637 // Was both the up and down on the same map tile?
638 if (loc != drag_from_hex_) {
639 return false;
640 }
641 // We allow this event to be consumed, but not up/down
642 return lk->mouse_button_callback(loc, buttons[button], "click");
643 }
644 }
645 return false;
646}
647
649{
651 if(res) {
652 return res;
653 }
654
655 return find_unit(last_hex_);
656}
657
659{
660 unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
661 if(it.valid()) {
662 return it;
663 }
664
665 return pc_.get_units().end();
666}
667
669{
670 return pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
671}
672
674{
675 unit_map::iterator it = pc_.gamestate().board_.find_visible_unit(hex, gui().viewing_team());
676 return it.valid() ? &*it : nullptr;
677}
678
680{
682 return it.valid() ? &*it : nullptr;
683}
684
686{
687 float x = -1;
688 float y = -1;
689 sdl::get_mouse_state(&x, &y);
690 return gui_->hex_clicked_on(x, y);
691}
692
694{
695 return find_unit(hex).valid();
696}
697
698// the attack dialog is shown as if the unit is standing at the hex, this may be not the best approach for units possessing weapons of different ranges
700{
701 if(loc == selected_hex_) {
702 return map_location();
703 }
704
705 bool wb_active = pc_.get_whiteboard()->is_active();
706 std::set<int> attackable_distances;
707
708 {
709 // Check the unit SOURCE of the attack
710
711 // Check that there's a selected unit
713
714 bool source_eligible = source_unit.valid();
715 if(!source_eligible) {
716 return map_location();
717 }
718
719 // The selected unit must at least belong to the player currently controlling this client.
720 source_eligible &= source_unit->side() == gui_->viewing_team().side();
721 if(!source_eligible) {
722 return map_location();
723 }
724
725 // In addition:
726 // - If whiteboard is enabled, we allow planning attacks outside of player's turn
727 // - If whiteboard is disabled, it must be the turn of the player controlling this client
728 if(!wb_active) {
729 source_eligible &= gui_->viewing_team().side() == pc_.current_side();
730 if(!source_eligible) {
731 return map_location();
732 }
733 }
734
735 // Unit must have attacks left
736 source_eligible &= source_unit->attacks_left() != 0;
737 if(!source_eligible) {
738 return map_location();
739 }
740
741 // Check the unit TARGET of the attack
742
743 const team& viewer = gui().viewing_team();
744
745 // Check that there's a unit at the target location
746 const unit_map::const_iterator target_unit = find_unit(loc);
747
748 bool target_eligible = target_unit.valid();
749 if(!target_eligible) {
750 return map_location();
751 }
752
753 // The player controlling this client must be an enemy of the target unit's side
754 target_eligible &= viewer.is_enemy(target_unit->side());
755 if(!target_eligible) {
756 return map_location();
757 }
758
759 // Sanity check: source and target of the attack shouldn't be on the same team
760 assert(source_unit->side() != target_unit->side());
761
762 target_eligible &= !target_unit->incapacitated();
763 if(!target_eligible) {
764 return map_location();
765 }
766
767 for (const auto& attack : source_unit->attacks()) {
768 for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
769 attackable_distances.insert(i);
770 }
771 }
772 }
773
774 //invalid attack ranges
775 if(attackable_distances.empty()) {
776 return map_location{};
777 }
778
779 //ranged attack
780 if(*attackable_distances.rbegin() > 1){
781 if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))) {
782 return selected_hex_;
783 }
784 map_location res;
785 int best_move = -1;
787 map_location dst = step.curr;
788 if(utils::contains(attackable_distances, distance_between(loc, dst))) {
789 if (step.move_left > best_move){
790 best_move = step.move_left;
791 res=dst;
792 }
793 }
794 }
795 return res;
796 }
797
798 //no ranged attack
801
802 int best_rating = 100; // smaller is better
803
804 map_location res;
805 const auto adj = get_adjacent_tiles(loc);
806
807 for(std::size_t n = 0; n < adj.size(); ++n) {
808 if(pc_.get_map().on_board(adj[n]) == false) {
809 continue;
810 }
811
812 if(adj[n] != selected_hex_ && find_unit(adj[n])) {
813 continue;
814 }
815
817 static const std::size_t ndirections = static_cast<int>(map_location::direction::indeterminate);
818
819 unsigned int difference = std::abs(static_cast<int>(static_cast<int>(preferred) - n));
820 if(difference > ndirections / 2) {
821 difference = ndirections - difference;
822 }
823
824 unsigned int second_difference = std::abs(static_cast<int>(static_cast<int>(second_preferred) - n));
825 if(second_difference > ndirections / 2) {
826 second_difference = ndirections - second_difference;
827 }
828
829 const int rating = difference * 2 + (second_difference > difference);
830 if(rating < best_rating || res.valid() == false) {
831 best_rating = rating;
832 res = adj[n];
833 }
834 }
835 }
836
837 return res;
838}
839
841{
842 game_board& board = pc_.gamestate().board_;
843
844 // The pathfinder will check unit visibility (fogged/stealthy).
845 const pathfind::shortest_path_calculator calc(*un, team, board.teams(), board.map());
846
847 pathfind::teleport_map allowed_teleports = pathfind::get_teleport_locations(*un, gui().viewing_team());
848
850
852 un->get_location(), go_to, 10000.0, calc, board.map().w(), board.map().h(), &allowed_teleports);
853
854 return mark_route(route);
855}
856
857bool mouse_handler::right_click_show_menu(int x, int y, const bool /*browse*/)
858{
860 unselected_reach_ = false;
861 return false;
862 }
863
864 return gui().map_area().contains(x, y);
865}
866
868{
869 // Load whiteboard partial moves
870 //wb::future_map_if_active planned_unit_map;
871
872 if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
873 lk->select_hex_callback(last_hex_);
874 }
875
878
879 if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
880 (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
881 ) {
883 teleport_selected_ = true;
885 gui().set_route(nullptr);
886 }
887}
888
890{
891 // Set the teleport to active so that we can use existing functions
892 // for teleport
893 teleport_selected_ = false;
894
900 gui().set_route(nullptr);
901
902 // Select and deselect the units hex to prompt updates for hover
903 select_hex(last_hex_, false);
904 deselect_hex();
905 current_route_.steps.clear();
906}
907
909{
910 if(!pc_.get_map().on_board(last_hex_)) {
912 return;
913 }
914
915 // Load whiteboard partial moves
916 wb::future_map_if_active planned_unit_map;
917
918 if(game_lua_kernel* lk = pc_.gamestate().lua_kernel_.get()) {
919 lk->select_hex_callback(last_hex_);
920 }
921
924
925 if(clicked_u && (!selected_u || selected_u->side() != side_num_ ||
926 (clicked_u->side() == side_num_ && clicked_u->id() != selected_u->id()))
927 ) {
928 select_hex(last_hex_, false);
929 }
930#ifdef __ANDROID__
931 else if (clicked_u && clicked_u->side() != side_num_) {
932 select_hex(last_hex_, false);
933 }
934#endif
935 else {
936 move_action(browse);
937 }
938 teleport_selected_ = false;
939}
940
942{
943 // Lock whiteboard activation state to avoid problems due to
944 // its changing while an animation takes place.
945 wb::whiteboard_lock wb_lock = pc_.get_whiteboard()->get_activation_state_lock();
946
947 // we use the last registered highlighted hex
948 // since it's what update our global state
950
951 // TODO
952 // // Clicks on border hexes mean to deselect.
953 // // (Check this before doing processing that might not be needed.)
954 // if ( !pc_.get_map().on_board(hex) ) {
955 // deselect_hex();
956 // return false;
957 // }
958
959 unit* u = nullptr;
960 const unit* clicked_u = nullptr;
961
963 pathfind::paths orig_paths;
964 map_location attack_from;
965
966 { // start planned unit map scope
967 wb::future_map_if_active planned_unit_map;
969
970 // if the unit is selected and then itself clicked on,
971 // any goto command is canceled
972 if(u && !browse && selected_hex_ == hex && u->side() == side_num_) {
974 }
975
976 clicked_u = find_unit_nonowning(hex);
977
979 orig_paths = current_paths_;
980 attack_from = current_unit_attacks_from(hex);
981 } // end planned unit map scope
982
983 // See if the teleport option is toggled
986 }
987 // see if we're trying to do a attack or move-and-attack
988 else if((!browse || pc_.get_whiteboard()->is_active()) && attack_from.valid()) {
989 // Ignore this command if commands are disabled.
991 return;
992 }
993
994 if(((u != nullptr && u->side() == side_num_) || pc_.get_whiteboard()->is_active()) && clicked_u != nullptr) {
995 if(attack_from == selected_hex_) { // no move needed
996 int choice = -1;
997 {
998 wb::future_map_if_active planned_unit_map; // start planned unit map scope
999 choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1000 } // end planned unit map scope
1001
1002 if(choice >= 0) {
1003 if(pc_.get_whiteboard()->is_active()) {
1004 save_whiteboard_attack(attack_from, clicked_u->get_location(), choice);
1005 } else {
1006 // clear current unit selection so that any other unit selected
1007 // triggers a new selection
1009
1010 attack_enemy(u->get_location(), clicked_u->get_location(), choice);
1011 }
1012 }
1013
1014 return;
1015 } else {
1016 int choice = -1; // for the attack dialog
1017
1018 {
1019 wb::future_map_if_active planned_unit_map; // start planned unit map scope
1020 // we will now temporary move next to the enemy
1021 pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attack_from);
1022 if(itor == current_paths_.destinations.end()) {
1023 // can't reach the attacking location
1024 // not supposed to happen, so abort
1025 return;
1026 }
1027
1028 // block where we temporary move the unit
1029 {
1030 choice = show_attack_dialog(attack_from, clicked_u->get_location(), src);
1031 }
1032
1033 if(choice < 0) {
1034 // user hit cancel or attack is invalid, don't start move&attack
1035 return;
1036 }
1037 } // end planned unit map scope
1038
1039 if(pc_.get_whiteboard()->is_active()) {
1040 save_whiteboard_attack(attack_from, hex, choice);
1041 } else {
1042 bool not_interrupted = move_unit_along_current_route();
1043 bool alt_unit_selected = (selected_hex_ != src);
1045 // clear current unit selection so that any other unit selected
1046 // triggers a new selection
1048
1049 if(not_interrupted)
1050 attack_enemy(attack_from, hex, choice); // Fight !!
1051
1052 // TODO: Maybe store the attack choice so "press t to continue"
1053 // can also continue the attack?
1054
1055 if(alt_unit_selected && !selected_hex_.valid()) {
1056 // reselect other unit if selected during movement animation
1057 select_hex(src, browse);
1058 }
1059 }
1060
1061 return;
1062 }
1063 }
1064 }
1065 // otherwise we're trying to move to a hex
1066 else if(
1067 // The old use case: move selected unit to mouse hex field.
1068 (
1069 (!browse || pc_.get_whiteboard()->is_active())
1070 && selected_hex_.valid()
1071 && selected_hex_ != hex
1072 && u != nullptr
1073 && (u->side() == side_num_ || pc_.get_whiteboard()->is_active())
1074 && !clicked_u
1075 && !current_route_.steps.empty()
1076 && current_route_.steps.front() == selected_hex_
1077 )
1078 || // The new use case: move mouse unit to selected hex field.
1079 (
1080 (!browse || pc_.get_whiteboard()->is_active())
1081 && selected_hex_.valid()
1082 && selected_hex_ != hex
1083 && clicked_u
1084 && !current_route_.steps.empty()
1086 && !u
1087 && clicked_u->side() == side_num_
1088 )
1089 ) {
1090 // Ignore this command if commands are disabled.
1091 if(commands_disabled) {
1092 return;
1093 }
1094
1095 // If the whiteboard is active, it intercepts any unit movement.
1096 if(pc_.get_whiteboard()->is_active()) {
1097 // Deselect the current hex, and create planned move for whiteboard.
1099
1102 gui().set_route(nullptr);
1103
1104 show_partial_move_ = false;
1105
1107
1109 current_route_.steps.clear();
1110
1111 pc_.get_whiteboard()->save_temp_move();
1112
1113 // Otherwise proceed to normal unit movement
1114 } else {
1115 // Don't move if the unit already has actions
1116 // from the whiteboard.
1117 if(pc_.get_whiteboard()->unit_has_actions(u ? u : clicked_u)) {
1118 return;
1119 }
1120
1122
1123 // During the move, we may have selected another unit
1124 // (but without triggering a select event (command was disabled)
1125 // in that case reselect it now to fire the event (+ anim & sound)
1126 if(selected_hex_ != src) {
1127 select_hex(selected_hex_, browse);
1128 }
1129 }
1130
1131 return;
1132 }
1133}
1134
1135void mouse_handler::touch_action(const map_location touched_hex, bool browse)
1136{
1137 unit_map::iterator unit = find_unit(touched_hex);
1138#ifdef __ANDROID__
1139 if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1140 if(touched_hex == selected_hex_) {
1141 deselect_hex();
1144 } else {
1146 select_or_action(browse);
1147 }
1148 }
1149#else
1150 if(touched_hex.valid() && unit.valid() && !unit->get_hidden()) {
1151 select_or_action(browse);
1152 }
1153#endif
1154}
1155
1156void mouse_handler::select_hex(const map_location& hex, const bool browse, const bool highlight, const bool fire_event, const bool force_unhighlight)
1157{
1158 bool unhighlight = selected_hex_.valid() && force_unhighlight;
1159
1160 selected_hex_ = hex;
1161
1164 gui().set_route(nullptr);
1165
1166 show_partial_move_ = false;
1167
1168 wb::future_map_if_active planned_unit_map; // lasts for whole method
1169
1171
1172 if(selected_hex_.valid() && unit.valid() && !unit->get_hidden()) {
1174
1175 {
1176 current_paths_ = pathfind::paths(*unit, false, true, gui().viewing_team(), path_turns_);
1177 }
1178
1179 if(highlight) {
1181 }
1182
1183 // The highlight now comes from selection
1184 // and not from the mouseover on an enemy
1185 unselected_paths_ = false;
1186 gui().set_route(nullptr);
1187
1188 // Selection have impact only if we are not observing and it's our unit
1189 if((!commands_disabled || pc_.get_whiteboard()->is_active()) && unit->side() == gui().viewing_team().side()) {
1190 if(!(browse || pc_.get_whiteboard()->unit_has_actions(&*unit))) {
1191 sound::play_UI_sound("select-unit.wav");
1192
1194
1195 if(fire_event) {
1196 // Ensure unit map is back to normal while event is fired
1197 wb::real_map srum;
1198 pc_.pump().fire("select", hex);
1199 // end forced real unit map
1200 }
1201 }
1202 }
1203
1204 return;
1205 }
1206
1207 if(selected_hex_.valid() && !unit) {
1208 // When no unit is selected, compute unit in range of the empty selected_hex field
1209
1211
1212 pathfind::paths reaching_unit_locations;
1213
1214 pathfind::paths clicked_location;
1215 clicked_location.destinations.insert(hex);
1216
1217 for(const ::unit& u : pc_.get_units()) {
1218 bool invisible = u.invisible(u.get_location());
1219
1220 if(!gui_->fogged(u.get_location()) && !u.incapacitated() && !invisible) {
1221 const pathfind::paths& path =
1222 pathfind::paths(u, false, true, gui().viewing_team(), path_turns_, false, false);
1223
1224 if(path.destinations.find(hex) != path.destinations.end()) {
1225 reaching_unit_locations.destinations.insert(u.get_location());
1226 gui_->highlight_another_reach(clicked_location);
1227 }
1228 }
1229 }
1230
1231 gui_->highlight_another_reach(reaching_unit_locations);
1232 } else {
1233 // unhighlight is needed because the highlight_reach here won't be reset with highlight assigned false.
1234 if(!pc_.get_units().find(last_hex_) || unhighlight) {
1236 }
1237
1239 current_route_.steps.clear();
1240
1241 pc_.get_whiteboard()->on_deselect_hex();
1242 }
1243}
1244
1246{
1247 select_hex(map_location(), true);
1248}
1249
1250/**
1251 * Moves a unit along the currently cached route.
1252 *
1253 * @returns true if the end of the route was reached and no information was
1254 * uncovered that would warrant interrupting a chain of actions;
1255 * false otherwise.
1256 */
1258{
1259 // Capture the full intended route before movement modifies or clears it.
1260 const pathfind::marked_route intended_route = current_route_;
1261
1262 // do not show footsteps during movement
1263 gui().set_route(nullptr);
1265
1266 // do not keep the hex highlighted that we started from
1269
1270 bool interrupted = false;
1271 bool halted_by_discovery = false;
1272 std::size_t num_moves = 0;
1273
1274 if(intended_route.steps.size() > 1) {
1275 num_moves = move_unit_along_route(intended_route.steps, interrupted);
1276
1277 // Sightings, ambushes, blockers and WML aborts store no goto, so the remainder
1278 // is not resumed automatically; the player must use Continue Move.
1279 halted_by_discovery = interrupted;
1280
1281 interrupted = interrupted || num_moves + 1 < intended_route.steps.size();
1282 next_unit_ = intended_route.steps[num_moves];
1283 }
1284
1285 // TRIGGER FLASH-FADE:
1286 // If the unit ran out of movement, visualize the remainder it will travel later.
1287 if(!halted_by_discovery && intended_route.steps.size() > num_moves + 1) {
1288 pathfind::marked_route remaining_route;
1289 for(auto i = intended_route.steps.begin() + num_moves; i != intended_route.steps.end(); ++i) {
1290 remaining_route.steps.push_back(*i);
1291 if(auto it = intended_route.marks.find(*i); it != intended_route.marks.end()) {
1292 remaining_route.marks[*i] = it->second;
1293 }
1294 }
1295
1296 gui().flash_fade_route(remaining_route);
1297 }
1298
1299 // Cleanup state
1301 current_route_.steps.clear();
1302
1303 return !interrupted;
1304}
1305
1306/**
1307 * Moves a unit across the board for a player.
1308 * This is specifically for movement at the time it is initiated by a player,
1309 * whether via a mouse click or executing whiteboard actions. Continued moves
1310 * (including goto execution) can bypass this and call actions::move_unit() directly.
1311 * This function call may include time for an animation, so make sure the
1312 * provided route will remain unchanged (the caller should probably make a local
1313 * copy).
1314 *
1315 * @param[in] steps The route to be traveled. The unit to be moved is at the beginning of this route.
1316 * @param[out] interrupted This is set to true if information was uncovered that warrants interrupting a chain of
1317 * actions (and set to false otherwise).
1318 *
1319 * @returns The number of hexes entered. This can safely be used as an index
1320 * into steps to get the location where movement ended, provided
1321 * steps is not empty (the return value is guaranteed to be less
1322 * than steps.size() ).
1323 */
1324std::size_t mouse_handler::move_unit_along_route(const std::vector<map_location>& steps, bool& interrupted)
1325{
1326 if(steps.empty()) {
1327 interrupted = false;
1328 return 0;
1329 }
1330
1331 // Default return value.
1332 interrupted = true;
1333
1334 // If this is a leader on a keep, ask permission to the whiteboard to move it
1335 // since otherwise it may cause planned recruits to be erased.
1336 if(pc_.get_map().is_keep(steps.front())) {
1337 unit_map::const_iterator const u = pc_.get_units().find(steps.front());
1338
1339 if(u && u->can_recruit() && u->side() == gui().viewing_team().side()
1340 && !pc_.get_whiteboard()->allow_leader_to_move(*u)) {
1342 _("You cannot move your leader away from the keep with some planned recruits or recalls left."));
1343 return 0;
1344 }
1345 }
1346
1347 LOG_NG << "move unit along route from " << steps.front() << " to " << steps.back();
1348 std::size_t moves = actions::move_unit_and_record(steps, false, &interrupted);
1349
1352
1353 if(moves == 0)
1354 return 0;
1355
1356 if(interrupted && moves + 1 < steps.size()) {
1357 // reselect the unit (for "press t to continue")
1358 select_hex(steps[moves], false, false, false);
1359 // the new discovery is more important than the new movement range
1360 show_partial_move_ = true;
1361 }
1362
1363 return moves;
1364}
1365
1367 const map_location& attacker_loc, const map_location& defender_loc, int weapon_choice)
1368{
1369 {
1370 // @todo Fix flickering/reach highlight anomaly after the weapon choice dialog is closed
1371 // This method should do the cleanup of highlights and selection but it doesn't work properly
1372
1373 // gui().highlight_hex(map_location());
1374
1377
1378 // remove footsteps if any - useless for whiteboard as of now
1379 gui().set_route(nullptr);
1380
1381 // do not keep the hex that we started from highlighted
1384 show_partial_move_ = false;
1385
1386 // invalid after saving the move
1388 current_route_.steps.clear();
1389 }
1390
1391 // create planned attack for whiteboard
1392 pc_.get_whiteboard()->save_temp_attack(attacker_loc, defender_loc, weapon_choice);
1393}
1394
1396 std::vector<battle_context>& bc_vector, const unit_map::iterator& attacker, const unit_map::iterator& defender)
1397{
1398 int best = 0;
1399 for(unsigned int i = 0; i < attacker->attacks().size(); i++) {
1400 // skip weapons with attack_weight=0
1401 if(attacker->attacks()[i].attack_weight() > 0) {
1402 // If Reduced RNG is enabled, we use it for the "Attack Enemy" and "Damage Calculations" windows.
1403 // (We don't use it when the AI is evaluating attacks, because the Reduced RNG monte carlo simulation is slow)
1404 battle_context bc(pc_.get_units(), attacker->get_location(), defender->get_location(), i, -1, 0.0, nullptr,
1406
1407 // Don't include if the attacker's weapon has at least one active "disable" special.
1408 if(bc.get_attacker_stats().disable) {
1409 continue;
1410 }
1411
1412 if(!bc_vector.empty() && bc.better_attack(bc_vector[best], 0.5)) {
1413 // as some weapons can be hidden, i is not a valid index into the resulting vector
1414 best = bc_vector.size();
1415 }
1416
1417 bc_vector.emplace_back(std::move(bc));
1418 }
1419 }
1420
1421 return best;
1422}
1423
1424int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const map_location& defender_loc, const map_location& attacker_src)
1425{
1426 game_board& board = pc_.gamestate().board_;
1427 unit_map::iterator attacker;
1428 unit_map::iterator defender;
1429 std::vector<battle_context> bc_vector;
1430 std::vector<gui2::widget_data> bc_widget_data_vector;
1431 int best;
1432 {
1433 pathfind::paths::dest_vect::const_iterator itor = current_paths_.destinations.find(attacker_loc);
1434 temporary_unit_mover temp_mover(pc_.get_units(), attacker_src, attacker_loc, itor->move_left, true);
1435
1436 attacker = board.units().find(attacker_loc);
1437 defender = board.units().find(defender_loc);
1438
1439 if(!attacker || !defender) {
1440 if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
1441 if (!defender) {ERR_NG << "Defender is missing, can't attack";}
1442 return -1; // abort, click will do nothing
1443 }
1444
1445 best = fill_weapon_choices(bc_vector, attacker, defender);
1446
1447 if (bc_vector.empty()) {
1448 gui2::show_transient_message(_("No Attacks"), _("This unit has no usable weapons."));
1449
1450 return -1;
1451 }
1452
1453 static const config empty;
1454 static const_attack_ptr no_weapon(new attack_type(empty));
1455
1456 // Possible TODO: If a "blank weapon" is generally useful, add it as a static member in attack_type.
1457 for(const auto& weapon : bc_vector) {
1458 const battle_context_unit_stats& attacker_stats = weapon.get_attacker_stats();
1459 const battle_context_unit_stats& defender_stats = weapon.get_defender_stats();
1460
1461 const attack_type& attacker_weapon = *attacker_stats.weapon;
1462 const attack_type& defender_weapon = defender_stats.weapon ? *defender_stats.weapon : *no_weapon;
1463
1464 const color_t a_cth_color = game_config::red_to_green(attacker_stats.chance_to_hit);
1465 const color_t d_cth_color = game_config::red_to_green(defender_stats.chance_to_hit);
1466
1467 const std::string attw_name = !attacker_weapon.name().empty() ? attacker_weapon.name() : " ";
1468 const std::string defw_name = !defender_weapon.name().empty() ? defender_weapon.name() : " ";
1469
1470 std::string range = attacker_weapon.range().empty() ? defender_weapon.range() : attacker_weapon.range();
1471 if(!range.empty()) {
1472 range = string_table["range_" + range];
1473 }
1474
1475 auto ctx = specials_context_t::make(
1476 { attacker.get_shared_ptr(), attacker->get_location(), attacker_stats.weapon },
1477 { defender.get_shared_ptr(), defender->get_location(), defender_stats.weapon },
1478 true);
1479
1480 std::string types = attacker_weapon.effective_damage_type().first;
1481 std::string attw_type = !(types).empty() ? types : attacker_weapon.type();
1482 if(!attw_type.empty()) {
1483 attw_type = string_table["type_" + attw_type];
1484 }
1485 std::string def_types = defender_weapon.effective_damage_type().first;
1486 std::string defw_type = !(def_types).empty() ? def_types : defender_weapon.type();
1487 if(!defw_type.empty()) {
1488 defw_type = string_table["type_" + defw_type];
1489 }
1490
1491 const std::set<std::string> checking_tags_other = {"damage_type", "disable", "berserk", "drains",
1492 "heal_on_hit", "plague", "slow", "petrifies", "firststrike", "poison"};
1493 std::string attw_specials = ctx.describe_weapon_specials(attacker_weapon);
1494 std::string attw_specials_dmg = ctx.describe_weapon_specials_value(attacker_weapon, {"leadership", "damage"});
1495 std::string attw_specials_atk = ctx.describe_weapon_specials_value(attacker_weapon, {"attacks", "swarm"});
1496 std::string attw_specials_cth = ctx.describe_weapon_specials_value(attacker_weapon, {"chance_to_hit"});
1497 std::string attw_specials_others = ctx.describe_weapon_specials_value(attacker_weapon, checking_tags_other);
1498 bool defender_attack = !(defender_weapon.name().empty() && defender_weapon.damage() == 0
1499 && defender_weapon.num_attacks() == 0 && defender_stats.chance_to_hit == 0);
1500 std::string defw_specials = defender_attack ? ctx.describe_weapon_specials(defender_weapon) : "";
1501 std::string defw_specials_dmg
1502 = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"leadership", "damage"}) : "";
1503 std::string defw_specials_atk
1504 = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"attacks", "swarm"}) : "";
1505 std::string defw_specials_cth
1506 = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, {"chance_to_hit"}) : "";
1507 std::string defw_specials_others
1508 = defender_attack ? ctx.describe_weapon_specials_value(defender_weapon, checking_tags_other) : "";
1509
1510 if(!attw_specials.empty()) {
1511 attw_specials = " " + attw_specials;
1512 }
1513 if(!attw_specials_dmg.empty()) {
1514 attw_specials_dmg = " " + attw_specials_dmg;
1515 }
1516 if(!attw_specials_atk.empty()) {
1517 attw_specials_atk = " " + attw_specials_atk;
1518 }
1519 if(!attw_specials_cth.empty()) {
1520 attw_specials_cth = " " + attw_specials_cth;
1521 }
1522 if(!attw_specials_others.empty()) {
1523 attw_specials_others
1524 = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(attw_specials_others);
1525 }
1526 if(!defw_specials.empty()) {
1527 defw_specials = " " + defw_specials;
1528 }
1529 if(!defw_specials_dmg.empty()) {
1530 defw_specials_dmg = " " + defw_specials_dmg;
1531 }
1532 if(!defw_specials_atk.empty()) {
1533 defw_specials_atk = " " + defw_specials_atk;
1534 }
1535 if(!defw_specials_cth.empty()) {
1536 defw_specials_cth = " " + defw_specials_cth;
1537 }
1538 if(!defw_specials_others.empty()) {
1539 defw_specials_others
1540 = "\n" + markup::bold(_("Other aspects: ")) + "\n" + markup::italic(defw_specials_others);
1541 }
1542
1543 std::stringstream attacker_stats_str, defender_stats_str, attacker_tooltip, defender_tooltip;
1544
1545 // Use attacker/defender.num_blows instead of attacker/defender_weapon.num_attacks() because the latter does
1546 // not consider the swarm weapon special
1547 attacker_stats_str << markup::bold(attw_name) << "\n"
1548 << attw_type << "\n"
1549 << attacker_stats.damage << font::weapon_numbers_sep << attacker_stats.num_blows
1550 << attw_specials << "\n"
1551 << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%");
1552
1553 attacker_tooltip << _("Weapon: ") << markup::bold(attw_name) << "\n"
1554 << _("Type: ") << attw_type << "\n"
1555 << _("Damage: ") << attacker_stats.damage << markup::italic(attw_specials_dmg) << "\n"
1556 << _("Attacks: ") << attacker_stats.num_blows << markup::italic(attw_specials_atk) << "\n"
1557 << _("Chance to hit: ")
1558 << markup::span_color(a_cth_color, attacker_stats.chance_to_hit, "%")
1559 << markup::italic(attw_specials_cth) << attw_specials_others;
1560
1561 defender_stats_str << markup::bold(defw_name) << "\n"
1562 << defw_type << "\n"
1563 << defender_stats.damage << font::weapon_numbers_sep << defender_stats.num_blows
1564 << defw_specials << "\n"
1565 << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%");
1566
1567 defender_tooltip << _("Weapon: ") << markup::bold(defw_name) << "\n"
1568 << _("Type: ") << defw_type << "\n"
1569 << _("Damage: ") << defender_stats.damage << markup::italic(defw_specials_dmg) << "\n"
1570 << _("Attacks: ") << defender_stats.num_blows << markup::italic(defw_specials_atk) << "\n"
1571 << _("Chance to hit: ")
1572 << markup::span_color(d_cth_color, defender_stats.chance_to_hit, "%")
1573 << markup::italic(defw_specials_cth) << defw_specials_others;
1574
1576 gui2::widget_item item;
1577
1578 item["use_markup"] = "true";
1579
1580 item["label"] = attacker_weapon.icon();
1581 data.emplace("attacker_weapon_icon", item);
1582
1583 item["tooltip"] = attacker_tooltip.str();
1584 item["label"] = attacker_stats_str.str();
1585 data.emplace("attacker_weapon", item);
1586 item["tooltip"] = "";
1587
1588 item["label"]
1589 = markup::span_color("#a69275", font::unicode_em_dash, " ", range, " ", font::unicode_em_dash);
1590 data.emplace("range", item);
1591
1592 item["tooltip"] = defender_attack ? defender_tooltip.str() : "";
1593 item["label"] = defender_stats_str.str();
1594 data.emplace("defender_weapon", item);
1595
1596 item["tooltip"] = "";
1597 item["label"] = defender_weapon.icon();
1598 data.emplace("defender_weapon_icon", item);
1599
1600 bc_widget_data_vector.emplace_back(data);
1601 }
1602 }
1603 // bc_widget_data_vector won't be empty when it reaches here.
1604 gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector);
1605
1606 if(dlg.show()) {
1607 return dlg.get_selected_weapon();
1608 }
1609
1610 return -1;
1611}
1612
1613void mouse_handler::attack_enemy(const map_location& attacker_loc, const map_location& defender_loc, int choice)
1614{
1615 try {
1616 attack_enemy_(attacker_loc, defender_loc, choice);
1617 } catch(const std::bad_alloc&) {
1618 lg::log_to_chat() << "Memory exhausted a unit has either a lot hitpoints or a negative amount.\n";
1619 ERR_WML << "Memory exhausted a unit has either a lot hitpoints or a negative amount.";
1620 }
1621}
1622
1623void mouse_handler::attack_enemy_(const map_location& att_loc, const map_location& def_loc, int choice)
1624{
1625 // NOTE: copy the values because the const reference may change!
1626 // (WML events and mouse inputs during animations may modify
1627 // the data of the caller)
1628 const map_location attacker_loc = att_loc;
1629 const map_location defender_loc = def_loc;
1630
1631 unit* attacker = nullptr;
1632 const unit* defender = nullptr;
1633 std::vector<battle_context> bc_vector;
1634
1635 {
1636 unit_map::iterator attacker_it = find_unit(attacker_loc);
1637 if(!attacker_it || attacker_it->side() != side_num_ || attacker_it->incapacitated()) {
1638 return;
1639 }
1640
1641 unit_map::iterator defender_it = find_unit(defender_loc);
1642 if(!defender_it || current_team().is_enemy(defender_it->side()) == false || defender_it->incapacitated()) {
1643 return;
1644 }
1645
1646 fill_weapon_choices(bc_vector, attacker_it, defender_it);
1647
1648 attacker = &*attacker_it;
1649 defender = &*defender_it;
1650 }
1651
1652 if(std::size_t(choice) >= bc_vector.size()) {
1653 return;
1654 }
1655
1656 events::command_disabler disabler;
1657 const battle_context_unit_stats& att = bc_vector[choice].get_attacker_stats();
1658 const battle_context_unit_stats& def = bc_vector[choice].get_defender_stats();
1659
1660 attacker->set_goto(map_location());
1661
1663
1664 // make the attacker's stats appear during the attack
1665 gui().display_unit_hex(attacker_loc);
1666
1667 // remove highlighted hexes etc..
1672
1674 // TODO: change ToD to be location specific for the defender
1675
1676 const tod_manager& tod_man = pc_.get_tod_manager();
1677
1680 attacker_loc,
1681 defender_loc,
1682 att.attack_num,
1683 def.attack_num,
1684 attacker->type_id(),
1685 defender->type_id(),
1686 att.level,
1687 def.level,
1688 tod_man.turn(),
1689 tod_man.get_time_of_day()
1690 )
1691 );
1692}
1693
1695{
1696 game_board& board = pc_.gamestate().board_;
1697
1698 if(!it) {
1699 return false;
1700 }
1701
1702 if(it->side() != side_num_ || it->user_end_turn() || gui().fogged(it->get_location()) || !board.unit_can_move(*it)) {
1703 return false;
1704 }
1705
1706 if(current_team().is_enemy(gui().viewing_team().side()) && it->invisible(it->get_location())) {
1707 return false;
1708 }
1709
1710 if(it->get_hidden()) {
1711 return false;
1712 }
1713
1714 return true;
1715}
1716
1717void mouse_handler::cycle_units(const bool browse, const bool reverse)
1718{
1719 unit_map& units = pc_.get_units();
1720
1721 if(units.begin() == units.end()) {
1722 return;
1723 }
1724
1726 if(!it) {
1727 it = units.begin();
1728 }
1729
1730 const unit_map::const_iterator itx = it;
1731
1732 do {
1733 if(reverse) {
1734 if(it == units.begin()) {
1735 it = units.end();
1736 }
1737
1738 --it;
1739 } else {
1740 if(it == units.end()) {
1741 it = units.begin();
1742 } else {
1743 ++it;
1744 }
1745 }
1746 } while(it != itx && !unit_in_cycle(it));
1747
1748 if(unit_in_cycle(it)) {
1749 gui().scroll_to_tile(it->get_location(), game_display::WARP);
1750 gui().highlight_hex(it->get_location());
1751
1752 select_hex(it->get_location(), browse);
1753 // mouse_update(browse);
1754 }
1755}
1756
1758{
1760
1761 current_paths_ = new_paths;
1762 current_route_.steps.clear();
1763
1764 gui().set_route(nullptr);
1765
1766 pc_.get_whiteboard()->erase_temp_move();
1767}
1768
1770{
1771 return pc_.get_teams()[side_num_ - 1];
1772}
1773
1775
1780
1785
1786} // end namespace events
static bool is_enemy(std::size_t side, std::size_t other_side)
Various functions that implement attacks and attack calculations.
map_location loc
Definition move.cpp:172
Various functions related to moving units.
#define ERR_NG
#define LOG_NG
const t_string & name() const
int num_attacks() const
const std::string & icon() const
const std::string & range() const
const std::string & type() const
int damage() const
std::pair< std::string, int > effective_damage_type() const
The type of attack used and the resistance value that does the most damage.
Computes the statistics of a battle between an attacker and a defender unit.
Definition attack.hpp:183
const battle_context_unit_stats & get_attacker_stats() const
This method returns the statistics of the attacker.
Definition attack.hpp:211
bool better_attack(class battle_context &that, double harm_weight)
Given this harm_weight, is this attack better than that?
Definition attack.cpp:376
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
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:337
void invalidate_game_status()
Function to invalidate the game status displayed on the sidebar.
Definition display.hpp:308
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:1815
bool fogged(const map_location &loc) const
Returns true if location (x,y) is covered in fog.
Definition display.cpp:669
void invalidate_all()
Function to invalidate all tiles.
Definition display.cpp:2920
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:691
rect map_area() const
Returns the area used for the map.
Definition display.cpp:490
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:533
bool scroll(const point &amount, bool force=false)
Scrolls the display by amount pixels.
Definition display.cpp:1540
bool minimap_scrolling_
minimap scrolling (scroll-drag) state flag
map_location last_hex_
last highlighted hex
bool scroll_started_
Scroll start flag.
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
void show_reach_for_unit(const unit_ptr &un)
Highlight the hexes that a unit can move to.
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)
int show_attack_dialog(const map_location &attacker_loc, const map_location &defender_loc, const map_location &attacker_src)
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...
mouse_handler(play_controller &pc)
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)
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
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...
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)
game_display & gui() override
Due to the way this class is constructed we can assume that the display* gui_ member actually points ...
void cycle_units(const bool browse, const bool reverse=false)
std::size_t move_unit_along_route(const std::vector< map_location > &steps, bool &interrupted)
Moves a unit across the board for a player.
void mouse_motion(int x, int y, const bool browse, bool update=false, map_location loc=map_location::null_location()) override
Use update to force an update of the mouse state.
Game board class.
virtual const gamemap & map() const override
virtual const std::vector< team > & teams() const override
unit_map::iterator find_visible_unit(const map_location &loc, const team &current_team, bool see_all=false)
virtual const unit_map & units() const override
void display_unit_hex(map_location hex)
Change the unit to be displayed in the sidebar.
void flash_fade_route(const pathfind::marked_route &route)
Visualizes a path by flashing it white then fade out over 2 seconds.
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_
game_board board_
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:347
bool is_keep(const map_location &loc) const
Definition map.cpp:66
bool show(const unsigned auto_close_time=0)
Shows the window.
const tod_manager & get_tod_manager() const
const unit_map & get_units() const
const gamemap & get_map() const
std::vector< team > & get_teams()
int current_side() const
Returns the number of the side whose turn it is.
std::shared_ptr< wb::manager > get_whiteboard() const
game_events::wml_event_pump & pump()
game_state & gamestate()
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 specials_context_t make(specials_combatant &&self, specials_combatant &&other, bool attacking)
static bool run_and_throw(const std::string &commandname, const config &data, action_spectator &spectator=get_default_spectator())
bool empty() const
Definition tstring.hpp:200
This class stores all the data for a single 'side' (in game nomenclature).
Definition team.hpp:74
int side() const
Definition team.hpp:182
bool is_enemy(int n) const
Definition team.hpp:270
void increment_action_bonus_count()
Definition team.hpp:240
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.
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:39
Definitions for the interface to Wesnoth Markup Language (WML).
#define ERR_WML
static std::string _(const char *str)
Definition gettext.hpp:100
const std::string & type_id() const
The id of this unit's type.
Definition unit.cpp:1964
bool get_hidden() const
Gets whether this unit is currently hidden on the map.
Definition unit.hpp:642
int side() const
The side this unit belongs to.
Definition unit.hpp:249
unit_animation_component & anim_comp() const
Definition unit.hpp:1537
const map_location & get_location() const
The current map location this unit is at.
Definition unit.hpp:1327
void set_goto(const map_location &new_goto)
Sets this unit's long term destination.
Definition unit.hpp:1364
Contains functions for cleanly handling SDL input.
auto string_table
Definition language.hpp:81
std::size_t distance_between(const map_location &a, const map_location &b)
Function which gives the number of hexes between two tiles (i.e.
Definition location.cpp:584
void get_adjacent_tiles(const map_location &a, utils::span< map_location, 6 > res)
Function which, given a location, will place all adjacent locations in res.
Definition location.cpp:513
Standard logging facilities (interface).
static lg::log_domain log_engine("engine")
static lg::log_domain log_wml("wml")
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:1403
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:1435
bool enabled()
Returns true if the current game uses the biased random mode with a local RNG.
Definition attack.cpp:78
CURSOR_TYPE get()
Definition cursor.cpp:185
@ 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:151
Handling of system events.
const std::string unicode_em_dash
Definition constants.cpp:44
const std::string weapon_numbers_sep
Definition constants.cpp:49
color_t red_to_green(double val, bool for_text)
Return a color corresponding to the value val red for val=0.0 to green for val=100....
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
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
std::map< std::string, t_string > widget_item
Definition widget.hpp:33
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:550
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition markup.hpp:176
std::string bold(Args &&... data)
Applies bold Pango markup to the input.
Definition markup.hpp:161
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
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)
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(float *x, float *y)
A wrapper for SDL_GetMouseState that gives coordinates in draw space.
Definition input.cpp:28
point get_mouse_location()
Returns the current mouse location in draw space.
Definition input.cpp:49
void play_UI_sound(const std::string &files)
Definition sound.cpp:1009
bool click(int mousex, int mousey)
Definition tooltips.cpp:350
void process(int mousex, int mousey)
Definition tooltips.cpp:334
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition general.hpp:88
std::shared_ptr< bool > whiteboard_lock
Definition typedefs.hpp:58
std::string_view data
Definition picture.cpp:188
std::shared_ptr< const unit > unit_const_ptr
Definition ptr.hpp:27
std::shared_ptr< const attack_type > const_attack_ptr
Definition ptr.hpp:34
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 dst
Location on the final composed sheet.
rect src
Non-transparent portion of the surface to compose.
Structure describing the statistics of a unit involved in the battle.
Definition attack.hpp:72
unsigned int num_blows
Effective number of blows, takes swarm into account.
Definition attack.hpp:99
const_attack_ptr weapon
The weapon used by the unit to attack the opponent, or nullptr if there is none.
Definition attack.hpp:73
int damage
Effective damage of the weapon (all factors accounted for).
Definition attack.hpp:95
bool disable
Attack has disable special.
Definition attack.hpp:85
unsigned int chance_to_hit
Effective chance to hit as a percentage (all factors accounted for).
Definition attack.hpp:94
int attack_num
Index into unit->attacks() or -1 for none.
Definition attack.hpp:74
The basic class for representing 8-bit RGB or RGBA colour values.
Definition color.hpp:51
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
direction get_relative_dir(const map_location &loc, map_location::RELATIVE_DIR_MODE mode) const
Definition location.cpp:238
static const map_location & null_location()
Definition location.hpp:103
Structure which holds a single route and marks for special events.
Definition pathfind.hpp:142
std::vector< map_location > & steps
Definition pathfind.hpp:187
void insert(const map_location &)
Definition pathfind.cpp:485
bool contains(const map_location &) const
Definition pathfind.cpp:514
const_iterator find(const map_location &) const
Definition pathfind.cpp:478
Object which contains all the possible locations a unit can move to, with associated best routes to t...
Definition pathfind.hpp:73
dest_vect destinations
Definition pathfind.hpp:101
Structure which holds a single route between one location and another.
Definition pathfind.hpp:133
Holds a 2D point.
Definition point.hpp:25
bool contains(int x, int y) const
Whether the given point lies within the rectangle.
Definition rect.cpp:54
This object is used to temporary move a unit in the unit map, swapping out any unit that is already t...
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.
#define i