Skip to content

Commit 9584ae4

Browse files
author
tom
committed
feat(godot): deplacements mer et air depuis la carte
Cycle de selection des pieces sur une case, surbrillances par domaine, tests smoke QG vers Space_5.
1 parent e1b2ecb commit 9584ae4

7 files changed

Lines changed: 125 additions & 9 deletions

File tree

godot/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,13 @@ Format des entrées : `AAAA-MM-JJ HH:MM:SS` (heure locale).
2727

2828
- **File d’ordres**`%OrdersQueue` dans la sidebar (pad `O : HQ > CE`, compteur 0/5), journal `%OrdersLog` pour événements de manche
2929

30+
### Réalisé (suite)
31+
32+
- **Déplacements mer/air** — sélection pièce par reclic sur la case (cycle soldat/tank/chasseur/croiseur), surbrillance bleue mer / jaune air, tests smoke croiseur QG→`Space_5`
33+
3034
### À faire (prochaine étape critique)
3135

32-
- [ ] Déplacements mer/air depuis la carte (pas seulement terre)
36+
- [ ] Bombes H et combats détaillés (résolution legacy)
3337
- [ ] Tuiles directionnelles emboîtées (échelle Unity) si souhaité
3438
- [ ] `Battleground.unity` pour calibration pixel-perfect
3539
- [ ] Bombes H, combats détaillés, animations

godot/scenes/battle/battle.tscn

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ unique_name_in_owner = true
6060
layout_mode = 2
6161
theme_override_colors/font_color = Color(0.9, 0.9, 0.92, 1)
6262

63+
[node name="CasePiece" type="Label" parent="Sidebar/CasePanel/VBox"]
64+
unique_name_in_owner = true
65+
layout_mode = 2
66+
theme_override_colors/font_color = Color(0.55, 0.82, 0.95, 1)
67+
theme_override_font_sizes/font_size = 11
68+
text = ""
69+
6370
[node name="ReservePanel" type="PanelContainer" parent="Sidebar"]
6471
layout_mode = 2
6572

godot/scripts/battle/battle.gd

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const AiPlanner = preload("res://scripts/ai/ai_planner.gd")
1919
var _state: GameState
2020
var _selected_sector: String = ""
2121
var _selected_piece_id: int = -1
22+
var _sector_pick_index: int = 0
2223
var _planning_elapsed: int = 1
2324
var _timer_accum: float = 0.0
2425

@@ -54,6 +55,7 @@ func _process(delta: float) -> void:
5455
_state.human_camp,
5556
_selected_sector,
5657
_planning_elapsed,
58+
_selected_piece(),
5759
)
5860

5961

@@ -98,6 +100,7 @@ func _refresh_ui() -> void:
98100
_state.human_camp,
99101
_selected_sector,
100102
_planning_elapsed,
103+
_selected_piece(),
101104
)
102105
if _status:
103106
_status.text = "Phase %s | ordres %d/%d" % [
@@ -107,19 +110,50 @@ func _refresh_ui() -> void:
107110
]
108111

109112

113+
func _selected_piece() -> PieceInstance:
114+
if _selected_piece_id < 0:
115+
return null
116+
return _state.find_piece(_selected_piece_id)
117+
118+
119+
func _movable_human_on_sector(sector_id: String) -> Array[PieceInstance]:
120+
var result: Array[PieceInstance] = []
121+
for p: PieceInstance in _state.human_pieces_on_sector(sector_id):
122+
if not _state.has_piece_moved(p.id):
123+
result.append(p)
124+
result.sort_custom(func(a: PieceInstance, b: PieceInstance) -> bool:
125+
return int(a.type) < int(b.type)
126+
)
127+
return result
128+
129+
110130
func _update_move_highlights() -> void:
111131
if not _board_map.has_method("set_move_highlights"):
112132
return
113133
if _selected_piece_id < 0:
114134
_board_map.call("set_move_highlights", PackedStringArray())
135+
if _board_map.has_method("set_highlight_color"):
136+
_board_map.call("set_highlight_color", Color(0.95, 0.85, 0.35, 0.55))
115137
return
116-
var piece: PieceInstance = _state.find_piece(_selected_piece_id)
138+
var piece: PieceInstance = _selected_piece()
117139
if piece == null or piece.in_reserve:
118140
_board_map.call("set_move_highlights", PackedStringArray())
119141
return
142+
if _board_map.has_method("set_highlight_color"):
143+
_board_map.call("set_highlight_color", _highlight_color_for_piece(piece))
120144
_board_map.call("set_move_highlights", _state.destinations_for(piece))
121145

