Repository: wesnoth/wesnoth
Pull request #9286: UI support for real-ranged attacks
Author: login: hladkyjakub
Labels: name: UI
description: User interface issues, including both back-end and front-end issues.
name: Engine
description: General game engine issues that do not fit in any other category.
Created: 2024-09-01T16:29:21Z | Merged: 2025-03-27T20:35:33Z

This makes units with weapons with custom min/max_range capable of attack when controlled by player.
(I have the AI code too, but not in this PR)
For proper working requires these PR merged:
Propper max/min_range: https://github.com/wesnoth/wesnoth/pull/9203
Propper UI preview: https://github.com/wesnoth/wesnoth/pull/9255


Diff:
diff --git a/src/map/location.hpp b/src/map/location.hpp
index ade8990dc78a8..945947b5dbd77 100644
--- a/src/map/location.hpp
+++ b/src/map/location.hpp
@@ -175,6 +175,40 @@ struct map_location {
 		return map_location(x, y);
 	}
 
+	std::vector<map_location> get_ring(int min, int max) const {
+		std::vector<map_location> tiles;
+
+		// Convert this location to cubic coordinates
+		cubic_location cubic_center = this->to_cubic();
+
+		// Enumerate range in cubic coordinates
+		for (int dx = -max; dx <= max; ++dx) {
+			for (int dy = std::max(-max, -dx-max); dy <= std::min(max, -dx+max); ++dy) {
+				int dz = -dx - dy;
+
+				// Calculate Manhattan distance in cubic coordinates
+				int distance = (std::abs(dx) + std::abs(dy) + std::abs(dz)) / 2;
+
+				// Skip positions outside our min/max range
+				if (distance < min || distance > max) {
+					continue;
+				}
+
+				// Create new cubic location
+				cubic_location neighbor{
+					cubic_center.q + dx,
+					cubic_center.r + dy,
+					cubic_center.s + dz
+				};
+
+				// Convert back to map coordinates and add to tiles
+				tiles.push_back(from_cubic(neighbor));
+			}
+		}
+
+		return tiles;
+	}
+
 	// Rotates the map_location clockwise in 60 degree increments around a center point. Negative numbers of steps are permitted.
 	map_location rotate_right_around_center(const map_location& center, int k) const;
 
diff --git a/src/mouse_events.cpp b/src/mouse_events.cpp
index 767e7ce2de09b..15c21fa9cd020 100644
--- a/src/mouse_events.cpp
+++ b/src/mouse_events.cpp
@@ -36,6 +36,7 @@
 #include "synced_context.hpp"
 #include "team.hpp" // for team
 #include "tod_manager.hpp"
+#include "map/location.hpp"
 #include "units/animation_component.hpp"
 #include "units/ptr.hpp"           // for unit_const_ptr
 #include "units/unit.hpp"          // for unit
@@ -683,6 +684,7 @@ bool mouse_handler::hex_hosts_unit(const map_location& hex) const
 	return find_unit(hex).valid();
 }
 
+// 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
 map_location mouse_handler::current_unit_attacks_from(const map_location& loc) const
 {
 	if(loc == selected_hex_) {
@@ -690,6 +692,7 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 	}
 
 	bool wb_active = pc_.get_whiteboard()->is_active();
+	std::set<int> attackable_distances;
 
 	{
 		// Check the unit SOURCE of the attack
@@ -749,8 +752,39 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		for (const auto& attack : source_unit->attacks()) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))) {
+			return selected_hex_;
+		}
+		map_location res;
+		int best_move = -1;
+		for (const pathfind::paths::step& step : current_paths_.destinations) {
+			map_location dst = step.curr;
+			if(utils::contains(attackable_distances, distance_between(loc, dst))) {
+				if (step.move_left > best_move){
+					best_move = step.move_left;
+					res=dst;
+				}
+			}
+		}
+		return res;
 	}
 
+	//no ranged attack
 	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
 	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
 
@@ -980,7 +1014,7 @@ void mouse_handler::move_action(bool browse)
 					}
 
 					if(choice < 0) {
-						// user hit cancel, don't start move+attack
+						// user hit cancel or attack is invalid, don't start move&attack
 						return;
 					}
 				} // end planned unit map scope
