feature updates

This commit is contained in:
2026-02-02 16:28:53 -05:00
parent bf9aa3fa23
commit 44c06530ac
83 changed files with 282641 additions and 11251 deletions

View File

@@ -0,0 +1,751 @@
extends GutTest
## Tests for EffectResolver effect execution
## Covers core effects: DAMAGE, POWER_MOD, DULL, ACTIVATE, DRAW, BREAK, RETURN
var resolver: EffectResolver
func before_each() -> void:
resolver = EffectResolver.new()
# =============================================================================
# DAMAGE EFFECT TESTS
# =============================================================================
func test_resolve_damage_basic() -> void:
var card = _create_mock_forward(7000)
var game_state = _create_mock_game_state_with_forward(card, 0)
var effect = {"type": "DAMAGE", "amount": 3000}
resolver.resolve(effect, null, [card], game_state)
assert_eq(card.damage_received, 3000, "Card should have received 3000 damage")
func test_resolve_damage_breaks_forward() -> void:
var card = _create_mock_forward(5000)
var game_state = _create_mock_game_state_with_forward(card, 0)
var effect = {"type": "DAMAGE", "amount": 5000}
resolver.resolve(effect, null, [card], game_state)
assert_true(game_state.players[0].break_zone.has_card(card), "Forward should be in break zone")
assert_false(game_state.players[0].field_forwards.has_card(card), "Forward should not be on field")
func test_resolve_damage_exact_power_breaks() -> void:
var card = _create_mock_forward(6000)
var game_state = _create_mock_game_state_with_forward(card, 0)
var effect = {"type": "DAMAGE", "amount": 6000}
resolver.resolve(effect, null, [card], game_state)
assert_true(game_state.players[0].break_zone.has_card(card), "Forward should break when damage equals power")
func test_resolve_damage_multiple_targets() -> void:
var card1 = _create_mock_forward(8000)
var card2 = _create_mock_forward(5000)
var game_state = _create_mock_game_state()
game_state.players[0].field_forwards.add_card(card1)
game_state.players[0].field_forwards.add_card(card2)
card1.controller_index = 0
card2.controller_index = 0
var effect = {"type": "DAMAGE", "amount": 3000}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_eq(card1.damage_received, 3000, "First card should have 3000 damage")
assert_eq(card2.damage_received, 3000, "Second card should have 3000 damage")
func test_resolve_damage_ignores_non_forwards() -> void:
var backup = _create_mock_backup()
var game_state = _create_mock_game_state()
var effect = {"type": "DAMAGE", "amount": 5000}
resolver.resolve(effect, null, [backup], game_state)
assert_eq(backup.damage_received, 0, "Backup should not receive damage")
# =============================================================================
# POWER_MOD EFFECT TESTS
# =============================================================================
func test_resolve_power_mod_positive() -> void:
var card = _create_mock_forward(5000)
var game_state = _create_mock_game_state()
var effect = {"type": "POWER_MOD", "amount": 2000}
resolver.resolve(effect, null, [card], game_state)
assert_eq(card.power_modifiers.size(), 1, "Should have one power modifier")
assert_eq(card.power_modifiers[0], 2000, "Power modifier should be +2000")
func test_resolve_power_mod_negative() -> void:
var card = _create_mock_forward(7000)
var game_state = _create_mock_game_state()
var effect = {"type": "POWER_MOD", "amount": -3000}
resolver.resolve(effect, null, [card], game_state)
assert_eq(card.power_modifiers[0], -3000, "Power modifier should be -3000")
func test_resolve_power_mod_multiple_targets() -> void:
var card1 = _create_mock_forward(5000)
var card2 = _create_mock_forward(6000)
var game_state = _create_mock_game_state()
var effect = {"type": "POWER_MOD", "amount": 1000}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_eq(card1.power_modifiers[0], 1000, "First card should have +1000")
assert_eq(card2.power_modifiers[0], 1000, "Second card should have +1000")
func test_resolve_power_mod_applies_to_source_if_no_targets() -> void:
var source = _create_mock_forward(5000)
var game_state = _create_mock_game_state()
var effect = {"type": "POWER_MOD", "amount": 3000}
resolver.resolve(effect, source, [], game_state)
assert_eq(source.power_modifiers[0], 3000, "Source should get modifier when no targets")
# =============================================================================
# DULL EFFECT TESTS
# =============================================================================
func test_resolve_dull_single_target() -> void:
var card = _create_mock_forward(5000)
card.activate() # Ensure card starts active
var game_state = _create_mock_game_state()
var effect = {"type": "DULL"}
assert_true(card.is_active(), "Card should start active")
resolver.resolve(effect, null, [card], game_state)
assert_true(card.is_dull(), "Card should be dulled")
func test_resolve_dull_multiple_targets() -> void:
var card1 = _create_mock_forward(5000)
var card2 = _create_mock_forward(6000)
card1.activate()
card2.activate()
var game_state = _create_mock_game_state()
var effect = {"type": "DULL"}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_true(card1.is_dull(), "First card should be dulled")
assert_true(card2.is_dull(), "Second card should be dulled")
func test_resolve_dull_already_dull_card() -> void:
var card = _create_mock_forward(5000)
card.dull() # Already dull
var game_state = _create_mock_game_state()
var effect = {"type": "DULL"}
resolver.resolve(effect, null, [card], game_state)
assert_true(card.is_dull(), "Card should remain dulled")
# =============================================================================
# ACTIVATE EFFECT TESTS
# =============================================================================
func test_resolve_activate_single_target() -> void:
var card = _create_mock_forward(5000)
card.dull() # Start dull
var game_state = _create_mock_game_state()
var effect = {"type": "ACTIVATE"}
assert_true(card.is_dull(), "Card should start dull")
resolver.resolve(effect, null, [card], game_state)
assert_true(card.is_active(), "Card should be activated")
func test_resolve_activate_multiple_targets() -> void:
var card1 = _create_mock_forward(5000)
var card2 = _create_mock_forward(6000)
card1.dull()
card2.dull()
var game_state = _create_mock_game_state()
var effect = {"type": "ACTIVATE"}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_true(card1.is_active(), "First card should be active")
assert_true(card2.is_active(), "Second card should be active")
func test_resolve_activate_already_active_card() -> void:
var card = _create_mock_forward(5000)
card.activate() # Already active
var game_state = _create_mock_game_state()
var effect = {"type": "ACTIVATE"}
resolver.resolve(effect, null, [card], game_state)
assert_true(card.is_active(), "Card should remain active")
# =============================================================================
# DRAW EFFECT TESTS
# =============================================================================
func test_resolve_draw_single_card() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var game_state = _create_mock_game_state_with_deck(5)
var effect = {"type": "DRAW", "amount": 1}
var initial_deck_size = game_state.players[0].deck.get_count()
var initial_hand_size = game_state.players[0].hand.get_count()
resolver.resolve(effect, source, [], game_state)
assert_eq(game_state.players[0].deck.get_count(), initial_deck_size - 1, "Deck should have 1 less card")
assert_eq(game_state.players[0].hand.get_count(), initial_hand_size + 1, "Hand should have 1 more card")
func test_resolve_draw_multiple_cards() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var game_state = _create_mock_game_state_with_deck(10)
var effect = {"type": "DRAW", "amount": 3}
var initial_deck_size = game_state.players[0].deck.get_count()
resolver.resolve(effect, source, [], game_state)
assert_eq(game_state.players[0].deck.get_count(), initial_deck_size - 3, "Deck should have 3 fewer cards")
func test_resolve_draw_opponent() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var game_state = _create_mock_game_state_with_deck(5)
# Add cards to opponent's deck too
for i in range(5):
var deck_card = _create_mock_forward(1000)
game_state.players[1].deck.add_card(deck_card)
var effect = {"type": "DRAW", "amount": 1, "target": {"type": "OPPONENT"}}
var initial_opp_deck = game_state.players[1].deck.get_count()
var initial_opp_hand = game_state.players[1].hand.get_count()
resolver.resolve(effect, source, [], game_state)
assert_eq(game_state.players[1].deck.get_count(), initial_opp_deck - 1, "Opponent deck should have 1 less card")
assert_eq(game_state.players[1].hand.get_count(), initial_opp_hand + 1, "Opponent hand should have 1 more card")
# =============================================================================
# BREAK EFFECT TESTS
# =============================================================================
func test_resolve_break_single_target() -> void:
var card = _create_mock_forward(7000)
var game_state = _create_mock_game_state_with_forward(card, 0)
var effect = {"type": "BREAK"}
assert_true(game_state.players[0].field_forwards.has_card(card), "Card should start on field")
resolver.resolve(effect, null, [card], game_state)
assert_false(game_state.players[0].field_forwards.has_card(card), "Card should not be on field")
assert_true(game_state.players[0].break_zone.has_card(card), "Card should be in break zone")
func test_resolve_break_multiple_targets() -> void:
var card1 = _create_mock_forward(5000)
var card2 = _create_mock_forward(6000)
var game_state = _create_mock_game_state()
game_state.players[0].field_forwards.add_card(card1)
game_state.players[0].field_forwards.add_card(card2)
card1.controller_index = 0
card2.controller_index = 0
var effect = {"type": "BREAK"}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_true(game_state.players[0].break_zone.has_card(card1), "First card should be in break zone")
assert_true(game_state.players[0].break_zone.has_card(card2), "Second card should be in break zone")
func test_resolve_break_backup() -> void:
var backup = _create_mock_backup()
var game_state = _create_mock_game_state()
game_state.players[0].field_backups.add_card(backup)
backup.controller_index = 0
var effect = {"type": "BREAK"}
resolver.resolve(effect, null, [backup], game_state)
assert_true(game_state.players[0].break_zone.has_card(backup), "Backup should be in break zone")
# =============================================================================
# RETURN EFFECT TESTS
# =============================================================================
func test_resolve_return_forward_to_hand() -> void:
var card = _create_mock_forward(5000)
var game_state = _create_mock_game_state_with_forward(card, 0)
var effect = {"type": "RETURN"}
resolver.resolve(effect, null, [card], game_state)
assert_false(game_state.players[0].field_forwards.has_card(card), "Card should not be on field")
assert_true(game_state.players[0].hand.has_card(card), "Card should be in hand")
func test_resolve_return_backup_to_hand() -> void:
var backup = _create_mock_backup()
var game_state = _create_mock_game_state()
game_state.players[0].field_backups.add_card(backup)
backup.controller_index = 0
backup.owner_index = 0
var effect = {"type": "RETURN"}
resolver.resolve(effect, null, [backup], game_state)
assert_false(game_state.players[0].field_backups.has_card(backup), "Backup should not be on field")
assert_true(game_state.players[0].hand.has_card(backup), "Backup should be in hand")
func test_resolve_return_multiple_targets() -> void:
var card1 = _create_mock_forward(5000)
var card2 = _create_mock_forward(6000)
var game_state = _create_mock_game_state()
game_state.players[0].field_forwards.add_card(card1)
game_state.players[0].field_forwards.add_card(card2)
card1.controller_index = 0
card1.owner_index = 0
card2.controller_index = 0
card2.owner_index = 0
var effect = {"type": "RETURN"}
resolver.resolve(effect, null, [card1, card2], game_state)
assert_true(game_state.players[0].hand.has_card(card1), "First card should be in hand")
assert_true(game_state.players[0].hand.has_card(card2), "Second card should be in hand")
# =============================================================================
# SCALING_EFFECT TESTS
# =============================================================================
func test_resolve_scaling_effect_by_forwards() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(10000)
target.controller_index = 1
var game_state = _create_mock_game_state()
# Add 3 forwards to controller's field
for i in range(3):
var forward = _create_mock_forward(4000)
forward.controller_index = 0
game_state.players[0].field_forwards.add_card(forward)
# Add target to opponent's field
game_state.players[1].field_forwards.add_card(target)
var effect = {
"type": "SCALING_EFFECT",
"base_effect": {"type": "DAMAGE"},
"scale_by": "FORWARDS_CONTROLLED",
"multiplier": 1000,
"scale_filter": {}
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 3000, "Should deal 3000 damage (3 forwards * 1000)")
func test_resolve_scaling_effect_by_backups() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(10000)
target.controller_index = 1
var game_state = _create_mock_game_state()
# Add 4 backups to controller's field
for i in range(4):
var backup = _create_mock_backup()
backup.controller_index = 0
game_state.players[0].field_backups.add_card(backup)
game_state.players[1].field_forwards.add_card(target)
var effect = {
"type": "SCALING_EFFECT",
"base_effect": {"type": "DAMAGE"},
"scale_by": "BACKUPS_CONTROLLED",
"multiplier": 500,
"scale_filter": {}
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 2000, "Should deal 2000 damage (4 backups * 500)")
func test_resolve_scaling_effect_with_filter() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(10000)
target.controller_index = 1
var game_state = _create_mock_game_state()
# Add 2 Fire forwards and 2 Ice forwards
for i in range(2):
var fire_forward = _create_mock_forward_with_element(Enums.Element.FIRE)
fire_forward.controller_index = 0
game_state.players[0].field_forwards.add_card(fire_forward)
for i in range(2):
var ice_forward = _create_mock_forward_with_element(Enums.Element.ICE)
ice_forward.controller_index = 0
game_state.players[0].field_forwards.add_card(ice_forward)
game_state.players[1].field_forwards.add_card(target)
var effect = {
"type": "SCALING_EFFECT",
"base_effect": {"type": "DAMAGE"},
"scale_by": "FORWARDS_CONTROLLED",
"multiplier": 2000,
"scale_filter": {"element": "FIRE"} # Only count Fire forwards
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 4000, "Should deal 4000 damage (2 Fire forwards * 2000)")
func test_resolve_scaling_effect_zero_count() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(10000)
target.controller_index = 1
var game_state = _create_mock_game_state()
# No forwards on controller's field
game_state.players[1].field_forwards.add_card(target)
var effect = {
"type": "SCALING_EFFECT",
"base_effect": {"type": "DAMAGE"},
"scale_by": "FORWARDS_CONTROLLED",
"multiplier": 1000,
"scale_filter": {}
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 0, "Should deal 0 damage (0 forwards * 1000)")
func test_resolve_scaling_effect_power_mod() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var game_state = _create_mock_game_state()
# Add 2 backups
for i in range(2):
var backup = _create_mock_backup()
backup.controller_index = 0
game_state.players[0].field_backups.add_card(backup)
var effect = {
"type": "SCALING_EFFECT",
"base_effect": {"type": "POWER_MOD"},
"scale_by": "BACKUPS_CONTROLLED",
"multiplier": 1000,
"scale_filter": {}
}
resolver.resolve(effect, source, [source], game_state)
assert_eq(source.power_modifiers[0], 2000, "Should get +2000 power (2 backups * 1000)")
# =============================================================================
# CONDITIONAL EFFECT TESTS
# =============================================================================
func test_resolve_conditional_then_branch() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(7000)
var game_state = _create_mock_game_state()
game_state.players[0].damage = 5 # 5 damage points
# Setup ConditionChecker mock
resolver.condition_checker = MockConditionChecker.new(true)
var effect = {
"type": "CONDITIONAL",
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 3},
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
"else_effects": []
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 5000, "Should execute then_effects branch")
func test_resolve_conditional_else_branch() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(7000)
var game_state = _create_mock_game_state()
game_state.players[0].damage = 1 # Only 1 damage
# Setup ConditionChecker mock that returns false
resolver.condition_checker = MockConditionChecker.new(false)
var effect = {
"type": "CONDITIONAL",
"condition": {"type": "DAMAGE_RECEIVED", "comparison": "GTE", "value": 5},
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
"else_effects": [{"type": "DAMAGE", "amount": 1000}]
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 1000, "Should execute else_effects branch")
func test_resolve_conditional_no_else_branch() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(7000)
var game_state = _create_mock_game_state()
# Setup ConditionChecker mock that returns false
resolver.condition_checker = MockConditionChecker.new(false)
var effect = {
"type": "CONDITIONAL",
"condition": {"type": "SOME_CONDITION"},
"then_effects": [{"type": "DAMAGE", "amount": 5000}],
"else_effects": [] # No else branch
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 0, "Should do nothing when condition false and no else")
func test_resolve_conditional_multiple_then_effects() -> void:
var source = _create_mock_forward(5000)
source.controller_index = 0
var target = _create_mock_forward(7000)
var game_state = _create_mock_game_state()
resolver.condition_checker = MockConditionChecker.new(true)
var effect = {
"type": "CONDITIONAL",
"condition": {"type": "ALWAYS_TRUE"},
"then_effects": [
{"type": "DAMAGE", "amount": 2000},
{"type": "POWER_MOD", "amount": -1000}
],
"else_effects": []
}
resolver.resolve(effect, source, [target], game_state)
assert_eq(target.damage_received, 2000, "Should apply damage")
assert_eq(target.power_modifiers[0], -1000, "Should apply power mod")
# =============================================================================
# EFFECT_COMPLETED SIGNAL TEST
# =============================================================================
func test_effect_completed_signal_emitted() -> void:
var card = _create_mock_forward(5000)
var game_state = _create_mock_game_state()
var effect = {"type": "DULL"}
var signal_received = false
var received_effect = {}
var received_targets = []
resolver.effect_completed.connect(func(e, t):
signal_received = true
received_effect = e
received_targets = t
)
resolver.resolve(effect, null, [card], game_state)
assert_true(signal_received, "effect_completed signal should be emitted")
assert_eq(received_effect.type, "DULL", "Signal should contain effect")
assert_eq(received_targets.size(), 1, "Signal should contain targets")
# =============================================================================
# COMBINED DAMAGE AND BREAK TEST
# =============================================================================
func test_damage_accumulates_before_break() -> void:
var card = _create_mock_forward(10000)
var game_state = _create_mock_game_state_with_forward(card, 0)
# Apply 4000 damage (shouldn't break)
resolver.resolve({"type": "DAMAGE", "amount": 4000}, null, [card], game_state)
assert_eq(card.damage_received, 4000, "Should have 4000 damage")
assert_true(game_state.players[0].field_forwards.has_card(card), "Should still be on field")
# Apply 6000 more (total 10000, should break)
resolver.resolve({"type": "DAMAGE", "amount": 6000}, null, [card], game_state)
assert_true(game_state.players[0].break_zone.has_card(card), "Should be broken with 10000 total damage")
# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
func _create_mock_forward(power: int) -> CardInstance:
var card = CardInstance.new()
var data = CardDatabase.CardData.new()
data.type = Enums.CardType.FORWARD
data.power = power
data.cost = 3
data.name = "Test Forward"
data.elements = [Enums.Element.FIRE]
card.card_data = data
card.current_power = power
return card
func _create_mock_backup() -> CardInstance:
var card = CardInstance.new()
var data = CardDatabase.CardData.new()
data.type = Enums.CardType.BACKUP
data.cost = 2
data.name = "Test Backup"
data.elements = [Enums.Element.WATER]
card.card_data = data
return card
func _create_mock_forward_with_element(element: Enums.Element) -> CardInstance:
var card = _create_mock_forward(5000)
card.card_data.elements = [element]
return card
func _create_mock_game_state() -> MockGameState:
return MockGameState.new()
func _create_mock_game_state_with_forward(card: CardInstance, player_index: int) -> MockGameState:
var gs = MockGameState.new()
gs.players[player_index].field_forwards.add_card(card)
card.controller_index = player_index
card.owner_index = player_index
return gs
func _create_mock_game_state_with_deck(card_count: int) -> MockGameState:
var gs = MockGameState.new()
for i in range(card_count):
var deck_card = _create_mock_forward(1000 + i * 1000)
gs.players[0].deck.add_card(deck_card)
return gs
# =============================================================================
# MOCK CLASSES
# =============================================================================
class MockGameState:
var players: Array
func _init():
players = [MockPlayer.new(0), MockPlayer.new(1)]
func get_player(index: int):
if index >= 0 and index < players.size():
return players[index]
return null
class MockPlayer:
var player_index: int
var damage: int = 0
var deck: Zone
var hand: Zone
var field_forwards: Zone
var field_backups: Zone
var break_zone: Zone
func _init(index: int = 0):
player_index = index
deck = Zone.new(Enums.ZoneType.DECK, index)
hand = Zone.new(Enums.ZoneType.HAND, index)
field_forwards = Zone.new(Enums.ZoneType.FIELD, index)
field_backups = Zone.new(Enums.ZoneType.FIELD, index)
break_zone = Zone.new(Enums.ZoneType.BREAK_ZONE, index)
func draw_cards(count: int) -> Array:
var drawn: Array = []
for i in range(count):
var card = deck.pop_top_card()
if card:
hand.add_card(card)
drawn.append(card)
return drawn
func break_card(card: CardInstance) -> bool:
var removed = false
if field_forwards.has_card(card):
field_forwards.remove_card(card)
removed = true
elif field_backups.has_card(card):
field_backups.remove_card(card)
removed = true
if removed:
break_zone.add_card(card)
return true
return false
class MockConditionChecker:
var _return_value: bool
func _init(return_value: bool = true):
_return_value = return_value
func evaluate(_condition: Dictionary, _context: Dictionary) -> bool:
return _return_value