fix: choisir une action ennemie compatible avec portée et ligne de vue (#28) #32

Merged
Reliodas merged 1 commit from feature/28-choix-action-ia into develop 2026-06-20 10:32:49 +00:00
Owner

Closes #28

L'IA ennemie considérait un monstre attaquable à partir de la portée maximale de toutes ses actions, puis déléguait à RulesEngine.resolve_multiattack — qui, pour un monstre sans multiattaque, résout toujours actions[0]. Le gobelin pouvait donc être autorisé à tirer tout en résolvant son cimeterre (première action). Cette MR introduit une sélection d'action pure, testable et cohérente avec la position courante.

Changements

  • CombatController._valid_monster_attacks(actor, target) : filtre pur des attaques du stat block réellement utilisables. Une MELEE_ATTACK n'est valide qu'au contact (distance ≤ reach_metres) ; une RANGED_ATTACK qu'à portée longue et en ligne de vue (TacticalGrid.has_line_of_sight), avec disadvantage = distance > range_normal_metres. Liste de base = entrées de multiattack (étendues via get_action) si définies, sinon actions.
  • CombatController._enemy_attack_plan(actor, target) : pour un bloc à multiattaque, toutes les entrées spatialement valides ; sinon une action — mêlée au contact de préférence, à défaut le premier tir valide.
  • can_attack(attacker, target) dépend maintenant de _valid_monster_attacks pour les monstres (au lieu de la portée max) : ligne de vue coupée ⇒ aucune attaque valide ⇒ déplacement ou pas d'attaque. Le chemin héros (portée de l'arme équipée) est inchangé ; _attacker_reach_m est simplifié au seul cas du héros.
  • _enemy_take_turn résout chaque action retenue via RulesEngine.resolve_monster_action(actor, action, hero, false, disadvantage) — le moteur applique le désavantage (dnd55e 1.17.0). Le combat abstrait (sans grille) conserve resolve_multiattack.

Critères d'acceptation

  • IA choisit une action dont le type et la portée conviennent à la position courante ;
  • le gobelin utilise le cimeterre au contact et arc court à distance ;
  • une ligne de vue bloquée interdit attaque à distance et provoque déplacement ou absence d'attaque ;
  • la portée longue applique le désavantage prévu par dnd55e aux monstres aussi ;
  • les multiattaques ne résolvent que des actions spatialement valides ;
  • tests GUT vérifient action choisie, dégâts ou événements associés, pas seulement absence de déplacement.

Tests

  • tests/unit/test_combat_controller.gd : test_enemy_picks_melee_at_contact, test_enemy_picks_ranged_at_distance, test_enemy_ranged_long_range_has_disadvantage, test_enemy_no_attack_when_line_of_sight_blocked, test_enemy_takes_turn_deals_ranged_damage_not_melee.
  • Suite GUT complète verte : 152 tests, 462 assertions (--headless --import sans SCRIPT ERROR/Parse Error, puis gut_cmdln.gd sur res://tests/).
Closes #28 L'IA ennemie considérait un monstre attaquable à partir de la portée maximale de **toutes** ses actions, puis déléguait à `RulesEngine.resolve_multiattack` — qui, pour un monstre sans multiattaque, résout toujours `actions[0]`. Le gobelin pouvait donc être autorisé à tirer tout en résolvant son cimeterre (première action). Cette MR introduit une sélection d'action pure, testable et cohérente avec la position courante. ## Changements - `CombatController._valid_monster_attacks(actor, target)` : filtre pur des attaques du stat block réellement utilisables. Une `MELEE_ATTACK` n'est valide qu'au contact (`distance ≤ reach_metres`) ; une `RANGED_ATTACK` qu'à portée longue **et** en ligne de vue (`TacticalGrid.has_line_of_sight`), avec `disadvantage = distance > range_normal_metres`. Liste de base = entrées de `multiattack` (étendues via `get_action`) si définies, sinon `actions`. - `CombatController._enemy_attack_plan(actor, target)` : pour un bloc à multiattaque, **toutes** les entrées spatialement valides ; sinon **une** action — mêlée au contact de préférence, à défaut le premier tir valide. - `can_attack(attacker, target)` dépend maintenant de `_valid_monster_attacks` pour les monstres (au lieu de la portée max) : ligne de vue coupée ⇒ aucune attaque valide ⇒ déplacement ou pas d'attaque. Le chemin héros (portée de l'arme équipée) est inchangé ; `_attacker_reach_m` est simplifié au seul cas du héros. - `_enemy_take_turn` résout chaque action retenue via `RulesEngine.resolve_monster_action(actor, action, hero, false, disadvantage)` — le moteur applique le désavantage (dnd55e 1.17.0). Le combat abstrait (sans grille) conserve `resolve_multiattack`. ## Critères d'acceptation - [x] IA choisit une action dont le type et la portée conviennent à la position courante ; - [x] le gobelin utilise le cimeterre au contact et arc court à distance ; - [x] une ligne de vue bloquée interdit attaque à distance et provoque déplacement ou absence d'attaque ; - [x] la portée longue applique le désavantage prévu par dnd55e aux monstres aussi ; - [x] les multiattaques ne résolvent que des actions spatialement valides ; - [x] tests GUT vérifient action choisie, dégâts ou événements associés, pas seulement absence de déplacement. ## Tests - `tests/unit/test_combat_controller.gd` : `test_enemy_picks_melee_at_contact`, `test_enemy_picks_ranged_at_distance`, `test_enemy_ranged_long_range_has_disadvantage`, `test_enemy_no_attack_when_line_of_sight_blocked`, `test_enemy_takes_turn_deals_ranged_damage_not_melee`. - Suite GUT complète verte : **152 tests, 462 assertions** (`--headless --import` sans `SCRIPT ERROR`/`Parse Error`, puis `gut_cmdln.gd` sur `res://tests/`).
fix: choisir une action ennemie compatible avec portée et ligne de vue (#28)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
ff8394b275
L'IA résolvait toujours actions[0] (cimeterre du gobelin) via
resolve_multiattack, même autorisée à tirer. Nouveau filtre pur
CombatController._valid_monster_attacks (mêlée au contact, distance à portée
longue + ligne de vue, désavantage au-delà de la portée normale) ; can_attack
s'y appuie pour les monstres et _enemy_take_turn résout action par action via
resolve_monster_action. Le gobelin frappe au cimeterre au contact, tire à l'arc
à distance, et ne fait rien si la ligne de vue est coupée.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reliodas merged commit 4c35eb07ad into develop 2026-06-20 10:32:49 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: jeux/beaulieu-sur-brume#32
No description provided.