@@ -1144,7 +1178,7 @@ void mouse_handler::select_hex(const map_location& hex, const bool browse, const
 	}
 
 	if(selected_hex_.valid() && !unit) {
-		// Compute unit in range of the empty selected_hex field
+		// When no unit is selected, compute unit in range of the empty selected_hex field
 
 		gui_->unhighlight_reach();
 
@@ -1354,7 +1388,8 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 		defender = board.units().find(defender_loc);
 
 		if(!attacker || !defender) {
-			ERR_NG << "One fighter is missing, can't attack";
+			if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
+			if (!defender) {ERR_NG << "Defender is missing, can't attack";}
 			return -1; // abort, click will do nothing
 		}
 


Reviews:
user: login: CelticMinstrel
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-02T14:33:55Z
user: login: CelticMinstrel
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-02T14:34:21Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-02T14:38:34Z
user: login: CelticMinstrel
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-02T14:51:08Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-02T14:52:47Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-02T14:52:58Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-03T18:40:28Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-03T18:59:46Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-03T19:02:24Z
user: login: CelticMinstrel
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-03T23:07:45Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T05:22:56Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T08:43:07Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T09:03:01Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T09:12:45Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T09:15:25Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T09:29:10Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T09:56:00Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T09:58:01Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T09:59:29Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2024-09-04T14:13:39Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T15:08:23Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T15:11:00Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2024-09-04T15:18:07Z
user: login: soliton-
body: Just to be clear that I do not think this is ready to merge (neither was #9255 IMO).

The hex math should be put in a separate function (or use suitable existing functions) and documented properly (same as for #9255).
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2024-09-14T17:47:23Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T21:24:56Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-16T21:29:14Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T07:37:42Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T16:14:11Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T13:20:07Z
user: login: Vultraz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T08:03:26Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T08:15:09Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T10:27:30Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T10:28:54Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:29:15Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:33:47Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:38:03Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:39:14Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T10:40:00Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T10:42:03Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:51:25Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:51:29Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T15:59:52Z
user: login: soliton-
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T22:28:39Z
user: login: soliton-
body: Disregarding the confusing comment this looks good to me.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-19T22:35:12Z
user: login: Vultraz
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-27T12:19:48Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:20:29Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:21:06Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:22:34Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:23:27Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:24:23Z
user: login: Vultraz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:34:10Z
user: login: Vultraz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:35:12Z
user: login: Vultraz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T13:36:38Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T13:59:25Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:00:04Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:01:20Z
user: login: Vultraz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-27T14:24:06Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T14:25:56Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:15:42Z
user: login: hladkyjakub
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:25:50Z
user: login: Vultraz
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-27T20:34:42Z

Review comments:
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
path: src/mouse_events.cpp
user: login: CelticMinstrel
body: As with the other PR, let's use `floor(a.x / 2.0)` here.
created_at: 2024-09-02T14:33:55Z
author_association: MEMBER
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
+	std::vector<map_location> tiles;
+	// enumerate range
+	for (int dx = -n; dx <= n; ++dx) {
+		for (int dy = std::max(-n, -dx-n); dy <= std::min(n, -dx+n); ++dy) {
+			int dz = -dx - dy;
+			if (std::abs(dx) + std::abs(dy) + std::abs(dz) < 2*min) {
+				continue;
+			}
+			int z = cz + dz;
+			int x = a.x + dx;
+			// convert back to offset
+			tiles.emplace_back(x, z + (x - (x&1)) / 2);
path: src/mouse_events.cpp
user: login: CelticMinstrel
body: `floor(x / 2.0)`
created_at: 2024-09-02T14:34:20Z
author_association: MEMBER
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
path: src/mouse_events.cpp
user: login: hladkyjakub
body: This is generally used code man, this is the current best way.
...
Do we really have to do this?

created_at: 2024-09-02T14:38:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
path: src/mouse_events.cpp
user: login: CelticMinstrel
body: I think it's clearer if code expresses is intent rather than using obscure bitwise operations. Yes, even if "everyone else" uses obscure bitwise operations.

Based on the link you put in the code a few lines up, there's the suggestion of using `abs(x)%2` instead of `x&1`, which would be slightly better, but `(x - x%2) / 2` still looks weird (and divides _twice_!), so I think the floor is still preferred.

Although, it would be even better if the logic is extracted out into a function, probably a member function of `map_location`. It could be called `to_cubic` or similar and return a `tuple<int,int,int>` or a new custom struct with three members `q, r, s`.
created_at: 2024-09-02T14:51:08Z
author_association: MEMBER
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Bruh

created_at: 2024-09-02T14:52:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1484,8 +1515,50 @@ void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
+	const map_location& a = u->get_location();
+
+	int cz = a.y - (a.x - (a.x&1)) / 2;
+	std::vector<map_location> tiles;
+	// enumerate range
+	for (int dx = -n; dx <= n; ++dx) {
+		for (int dy = std::max(-n, -dx-n); dy <= std::min(n, -dx+n); ++dy) {
+			int dz = -dx - dy;
+			if (std::abs(dx) + std::abs(dy) + std::abs(dz) < 2*min) {
+				continue;
+			}
+			int z = cz + dz;
+			int x = a.x + dx;
+			// convert back to offset
+			tiles.emplace_back(x, z + (x - (x&1)) / 2);
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Here you go
created_at: 2024-09-02T14:52:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: Why are these dereferences allowed? I suggest checking for empty.
created_at: 2024-09-03T18:40:28Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: What do you mean?
created_at: 2024-09-03T18:59:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: I do check if the attacks are empty already
created_at: 2024-09-03T19:02:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: CelticMinstrel
body: The set is known to be non-empty because the only way for it to be empty is if there are no attacks, and if there are no attacks then it doesn't even reach this line due to the check on line 1510.

Both dereferences are guaranteed to be valid if there's at least one element, and we know there's at least one element. (I suppose you could add `assert(!attackable_distances.empty());` above these lines to make it clearer that we know there's at least one element.)
created_at: 2024-09-03T23:07:45Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Ok
created_at: 2024-09-04T05:22:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: Unless there is code elsewhere that checks min and max range there is no reason to expect the set to be non-empty. I suggest to not add an assert.
created_at: 2024-09-04T08:43:07Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body:  I added assert, if the set is empty already.
You've seen that?
created_at: 2024-09-04T09:03:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: Yes, hence now it will abort the process instead of more reasonable behavior.
created_at: 2024-09-04T09:12:45Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: It won't evaluate that assert as true.
created_at: 2024-09-04T09:15:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: No clue how you come to that conclusion. Where is the code that guarantees it?
created_at: 2024-09-04T09:29:10Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: > No clue how you come to that conclusion. Where is the code that guarantees it?

Tell me example, it could fail.
created_at: 2024-09-04T09:56:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: Pick min and max ranges so nothing is added to the set.
created_at: 2024-09-04T09:58:01Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Isn't that true only for invalid min and max ranges?
created_at: 2024-09-04T09:59:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: soliton-
body: Can you elaborate on what that is and where that is handled?
created_at: 2024-09-04T14:13:39Z
author_association: MEMBER
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: > Can you elaborate on what that is and where that is handled?

I'm sure it happen only when min_range > max_range,
Which is invalid. ...
But I see you point, I'll add a fallback if someone sets those numbers wrongly.
created_at: 2024-09-04T15:08:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: *instead of the assert.
created_at: 2024-09-04T15:11:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1476,16 +1507,58 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 void mouse_handler::show_attack_options(const unit_map::const_iterator& u)
 {
 	// Cannot attack if no attacks are left.
-	if(u->attacks_left() == 0) {
+	if(u->attacks_left() == 0 || u->attacks().empty()) {
 		return;
 	}
 
 	// Get the teams involved.
 	const team& cur_team = current_team();
 	const team& u_team = pc_.get_teams()[u->side() - 1];
 
-	// Check each adjacent hex.
-	for(const map_location& loc : get_adjacent_tiles(u->get_location())) {
+
+	// convert to cubical -> enumerate tiles -> convert back
+	// see https://www.redblobgames.com/grids/hexagons/ for detailed description
+
+
+	// convert to cubical coordinates
+	// cx == a.x, cy is not needed
+	const auto& attacks = u->attacks();
+	std::set<int> attackable_distances;
+
+    for (const auto& attack : attacks) {
+        for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+            attackable_distances.insert(i);
+        }
+    }
+
+	// for optimalization, I think iterating over ranges here, would be better
+
+	int min = *attackable_distances.begin();
+	int n = *std::prev(attackable_distances.end());
path: src/mouse_events.cpp
user: login: hladkyjakub
body: (Same for the othor UI PR)
created_at: 2024-09-04T15:18:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: soliton-
body: Instead of putting the rest of the function in the if invert the check and return early. You're also missing a check for empty before you assume there is one element.
created_at: 2025-03-16T21:24:56Z
author_association: MEMBER
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: soliton-
body: Well, I guess this is not the rest of the function. The diff is unreadable so I'll wait for the explanation.
created_at: 2025-03-16T21:29:14Z
author_association: MEMBER
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: hladkyjakub
body: First I check if enemy is valid and create attackable distances, then if unit cannot attaxk further than one hex, I leave the old code there, else my ranged code is executed.
created_at: 2025-03-17T07:37:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: hladkyjakub
body: @soliton- So what do you think?

created_at: 2025-03-17T16:14:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: hladkyjakub
body: @soliton- I don't mean to be rude, but I'm really waiting for your response, so I can move on with the work.
created_at: 2025-03-18T13:20:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,14 +751,41 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
+	map_location res;
+
+	if(attackable_distances.empty()) {return res} 	//invalid attack ranges
path: src/mouse_events.cpp
user: login: Vultraz
body: Cleanup suggestions:
```cpp
	if(attackable_distances.empty()) {
		return map_location{};
	}

	// ranged attack
	if(attackable_distances.back() > 1) {
		if(utils::contains(attackable_distances, distance_between(selected_hex_, loc)) {
			return selected_hex_;
		}

		int best_move = -1;
		map_location res;

		for(const pathfind::paths::step& step : current_paths_.destinations) {
			const map_location& dst = step.curr;

			if(utils::contains(attackable_distances, distance_between(loc, dst)) {
				if(step.move_left > best_move) {
					best_move = step.move_left;
					res = dst;
				}
			}
		}

		return res;
	}
```
created_at: 2025-03-19T08:03:26Z
author_association: MEMBER
diff_hunk: @@ -749,14 +751,41 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
+	map_location res;
+
+	if(attackable_distances.empty()) {return res} 	//invalid attack ranges
path: src/mouse_events.cpp
user: login: hladkyjakub
body: ok
created_at: 2025-03-19T08:15:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: soliton-
body: Is this a question or a clarifying comment or what?
created_at: 2025-03-19T10:27:30Z
author_association: MEMBER
diff_hunk: @@ -1525,7 +1565,7 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 	// bc_widget_data_vector won't be empty when it reaches here.
 	gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector, leadership_bonus);
 
-	if(dlg.show()) {
+	if(dlg.show()) { //dlg.show() is what actually shows the dialog
path: src/mouse_events.cpp
user: login: soliton-
body: I'm all for comments explaining non-obvious code but this seems a bit much.
created_at: 2025-03-19T10:28:53Z
author_association: MEMBER
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: hladkyjakub
body: It's supposed to be a clarifying comment ,:D
created_at: 2025-03-19T10:29:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1525,7 +1565,7 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 	// bc_widget_data_vector won't be empty when it reaches here.
 	gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector, leadership_bonus);
 
-	if(dlg.show()) {
+	if(dlg.show()) { //dlg.show() is what actually shows the dialog
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Okay, it just took me half an hour to find out that what actually shows the dialog is in an if... but yes, seems obviois now
created_at: 2025-03-19T10:33:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: hladkyjakub
body: What do you want to be done about it?

created_at: 2025-03-19T10:38:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1525,7 +1565,7 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 	// bc_widget_data_vector won't be empty when it reaches here.
 	gui2::dialogs::unit_attack dlg(attacker, defender, std::move(bc_vector), best, bc_widget_data_vector, leadership_bonus);
 
-	if(dlg.show()) {
+	if(dlg.show()) { //dlg.show() is what actually shows the dialog
path: src/mouse_events.cpp
user: login: hladkyjakub
body: removing it

created_at: 2025-03-19T10:39:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1630,6 +1670,69 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 	return res;
 }
 
+/**
+ * NOT NEEDED now, keeping for future work on ranged attacks UI
path: src/mouse_events.cpp
user: login: soliton-
body: This does not seem sensible. If you want to keep code around for future work then do that privately. No point in leaving in dead code.
created_at: 2025-03-19T10:40:00Z
author_association: MEMBER
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: soliton-
body: Should be written to be understandable.
created_at: 2025-03-19T10:42:03Z
author_association: MEMBER
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Would:
//the attack dialog is showed like if the unit is standing at the hex, this may be not the best approach
Work?

created_at: 2025-03-19T10:51:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1630,6 +1670,69 @@ std::set<map_location> mouse_handler::get_adj_enemies(const map_location& loc, i
 	return res;
 }
 
+/**
+ * NOT NEEDED now, keeping for future work on ranged attacks UI
path: src/mouse_events.cpp
user: login: hladkyjakub
body: ok
created_at: 2025-03-19T15:51:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,47 +751,76 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
 	}
 
-	const map_location::direction preferred = loc.get_relative_dir(previous_hex_);
-	const map_location::direction second_preferred = loc.get_relative_dir(previous_free_hex_);
+	if(*attackable_distances.rbegin() <= 1){
path: src/mouse_events.cpp
user: login: hladkyjakub
body: resolved ig
created_at: 2025-03-19T15:59:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -926,7 +964,7 @@ void mouse_handler::move_action(bool browse)
 
 		src = selected_hex_;
 		orig_paths = current_paths_;
-		attack_from = current_unit_attacks_from(hex);
+		attack_from = current_unit_attacks_from(hex); //Where does the attack dialog gonna be showed from, only one tile now
path: src/mouse_events.cpp
user: login: soliton-
body: `// the attack dialog is shown as if the unit is standing at the hex, this may be not the best approach`

What hex? The selected one, I guess? And this is talking about real ranged attacks or what? Why is this comment here and not inside the function?
created_at: 2025-03-19T22:28:39Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
path: src/mouse_events.cpp
user: login: Vultraz
body: ```suggestion
		for(const auto& attack :  source_unit->attacks()) {
```
created_at: 2025-03-27T12:15:29Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
path: src/mouse_events.cpp
user: login: Vultraz
body: Please remove excess newlines. Should be only 1.
created_at: 2025-03-27T12:15:40Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: Vultraz
body: Does `utils::contains` not work here (and below)?
created_at: 2025-03-27T12:18:44Z
author_association: MEMBER
diff_hunk: @@ -788,8 +827,8 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 			}
 		}
 	}
-
 	return res;
+
path: src/mouse_events.cpp
user: login: Vultraz
body: Please undo these two whitespace changes.
created_at: 2025-03-27T12:19:07Z
author_association: MEMBER
diff_hunk: @@ -1354,7 +1393,8 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 		defender = board.units().find(defender_loc);
 
 		if(!attacker || !defender) {
-			ERR_NG << "One fighter is missing, can't attack";
+			if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
path: src/mouse_events.cpp
user: login: Vultraz
body: Why is this additional logging needed?
created_at: 2025-03-27T12:19:43Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Why not attacks?
created_at: 2025-03-27T13:20:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Ok
created_at: 2025-03-27T13:21:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -788,8 +827,8 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 			}
 		}
 	}
-
 	return res;
+
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Ok... you commented only one
created_at: 2025-03-27T13:22:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1354,7 +1393,8 @@ int mouse_handler::show_attack_dialog(const map_location& attacker_loc, const ma
 		defender = board.units().find(defender_loc);
 
 		if(!attacker || !defender) {
-			ERR_NG << "One fighter is missing, can't attack";
+			if (!attacker) {ERR_NG << "Attacker is missing, can't attack";}
path: src/mouse_events.cpp
user: login: hladkyjakub
body: More telling to you, it isn't _needed_.
created_at: 2025-03-27T13:23:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Rephrase the question, please.
created_at: 2025-03-27T13:24:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -788,8 +827,8 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 			}
 		}
 	}
-
 	return res;
+
path: src/mouse_events.cpp
user: login: Vultraz
body: You added a line and removed a line. Just move `return res` back to the end of the function.
created_at: 2025-03-27T13:34:10Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: Vultraz
body: If you use `if(utils::contains(attackable_distances, distance_between(selected_hex_, loc))`, does it compile? If so, you should also use `utils::contains` in the same check a few lines down.
created_at: 2025-03-27T13:35:12Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
path: src/mouse_events.cpp
user: login: Vultraz
body: You don't need the separate local variable `attacks` (it can be removed). Just loop over `source_unit->attacks()` directly.
created_at: 2025-03-27T13:36:37Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Gonna check it out, if I do access it only few rimes, you're eight it shouldn't be local variable (may be that it's lefover from more complex, mine old code, now removed).
created_at: 2025-03-27T13:59:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -788,8 +827,8 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 			}
 		}
 	}
-
 	return res;
+
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Ok ^^
created_at: 2025-03-27T14:00:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: I can try.
What would be the advantage of the method?
created_at: 2025-03-27T14:01:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: Vultraz
body: Cleaner, easier-to-read code, and we use `utils::contains` widely in the codebase.
created_at: 2025-03-27T14:24:06Z
author_association: MEMBER
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: If that's so, is is documentated somewhere? Are utils wesnoth's own utils?
created_at: 2025-03-27T14:25:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: yes, that has no point in there

created_at: 2025-03-27T16:15:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -749,8 +752,44 @@ map_location mouse_handler::current_unit_attacks_from(const map_location& loc) c
 		if(!target_eligible) {
 			return map_location();
 		}
+
+		const auto& attacks = source_unit->attacks();
+		for (const auto& attack : attacks) {
+			for (int i = attack.min_range(); i <= attack.max_range(); ++i) {
+				attackable_distances.insert(i);
+			}
+		}
+	}
+
+
+
+	//invalid attack ranges
+	if(attackable_distances.empty()) {
+		return map_location{};
+	}
+
+	//ranged attack
+	if(*attackable_distances.rbegin() > 1){
+		int distance = distance_between(selected_hex_, loc);
+		if (attackable_distances.find(distance) != attackable_distances.end() ) {
path: src/mouse_events.cpp
user: login: hladkyjakub
body: Works fine :D
created_at: 2025-03-27T16:25:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: CelticMinstrel
created_at: 2024-09-02T14:06:30Z
body: For me, "multihex attacks" sounds like "an attack that hits more than one hex", which is totally different from what this PR actually is.

It would be helpful to see screenshots of this PR in action.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-09-02T14:28:14Z
body: > For me, "multihex attacks" sounds like "an attack that hits more than one hex", which is totally different from what this PR actually is.

real-ranged is ok?
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2024-09-02T14:33:24Z
body: > It would be helpful to see screenshots of this PR in action.

Sure:
![Screenshot_20240902_163005](https://github.com/user-attachments/assets/d4482a2e-286a-4adc-a2e9-4916111b4dd4)
![Screenshot_20240902_163021](https://github.com/user-attachments/assets/6949d2d3-54f3-444e-b919-07d57a88c597)
![Screenshot_20240902_163128](https://github.com/user-attachments/assets/71536d2e-7060-4870-b273-79134b875fa0)

author_association: CONTRIBUTOR
user: login: CelticMinstrel
created_at: 2024-09-04T06:33:49Z
body: Okay, so after some investigation and testing, I've determined that we _already have_ a function that does the conversion you want. It doesn't match up very well with the code on redblobgames.com, and it's probably a lot less efficient, but it appears to produce equivalent results.

The function is `map_location::get_in_basis_N_NE`. It returns a pair where `first` is what redblobgames.com calls `s`, and `second` is what redblobgames.com calls `q`.  That means that if you need what redblobgames calls `r` you can calculate it as `-second-first`.

So, please use `get_in_basis_N_NE` instead of the `floor` formula I previously asked you to use. If you need help figuring out exactly how to use `get_in_basis_N_NE` to find the same answer, feel free to ask.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-09-04T06:49:28Z
body: Why? Why? Why?
If it's less efficient, why to use it?
Btw also found out how to check for big hexagon, not rectangle so no need for distance_between check, so it's more effective, also works well for max-min ranges, I thought, I could implement that.
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2024-09-04T08:47:43Z
body: > Btw also found out how to check for big hexagon, not rectangle so no need for distance_between check, so it's more effective, also works well for max-min ranges, I thought, I could implement that.

Implement that in a separate function. `map_location` might be a good place for it.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-09-04T09:04:26Z
body: 
> Implement that in a separate function. `map_location` might be a good place for it.

Ok, I can do that.
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2024-09-04T09:33:03Z
body: A function that returns a list of locations in a certain distance range to a center location sounds like something that could be useful to expose to Lua and WFL as well.
author_association: MEMBER
user: login: CelticMinstrel
created_at: 2024-09-04T12:45:27Z
body: > Why? Why? Why?
> If it's less efficient, why to use it?

Because it makes clear your intent. If it's too inefficient, we can just make it more efficient, but the point is that we want to be able to look at the code and know what it's doing.

> A function that returns a list of locations in a certain distance range to a center location sounds like something that could be useful to expose to Lua and WFL as well.

Sounds like `wesnoth.map.get_hexes_in_radius` (or in C++, the global function `get_tiles_in_radius`). Please don't reimplement things that we already have, just use them!

If you're looking for _just_ the single ring at exactly the target radius, that also exists too (though only in C++) – it's called `get_tile_ring`.

Both functions are in `"pathutils.hpp"`.
author_association: MEMBER
user: login: CelticMinstrel
created_at: 2024-09-04T14:06:45Z
body: > The function is `map_location::get_in_basis_N_NE`. It returns a pair where `first` is what redblobgames.com calls `s`, and `second` is what redblobgames.com calls `q`. That means that if you need what redblobgames calls `r` you can calculate it as `-second-first`.

Looking more closely at the info on redblobgames.com, it seems I misunderstood something about how the axial/cube coordinates work. I'll get back to you on this later, but probably don't make the changes yet.
author_association: MEMBER
user: login: soliton-
created_at: 2024-09-04T14:12:49Z
body: Obviously if the function already exists don't add another one. I did not check if any exists at all.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-09-04T15:20:29Z
body: okay... I'm waiting.
author_association: CONTRIBUTOR
user: login: CelticMinstrel
created_at: 2024-09-04T17:35:34Z
body: What I missed in my earlier perusal is that the axial coordinates described on redblobgames.com use axes that are _not_ aligned with the directions of the hex grid. (They are in fact the "diagonal directions" of the hex grid.) The basis_N_NE coordinates used in Wesnoth for rotations are _not_ like that – their axes _are_ aligned with the directions of the hex grid. So it's doubtful whether they are interchangeable like I implied earlier.

So, this basically leaves you with two options:

1. You can try to figure out a way to use `get_in_basis_N_NE` to calculate whatever you need to calculate. I think you were looking for distance, so the N-NE basis may be satisfactory, but I'm not sure.
2. You can create a new function in `map_location` which converts to cube or axial coordinates by redblobgames's definition. I think it could be called `get_in_basis_E_SW` to mirror the name of the existing function, but I'm open to other naming suggestions. It should probably return a struct with elements `q` and `r` (which I think you've been calling `x` and `y` in the code so far), and maybe `s` either as an element or a member function.

The main point here is that putting the logic behind a function makes it clearer what you're doing. I'd be less opposed to using the bit-manipulation method if it's in a function.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-09-04T18:25:03Z
body: >     2. You can create a new function in `map_location` which converts to cube or axial coordinates by redblobgames's definition. I think it could be called `get_in_basis_E_SW` to mirror the name of the existing function, but I'm open to other naming suggestions. It should probably return a struct with elements `q` and `r` (which I think you've been calling `x` and `y` in the code so far), and maybe `s` either as an element or a member function.
> 
> The main point here is that putting the logic behind a function makes it clearer what you're doing. I'd be less opposed to using the bit-manipulation method if it's in a function.

I could try the 2nd option, you gave me, haven't ever created a function, included in the header file, is it complicated?
It's late today, I'll get to it tomorrow.

author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2024-09-14T17:50:15Z
body: I more or less agree, but got no time nor will now to be honest.
author_association: CONTRIBUTOR
user: login: CelticMinstrel
created_at: 2024-09-14T21:28:37Z
body: Don't worry, I made the function for you. Once #9340 is merged you can rebase this and use the new function.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2024-11-13T12:26:27Z
body: I couldn't find the function in the PR.
What function were you refering to?
author_association: CONTRIBUTOR
user: login: Pentarctagon
created_at: 2024-12-06T20:47:58Z
body: @CelticMinstrel  What function were you referring to?
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-02-28T20:53:55Z
body: @CelticMinstrel 
Why this does return an error?
to_cubic returns cubic_location, in definition
```
const map_location& a = u->get_location();
cubic_location center = a.to_cubic();
```
error: ‘cubic_location’ was not declared in this scope; did you mean ‘map_location’?
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-01T11:28:42Z
body: Ok, I'm back on the track.
@soliton- 
Would code like this:
```
cubic_location center = a.to_cubic();

	// Enumerate range in cubic coordinates
	for (int dx = -n; dx <= n; ++dx) {
		for (int dy = std::max(-n, -dx-n); dy <= std::min(n, -dx+n); ++dy) {
			int dz = -dx - dy;

			// Skip positions that are too close to center
			if (std::abs(dx) + std::abs(dy) + std::abs(dz) < 2*min) {
				continue;
			}

			// Create new cubic location
			cubic_location neighbor{
				center.q + dx,
				center.r + dy,
				center.s + dz
			};

			// Convert back to map coordinates and add to tiles
			tiles.push_back(map_location::from_cubic(neighbor));
		}
	}
	```
	resolve your issue?
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-01T11:36:12Z
body: That looks like it should be a function inrange or so that returns or adds to a list of locations.

I don't know what min is but comparing to 2*min looks odd.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-01T12:19:29Z
body: > That looks like it should be a function inrange or so that returns or adds to a list of locations.

The whole code, I posted should be a function defined in mouse_events.cpp?

> I don't know what min is but comparing to 2*min looks odd.

It has purpose, this is the old function, just rewritten with from/to_cubic.
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-01T12:31:30Z
body: The function would be somewhere fitting. Probably near `cubic_location`.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-01T12:35:38Z
body: > The function would be somewhere fitting. Probably near `cubic_location`.

I can do that.
Takes location and 2 numbers and returns vector of locations that are at least n1 and most n2 distant from the center, unchecked if existing
How to name it tho?
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-01T12:39:46Z
body: My ideas would be:
get_location_ring/ just get_ring or
locations_within_distance_range
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-01T13:29:00Z
body: @soliton- Would that work?
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-01T19:11:54Z
body: Sure
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-01T19:58:15Z
body: Cool
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-01T20:03:09Z
body: After I'll have the basics done, what is the way to handle move&attack command UI?
I would put both ranged and melee weapons into the very same attack dialog.
This is very important as whaever I'll do, I belive shouldn't affect any content that doesn't use real ranged attacks, and as well shouldn't limit UI for units who have a real-ramged attack, but want to perform move&close-attack action
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-02T09:00:06Z
body: So the function is ready and working... but now I have to know what to do next
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-02T10:28:00Z
body: @soliton- @CelticMinstrel - You're the two who were involved in this UI,
(And I assume if nobody else reacts, and we three agree on something, then later nobody can come and say that he dislikes it and should be reworked from scratch)
What should the UI behave like?

author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-10T06:32:38Z
body: 
@soliton- 
(And what to highlight when you have an archer selected and hover over an enemy?)
I believe whatever I'll do, I belive shouldn't affect any content that doesn't use real ranged attacks, and as well shouldn't limit UI for units who have a real-ranged attack, but want to perform move&close-attack action.

I suggest placing both ranged and melee weapons into the very same attack dialog, so by selecting your unit and then enemy, one attack dialog would pop up, giving you chance to either attack ranged, or to move&attack.
author_association: CONTRIBUTOR
user: login: ProditorMagnus
created_at: 2025-03-10T08:28:59Z
body: When attack dialog is used, then unit moves exactly as close to the target as specific attack needs. When player desires different positioning, he can move before using dialog.
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-10T08:45:22Z
body: > When attack dialog is used, then unit moves exactly as close to the target as specific attack needs. When player desires different positioning, he can move before using dialog.

I like this approach, only I would be fan of: moves as little as possible to attack.
That makes more sence if you have archer with range 2-3 and enemy like 5 hexes away he moves only 2 tiles and then attacks.
author_association: CONTRIBUTOR
user: login: ProditorMagnus
created_at: 2025-03-10T08:48:46Z
body: That is what I said. Before each movement, check if it is possible to attack, if not, do next movement.
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-10T09:20:23Z
body: > That is what I said. Before each movement, check if it is possible to attack, if not, do next movement.

That won't work as I need to know the attacking location forehand, so I can use it in the attack, it will be precauculated (easier to do too).
I would then pass one location for every weapon to the attack dialog, so it can count with it.
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-14T08:55:04Z
body: Ok, so the current system doesn't highlight attackable enemies, only changes border highlight on adjacent tiles reachable by the attacker
... that's new, huh

(on early 1.19.3, when I worked on it, the highlight was simply adding enemies to the reachmap, thus highlighting them correctly, but possible target enemies aren't highlighted currently, I ask what to do)

...
So how am I supposed to highlight the possible targets for a ranged attack in the new GUI?
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-14T09:53:06Z
body: > So how am I supposed to highlight the possible targets for a ranged attack in the new GUI?

Where does this come from? Who or what is supposing you do something or other?

If you think some highlight is needed then surely you have some idea what that should look like?

I don't see anything special in your old screenshots either though so I don't know why you must do something special now.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-14T10:01:09Z
body: > I don't see anything special in your old screenshots either though so I don't know why you must do something special now.

It was added after the screenshot, the same way the original PR did.
Ok, it certainly isn't a thing that would require immediate attention.

author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-14T12:34:18Z
body: https://github.com/wesnoth/wesnoth/blob/master/src/mouse_events.cpp#L1528
dlg.show() keeps returning me false (and showing no dialog).
Where is the function defined? (I couldn't find anything in src/gui/dialogs)
Is the function somehow edited, but not defined for the gui2::dialogs::unit_attack class?
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-14T15:37:19Z
body: Ok, it shows that I canged 9 files in total which I definitely didn't touched, so probably pulling changes f*cked up somehow, or those are some kind of tmp files... no idea
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-14T15:38:53Z
body: ... nvm, I don't see it now
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-14T18:42:19Z
body: There's code, I forgot to erase from some files, before mering, but have you got any idea why does it keep showing me that I've changed 42 files in src/modules/lua?

author_association: CONTRIBUTOR
user: login: ProditorMagnus
created_at: 2025-03-14T19:17:51Z
body: You pushed some random commits to this branch. Remove them, rebase on master.
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-19T18:47:42Z
body: So ... can it be merged now?

author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-20T20:02:54Z
body: > Disregarding the confusing comment this looks good to me.

I'd like to just mention that in there, if you want, you can suggest how should it be.

So ... what happens now?
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-20T21:57:44Z
body: > > Disregarding the confusing comment this looks good to me.
> 
> I'd like to just mention that in there, if you want, you can suggest how should it be.
> 
> So ... what happens now?

I've asked multiple questions to understand what the comment is meant to express. You could answer them. Otherwise I cannot suggest anything since I have no idea what you're trying to say.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-21T06:26:06Z
body: > I've asked multiple questions to understand what the comment is meant to express. You could answer them. Otherwise I cannot suggest anything since I have no idea what you're trying to say.

Sorry, as I marked it as resolved, I couldn't see it.
The hex I mean (the variable named hex)
Maybe I should mention that this isn't ideal _because_ of ranged attacks?
Maybe I should move it to the function.
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-21T10:35:01Z
body: > Maybe I should mention that this isn't ideal _because_ of ranged attacks? Maybe I should move it to the function.

That would make sense to me.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-21T11:24:20Z
body: Ok
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-24T09:52:00Z
body: Since it seems difficult to get to an understandable comment I'll merge this without the comment in the next days.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-24T10:04:06Z
body: > Since it seems difficult to get to an understandable comment I'll merge this without the comment in the next days.

Cool.
Can you rephrase it then?
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-24T10:19:02Z
body: I've already rephrased it and that was ignored. I'm not really interested in wasting more time with this.
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-24T12:24:53Z
body: > I've already rephrased it and that was ignored. I'm not really interested in wasting more time with this.

I'm sorry, but I've searched throught this whole PR and have found no rephrasing suggestion. You may haven't actually posted it.
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-24T17:18:02Z
body: https://github.com/wesnoth/wesnoth/pull/9286#discussion_r2004405965
author_association: MEMBER
user: login: hladkyjakub
created_at: 2025-03-24T17:22:26Z
body: > https://github.com/wesnoth/wesnoth/pull/9286#discussion_r2004405965

Yes, after that I came with two new versions, following your advise... and I don't know what's wrong with them. (And sorry for being pointlich, but you haven't offered a rephrase)
author_association: CONTRIBUTOR
user: login: soliton-
created_at: 2025-03-24T17:43:28Z
body: This is getting ridiculous. Is your browser broken or something? I linked to the last comment in that thread there is no follow up message from you and the next commit uses the same broken english that I corrected there.

I have no idea where you offered two new versions.
author_association: MEMBER
user: login: ProditorMagnus
created_at: 2025-03-24T19:14:07Z
body: I expect ZK thought monospace meant quote and skipped reading it.
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-24T21:08:48Z
body: > I expect ZK thought monospace meant quote and skipped reading it.

Oh, ... thanks, you're 100% right.
I'm deeply sorry, Soliton. :/
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-24T21:33:36Z
body: Edited to this then... hope that'll work
// the attack dialog is shown as if the unit is standing at the hex, this may be not the best approach
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-26T17:07:11Z
body: @soliton- Is there a problem? I'd like to get it merged...
author_association: CONTRIBUTOR
user: login: hladkyjakub
created_at: 2025-03-27T20:49:06Z
body: Nice.
If someone would be interested in trying out how the basic UI works, 
you can download _Ranged Attacks Preshow_ add-on on 1.19 add-on server (use latest build).

Also,...  I want to continue work on ranged attacks UI as many aspects of it need to be improved.
What should I do/where should I do it? (should I open a discussion thread, should I continue here?)

For the further development:

1. Should there be further development of the functionality required (the non-UI part)?

2. How can we make projectile animations to travel from attackers to defenders' hexes?

3. Should a way to connect combatants with the red arrow be found? Or should a better graphic for that be made?

4. New 1.19 UI doesn't allow to highlight all possible targets, hat way should they be highlighted?
author_association: CONTRIBUTOR