259 lines
7.6 KiB
GDScript
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()
|