823 lines
26 KiB
GDScript
823 lines
26 KiB
GDScript
extends Node3D
|
|
|
|
## Main - Root scene that coordinates the game
|
|
|
|
# Components
|
|
var table_setup: TableSetup
|
|
var game_ui: GameUI
|
|
var hand_display: HandDisplay
|
|
var hand_layer: CanvasLayer
|
|
var action_log: ActionLog
|
|
var choice_modal: ChoiceModal
|
|
|
|
# Player damage displays
|
|
var damage_displays: Array[DamageDisplay] = []
|
|
|
|
# Deck configurations (set by GameController before game starts)
|
|
var player1_deck: Array = []
|
|
var player2_deck: Array = []
|
|
|
|
# AI settings (set by GameController before game starts)
|
|
var is_vs_ai: bool = false
|
|
var ai_difficulty: int = AIStrategy.Difficulty.NORMAL
|
|
var ai_controller: AIController = null
|
|
var ai_player_index: int = 1 # AI is always Player 2
|
|
|
|
# Online game settings (set by GameController before game starts)
|
|
var is_online_game: bool = false
|
|
var online_game_data: Dictionary = {}
|
|
var local_player_index: int = 0
|
|
var online_game_id: String = ""
|
|
var opponent_info: Dictionary = {}
|
|
var turn_timer_label: Label = null
|
|
|
|
func _ready() -> void:
|
|
_setup_table()
|
|
_setup_ui()
|
|
_connect_signals()
|
|
|
|
# Start game when ready
|
|
if GameManager.is_initialized:
|
|
_start_game()
|
|
else:
|
|
GameManager.game_ready.connect(_start_game)
|
|
|
|
func _setup_table() -> void:
|
|
table_setup = TableSetup.new()
|
|
add_child(table_setup)
|
|
|
|
# Connect table signals
|
|
table_setup.card_clicked.connect(_on_table_card_clicked)
|
|
|
|
func _setup_ui() -> void:
|
|
# Main game UI overlay (has its own CanvasLayer)
|
|
game_ui = GameUI.new()
|
|
game_ui.layer = 10 # Base UI layer
|
|
add_child(game_ui)
|
|
|
|
# Action log (collapsible panel on right side)
|
|
var log_layer = CanvasLayer.new()
|
|
log_layer.layer = 12 # Above other UI
|
|
add_child(log_layer)
|
|
|
|
action_log = ActionLog.new()
|
|
log_layer.add_child(action_log)
|
|
# Position on right side of screen
|
|
action_log.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
|
|
action_log.offset_left = -290
|
|
action_log.offset_right = -10
|
|
action_log.offset_top = -200
|
|
action_log.offset_bottom = 200
|
|
|
|
# Connect undo signal
|
|
action_log.undo_requested.connect(_on_undo_requested)
|
|
|
|
# Connect to GameManager undo signals
|
|
GameManager.undo_available_changed.connect(_on_undo_available_changed)
|
|
|
|
# Ensure ActionLog connects to GameManager after it's in the tree
|
|
action_log.call_deferred("_connect_game_manager_signals")
|
|
|
|
# Hand display needs its own CanvasLayer to render on top of 3D
|
|
hand_layer = CanvasLayer.new()
|
|
hand_layer.layer = 11 # Above the game UI
|
|
add_child(hand_layer)
|
|
|
|
# Container for hand at bottom of screen - must fill the viewport
|
|
var hand_container = Control.new()
|
|
hand_layer.add_child(hand_container)
|
|
hand_container.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
hand_container.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
# Hand display positioned at bottom - use explicit positioning
|
|
hand_display = HandDisplay.new()
|
|
hand_container.add_child(hand_display)
|
|
|
|
# Position at bottom of screen with explicit coordinates
|
|
# We'll update position in _process or use a deferred call after container is sized
|
|
call_deferred("_position_hand_display")
|
|
|
|
hand_display.card_action_requested.connect(_on_hand_card_action)
|
|
hand_display.card_hovered.connect(_on_hand_card_hovered)
|
|
hand_display.card_unhovered.connect(_on_hand_card_unhovered)
|
|
hand_display.card_selected.connect(_on_hand_card_selected)
|
|
hand_display.hand_minimized_changed.connect(_on_hand_minimized_changed)
|
|
|
|
# Create damage displays (positioned by 3D camera overlay later)
|
|
for i in range(2):
|
|
var damage_display = DamageDisplay.new()
|
|
damage_displays.append(damage_display)
|
|
|
|
# Choice modal for multi-modal ability choices (layer 200 - highest priority)
|
|
choice_modal = ChoiceModal.new()
|
|
add_child(choice_modal)
|
|
|
|
# Connect choice modal to AbilitySystem autoload
|
|
if AbilitySystem:
|
|
AbilitySystem.choice_modal = choice_modal
|
|
|
|
func _position_hand_display() -> void:
|
|
var viewport = get_viewport()
|
|
if viewport:
|
|
# get_visible_rect() gives the coordinate space for 2D content
|
|
# With canvas_items + expand, this is the expanded design space
|
|
var vp_size = viewport.get_visible_rect().size
|
|
var is_min = hand_display and hand_display.is_minimized
|
|
|
|
var card_height = 273.0
|
|
if is_min:
|
|
# Minimized: push cards down so only the top portion peeks up
|
|
var peek_height = 65.0 # How much of the card top is visible
|
|
hand_display.position = Vector2(10, vp_size.y - peek_height)
|
|
hand_display.size = Vector2(vp_size.x - 20, card_height + 25.0)
|
|
else:
|
|
# Full hand: cards at the bottom of the screen
|
|
var hand_height = card_height + 25.0 # Extra space for hover lift
|
|
var bottom_offset = 10.0
|
|
hand_display.position = Vector2(10, vp_size.y - bottom_offset - card_height)
|
|
hand_display.size = Vector2(vp_size.x - 20, hand_height)
|
|
|
|
if not viewport.size_changed.is_connected(_on_viewport_resized):
|
|
viewport.size_changed.connect(_on_viewport_resized)
|
|
|
|
func _on_viewport_resized() -> void:
|
|
var viewport = get_viewport()
|
|
if viewport and hand_display:
|
|
_position_hand_display()
|
|
|
|
func _on_hand_minimized_changed(_minimized: bool) -> void:
|
|
_position_hand_display()
|
|
|
|
func _connect_signals() -> void:
|
|
# GameManager signals
|
|
GameManager.game_started.connect(_on_game_started)
|
|
GameManager.game_ended.connect(_on_game_ended)
|
|
GameManager.turn_changed.connect(_on_turn_changed)
|
|
GameManager.phase_changed.connect(_on_phase_changed)
|
|
GameManager.damage_dealt.connect(_on_damage_dealt)
|
|
|
|
# Field card action signal (deferred to ensure game_ui is ready)
|
|
call_deferred("_connect_field_card_signals")
|
|
|
|
# Connect attack signal for AI blocking (deferred to ensure game_state exists)
|
|
call_deferred("_connect_attack_signal")
|
|
|
|
|
|
# ======= ONLINE GAME SETUP =======
|
|
|
|
func setup_online_game(game_data: Dictionary) -> void:
|
|
is_online_game = true
|
|
online_game_data = game_data
|
|
online_game_id = game_data.get("game_id", "")
|
|
local_player_index = game_data.get("your_player_index", 0)
|
|
opponent_info = game_data.get("opponent", {})
|
|
|
|
print("Setting up online game: ", online_game_id)
|
|
print("Local player index: ", local_player_index)
|
|
print("Opponent: ", opponent_info.get("username", "Unknown"))
|
|
|
|
# Connect network signals
|
|
_connect_network_signals()
|
|
|
|
# Create turn timer UI
|
|
_create_turn_timer_ui()
|
|
|
|
# Create opponent info UI
|
|
_create_opponent_info_ui()
|
|
|
|
|
|
func _connect_network_signals() -> void:
|
|
if not NetworkManager:
|
|
return
|
|
|
|
# Game events
|
|
if not NetworkManager.opponent_action_received.is_connected(_on_opponent_action):
|
|
NetworkManager.opponent_action_received.connect(_on_opponent_action)
|
|
|
|
if not NetworkManager.turn_timer_update.is_connected(_on_turn_timer_update):
|
|
NetworkManager.turn_timer_update.connect(_on_turn_timer_update)
|
|
|
|
if not NetworkManager.phase_changed.is_connected(_on_network_phase_changed):
|
|
NetworkManager.phase_changed.connect(_on_network_phase_changed)
|
|
|
|
if not NetworkManager.action_confirmed.is_connected(_on_action_confirmed):
|
|
NetworkManager.action_confirmed.connect(_on_action_confirmed)
|
|
|
|
if not NetworkManager.action_failed.is_connected(_on_action_failed):
|
|
NetworkManager.action_failed.connect(_on_action_failed)
|
|
|
|
if not NetworkManager.opponent_disconnected.is_connected(_on_opponent_disconnected):
|
|
NetworkManager.opponent_disconnected.connect(_on_opponent_disconnected)
|
|
|
|
if not NetworkManager.opponent_reconnected.is_connected(_on_opponent_reconnected):
|
|
NetworkManager.opponent_reconnected.connect(_on_opponent_reconnected)
|
|
|
|
if not NetworkManager.game_state_sync.is_connected(_on_game_state_sync):
|
|
NetworkManager.game_state_sync.connect(_on_game_state_sync)
|
|
|
|
|
|
func _disconnect_network_signals() -> void:
|
|
if not NetworkManager:
|
|
return
|
|
|
|
if NetworkManager.opponent_action_received.is_connected(_on_opponent_action):
|
|
NetworkManager.opponent_action_received.disconnect(_on_opponent_action)
|
|
|
|
if NetworkManager.turn_timer_update.is_connected(_on_turn_timer_update):
|
|
NetworkManager.turn_timer_update.disconnect(_on_turn_timer_update)
|
|
|
|
if NetworkManager.phase_changed.is_connected(_on_network_phase_changed):
|
|
NetworkManager.phase_changed.disconnect(_on_network_phase_changed)
|
|
|
|
if NetworkManager.action_confirmed.is_connected(_on_action_confirmed):
|
|
NetworkManager.action_confirmed.disconnect(_on_action_confirmed)
|
|
|
|
if NetworkManager.action_failed.is_connected(_on_action_failed):
|
|
NetworkManager.action_failed.disconnect(_on_action_failed)
|
|
|
|
if NetworkManager.opponent_disconnected.is_connected(_on_opponent_disconnected):
|
|
NetworkManager.opponent_disconnected.disconnect(_on_opponent_disconnected)
|
|
|
|
if NetworkManager.opponent_reconnected.is_connected(_on_opponent_reconnected):
|
|
NetworkManager.opponent_reconnected.disconnect(_on_opponent_reconnected)
|
|
|
|
if NetworkManager.game_state_sync.is_connected(_on_game_state_sync):
|
|
NetworkManager.game_state_sync.disconnect(_on_game_state_sync)
|
|
|
|
|
|
func _create_turn_timer_ui() -> void:
|
|
# Create turn timer label
|
|
turn_timer_label = Label.new()
|
|
turn_timer_label.text = "2:00"
|
|
turn_timer_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
turn_timer_label.add_theme_font_size_override("font_size", 28)
|
|
turn_timer_label.add_theme_color_override("font_color", Color.WHITE)
|
|
|
|
# Position at top center
|
|
turn_timer_label.set_anchors_preset(Control.PRESET_CENTER_TOP)
|
|
turn_timer_label.offset_top = 10
|
|
turn_timer_label.offset_bottom = 50
|
|
turn_timer_label.offset_left = -50
|
|
turn_timer_label.offset_right = 50
|
|
|
|
if game_ui:
|
|
game_ui.add_child(turn_timer_label)
|
|
|
|
|
|
func _create_opponent_info_ui() -> void:
|
|
# Create opponent info label (next to timer)
|
|
var opponent_label = Label.new()
|
|
opponent_label.name = "OpponentInfo"
|
|
opponent_label.text = "vs %s (ELO: %d)" % [
|
|
opponent_info.get("username", "Unknown"),
|
|
opponent_info.get("elo", 1000)
|
|
]
|
|
opponent_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
opponent_label.add_theme_font_size_override("font_size", 18)
|
|
opponent_label.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8))
|
|
|
|
# Position below timer
|
|
opponent_label.set_anchors_preset(Control.PRESET_CENTER_TOP)
|
|
opponent_label.offset_top = 45
|
|
opponent_label.offset_bottom = 70
|
|
opponent_label.offset_left = -150
|
|
opponent_label.offset_right = 150
|
|
|
|
if game_ui:
|
|
game_ui.add_child(opponent_label)
|
|
|
|
|
|
# ======= ONLINE GAME HELPERS =======
|
|
|
|
func _is_local_player_turn() -> bool:
|
|
if not is_online_game:
|
|
return true
|
|
if not GameManager.game_state:
|
|
return true
|
|
return GameManager.game_state.turn_manager.current_player_index == local_player_index
|
|
|
|
|
|
func _can_perform_local_action() -> bool:
|
|
if not is_online_game:
|
|
return true
|
|
return _is_local_player_turn()
|
|
|
|
|
|
# ======= NETWORK EVENT HANDLERS =======
|
|
|
|
func _on_opponent_action(action_data: Dictionary) -> void:
|
|
var action_type = action_data.get("action_type", "")
|
|
var payload = action_data.get("payload", {})
|
|
|
|
print("Received opponent action: ", action_type)
|
|
|
|
match action_type:
|
|
"play_card":
|
|
_apply_opponent_play_card(payload)
|
|
"attack":
|
|
_apply_opponent_attack(payload)
|
|
"block":
|
|
_apply_opponent_block(payload)
|
|
"pass":
|
|
_apply_opponent_pass()
|
|
"discard_cp":
|
|
_apply_opponent_discard_cp(payload)
|
|
"dull_backup_cp":
|
|
_apply_opponent_dull_backup_cp(payload)
|
|
"attack_resolved":
|
|
_apply_opponent_attack_resolved()
|
|
_:
|
|
print("Unknown opponent action: ", action_type)
|
|
|
|
# Update visuals after opponent action
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
|
|
func _apply_opponent_play_card(payload: Dictionary) -> void:
|
|
var card_instance_id = payload.get("card_instance_id", 0)
|
|
# Find the card in opponent's hand and play it
|
|
# For online, we trust the server - the opponent's hand is hidden anyway
|
|
# Just sync visuals when server confirms
|
|
print("Opponent played card: ", card_instance_id)
|
|
|
|
|
|
func _apply_opponent_attack(payload: Dictionary) -> void:
|
|
var attacker_instance_id = payload.get("attacker_instance_id", 0)
|
|
print("Opponent declared attack with card: ", attacker_instance_id)
|
|
# Find attacker and apply attack declaration
|
|
# The server will handle phase changes
|
|
|
|
|
|
func _apply_opponent_block(payload: Dictionary) -> void:
|
|
var blocker_instance_id = payload.get("blocker_instance_id", null)
|
|
if blocker_instance_id == null:
|
|
print("Opponent chose not to block")
|
|
else:
|
|
print("Opponent declared block with card: ", blocker_instance_id)
|
|
|
|
|
|
func _apply_opponent_pass() -> void:
|
|
print("Opponent passed priority")
|
|
# Server handles phase advancement
|
|
|
|
|
|
func _apply_opponent_discard_cp(payload: Dictionary) -> void:
|
|
var card_instance_id = payload.get("card_instance_id", 0)
|
|
print("Opponent discarded card for CP: ", card_instance_id)
|
|
|
|
|
|
func _apply_opponent_dull_backup_cp(payload: Dictionary) -> void:
|
|
var card_instance_id = payload.get("card_instance_id", 0)
|
|
print("Opponent dulled backup for CP: ", card_instance_id)
|
|
|
|
|
|
func _apply_opponent_attack_resolved() -> void:
|
|
print("Attack resolved")
|
|
|
|
|
|
func _on_turn_timer_update(seconds_remaining: int) -> void:
|
|
if turn_timer_label:
|
|
var minutes = seconds_remaining / 60
|
|
var secs = seconds_remaining % 60
|
|
turn_timer_label.text = "%d:%02d" % [minutes, secs]
|
|
|
|
# Color based on time remaining
|
|
if seconds_remaining <= 10:
|
|
turn_timer_label.add_theme_color_override("font_color", Color.RED)
|
|
elif seconds_remaining <= 30:
|
|
turn_timer_label.add_theme_color_override("font_color", Color.YELLOW)
|
|
else:
|
|
turn_timer_label.add_theme_color_override("font_color", Color.WHITE)
|
|
|
|
|
|
func _on_network_phase_changed(phase_data: Dictionary) -> void:
|
|
var phase = phase_data.get("phase", 0)
|
|
var current_player_index = phase_data.get("current_player_index", 0)
|
|
var turn_number = phase_data.get("turn_number", 1)
|
|
|
|
# Validate player index bounds
|
|
if current_player_index < 0 or current_player_index > 1:
|
|
push_error("Invalid player index from server: %d" % current_player_index)
|
|
return
|
|
|
|
print("Network phase changed: phase=", phase, " player=", current_player_index, " turn=", turn_number)
|
|
|
|
# Update local game state to match server
|
|
if GameManager.game_state:
|
|
GameManager.game_state.turn_manager.current_player_index = current_player_index
|
|
GameManager.game_state.turn_manager.turn_number = turn_number
|
|
# Phase enum values should match between server and client
|
|
GameManager.game_state.turn_manager.current_phase = phase
|
|
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
# Switch camera to current player
|
|
if table_setup:
|
|
table_setup.switch_camera_to_player(current_player_index)
|
|
|
|
|
|
func _on_action_confirmed(action_type: String) -> void:
|
|
print("Action confirmed by server: ", action_type)
|
|
|
|
|
|
func _on_action_failed(action_type: String, error: String) -> void:
|
|
print("Action failed: ", action_type, " - ", error)
|
|
game_ui.show_message("Action failed: " + error)
|
|
|
|
|
|
func _on_opponent_disconnected(reconnect_timeout: int) -> void:
|
|
game_ui.show_message("Opponent disconnected. Waiting %d seconds for reconnect..." % reconnect_timeout)
|
|
|
|
|
|
func _on_opponent_reconnected() -> void:
|
|
game_ui.show_message("Opponent reconnected!")
|
|
# Hide the message after a moment
|
|
await get_tree().create_timer(2.0).timeout
|
|
game_ui.hide_message()
|
|
|
|
|
|
func _on_game_state_sync(state: Dictionary) -> void:
|
|
print("Received game state sync: ", state)
|
|
# This is for reconnection - sync local state with server
|
|
var current_player_index = state.get("current_player_index", 0)
|
|
var current_phase = state.get("current_phase", 0)
|
|
var turn_number = state.get("turn_number", 1)
|
|
|
|
# Validate player index bounds
|
|
if current_player_index < 0 or current_player_index > 1:
|
|
push_error("Invalid player index in game state sync: %d" % current_player_index)
|
|
return
|
|
|
|
if GameManager.game_state:
|
|
GameManager.game_state.turn_manager.current_player_index = current_player_index
|
|
GameManager.game_state.turn_manager.current_phase = current_phase
|
|
GameManager.game_state.turn_manager.turn_number = turn_number
|
|
|
|
# Sync timer display from server state
|
|
var timer_seconds = state.get("turn_timer_seconds", 120)
|
|
if turn_timer_label:
|
|
var minutes = timer_seconds / 60
|
|
var secs = timer_seconds % 60
|
|
turn_timer_label.text = "%d:%02d" % [minutes, secs]
|
|
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
|
|
func _connect_attack_signal() -> void:
|
|
# Wait for game_state to be available
|
|
if GameManager.game_state:
|
|
GameManager.game_state.attack_declared.connect(_on_attack_declared)
|
|
|
|
|
|
func _on_attack_declared(attacker: CardInstance) -> void:
|
|
# If human attacks and AI is the defender, AI needs to decide on blocking
|
|
if is_vs_ai and ai_controller:
|
|
var current_player_index = GameManager.game_state.turn_manager.current_player_index
|
|
# AI is always player index 1, so if current player is 0 (human), AI needs to block
|
|
if current_player_index != ai_player_index:
|
|
# AI needs to make a block decision
|
|
call_deferred("_process_ai_block", attacker)
|
|
|
|
|
|
func _process_ai_block(attacker: CardInstance) -> void:
|
|
if ai_controller and not ai_controller.is_processing:
|
|
ai_controller.process_block_decision(attacker)
|
|
|
|
func _start_game() -> void:
|
|
GameManager.start_new_game(player1_deck, player2_deck)
|
|
|
|
# Setup AI controller if playing vs AI
|
|
if is_vs_ai:
|
|
_setup_ai_controller()
|
|
|
|
# Force an update of visuals after a frame to ensure everything is ready
|
|
call_deferred("_force_initial_update")
|
|
|
|
|
|
func _setup_ai_controller() -> void:
|
|
ai_controller = AIController.new()
|
|
add_child(ai_controller)
|
|
ai_controller.setup(ai_player_index, ai_difficulty, GameManager)
|
|
ai_controller.set_game_state(GameManager.game_state)
|
|
|
|
# Connect AI signals
|
|
ai_controller.ai_thinking.connect(_on_ai_thinking)
|
|
ai_controller.ai_action_completed.connect(_on_ai_action_completed)
|
|
|
|
|
|
func _on_ai_thinking(_player_index: int) -> void:
|
|
game_ui.show_message("AI is thinking...")
|
|
|
|
|
|
func _on_ai_action_completed() -> void:
|
|
game_ui.hide_message()
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
# Check if we need to continue AI processing
|
|
if _is_ai_turn():
|
|
call_deferred("_process_ai_turn")
|
|
|
|
|
|
func _is_ai_turn() -> bool:
|
|
if not is_vs_ai or not GameManager.game_state:
|
|
return false
|
|
return GameManager.game_state.turn_manager.current_player_index == ai_player_index
|
|
|
|
|
|
func _process_ai_turn() -> void:
|
|
if _is_ai_turn() and ai_controller and not ai_controller.is_processing:
|
|
ai_controller.process_turn()
|
|
|
|
func _force_initial_update() -> void:
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
func _on_game_started() -> void:
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
|
|
# Set initial camera to first player's perspective
|
|
if GameManager.game_state and table_setup:
|
|
var player_index = GameManager.game_state.turn_manager.current_player_index
|
|
table_setup.switch_camera_to_player(player_index)
|
|
|
|
func _on_game_ended(winner_name: String) -> void:
|
|
game_ui.show_message(winner_name + " wins the game!")
|
|
|
|
# For online games, report game end to server
|
|
if is_online_game and GameManager.game_state:
|
|
var winner_index = -1
|
|
for i in range(2):
|
|
var player = GameManager.game_state.get_player(i)
|
|
if player and player.player_name == winner_name:
|
|
winner_index = i
|
|
break
|
|
|
|
if winner_index != -1:
|
|
# Determine winner user ID based on index
|
|
# Local player is either index 0 or 1, with opponent being the other
|
|
var winner_user_id = ""
|
|
if winner_index == local_player_index:
|
|
winner_user_id = NetworkManager.current_user.get("id", "")
|
|
# Server will determine actual winner from both clients reporting
|
|
|
|
var reason = "damage" # Could be "deck_out" if deck is empty
|
|
var losing_player = GameManager.game_state.get_player(1 - winner_index)
|
|
if losing_player and losing_player.deck.is_empty():
|
|
reason = "deck_out"
|
|
|
|
NetworkManager.send_report_game_end(winner_user_id, reason)
|
|
|
|
# Disconnect network signals
|
|
if is_online_game:
|
|
_disconnect_network_signals()
|
|
|
|
func _on_turn_changed(_player_name: String, _turn_number: int) -> void:
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
# Rotate camera to face the current player's mat
|
|
if GameManager.game_state and table_setup:
|
|
var player_index = GameManager.game_state.turn_manager.current_player_index
|
|
table_setup.switch_camera_to_player(player_index)
|
|
|
|
# If AI's turn, start AI processing after a brief delay
|
|
if _is_ai_turn():
|
|
# Defer to allow visuals to update first
|
|
call_deferred("_process_ai_turn")
|
|
|
|
func _on_phase_changed(_phase_name: String) -> void:
|
|
_update_playable_highlights()
|
|
_update_cp_display()
|
|
# Refresh hand after draw phase completes (hand updates on entering main phase)
|
|
_update_hand_display()
|
|
|
|
# If AI's turn and we're in a decision phase, continue AI processing
|
|
if _is_ai_turn():
|
|
call_deferred("_process_ai_turn")
|
|
|
|
func _on_damage_dealt(player_name: String, _amount: int) -> void:
|
|
# Find player index
|
|
if GameManager.game_state:
|
|
for i in range(2):
|
|
var player = GameManager.game_state.get_player(i)
|
|
if player and player.player_name == player_name:
|
|
if i < damage_displays.size():
|
|
damage_displays[i].set_damage(player.damage_zone.get_count())
|
|
|
|
func _sync_visuals() -> void:
|
|
if GameManager.game_state and table_setup:
|
|
table_setup.sync_with_game_state(GameManager.game_state)
|
|
|
|
func _update_hand_display() -> void:
|
|
if not GameManager.game_state:
|
|
return
|
|
|
|
# Show current player's hand
|
|
var current_player = GameManager.get_current_player()
|
|
if current_player:
|
|
var cards = current_player.hand.get_cards()
|
|
hand_display.update_hand(cards)
|
|
|
|
func _update_cp_display() -> void:
|
|
if not GameManager.game_state:
|
|
return
|
|
|
|
var current_player = GameManager.get_current_player()
|
|
if current_player:
|
|
game_ui.update_cp_display(current_player.cp_pool)
|
|
|
|
func _update_playable_highlights() -> void:
|
|
if not GameManager.game_state:
|
|
return
|
|
|
|
var phase = GameManager.get_current_phase()
|
|
var player = GameManager.get_current_player()
|
|
|
|
if not player:
|
|
hand_display.clear_highlights()
|
|
return
|
|
|
|
match phase:
|
|
Enums.TurnPhase.MAIN_1, Enums.TurnPhase.MAIN_2:
|
|
# Highlight cards that can be played
|
|
hand_display.highlight_playable(func(card: CardInstance) -> bool:
|
|
return player.cp_pool.can_afford_card(card.card_data)
|
|
)
|
|
_:
|
|
hand_display.clear_highlights()
|
|
|
|
func _on_hand_card_action(card: CardInstance, action: String) -> void:
|
|
# Block input if not local player's turn in online game
|
|
if is_online_game and not _is_local_player_turn():
|
|
game_ui.show_message("Not your turn!")
|
|
await get_tree().create_timer(1.0).timeout
|
|
game_ui.hide_message()
|
|
return
|
|
|
|
match action:
|
|
"play":
|
|
# Try to play the card
|
|
if is_online_game:
|
|
NetworkManager.send_play_card(card.instance_id)
|
|
GameManager.try_play_card(card)
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
"discard_cp":
|
|
# Discard for CP
|
|
if is_online_game:
|
|
NetworkManager.send_discard_for_cp(card.instance_id)
|
|
GameManager.discard_card_for_cp(card)
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
|
|
"view":
|
|
# Show detailed card view
|
|
game_ui.show_card_detail(card)
|
|
|
|
func _on_hand_card_hovered(_card: CardInstance) -> void:
|
|
# Hand cards use the selection panel for detail view, not the GameUI hover preview
|
|
# So we intentionally do nothing here - no hover preview for hand cards
|
|
pass
|
|
|
|
func _on_hand_card_unhovered() -> void:
|
|
game_ui.hide_card_detail()
|
|
|
|
func _on_hand_card_selected(_card: CardInstance) -> void:
|
|
# Close field card panel if open
|
|
game_ui.hide_field_card_selection()
|
|
# Ensure any stale hover preview is hidden
|
|
game_ui.hide_card_detail()
|
|
|
|
func _on_undo_requested() -> void:
|
|
if GameManager.undo_last_action():
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
_update_playable_highlights()
|
|
# Restore input mode based on current phase
|
|
GameManager.restore_input_mode_for_phase()
|
|
|
|
func _on_undo_available_changed(available: bool) -> void:
|
|
if action_log:
|
|
action_log.set_undo_available(available)
|
|
|
|
func _on_table_card_clicked(card: CardInstance, zone_type: Enums.ZoneType, player_index: int) -> void:
|
|
var input_mode = GameManager.input_mode
|
|
|
|
# Handle special input modes first (these take priority)
|
|
match input_mode:
|
|
GameManager.InputMode.SELECT_CP_SOURCE:
|
|
if zone_type == Enums.ZoneType.FIELD_BACKUPS:
|
|
if player_index == GameManager.game_state.turn_manager.current_player_index:
|
|
# Block if not local player's turn in online game
|
|
if is_online_game and not _is_local_player_turn():
|
|
return
|
|
if is_online_game:
|
|
NetworkManager.send_dull_backup_for_cp(card.instance_id)
|
|
GameManager.dull_backup_for_cp(card)
|
|
_sync_visuals()
|
|
_update_cp_display()
|
|
return
|
|
|
|
GameManager.InputMode.SELECT_ATTACKER:
|
|
if zone_type == Enums.ZoneType.FIELD_FORWARDS:
|
|
if player_index == GameManager.game_state.turn_manager.current_player_index:
|
|
# Block if not local player's turn in online game
|
|
if is_online_game and not _is_local_player_turn():
|
|
return
|
|
if is_online_game:
|
|
NetworkManager.send_attack(card.instance_id)
|
|
GameManager.declare_attack(card)
|
|
_sync_visuals()
|
|
return
|
|
|
|
GameManager.InputMode.SELECT_BLOCKER:
|
|
if zone_type == Enums.ZoneType.FIELD_FORWARDS:
|
|
var opponent_index = 1 - GameManager.game_state.turn_manager.current_player_index
|
|
if player_index == opponent_index:
|
|
# In online games, only the defending player (non-active) can block
|
|
# Check if we are the defender
|
|
if is_online_game and local_player_index == GameManager.game_state.turn_manager.current_player_index:
|
|
# We are the attacker, can't block
|
|
return
|
|
if is_online_game:
|
|
NetworkManager.send_block(card.instance_id)
|
|
GameManager.declare_block(card)
|
|
_sync_visuals()
|
|
return
|
|
|
|
# Close hand selection panel if open
|
|
hand_display.deselect()
|
|
# Show field card selection panel with card image and actions
|
|
game_ui.show_field_card_selection(card, zone_type, player_index)
|
|
|
|
func _connect_field_card_signals() -> void:
|
|
if game_ui:
|
|
game_ui.field_card_action_requested.connect(_on_field_card_action)
|
|
|
|
func _on_field_card_action(card: CardInstance, zone_type: Enums.ZoneType, player_index: int, action: String) -> void:
|
|
# Block input if not local player's turn in online game
|
|
if is_online_game and not _is_local_player_turn():
|
|
game_ui.show_message("Not your turn!")
|
|
return
|
|
|
|
match action:
|
|
"dull_cp":
|
|
if is_online_game:
|
|
NetworkManager.send_dull_backup_for_cp(card.instance_id)
|
|
GameManager.dull_backup_for_cp(card)
|
|
_sync_visuals()
|
|
_update_cp_display()
|
|
"attack":
|
|
if is_online_game:
|
|
NetworkManager.send_attack(card.instance_id)
|
|
GameManager.declare_attack(card)
|
|
_sync_visuals()
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
# Keyboard shortcuts
|
|
if event is InputEventKey and event.pressed:
|
|
# Ctrl+Z for undo (disabled in online games)
|
|
if event.keycode == KEY_Z and event.ctrl_pressed:
|
|
if not is_online_game:
|
|
_on_undo_requested()
|
|
return
|
|
|
|
# L key to toggle action log
|
|
if event.keycode == KEY_L and not event.ctrl_pressed:
|
|
if action_log:
|
|
action_log.toggle_panel()
|
|
return
|
|
|
|
match event.keycode:
|
|
KEY_SPACE:
|
|
# Pass priority / end phase
|
|
if is_online_game:
|
|
if not _is_local_player_turn():
|
|
return
|
|
NetworkManager.send_pass()
|
|
GameManager.pass_priority()
|
|
_sync_visuals()
|
|
_update_hand_display()
|
|
_update_cp_display()
|
|
KEY_ESCAPE:
|
|
# Cancel current selection
|
|
GameManager.clear_selection()
|
|
table_setup.clear_all_highlights()
|
|
hand_display.clear_highlights()
|
|
game_ui.hide_field_card_selection()
|