122146

147+
func _highlight_color_for_piece(piece: PieceInstance) -> Color:
148+
match GameConstants.piece_movement_domain(piece.type):
149+
GameConstants.MovementDomain.SEA:
150+
return Color(0.45, 0.75, 0.95, 0.65)
151+
GameConstants.MovementDomain.AIR:
152+
return Color(0.85, 0.9, 0.45, 0.65)
153+
_:
154+
return Color(0.95, 0.85, 0.35, 0.55)
155+
156+
123157
func _log_last_order() -> void:
124158
pass
125159

@@ -133,24 +167,43 @@ func _phase_name(phase: GameConstants.GamePhase) -> String:
133167

134168
func _select_sector(sector_id: String) -> void:
135169
if _selected_piece_id >= 0:
136-
var active: PieceInstance = _state.find_piece(_selected_piece_id)
170+
var active: PieceInstance = _selected_piece()
137171
if active != null and not active.in_reserve and sector_id != active.sector_id:
138172
var dests := _state.destinations_for(active)
139173
if sector_id in dests:
140174
_try_move_to(sector_id)
141175
return
142176

143-
_selected_sector = sector_id
144-
_selected_piece_id = -1
177+
if sector_id == _selected_sector:
178+
_cycle_movable_piece(sector_id)
179+
else:
180+
_selected_sector = sector_id
181+
_sector_pick_index = 0
182+
_select_movable_piece_by_index(sector_id, 0)
183+
145184
if _board_map.has_method("set_selected"):
146185
_board_map.call("set_selected", sector_id)
147186
_update_sector_panel()
148-
for i in range(_piece_list.get_item_count()):
149-
_on_piece_selected(i)
150-
break
151187
_refresh_ui()
152188

153189

190+
func _cycle_movable_piece(sector_id: String) -> void:
191+
var movable := _movable_human_on_sector(sector_id)
192+
if movable.size() <= 1:
193+
return
194+
_sector_pick_index = (_sector_pick_index + 1) % movable.size()
195+
_selected_piece_id = movable[_sector_pick_index].id
196+
197+
198+
func _select_movable_piece_by_index(sector_id: String, index: int) -> void:
199+
var movable := _movable_human_on_sector(sector_id)
200+
if movable.is_empty():
201+
_selected_piece_id = -1
202+
return
203+
_sector_pick_index = index % movable.size()
204+
_selected_piece_id = movable[_sector_pick_index].id
205+
206+
154207
func _try_move_to(to_sector: String) -> void:
155208
var piece: PieceInstance = _state.find_piece(_selected_piece_id)
156209
if piece == null:

