class_name ZoneVisual extends Node3D ## ZoneVisual - Visual representation of a card zone signal card_clicked(card_visual: CardVisual) # Zone configuration @export var zone_type: Enums.ZoneType = Enums.ZoneType.HAND @export var player_index: int = 0 # Layout settings @export var card_spacing: float = 0.7 @export var stack_offset: float = 0.02 # Vertical offset for stacked cards @export var max_visible_cards: int = 10 @export var fan_angle: float = 5.0 # Degrees for hand fan # Position settings @export var zone_position: Vector3 = Vector3.ZERO @export var zone_rotation: float = 0.0 # Y rotation in degrees # Card visuals in this zone var card_visuals: Array[CardVisual] = [] # Zone indicator (optional visual for empty zones) var zone_indicator: MeshInstance3D = null func _ready() -> void: position = zone_position rotation.y = deg_to_rad(zone_rotation) _create_zone_indicator() func _create_zone_indicator() -> void: zone_indicator = MeshInstance3D.new() add_child(zone_indicator) var plane = PlaneMesh.new() plane.size = Vector2(CardVisual.CARD_WIDTH * 1.1, CardVisual.CARD_HEIGHT * 1.1) zone_indicator.mesh = plane var mat = StandardMaterial3D.new() mat.albedo_color = Color(0.2, 0.2, 0.3, 0.1) # Nearly transparent (mat texture shows zones) mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA zone_indicator.material_override = mat zone_indicator.rotation.x = deg_to_rad(-90) # Lay flat zone_indicator.visible = true ## Add a card to this zone func add_card(card_instance: CardInstance) -> CardVisual: var card_visual = CardVisual.new() add_child(card_visual) card_visual.setup(card_instance) # Connect signals card_visual.clicked.connect(_on_card_clicked) card_visuals.append(card_visual) _arrange_cards() return card_visual ## Remove a card from this zone func remove_card(card_visual: CardVisual) -> void: var index = card_visuals.find(card_visual) if index >= 0: card_visuals.remove_at(index) card_visual.queue_free() _arrange_cards() ## Remove card by instance func remove_card_instance(card_instance: CardInstance) -> CardVisual: for card_visual in card_visuals: if card_visual.card_instance == card_instance: remove_card(card_visual) return card_visual return null ## Find card visual by instance func find_card_visual(card_instance: CardInstance) -> CardVisual: for card_visual in card_visuals: if card_visual.card_instance == card_instance: return card_visual return null ## Arrange cards based on zone type func _arrange_cards() -> void: match zone_type: Enums.ZoneType.HAND: _arrange_hand() Enums.ZoneType.DAMAGE: _arrange_damage() Enums.ZoneType.DECK, Enums.ZoneType.BREAK: _arrange_stack() Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS: _arrange_field() _: _arrange_row() # Update zone indicator visibility zone_indicator.visible = card_visuals.size() == 0 ## Arrange as a fan (for hand) func _arrange_hand() -> void: var count = card_visuals.size() if count == 0: return var total_width = (count - 1) * card_spacing var start_x = -total_width / 2 for i in range(count): var card = card_visuals[i] var x = start_x + i * card_spacing var angle = (i - (count - 1) / 2.0) * fan_angle card.move_to( Vector3(x, i * 0.01, 0), # Slight y offset for overlap Vector3(0, 0, deg_to_rad(-angle)) ) ## Arrange as a stack (for deck, break zone) func _arrange_stack() -> void: for i in range(card_visuals.size()): var card = card_visuals[i] card.move_to(Vector3(0, i * stack_offset, 0)) ## Arrange damage cards spread along Z axis (vertical column on the mat) func _arrange_damage() -> void: var count = card_visuals.size() if count == 0: return # Spread damage cards along Z axis so they fan out visually var spacing = min(0.5, 3.0 / max(count, 1)) var total_depth = (count - 1) * spacing var start_z = -total_depth / 2.0 for i in range(count): var card = card_visuals[i] card.move_to(Vector3(0, i * stack_offset, start_z + i * spacing)) ## Arrange in a row (for field zones) func _arrange_field() -> void: var count = card_visuals.size() if count == 0: return var total_width = (count - 1) * card_spacing var start_x = -total_width / 2 for i in range(count): var card = card_visuals[i] var x = start_x + i * card_spacing var y = i * 0.01 # Slight Y offset so cards layer properly # Apply dull rotation if card is dull var rot_y = 0.0 if card.card_instance and card.card_instance.is_dull(): rot_y = deg_to_rad(90) card.move_to(Vector3(x, y, 0), Vector3(0, rot_y, 0)) ## Arrange in a simple row func _arrange_row() -> void: var count = card_visuals.size() if count == 0: return var total_width = (count - 1) * card_spacing var start_x = -total_width / 2 for i in range(count): var card = card_visuals[i] card.move_to(Vector3(start_x + i * card_spacing, 0, 0)) ## Clear all cards func clear() -> void: for card_visual in card_visuals: card_visual.queue_free() card_visuals.clear() zone_indicator.visible = true ## Get card count func get_card_count() -> int: return card_visuals.size() ## Highlight all cards that can be interacted with func highlight_interactive(predicate: Callable) -> void: for card_visual in card_visuals: var can_interact = predicate.call(card_visual.card_instance) card_visual.set_highlighted(can_interact) ## Clear all highlights func clear_highlights() -> void: for card_visual in card_visuals: card_visual.set_highlighted(false) card_visual.set_selected(false) ## Card click handler func _on_card_clicked(card_visual: CardVisual) -> void: card_clicked.emit(card_visual)