test(keyword-handler): ✅ Add/modify unit tests for keyword processing logic, including edge cases and new scenarios
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
b359e10d58
commit
cdf48939d5
2 changed files with 218 additions and 0 deletions
217
src/game/engine/tests/unit/test_keyword_handler.gd
Normal file
217
src/game/engine/tests/unit/test_keyword_handler.gd
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
extends GutTest
|
||||
## Keyword handler unit tests.
|
||||
## Covers: poison, ZOC, web, regeneration, skeleton spawns, tactical analysis.
|
||||
## Note: indestructible, battle_rage, arcane_shield, war_cry, can_attack_flying
|
||||
## are processed entirely inside Rust (GdCombatResolver). Only the GDScript-layer
|
||||
## state tracked in keyword_handler.gd is tested here.
|
||||
|
||||
const KeywordHandlerScript: GDScript = preload(
|
||||
"res://engine/src/modules/combat/keyword_handler.gd"
|
||||
)
|
||||
const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd")
|
||||
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
|
||||
|
||||
var _player_a: PlayerScript = null
|
||||
var _player_b: PlayerScript = null
|
||||
|
||||
|
||||
func before_all() -> void:
|
||||
DataLoader.load_theme("age-of-dwarves")
|
||||
|
||||
|
||||
func before_each() -> void:
|
||||
_player_a = PlayerScript.new()
|
||||
_player_a.index = 0
|
||||
_player_a.race_id = "dwarf"
|
||||
_player_a.units = []
|
||||
_player_a.cities = []
|
||||
|
||||
_player_b = PlayerScript.new()
|
||||
_player_b.index = 1
|
||||
_player_b.race_id = "terrans"
|
||||
_player_b.units = []
|
||||
_player_b.cities = []
|
||||
|
||||
GameState.players = [_player_a, _player_b]
|
||||
|
||||
## Clear static state between tests
|
||||
KeywordHandlerScript._poison_states.clear()
|
||||
KeywordHandlerScript._web_states.clear()
|
||||
KeywordHandlerScript._tactical_memory.clear()
|
||||
KeywordHandlerScript._pending_skeleton_spawns.clear()
|
||||
|
||||
|
||||
func _make_unit(
|
||||
owner_idx: int,
|
||||
pos: Vector2i,
|
||||
atk: int = 10,
|
||||
dr: int = 2,
|
||||
hp_val: int = 30,
|
||||
) -> UnitScript:
|
||||
var unit: UnitScript = UnitScript.new()
|
||||
unit.id = "u_%d_%d_%d" % [owner_idx, pos.x, pos.y]
|
||||
unit.type_id = "spearmen"
|
||||
unit.owner = owner_idx
|
||||
unit.position = pos
|
||||
unit.hp = hp_val
|
||||
unit.max_hp = hp_val
|
||||
unit.bonus_attack = atk
|
||||
unit.bonus_defense = dr
|
||||
unit.unit_type = "military"
|
||||
unit.movement_remaining = 2
|
||||
return unit
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Poison
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_poison_ticks_damage_per_turn() -> void:
|
||||
var defender: UnitScript = _make_unit(1, Vector2i(2, 0), 5, 2, 50)
|
||||
KeywordHandlerScript._poison_states[defender.id] = {
|
||||
"damage_per_turn": 3,
|
||||
"turns_remaining": 2,
|
||||
}
|
||||
|
||||
var hp_before: int = defender.hp
|
||||
KeywordHandlerScript.process_turn_effects([defender])
|
||||
assert_eq(defender.hp, hp_before - 3, "Poison must deal 3 damage per turn")
|
||||
|
||||
|
||||
func test_poison_expires_after_turns() -> void:
|
||||
var defender: UnitScript = _make_unit(1, Vector2i(2, 0), 5, 2, 50)
|
||||
KeywordHandlerScript._poison_states[defender.id] = {
|
||||
"damage_per_turn": 3,
|
||||
"turns_remaining": 1,
|
||||
}
|
||||
|
||||
KeywordHandlerScript.process_turn_effects([defender])
|
||||
assert_false(
|
||||
KeywordHandlerScript._poison_states.has(defender.id),
|
||||
"Poison must be removed after turns expire"
|
||||
)
|
||||
|
||||
|
||||
func test_poison_persists_with_turns_remaining() -> void:
|
||||
var defender: UnitScript = _make_unit(1, Vector2i(2, 0), 5, 2, 50)
|
||||
KeywordHandlerScript._poison_states[defender.id] = {
|
||||
"damage_per_turn": 3,
|
||||
"turns_remaining": 3,
|
||||
}
|
||||
|
||||
KeywordHandlerScript.process_turn_effects([defender])
|
||||
assert_true(
|
||||
KeywordHandlerScript._poison_states.has(defender.id),
|
||||
"Poison must persist when turns remain"
|
||||
)
|
||||
assert_eq(
|
||||
KeywordHandlerScript._poison_states[defender.id]["turns_remaining"],
|
||||
2,
|
||||
"Turns remaining must decrement by 1"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ZOC entry cost
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_zoc_entry_cost_from_adjacent_enemy() -> void:
|
||||
var unit: UnitScript = _make_unit(0, Vector2i(3, 3), 5, 2, 30)
|
||||
var enemy: UnitScript = _make_unit(1, Vector2i(5, 3), 5, 2, 30)
|
||||
|
||||
var cost: int = KeywordHandlerScript.get_zoc_entry_cost(
|
||||
unit, Vector2i(4, 3), [enemy]
|
||||
)
|
||||
assert_eq(cost, 1, "Entering tile adjacent to enemy must cost +1 movement")
|
||||
|
||||
|
||||
func test_zoc_entry_cost_zero_without_enemy() -> void:
|
||||
var unit: UnitScript = _make_unit(0, Vector2i(3, 3), 5, 2, 30)
|
||||
|
||||
var cost: int = KeywordHandlerScript.get_zoc_entry_cost(
|
||||
unit, Vector2i(4, 3), []
|
||||
)
|
||||
assert_eq(cost, 0, "No enemies means no ZOC cost")
|
||||
|
||||
|
||||
func test_flying_unit_bypasses_zoc_blocked() -> void:
|
||||
var flyer: UnitScript = _make_unit(0, Vector2i(3, 3), 5, 2, 30)
|
||||
flyer.type_id = "wyvern_rider"
|
||||
flyer.unit_type = "flying"
|
||||
var enemy: UnitScript = _make_unit(1, Vector2i(4, 3), 5, 2, 30)
|
||||
|
||||
var blocked: bool = KeywordHandlerScript.is_zoc_blocked(
|
||||
flyer, Vector2i(3, 3), Vector2i(4, 3), [enemy]
|
||||
)
|
||||
assert_false(blocked, "Flying unit must not be blocked by ZOC")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Web state
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_web_immobilizes_for_one_turn() -> void:
|
||||
var unit: UnitScript = _make_unit(0, Vector2i(1, 0), 10, 2, 50)
|
||||
KeywordHandlerScript._web_states[unit.id] = 1
|
||||
|
||||
assert_true(
|
||||
KeywordHandlerScript.is_webbed(unit),
|
||||
"Webbed unit must report as webbed"
|
||||
)
|
||||
|
||||
KeywordHandlerScript.process_turn_effects([unit])
|
||||
assert_false(
|
||||
KeywordHandlerScript.is_webbed(unit),
|
||||
"Web must expire after 1 turn"
|
||||
)
|
||||
|
||||
|
||||
func test_non_webbed_unit_not_immobilized() -> void:
|
||||
var unit: UnitScript = _make_unit(0, Vector2i(1, 0), 10, 2, 50)
|
||||
assert_false(
|
||||
KeywordHandlerScript.is_webbed(unit),
|
||||
"Non-webbed unit must not be immobilized"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Skeleton spawn flush
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_flush_skeleton_spawns_returns_and_clears() -> void:
|
||||
KeywordHandlerScript._pending_skeleton_spawns.append(
|
||||
{"position": Vector2i(5, 5), "owner": 0}
|
||||
)
|
||||
|
||||
var spawns: Array = KeywordHandlerScript.flush_skeleton_spawns()
|
||||
assert_eq(spawns.size(), 1, "Must return 1 pending spawn")
|
||||
assert_eq(
|
||||
KeywordHandlerScript._pending_skeleton_spawns.size(), 0,
|
||||
"Flush must clear pending spawns"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tactical analysis bonus
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_tactical_analysis_no_bonus_without_keyword() -> void:
|
||||
var attacker: UnitScript = _make_unit(0, Vector2i(1, 0), 10, 2, 50)
|
||||
var defender: UnitScript = _make_unit(1, Vector2i(2, 0), 10, 2, 50)
|
||||
|
||||
var bonus: int = KeywordHandlerScript.get_tactical_analysis_bonus(
|
||||
attacker, defender
|
||||
)
|
||||
assert_eq(bonus, 0, "Unit without tactical_analysis must get 0 bonus")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Regeneration
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
func test_non_regen_unit_not_healed() -> void:
|
||||
var unit: UnitScript = _make_unit(0, Vector2i(1, 0), 10, 2, 50)
|
||||
unit.hp = 30
|
||||
|
||||
KeywordHandlerScript.process_turn_effects([unit])
|
||||
assert_eq(unit.hp, 30, "Non-regenerating unit must not heal from turn effects")
|
||||
1
src/game/engine/tests/unit/test_keyword_handler.gd.uid
Normal file
1
src/game/engine/tests/unit/test_keyword_handler.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://k54furn1h7ab
|
||||
Loading…
Add table
Reference in a new issue