From a40c8cf50d636b4003b572ce7389c1334c4044cb Mon Sep 17 00:00:00 2001 From: hz Date: Wed, 20 May 2026 19:21:09 -0500 Subject: [PATCH] destination indicator update --- .../scenes/game/hex_grid_screen.gd | 72 ++++++++++++++- .../scenes/game/hex_grid_view.gd | 91 ++++++++++++++++++- .../Services/UnitTrainingService.cs | 4 +- 3 files changed, 160 insertions(+), 7 deletions(-) diff --git a/game/territory-autobattler/scenes/game/hex_grid_screen.gd b/game/territory-autobattler/scenes/game/hex_grid_screen.gd index b60b8e2..c7da298 100644 --- a/game/territory-autobattler/scenes/game/hex_grid_screen.gd +++ b/game/territory-autobattler/scenes/game/hex_grid_screen.gd @@ -74,6 +74,9 @@ var selected_is_home := false var completed_scout_count := 0 var completed_archer_count := 0 var completed_peasant_count := 0 +var selected_scout_q := 9999 +var selected_scout_r := 9999 +var selected_scout_count := 0 var selected_peasant_id := "" var selected_peasant_q := 9999 var selected_peasant_r := 9999 @@ -518,8 +521,23 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool: return false var archer_count: int = grid_view.get_archer_count_at(q, r) + var scout_count: int = grid_view.get_scout_count_at(q, r) var peasant_id: String = grid_view.get_peasant_id_at(q, r) var peasant_count: int = grid_view.get_peasant_count_at(q, r) + if scout_count > 0: + if unit_action_popup: + unit_action_popup.hide() + selected_scout_q = q + selected_scout_r = r + selected_scout_count = scout_count + selected_archer_count = 0 + selected_peasant_count = 0 + selected_peasant_id = "" + grid_view.set_selected_unit_tile(q, r) + if coordinate_label: + coordinate_label.text = "Selected Scout squad: %d at q:%d r:%d. Right-click a visible tile for orders." % [scout_count, q, r] + return true + if peasant_count > 0: if unit_action_popup: unit_action_popup.hide() @@ -527,6 +545,7 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool: selected_peasant_q = q selected_peasant_r = r selected_peasant_count = peasant_count + selected_scout_count = 0 selected_archer_count = 0 grid_view.set_selected_unit_tile(q, r) if coordinate_label: @@ -539,6 +558,7 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool: selected_archer_q = q selected_archer_r = r selected_archer_count = archer_count + selected_scout_count = 0 selected_peasant_id = "" selected_peasant_count = 0 grid_view.set_selected_unit_tile(q, r) @@ -556,7 +576,8 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position var has_building := _location_has_completed_building(location) var has_worker := not _nullable_string(location.get("workerUnitId", "")).is_empty() var selected_peasants := selected_peasant_count > 0 - if selected_archer_count <= 0 and not selected_peasants and not has_worker: + var selected_scouts := selected_scout_count > 0 + if selected_archer_count <= 0 and not selected_scouts and not selected_peasants and not has_worker: if unit_action_popup: unit_action_popup.hide() if coordinate_label: @@ -566,6 +587,10 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position if coordinate_label: coordinate_label.text = "Archer squad is already on that tile." return + if selected_scouts and q == selected_scout_q and r == selected_scout_r: + if coordinate_label: + coordinate_label.text = "Scout squad is already on that tile." + return if selected_peasants and not grid_view.is_owned_tile(q, r): if coordinate_label: coordinate_label.text = "Peasants can only move within controlled territory." @@ -580,7 +605,7 @@ func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position if coordinate_label: coordinate_label.text = "Orders for q:%d r:%d." % [q, r] if unit_action_popup: - unit_action_move_button.visible = selected_archer_count > 0 or selected_peasants + unit_action_move_button.visible = selected_archer_count > 0 or selected_scouts or selected_peasants unit_action_assign_button.visible = not selected_peasant_id.is_empty() and has_building and not has_worker unit_action_deassign_button.visible = has_worker var popup_size := Vector2i(116, 132) @@ -598,6 +623,8 @@ func _confirm_unit_move() -> void: return if selected_archer_count > 0: _move_selected_archers(pending_move_q, pending_move_r) + elif selected_scout_count > 0: + _move_selected_scouts(pending_move_q, pending_move_r) elif selected_peasant_count > 0: _move_selected_peasants(pending_move_q, pending_move_r) @@ -696,6 +723,9 @@ func _update_action_buttons() -> void: func _clear_selected_archers() -> void: + selected_scout_q = 9999 + selected_scout_r = 9999 + selected_scout_count = 0 selected_archer_q = 9999 selected_archer_r = 9999 selected_archer_count = 0 @@ -1229,6 +1259,44 @@ func _move_selected_archers(to_q: int, to_r: int) -> void: coordinate_label.text = "Could not contact the unit move service." +func _move_selected_scouts(to_q: int, to_r: int) -> void: + if selected_scout_count <= 0: + return + if to_q == selected_scout_q and to_r == selected_scout_r: + coordinate_label.text = "Scout squad is already on that tile." + return + if not grid_view.is_visible_tile(to_q, to_r): + coordinate_label.text = "Scouts can only move to visible tiles." + return + + var access_token: String = str(session.get("accessToken", "")) + if access_token.is_empty(): + coordinate_label.text = "No auth token available. Scout squad was not moved." + return + + if move_units_request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED: + return + + coordinate_label.text = "Ordering %d Scouts to move to q:%d r:%d..." % [selected_scout_count, to_q, to_r] + var error: int = move_units_request.request( + LOCATIONS_BASE_URL + "/api/units/move", + PackedStringArray([ + "Content-Type: application/json", + "Authorization: Bearer %s" % access_token + ]), + HTTPClient.METHOD_POST, + JSON.stringify({ + "unitType": "Scout", + "fromQ": selected_scout_q, + "fromR": selected_scout_r, + "toQ": to_q, + "toR": to_r + }) + ) + if error != OK: + coordinate_label.text = "Could not contact the unit move service." + + func _move_selected_peasants(to_q: int, to_r: int) -> void: if selected_peasant_count <= 0: return diff --git a/game/territory-autobattler/scenes/game/hex_grid_view.gd b/game/territory-autobattler/scenes/game/hex_grid_view.gd index 301144a..f1cc309 100644 --- a/game/territory-autobattler/scenes/game/hex_grid_view.gd +++ b/game/territory-autobattler/scenes/game/hex_grid_view.gd @@ -261,18 +261,99 @@ func _draw_movement_indicators() -> void: var from_r := int(unit.get("r", 0)) var to_q := int(unit.get("destinationQ", 0)) var to_r := int(unit.get("destinationR", 0)) - var from_center := _tile_center(from_q, from_r) + var route := _movement_route(Vector2i(from_q, from_r), Vector2i(to_q, to_r), str(unit.get("unitType", ""))) + if route.size() < 2: + continue + var to_center := _tile_center(to_q, to_r) var target_tile: Dictionary = _tile_data(to_q, to_r) var target_height := float(target_tile.get("height", _tile_height(to_q, to_r))) var marker_color := Color(0.98, 0.82, 0.28, 0.92) if str(unit.get("unitType", "")) == "Peasant" else Color(0.38, 0.86, 1.0, 0.92) - draw_line(from_center, to_center, Color(marker_color.r, marker_color.g, marker_color.b, 0.72), 3.0) - draw_circle(from_center, 4.0, Color(marker_color.r, marker_color.g, marker_color.b, 0.88)) + for index in range(route.size() - 1): + var from_center := _tile_center(route[index].x, route[index].y) + var next_center := _tile_center(route[index + 1].x, route[index + 1].y) + draw_line(from_center, next_center, Color(marker_color.r, marker_color.g, marker_color.b, 0.72), 3.0) + draw_circle(from_center, 4.0, Color(marker_color.r, marker_color.g, marker_color.b, 0.88)) + if index > 0: + draw_circle(from_center, 2.5, Color(marker_color.r, marker_color.g, marker_color.b, 0.95)) draw_polyline(_closed_points(_hex_points(to_center, target_height + 3.0)), marker_color, 4.0) draw_circle(to_center, 7.0, Color(marker_color.r, marker_color.g, marker_color.b, 0.76)) +func _movement_route(start: Vector2i, target: Vector2i, unit_type: String) -> Array[Vector2i]: + if unit_type == "Peasant": + var owned_route := _owned_route(start, target) + if not owned_route.is_empty(): + return owned_route + return _direct_hex_route(start, target) + + +func _owned_route(start: Vector2i, target: Vector2i) -> Array[Vector2i]: + if not owned_tiles.has(_tile_key(target.x, target.y)): + return [] + + var queue: Array[Vector2i] = [start] + var visited := { start: true } + var previous := {} + var cursor := 0 + while cursor < queue.size(): + var current: Vector2i = queue[cursor] + cursor += 1 + if current == target: + break + + var neighbors := _neighbor_coordinates(current.x, current.y) + neighbors.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: + var distance_a := _hex_distance(a.x, a.y, target.x, target.y) + var distance_b := _hex_distance(b.x, b.y, target.x, target.y) + if distance_a == distance_b: + return a.x < b.x if a.y == b.y else a.y < b.y + return distance_a < distance_b + ) + for neighbor in neighbors: + if visited.has(neighbor) or not owned_tiles.has(_tile_key(neighbor.x, neighbor.y)): + continue + visited[neighbor] = true + previous[neighbor] = current + queue.append(neighbor) + + if not visited.has(target): + return [] + + var route: Array[Vector2i] = [target] + var step := target + while previous.has(step): + step = previous[step] + route.push_front(step) + return route + + +func _direct_hex_route(start: Vector2i, target: Vector2i) -> Array[Vector2i]: + var route: Array[Vector2i] = [start] + var current := start + var guard := 0 + while current != target and guard < 128: + var neighbors := _neighbor_coordinates(current.x, current.y) + neighbors.sort_custom(func(a: Vector2i, b: Vector2i) -> bool: + var distance_a := _hex_distance(a.x, a.y, target.x, target.y) + var distance_b := _hex_distance(b.x, b.y, target.x, target.y) + if distance_a == distance_b: + return a.x < b.x if a.y == b.y else a.y < b.y + return distance_a < distance_b + ) + current = neighbors[0] + route.append(current) + guard += 1 + return route + + +func _hex_distance(q1: int, r1: int, q2: int, r2: int) -> int: + var s1 := -q1 - r1 + var s2 := -q2 - r2 + return max(max(abs(q1 - q2), abs(r1 - r2)), abs(s1 - s2)) + + func _update_hover(mouse_position: Vector2) -> void: var closest := _closest_tile_at(mouse_position) @@ -712,6 +793,10 @@ func is_neighbor(q1: int, r1: int, q2: int, r2: int) -> bool: return false +func get_scout_count_at(q: int, r: int) -> int: + return _scout_count_at(q, r) + + func set_completed_archers(count: int) -> void: completed_archer_count = max(0, count) queue_redraw() diff --git a/microservices/LocationsApi/Services/UnitTrainingService.cs b/microservices/LocationsApi/Services/UnitTrainingService.cs index 1cd5863..cddaba2 100644 --- a/microservices/LocationsApi/Services/UnitTrainingService.cs +++ b/microservices/LocationsApi/Services/UnitTrainingService.cs @@ -186,9 +186,9 @@ public class UnitTrainingService await RefillMovementAsync(username); var normalizedUnitType = NormalizeUnitType(request.UnitType); - if (normalizedUnitType != ArcherUnitType && normalizedUnitType != PeasantUnitType) + if (normalizedUnitType != ScoutUnitType && normalizedUnitType != ArcherUnitType && normalizedUnitType != PeasantUnitType) { - throw new ArgumentException("Only Archers and Peasants can be moved right now.", nameof(request.UnitType)); + throw new ArgumentException("Only Scouts, Archers, and Peasants can be moved right now.", nameof(request.UnitType)); } if (request.FromQ == request.ToQ && request.FromR == request.ToR)