godot/scripts/battle/battle_sidebar.gd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ extends Control
55
@onready var _case_title: Label = %CaseTitle
66
@onready var _case_hq: Label = %CaseHq
77
@onready var _case_stats: Label = %CaseStats
8+
@onready var _case_piece: Label = %CasePiece
89
@onready var _reserve_power: Label = %ReservePower
910
@onready var _reserve_outline: Label = %ReserveOutline
1011
@onready var _reserve_counts: Label = %ReserveCounts
@@ -18,9 +19,11 @@ func refresh(
1819
human_camp: GameConstants.Camp,
1920
selected_sector: String,
2021
planning_elapsed: int = 1,
22+
selected_piece: PieceInstance = null,
2123
) -> void:
2224
_case_hq.visible = false
2325
_case_stats.text = ""
26+
_case_piece.text = ""
2427
if selected_sector != "":
2528
var short := BoardCatalog.sector_short_label(selected_sector)
2629
_case_title.text = "Case Info — %s" % short
@@ -36,6 +39,15 @@ func refresh(
3639
lines.append("%s %d" % [key, int(counts[key])])
3740
_case_stats.text = "\n".join(lines) if not lines.is_empty() else "—"
3841

42+
if selected_piece != null and selected_sector != "" and selected_piece.sector_id == selected_sector:
43+
var domain := GameConstants.movement_domain_label(
44+
GameConstants.piece_movement_domain(selected_piece.type)
45+
)
46+
_case_piece.text = "Pièce : %s (%s) — recliquez la case pour changer" % [
47+
selected_piece.label(),
48+
domain,
49+
]
50+
3951
_reserve_power.text = "F %d" % state.camp_power(human_camp)
4052
var res: Dictionary = {}
4153
for p: PieceInstance in state.reserve_pieces(human_camp):

godot/scripts/battle/board_map.gd

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var _layout_meta: Dictionary = {}
1414
var _hits: Dictionary = {}
1515
var _selected: String = ""
1616
var _move_highlights: PackedStringArray = PackedStringArray()
17+
var _highlight_color: Color = Color(0.95, 0.85, 0.35, 0.55)
1718
var _state: GameState
1819

1920

@@ -112,6 +113,11 @@ func set_move_highlights(sectors: PackedStringArray) -> void:
112113
queue_redraw()
113114

114115

116+
func set_highlight_color(color: Color) -> void:
117+
_highlight_color = color
118+
queue_redraw()
119+
120+
115121
func _on_hit(event: InputEvent, sector_id: String) -> void:
116122
if event is InputEventMouseButton:
117123
var mb := event as InputEventMouseButton
@@ -143,7 +149,7 @@ func _draw() -> void:
143149
var hr: float = float(hd.get("r", 10)) * scale * 1.25
144150
var hcx := board.position.x + float(hd["x"]) * sx
145151
var hcy := board.position.y + float(hd["y"]) * sy
146-
draw_arc(Vector2(hcx, hcy), hr, 0.0, TAU, 32, Color(0.95, 0.85, 0.35, 0.55), 2.0)
152+
draw_arc(Vector2(hcx, hcy), hr, 0.0, TAU, 32, _highlight_color, 2.0)
147153

148154
if _selected != "" and _layout.has(_selected):
149155
var d: Dictionary = _layout[_selected] as Dictionary

godot/scripts/core/game_constants.gd

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ static func piece_movement_domain(piece: PieceType) -> MovementDomain:
7878
return MovementDomain.NONE
7979

8080

81+
static func movement_domain_label(domain: MovementDomain) -> String:
82+
match domain:
83+
MovementDomain.LAND:
84+
return "terre"
85+
MovementDomain.SEA:
86+
return "mer"
87+
MovementDomain.AIR:
88+
return "air"
89+
_:
90+
return "—"
91+
92+
8193
static func is_basic_buy(piece: PieceType) -> bool:
8294
return piece in [PieceType.SOLDIER, PieceType.RAIDER, PieceType.HUNTER, PieceType.CRUISER]
8395

godot/scripts/headless_test.gd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,28 @@ func _run_checks() -> PackedStringArray:
4848
var dests: PackedStringArray = BoardGraph.land_destinations("HQ_Green", 2)
4949
if dests.is_empty():
5050
failures.append("BoardGraph: pas de voisins HQ_Green")
51+
var cruiser: PieceInstance = null
52+
for p: PieceInstance in state.pieces:
53+
if p.camp == GameConstants.Camp.GREEN and p.type == GameConstants.PieceType.CRUISER:
54+
cruiser = p
55+
break
56+
if cruiser == null:
57+
failures.append("GameState: pas de croiseur vert")
58+
elif "Space_5" not in state.destinations_for(cruiser):
59+
failures.append("GameState: croiseur sans destination mer depuis QG")
60+
else:
61+
var err: String = state.try_move_human_piece(cruiser, "Space_5")
62+
if not err.is_empty():
63+
failures.append("GameState: déplacement mer refusé (%s)" % err)
64+
var hunter: PieceInstance = null
65+
for p: PieceInstance in state.pieces:
66+
if p.camp == GameConstants.Camp.GREEN and p.type == GameConstants.PieceType.HUNTER:
67+
hunter = p
68+
break
69+
if hunter != null and not hunter.in_reserve:
70+
var air_dests: PackedStringArray = state.destinations_for(hunter)
71+
if air_dests.is_empty():
72+
failures.append("GameState: chasseur sans destination air depuis QG")
5173
for path: String in [
5274
"res://scenes/menu/main_menu.tscn",
5375
"res://scenes/menu/game_setup.tscn",

0 commit comments

Comments
 (0)