Files
FFCardGame/scripts/visual/TableSetup.gd
2026-01-24 16:29:11 -05:00

259 lines
7.6 KiB
GDScript

class_name TableSetup
extends Node3D
## TableSetup - Sets up the 3D game table with all zones
signal card_clicked(card_instance: CardInstance, zone_type: Enums.ZoneType, player_index: int)
# Zone visuals for each player
var player_zones: Array[Dictionary] = [{}, {}]
# Deck indicators (simple card-back representation)
var deck_indicators: Array[MeshInstance3D] = [null, null]
var deck_count_labels: Array[Label3D] = [null, null]
# Table dimensions
const TABLE_WIDTH: float = 16.0
const TABLE_DEPTH: float = 12.0
# Zone positions (relative to table center)
const ZONE_POSITIONS = {
"deck": Vector3(5.5, 0.1, 3.5),
"damage": Vector3(3.5, 0.1, 3.5),
"backups": Vector3(0.0, 0.1, 3.5),
"break": Vector3(-3.5, 0.1, 3.5),
"forwards": Vector3(0.0, 0.1, 1.5),
"hand": Vector3(0.0, 0.5, 5.5)
}
# Components
var table_mesh: MeshInstance3D
var camera: TableCamera
func _ready() -> void:
_create_table()
_create_camera()
_create_lighting()
_create_zones()
_create_deck_indicators()
func _create_table() -> void:
table_mesh = MeshInstance3D.new()
add_child(table_mesh)
var plane = PlaneMesh.new()
plane.size = Vector2(TABLE_WIDTH, TABLE_DEPTH)
table_mesh.mesh = plane
# Create table material
var mat = StandardMaterial3D.new()
mat.albedo_color = Color(0.15, 0.2, 0.15) # Dark green felt
# Try to load playmat texture
if ResourceLoader.exists("res://assets/table/playmat.webp"):
var texture = load("res://assets/table/playmat.webp")
if texture:
mat.albedo_texture = texture
table_mesh.material_override = mat
# PlaneMesh in Godot 4 is already horizontal (facing up), no rotation needed
func _create_camera() -> void:
camera = TableCamera.new()
add_child(camera)
camera.current = true
func _create_lighting() -> void:
# Main directional light
var dir_light = DirectionalLight3D.new()
add_child(dir_light)
dir_light.position = Vector3(5, 10, 5)
dir_light.rotation = Vector3(deg_to_rad(-45), deg_to_rad(45), 0)
dir_light.light_energy = 0.8
dir_light.shadow_enabled = true
# Ambient light
var env = WorldEnvironment.new()
add_child(env)
var environment = Environment.new()
environment.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
environment.ambient_light_color = Color(0.3, 0.3, 0.35)
environment.ambient_light_energy = 0.5
environment.background_mode = Environment.BG_COLOR
environment.background_color = Color(0.1, 0.1, 0.15)
env.environment = environment
func _create_zones() -> void:
# Create zones for both players
for player_idx in range(2):
var flip = 1 if player_idx == 0 else -1
var rot = 0 if player_idx == 0 else 180
# Field - Forwards
var forwards_zone = _create_zone(
Enums.ZoneType.FIELD_FORWARDS, player_idx,
ZONE_POSITIONS["forwards"] * Vector3(1, 1, flip), rot
)
player_zones[player_idx]["forwards"] = forwards_zone
# Field - Backups
var backups_zone = _create_zone(
Enums.ZoneType.FIELD_BACKUPS, player_idx,
ZONE_POSITIONS["backups"] * Vector3(1, 1, flip), rot
)
player_zones[player_idx]["backups"] = backups_zone
# Damage Zone
var damage_zone = _create_zone(
Enums.ZoneType.DAMAGE, player_idx,
ZONE_POSITIONS["damage"] * Vector3(flip, 1, flip), rot
)
player_zones[player_idx]["damage"] = damage_zone
# Break Zone
var break_zone = _create_zone(
Enums.ZoneType.BREAK, player_idx,
ZONE_POSITIONS["break"] * Vector3(flip, 1, flip), rot
)
player_zones[player_idx]["break"] = break_zone
func _create_deck_indicators() -> void:
# Create simple card-back boxes for deck representation
for player_idx in range(2):
var flip = 1 if player_idx == 0 else -1
var pos = ZONE_POSITIONS["deck"] * Vector3(flip, 1, flip)
# Card back mesh (simple colored box)
var deck_mesh = MeshInstance3D.new()
add_child(deck_mesh)
var box = BoxMesh.new()
box.size = Vector3(0.63, 0.3, 0.88) # Card size with thickness for deck
deck_mesh.mesh = box
var mat = StandardMaterial3D.new()
mat.albedo_color = Color(0.15, 0.1, 0.3) # Dark blue for card back
deck_mesh.material_override = mat
deck_mesh.position = pos + Vector3(0, 0.15, 0) # Raise above table
deck_indicators[player_idx] = deck_mesh
# Card count label
var label = Label3D.new()
add_child(label)
label.text = "50"
label.font_size = 64
label.position = pos + Vector3(0, 0.35, 0)
label.rotation.x = deg_to_rad(-90) # Face up
if player_idx == 1:
label.rotation.z = deg_to_rad(180) # Flip for opponent
deck_count_labels[player_idx] = label
func _create_zone(zone_type: Enums.ZoneType, player_idx: int, pos: Vector3, rot: float) -> ZoneVisual:
var zone = ZoneVisual.new()
add_child(zone)
zone.zone_type = zone_type
zone.player_index = player_idx
zone.zone_position = pos
zone.zone_rotation = rot
# Configure based on zone type
match zone_type:
Enums.ZoneType.FIELD_FORWARDS, Enums.ZoneType.FIELD_BACKUPS:
zone.card_spacing = 0.8
Enums.ZoneType.DAMAGE, Enums.ZoneType.BREAK:
zone.stack_offset = 0.02
zone.card_clicked.connect(_on_zone_card_clicked.bind(zone_type, player_idx))
return zone
## Get a zone visual
func get_zone(player_idx: int, zone_name: String) -> ZoneVisual:
if player_idx >= 0 and player_idx < player_zones.size():
return player_zones[player_idx].get(zone_name)
return null
## Sync visual state with game state
func sync_with_game_state(game_state: GameState) -> void:
for player_idx in range(2):
var player = game_state.get_player(player_idx)
if not player:
continue
# Sync forwards
_sync_zone(player_zones[player_idx]["forwards"], player.field_forwards)
# Sync backups
_sync_zone(player_zones[player_idx]["backups"], player.field_backups)
# Update deck count (don't sync individual cards)
_update_deck_indicator(player_idx, player.deck.get_count())
# Sync damage
_sync_zone(player_zones[player_idx]["damage"], player.damage_zone)
# Sync break zone
_sync_zone(player_zones[player_idx]["break"], player.break_zone)
## Update deck indicator
func _update_deck_indicator(player_idx: int, count: int) -> void:
if deck_count_labels[player_idx]:
deck_count_labels[player_idx].text = str(count)
# Scale deck thickness based on count
if deck_indicators[player_idx]:
var thickness = max(0.05, count * 0.006) # Min thickness, scale with cards
var mesh = deck_indicators[player_idx].mesh as BoxMesh
if mesh:
mesh.size.y = thickness
deck_indicators[player_idx].position.y = 0.1 + thickness / 2
func _sync_zone(visual_zone: ZoneVisual, game_zone: Zone) -> void:
if not visual_zone or not game_zone:
return
# Get current visual cards
var visual_instances = {}
for cv in visual_zone.card_visuals:
if cv.card_instance:
visual_instances[cv.card_instance.instance_id] = cv
# Get game zone cards
var game_cards = game_zone.get_cards()
# Add missing cards
for card in game_cards:
if not visual_instances.has(card.instance_id):
visual_zone.add_card(card)
# Remove cards no longer in zone
var game_ids = {}
for card in game_cards:
game_ids[card.instance_id] = true
for instance_id in visual_instances:
if not game_ids.has(instance_id):
visual_zone.remove_card(visual_instances[instance_id])
## Card click handler
func _on_zone_card_clicked(card_visual: CardVisual, zone_type: Enums.ZoneType, player_idx: int) -> void:
if card_visual.card_instance:
card_clicked.emit(card_visual.card_instance, zone_type, player_idx)
## Highlight cards in a zone based on a condition
func highlight_zone_cards(player_idx: int, zone_name: String, predicate: Callable) -> void:
var zone = get_zone(player_idx, zone_name)
if zone:
zone.highlight_interactive(predicate)
## Clear all highlights
func clear_all_highlights() -> void:
for player_idx in range(2):
for zone_name in player_zones[player_idx]:
var zone = player_zones[player_idx][zone_name]
if zone:
zone.clear_highlights()