extends Node2D signal tile_selected(tile: Node2D) @export var tile_type: int = 0: set = _set_tile_type var grid_position: Vector2i var is_selected: bool = false: set = _set_selected var is_highlighted: bool = false: set = _set_highlighted var original_scale: Vector2 = Vector2.ONE # Store the original scale for the board @onready var sprite: Sprite2D = $Sprite2D # Target size for each tile to fit in the 54x54 grid cells const TILE_SIZE = 48 # Slightly smaller than 54 to leave some padding # All available gem textures var all_gem_textures: Array[Texture2D] = [ preload("res://assets/sprites/gems/bg_19.png"), # 0 - Blue gem preload("res://assets/sprites/gems/dg_19.png"), # 1 - Dark gem preload("res://assets/sprites/gems/gg_19.png"), # 2 - Green gem preload("res://assets/sprites/gems/mg_19.png"), # 3 - Magenta gem preload("res://assets/sprites/gems/rg_19.png"), # 4 - Red gem preload("res://assets/sprites/gems/yg_19.png"), # 5 - Yellow gem preload("res://assets/sprites/gems/pg_19.png"), # 6 - Purple gem preload("res://assets/sprites/gems/sg_19.png"), # 7 - Silver gem ] # Currently active gem types (indices into all_gem_textures) var active_gem_types: Array[int] = [] # Will be set from TileManager func _set_tile_type(value: int) -> void: tile_type = value # Fixed: Add sprite null check to prevent crashes during initialization if not sprite: return if value >= 0 and value < active_gem_types.size(): var texture_index = active_gem_types[value] sprite.texture = all_gem_textures[texture_index] _scale_sprite_to_fit() else: DebugManager.log_error( ( "Invalid tile type: " + str(value) + ". Available types: 0-" + str(active_gem_types.size() - 1) ), "Match3" ) func _scale_sprite_to_fit() -> void: # Fixed: Add additional null checks if sprite and sprite.texture: var texture_size = sprite.texture.get_size() var max_dimension = max(texture_size.x, texture_size.y) var scale_factor = TILE_SIZE / max_dimension original_scale = Vector2(scale_factor, scale_factor) sprite.scale = original_scale DebugManager.log_debug( ( "Set original scale to %s for tile (%d,%d)" % [original_scale, grid_position.x, grid_position.y] ), "Match3" ) func set_active_gem_types(gem_indices: Array[int]) -> void: if not gem_indices or gem_indices.is_empty(): DebugManager.log_error("Empty gem indices array provided", "Tile") return active_gem_types = gem_indices.duplicate() # Validate all gem indices are within bounds for gem_index in active_gem_types: if gem_index < 0 or gem_index >= all_gem_textures.size(): DebugManager.log_error( ( "Invalid gem index: %d (valid range: 0-%d)" % [gem_index, all_gem_textures.size() - 1] ), "Tile" ) # Use default fallback active_gem_types = [0, 1, 2, 3, 4] break # Re-validate current tile type if tile_type >= active_gem_types.size(): # Generate a new random tile type within valid range tile_type = randi() % active_gem_types.size() _set_tile_type(tile_type) func get_active_gem_count() -> int: return active_gem_types.size() func add_gem_type(gem_index: int) -> bool: if gem_index < 0 or gem_index >= all_gem_textures.size(): DebugManager.log_error("Invalid gem index: %d" % gem_index, "Tile") return false if not active_gem_types.has(gem_index): active_gem_types.append(gem_index) return true return false func remove_gem_type(gem_index: int) -> bool: var type_index = active_gem_types.find(gem_index) if type_index == -1: return false if active_gem_types.size() <= 2: # Keep at least 2 gem types DebugManager.log_warn("Cannot remove gem type - minimum 2 types required", "Tile") return false active_gem_types.erase(gem_index) # Update tile if it was using the removed type if tile_type >= active_gem_types.size(): tile_type = 0 _set_tile_type(tile_type) return true func _set_selected(value: bool) -> void: var old_value = is_selected is_selected = value DebugManager.log_debug( ( "Tile (%d,%d) selection changed: %s -> %s" % [grid_position.x, grid_position.y, old_value, value] ), "Match3" ) _update_visual_feedback() func _set_highlighted(value: bool) -> void: var old_value = is_highlighted is_highlighted = value DebugManager.log_debug( ( "Tile (%d,%d) highlight changed: %s -> %s" % [grid_position.x, grid_position.y, old_value, value] ), "Match3" ) _update_visual_feedback() func _update_visual_feedback() -> void: if not sprite: return # Determine target values based on priority: selected > highlighted > normal var target_modulate: Color var target_scale: Vector2 var scale_multiplier: float if is_selected: # Selected: bright and 20% larger than original board size target_modulate = Color(1.2, 1.2, 1.2, 1.0) scale_multiplier = 1.2 DebugManager.log_debug( ( "SELECTING tile (%d,%d): target scale %.2fx, current scale %s" % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] ), "Match3" ) elif is_highlighted: # Highlighted: subtle glow and 10% larger than original board size target_modulate = Color(1.1, 1.1, 1.1, 1.0) scale_multiplier = 1.1 DebugManager.log_debug( ( "HIGHLIGHTING tile (%d,%d): target scale %.2fx, current scale %s" % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] ), "Match3" ) else: # Normal state: white and original board size target_modulate = Color.WHITE scale_multiplier = 1.0 DebugManager.log_debug( ( "NORMALIZING tile (%d,%d): target scale %.2fx, current scale %s" % [grid_position.x, grid_position.y, scale_multiplier, sprite.scale] ), "Match3" ) # Calculate target scale relative to original board scale target_scale = original_scale * scale_multiplier # Apply modulate immediately sprite.modulate = target_modulate # Only animate scale if it's actually changing if sprite.scale != target_scale: DebugManager.log_debug( ( "Animating scale from %s to %s for tile (%d,%d)" % [sprite.scale, target_scale, grid_position.x, grid_position.y] ), "Match3" ) var tween = create_tween() tween.tween_property(sprite, "scale", target_scale, 0.15) # Add completion callback for debugging tween.tween_callback(_on_scale_animation_completed.bind(target_scale)) else: DebugManager.log_debug( "No scale change needed for tile (%d,%d)" % [grid_position.x, grid_position.y], "Match3" ) func _on_scale_animation_completed(expected_scale: Vector2) -> void: DebugManager.log_debug( ( "Scale animation completed for tile (%d,%d): expected %s, actual %s" % [grid_position.x, grid_position.y, expected_scale, sprite.scale] ), "Match3" ) func force_reset_visual_state() -> void: # Force reset all visual states - debug function is_selected = false is_highlighted = false if sprite: sprite.modulate = Color.WHITE sprite.scale = original_scale # Reset to original board scale, not 1.0 DebugManager.log_debug( ( "Forced visual reset on tile (%d,%d) to original scale %s" % [grid_position.x, grid_position.y, original_scale] ), "Match3" ) # Handle input for tile selection func _input(event: InputEvent) -> void: if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT and event.pressed: # Check if the mouse click is within the tile's bounds var local_position = to_local(get_global_mouse_position()) var sprite_rect = Rect2(-TILE_SIZE / 2.0, -TILE_SIZE / 2.0, TILE_SIZE, TILE_SIZE) if sprite_rect.has_point(local_position): tile_selected.emit(self) get_viewport().set_input_as_handled() # Called when the node enters the scene tree for the first time. func _ready() -> void: add_to_group("tiles") # Add to group for gem pool management # Initialize with default gem pool if not already set if active_gem_types.is_empty(): active_gem_types = [0, 1, 2, 3, 4] # Default to first 5 gems _set_tile_type(tile_type)