399 lines
10 KiB
GDScript3
399 lines
10 KiB
GDScript3
class_name GameState
|
|
extends RefCounted
|
|
|
|
## GameState - Central game state container and rules engine
|
|
|
|
signal game_started
|
|
signal game_ended(winner_index: int)
|
|
signal card_played(card: CardInstance, player_index: int)
|
|
signal summon_cast(card: CardInstance, player_index: int)
|
|
signal card_moved(card: CardInstance, from_zone: Enums.ZoneType, to_zone: Enums.ZoneType)
|
|
signal damage_dealt(player_index: int, amount: int, cards: Array[CardInstance])
|
|
signal forward_broken(card: CardInstance)
|
|
signal attack_declared(attacker: CardInstance)
|
|
signal block_declared(blocker: CardInstance)
|
|
signal combat_resolved(attacker: CardInstance, blocker: CardInstance)
|
|
signal cp_generated(player_index: int, element: Enums.Element, amount: int)
|
|
|
|
# Players
|
|
var players: Array[Player] = []
|
|
|
|
# Turn management
|
|
var turn_manager: TurnManager
|
|
|
|
# Shared stack for abilities/summons
|
|
var stack: Zone
|
|
|
|
# Game state flags
|
|
var game_active: bool = false
|
|
var winner_index: int = -1
|
|
|
|
func _init() -> void:
|
|
turn_manager = TurnManager.new()
|
|
stack = Zone.new(Enums.ZoneType.STACK, -1)
|
|
|
|
# Connect turn manager signals
|
|
turn_manager.phase_changed.connect(_on_phase_changed)
|
|
turn_manager.turn_started.connect(_on_turn_started)
|
|
turn_manager.turn_ended.connect(_on_turn_ended)
|
|
|
|
## Initialize a new game with two players
|
|
func setup_game(deck1: Array[String], deck2: Array[String]) -> void:
|
|
# Create players
|
|
players.clear()
|
|
players.append(Player.new(0, "Player 1"))
|
|
players.append(Player.new(1, "Player 2"))
|
|
|
|
# Setup decks
|
|
players[0].setup_deck(deck1)
|
|
players[1].setup_deck(deck2)
|
|
|
|
game_active = false
|
|
winner_index = -1
|
|
|
|
## Start the game
|
|
func start_game(first_player: int = -1) -> void:
|
|
if first_player < 0:
|
|
first_player = randi() % 2
|
|
|
|
players[first_player].is_first_player = true
|
|
|
|
# Draw initial hands (5 cards each)
|
|
players[0].draw_cards(5)
|
|
players[1].draw_cards(5)
|
|
|
|
game_active = true
|
|
|
|
# Connect ability system if available
|
|
var ability_system = Engine.get_singleton("AbilitySystem")
|
|
if ability_system == null:
|
|
# Try getting from scene tree (autoload)
|
|
var tree = Engine.get_main_loop()
|
|
if tree and tree.root.has_node("AbilitySystem"):
|
|
ability_system = tree.root.get_node("AbilitySystem")
|
|
if ability_system:
|
|
ability_system.connect_to_game(self)
|
|
|
|
turn_manager.start_game(first_player)
|
|
|
|
game_started.emit()
|
|
|
|
## Get current player
|
|
func get_current_player() -> Player:
|
|
return players[turn_manager.current_player_index]
|
|
|
|
## Get opponent of current player
|
|
func get_opponent() -> Player:
|
|
return players[1 - turn_manager.current_player_index]
|
|
|
|
## Get player by index
|
|
func get_player(index: int) -> Player:
|
|
if index >= 0 and index < players.size():
|
|
return players[index]
|
|
return null
|
|
|
|
## Execute Active Phase
|
|
func execute_active_phase() -> void:
|
|
var player = get_current_player()
|
|
player.activate_all()
|
|
turn_manager.advance_phase()
|
|
|
|
## Execute Draw Phase
|
|
func execute_draw_phase() -> void:
|
|
var player = get_current_player()
|
|
var draw_count = turn_manager.get_draw_count()
|
|
|
|
var drawn = player.draw_cards(draw_count)
|
|
|
|
# Check for loss condition (can't draw)
|
|
if drawn.size() < draw_count and player.deck.is_empty():
|
|
_check_loss_conditions()
|
|
|
|
turn_manager.advance_phase()
|
|
|
|
## End Main Phase
|
|
func end_main_phase() -> void:
|
|
# Clear any unused CP
|
|
get_current_player().cp_pool.clear()
|
|
turn_manager.advance_phase()
|
|
|
|
## Play a card from hand
|
|
## Returns dictionary of CP spent, or empty dict on failure
|
|
func play_card(player_index: int, card: CardInstance) -> Dictionary:
|
|
if not game_active:
|
|
return {}
|
|
|
|
if player_index != turn_manager.current_player_index:
|
|
return {}
|
|
|
|
if not turn_manager.is_main_phase():
|
|
return {}
|
|
|
|
var player = players[player_index]
|
|
|
|
# Validate and play (returns CP spent dict)
|
|
var cp_spent = player.play_card(card)
|
|
if not cp_spent.is_empty():
|
|
card_played.emit(card, player_index)
|
|
card_moved.emit(card, Enums.ZoneType.HAND,
|
|
Enums.ZoneType.FIELD_FORWARDS if card.is_forward() else Enums.ZoneType.FIELD_BACKUPS)
|
|
return cp_spent
|
|
|
|
return {}
|
|
|
|
## Cast a summon from hand
|
|
func cast_summon(player_index: int, card: CardInstance) -> bool:
|
|
if not game_active:
|
|
return false
|
|
|
|
if player_index != turn_manager.current_player_index:
|
|
return false
|
|
|
|
if not turn_manager.is_main_phase():
|
|
return false
|
|
|
|
var player = players[player_index]
|
|
|
|
# Cast the summon (pays cost, removes from hand)
|
|
var cast_card = player.cast_summon(card)
|
|
if cast_card:
|
|
# Emit signal for effect resolution (TODO: implement effect system)
|
|
summon_cast.emit(cast_card, player_index)
|
|
|
|
# Move to break zone after casting
|
|
player.break_zone.add_card(cast_card)
|
|
card_moved.emit(cast_card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
|
return true
|
|
|
|
return false
|
|
|
|
## Discard a card for CP
|
|
func discard_for_cp(player_index: int, card: CardInstance) -> bool:
|
|
if not game_active:
|
|
return false
|
|
|
|
var player = players[player_index]
|
|
var element = card.get_element()
|
|
|
|
if player.discard_for_cp(card):
|
|
cp_generated.emit(player_index, element, 2)
|
|
card_moved.emit(card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
|
return true
|
|
|
|
return false
|
|
|
|
## Dull a backup for CP
|
|
func dull_backup_for_cp(player_index: int, card: CardInstance) -> bool:
|
|
if not game_active:
|
|
return false
|
|
|
|
var player = players[player_index]
|
|
var element = card.get_element()
|
|
|
|
if player.dull_backup_for_cp(card):
|
|
cp_generated.emit(player_index, element, 1)
|
|
return true
|
|
|
|
return false
|
|
|
|
## Start Attack Phase
|
|
func start_attack_phase() -> void:
|
|
turn_manager.start_attack_declaration()
|
|
|
|
## Declare an attack
|
|
func declare_attack(attacker: CardInstance) -> bool:
|
|
if not game_active:
|
|
return false
|
|
|
|
if not turn_manager.is_attack_phase():
|
|
return false
|
|
|
|
# Must be in DECLARATION step to declare attack
|
|
if turn_manager.attack_step != Enums.AttackStep.DECLARATION:
|
|
return false
|
|
|
|
var player = get_current_player()
|
|
|
|
if not player.field_forwards.has_card(attacker):
|
|
return false
|
|
|
|
if not attacker.can_attack():
|
|
return false
|
|
|
|
# Dull the attacker (unless Brave)
|
|
if not attacker.has_brave():
|
|
attacker.dull()
|
|
|
|
attacker.attacked_this_turn = true
|
|
|
|
# Update attack step (now returns bool for validation)
|
|
if not turn_manager.set_attacker(attacker):
|
|
# Rollback dull if state transition failed
|
|
if not attacker.has_brave():
|
|
attacker.activate()
|
|
attacker.attacked_this_turn = false
|
|
return false
|
|
|
|
attack_declared.emit(attacker)
|
|
return true
|
|
|
|
## Declare a block
|
|
func declare_block(blocker: CardInstance) -> bool:
|
|
if not game_active:
|
|
return false
|
|
|
|
if turn_manager.attack_step != Enums.AttackStep.BLOCK_DECLARATION:
|
|
return false
|
|
|
|
var opponent = get_opponent()
|
|
|
|
if blocker != null:
|
|
if not opponent.field_forwards.has_card(blocker):
|
|
return false
|
|
|
|
if not blocker.can_block():
|
|
return false
|
|
|
|
# Update attack step (now returns bool for validation)
|
|
if not turn_manager.set_blocker(blocker):
|
|
return false
|
|
|
|
if blocker:
|
|
block_declared.emit(blocker)
|
|
|
|
return true
|
|
|
|
## Skip blocking
|
|
func skip_block() -> bool:
|
|
return declare_block(null)
|
|
|
|
## Resolve combat damage
|
|
func resolve_combat() -> void:
|
|
if turn_manager.attack_step != Enums.AttackStep.DAMAGE_RESOLUTION:
|
|
return
|
|
|
|
var attacker = turn_manager.current_attacker
|
|
var blocker = turn_manager.current_blocker
|
|
var player = get_current_player()
|
|
|
|
# Validate attacker is still on field (could have been broken by ability)
|
|
if not attacker or not player.field_forwards.has_card(attacker):
|
|
turn_manager.complete_attack()
|
|
return
|
|
|
|
if blocker == null:
|
|
# Unblocked - deal 1 damage to opponent
|
|
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
|
else:
|
|
# Validate blocker is still on field
|
|
var opponent = get_opponent()
|
|
if not opponent.field_forwards.has_card(blocker):
|
|
# Blocker was removed - attack goes through as unblocked
|
|
_deal_damage_to_player(1 - turn_manager.current_player_index, 1)
|
|
else:
|
|
# Blocked - exchange damage
|
|
var attacker_power = attacker.get_power()
|
|
var blocker_power = blocker.get_power()
|
|
|
|
# Apply damage
|
|
var attacker_broken = attacker.apply_damage(blocker_power)
|
|
var blocker_broken = blocker.apply_damage(attacker_power)
|
|
|
|
# Break destroyed forwards
|
|
if attacker_broken:
|
|
_break_forward(attacker, turn_manager.current_player_index)
|
|
|
|
if blocker_broken:
|
|
_break_forward(blocker, 1 - turn_manager.current_player_index)
|
|
|
|
combat_resolved.emit(attacker, blocker)
|
|
turn_manager.complete_attack()
|
|
|
|
## End Attack Phase (no more attacks)
|
|
func end_attack_phase() -> void:
|
|
turn_manager.end_attack_phase()
|
|
|
|
## Execute End Phase
|
|
func execute_end_phase() -> void:
|
|
var player = get_current_player()
|
|
|
|
# Discard to hand limit
|
|
var discarded = player.discard_to_hand_limit()
|
|
for card in discarded:
|
|
card_moved.emit(card, Enums.ZoneType.HAND, Enums.ZoneType.BREAK)
|
|
|
|
# Cleanup
|
|
player.end_turn_cleanup()
|
|
|
|
turn_manager.advance_phase()
|
|
|
|
## Deal damage to a player
|
|
func _deal_damage_to_player(player_index: int, amount: int) -> void:
|
|
var player = players[player_index]
|
|
var damage_cards = player.take_damage(amount)
|
|
|
|
damage_dealt.emit(player_index, amount, damage_cards)
|
|
|
|
# Check for EX Bursts (simplified - would need full implementation)
|
|
for card in damage_cards:
|
|
if card.card_data and card.card_data.has_ex_burst:
|
|
# TODO: Offer EX Burst choice to player
|
|
pass
|
|
|
|
_check_loss_conditions()
|
|
|
|
## Break a forward
|
|
func _break_forward(card: CardInstance, player_index: int) -> void:
|
|
var player = players[player_index]
|
|
if player.break_card(card):
|
|
forward_broken.emit(card)
|
|
card_moved.emit(card, Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.BREAK)
|
|
|
|
## Check for game-ending conditions
|
|
func _check_loss_conditions() -> void:
|
|
for i in range(players.size()):
|
|
var player = players[i]
|
|
|
|
# Check damage
|
|
if player.has_lost():
|
|
_end_game(1 - i) # Other player wins
|
|
return
|
|
|
|
# Check deck out (can't draw when needed)
|
|
# This is checked during draw phase
|
|
|
|
## End the game
|
|
func _end_game(winner: int) -> void:
|
|
game_active = false
|
|
winner_index = winner
|
|
game_ended.emit(winner)
|
|
|
|
## Phase change handler
|
|
func _on_phase_changed(phase: Enums.TurnPhase) -> void:
|
|
match phase:
|
|
Enums.TurnPhase.ACTIVE:
|
|
execute_active_phase()
|
|
Enums.TurnPhase.DRAW:
|
|
execute_draw_phase()
|
|
Enums.TurnPhase.ATTACK:
|
|
start_attack_phase()
|
|
Enums.TurnPhase.END:
|
|
execute_end_phase()
|
|
|
|
## Turn started handler
|
|
func _on_turn_started(player_index: int, _turn_number: int) -> void:
|
|
players[player_index].start_turn()
|
|
|
|
## Turn ended handler
|
|
func _on_turn_ended(_player_index: int) -> void:
|
|
pass
|
|
|
|
func _to_string() -> String:
|
|
if not game_active:
|
|
return "[GameState: Inactive]"
|
|
return "[GameState: Turn %d, Phase: %s, Player %d]" % [
|
|
turn_manager.turn_number,
|
|
turn_manager.get_phase_string(),
|
|
turn_manager.current_player_index + 1
|
|
]
|