From 762143918b761a8a9897b004b18f8df09053f4e5 Mon Sep 17 00:00:00 2001 From: hz Date: Tue, 19 May 2026 05:42:31 -0500 Subject: [PATCH] movemeent tweaks --- .../scenes/game/hex_grid_screen.gd | 86 +++++++++++++++++-- .../scenes/game/hex_grid_view.gd | 42 ++++++--- .../Services/UnitTrainingService.cs | 5 ++ 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/game/territory-autobattler/scenes/game/hex_grid_screen.gd b/game/territory-autobattler/scenes/game/hex_grid_screen.gd index 313c86f..ce55d94 100644 --- a/game/territory-autobattler/scenes/game/hex_grid_screen.gd +++ b/game/territory-autobattler/scenes/game/hex_grid_screen.gd @@ -54,6 +54,8 @@ var claim_button: Button var build_log_camp_button: Button var build_mine_button: Button var build_barracks_button: Button +var unit_action_popup: PopupPanel +var unit_action_move_button: Button var selected_q := 0 var selected_r := 0 var selected_can_claim := false @@ -67,6 +69,8 @@ var completed_archer_count := 0 var selected_archer_q := 9999 var selected_archer_r := 9999 var selected_archer_count := 0 +var pending_move_q := 9999 +var pending_move_r := 9999 var refresh_prompt_active := false var refresh_request_active := false @@ -170,9 +174,11 @@ func _build_ui() -> void: grid_view.set_anchors_preset(Control.PRESET_FULL_RECT) grid_view.tile_hovered.connect(_on_tile_hovered) grid_view.tile_selected.connect(_on_tile_selected) + grid_view.tile_action_requested.connect(_on_tile_action_requested) map_layer.add_child(grid_view) _build_map_overlays(map_layer) + _build_unit_action_menu() var bottom_bar := PanelContainer.new() bottom_bar.custom_minimum_size = Vector2(0, 46) @@ -412,6 +418,28 @@ func _build_refresh_dialog() -> void: add_child(refresh_dialog) +func _build_unit_action_menu() -> void: + unit_action_popup = PopupPanel.new() + unit_action_popup.transparent_bg = true + add_child(unit_action_popup) + + var panel := PanelContainer.new() + panel.add_theme_stylebox_override("panel", _panel_style(Color(0.06, 0.09, 0.09, 0.96), Color(0.17, 0.78, 0.83), 1, 6)) + unit_action_popup.add_child(panel) + + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 8) + margin.add_theme_constant_override("margin_right", 8) + margin.add_theme_constant_override("margin_top", 8) + margin.add_theme_constant_override("margin_bottom", 8) + panel.add_child(margin) + + unit_action_move_button = _small_button("Move") + unit_action_move_button.custom_minimum_size = Vector2(92, 34) + unit_action_move_button.pressed.connect(_confirm_unit_move) + margin.add_child(unit_action_move_button) + + func _on_tile_hovered(q: int, r: int, terrain: String) -> void: if coordinate_label: coordinate_label.text = "Hex q:%d r:%d | %s" % [q, r, terrain] @@ -440,21 +468,62 @@ func _handle_unit_selection_or_move(q: int, r: int) -> bool: var archer_count: int = grid_view.get_archer_count_at(q, r) if archer_count > 0: + if unit_action_popup: + unit_action_popup.hide() selected_archer_q = q selected_archer_r = r selected_archer_count = archer_count grid_view.set_selected_unit_tile(q, r) if coordinate_label: - coordinate_label.text = "Selected Archer squad: %d at q:%d r:%d. Select a visible non-rival tile to move." % [archer_count, q, r] - return true - - if selected_archer_count > 0: - _move_selected_archers(q, r) + coordinate_label.text = "Selected Archer squad: %d at q:%d r:%d. Right-click an adjacent visible tile for orders." % [archer_count, q, r] return true return false +func _on_tile_action_requested(q: int, r: int, _terrain: String, screen_position: Vector2) -> void: + if not grid_view: + return + if selected_archer_count <= 0: + if unit_action_popup: + unit_action_popup.hide() + if coordinate_label: + coordinate_label.text = "Select an Archer squad before giving orders." + return + if q == selected_archer_q and r == selected_archer_r: + if coordinate_label: + coordinate_label.text = "Archer squad is already on that tile." + return + if not grid_view.is_neighbor(selected_archer_q, selected_archer_r, q, r): + if coordinate_label: + coordinate_label.text = "Archers can only move to an adjacent tile." + return + if not grid_view.is_visible_non_rival_tile(q, r): + if coordinate_label: + coordinate_label.text = "Archers can only move to visible non-rival tiles." + return + + pending_move_q = q + pending_move_r = r + if coordinate_label: + coordinate_label.text = "Orders for Archer squad: q:%d r:%d." % [q, r] + if unit_action_popup: + var popup_size := Vector2i(116, 52) + var visible_rect := get_viewport().get_visible_rect() + var max_x := int(max(0.0, visible_rect.size.x - popup_size.x - 8.0)) + var max_y := int(max(0.0, visible_rect.size.y - popup_size.y - 8.0)) + var popup_position := Vector2i(clampi(int(screen_position.x), 8, max_x), clampi(int(screen_position.y), 8, max_y)) + unit_action_popup.popup(Rect2i(popup_position, popup_size)) + + +func _confirm_unit_move() -> void: + if unit_action_popup: + unit_action_popup.hide() + if pending_move_q == 9999: + return + _move_selected_archers(pending_move_q, pending_move_r) + + func _can_build_log_camp_on_selected_tile() -> bool: return _can_build_on_selected_tile("Forest") @@ -517,6 +586,10 @@ func _clear_selected_archers() -> void: selected_archer_q = 9999 selected_archer_r = 9999 selected_archer_count = 0 + pending_move_q = 9999 + pending_move_r = 9999 + if unit_action_popup: + unit_action_popup.hide() if grid_view: grid_view.clear_selected_unit_tile() @@ -961,6 +1034,9 @@ func _move_selected_archers(to_q: int, to_r: int) -> void: if to_q == selected_archer_q and to_r == selected_archer_r: coordinate_label.text = "Archer squad is already on that tile." return + if not grid_view.is_neighbor(selected_archer_q, selected_archer_r, to_q, to_r): + coordinate_label.text = "Archers can only move to an adjacent tile." + return if not grid_view.is_visible_non_rival_tile(to_q, to_r): coordinate_label.text = "Archers can only move to visible non-rival tiles." return diff --git a/game/territory-autobattler/scenes/game/hex_grid_view.gd b/game/territory-autobattler/scenes/game/hex_grid_view.gd index f3d5923..12e8f4a 100644 --- a/game/territory-autobattler/scenes/game/hex_grid_view.gd +++ b/game/territory-autobattler/scenes/game/hex_grid_view.gd @@ -2,6 +2,7 @@ extends Control signal tile_hovered(q: int, r: int, terrain: String) signal tile_selected(q: int, r: int, terrain: String) +signal tile_action_requested(q: int, r: int, terrain: String, screen_position: Vector2) const HEX_SIZE := 42.0 const ISO_Y_SCALE := 0.56 @@ -80,6 +81,14 @@ func _gui_input(event: InputEvent) -> void: var label: String = _tile_display_label(key, data) if visible_tiles.has(key) else "Unexplored" tile_selected.emit(selected_tile.x, selected_tile.y, label) queue_redraw() + elif event.button_index == MOUSE_BUTTON_RIGHT and event.pressed: + var action_tile := _closest_tile_at(event.position) + if action_tile.x != 9999: + var action_data: Dictionary = _tile_data(action_tile.x, action_tile.y) + var action_key: String = _tile_key(action_tile.x, action_tile.y) + var action_label: String = _tile_display_label(action_key, action_data) if visible_tiles.has(action_key) else "Unexplored" + tile_action_requested.emit(action_tile.x, action_tile.y, action_label, get_global_mouse_position()) + accept_event() elif event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed: zoom = clampf(zoom + 0.08, 0.65, 1.6) queue_redraw() @@ -229,7 +238,20 @@ func _draw_control_layer(control_tiles: Array, control: String, layer_scale: flo func _update_hover(mouse_position: Vector2) -> void: - var closest: Vector2i = Vector2i(9999, 9999) + var closest := _closest_tile_at(mouse_position) + + if closest != hovered_tile: + hovered_tile = closest + if hovered_tile.x != 9999: + var data: Dictionary = _tile_data(hovered_tile.x, hovered_tile.y) + var key: String = _tile_key(hovered_tile.x, hovered_tile.y) + var terrain: String = _tile_display_label(key, data) if visible_tiles.has(key) else "Unexplored" + tile_hovered.emit(hovered_tile.x, hovered_tile.y, terrain) + queue_redraw() + + +func _closest_tile_at(mouse_position: Vector2) -> Vector2i: + var closest := Vector2i(9999, 9999) var closest_distance: float = 999999.0 for tile in tiles: @@ -244,14 +266,7 @@ func _update_hover(mouse_position: Vector2) -> void: if closest_distance > HEX_SIZE * zoom: closest = Vector2i(9999, 9999) - if closest != hovered_tile: - hovered_tile = closest - if hovered_tile.x != 9999: - var data: Dictionary = _tile_data(hovered_tile.x, hovered_tile.y) - var key: String = _tile_key(hovered_tile.x, hovered_tile.y) - var terrain: String = _tile_display_label(key, data) if visible_tiles.has(key) else "Unexplored" - tile_hovered.emit(hovered_tile.x, hovered_tile.y, terrain) - queue_redraw() + return closest func _tile_center(q: int, r: int) -> Vector2: @@ -529,6 +544,13 @@ func has_owned_neighbor(q: int, r: int) -> bool: return false +func is_neighbor(q1: int, r1: int, q2: int, r2: int) -> bool: + for neighbor in _neighbor_coordinates(q1, r1): + if neighbor.x == q2 and neighbor.y == r2: + return true + return false + + func set_completed_archers(count: int) -> void: completed_archer_count = max(0, count) queue_redraw() @@ -660,7 +682,7 @@ func _archer_count_at(q: int, r: int) -> int: if int(unit.get("q", 0)) == q and int(unit.get("r", 0)) == r: count += 1 - if count == 0 and completed_archer_count > 0 and Vector2i(q, r) == _archer_station_tile(): + if unit_instances.is_empty() and count == 0 and completed_archer_count > 0 and Vector2i(q, r) == _archer_station_tile(): count = completed_archer_count return count diff --git a/microservices/LocationsApi/Services/UnitTrainingService.cs b/microservices/LocationsApi/Services/UnitTrainingService.cs index 20c4beb..6c5c0c7 100644 --- a/microservices/LocationsApi/Services/UnitTrainingService.cs +++ b/microservices/LocationsApi/Services/UnitTrainingService.cs @@ -153,6 +153,11 @@ public class UnitTrainingService throw new InvalidOperationException("Select a different destination tile."); } + if (HexDistance(request.FromQ, request.FromR, request.ToQ, request.ToR) != 1) + { + throw new InvalidOperationException("Archers can only move to adjacent tiles."); + } + if (!await CanMoveToAsync(username, request.ToQ, request.ToR)) { throw new InvalidOperationException("Archers can only move to visible non-rival tiles.");