Godot Version
4.2.1
Question
I’m building a grid-based farming game in Godot where the player selects a tool (seed, water, till, harvest), taps a tile, and an avatar moves to the tile before the action executes. The problem is: after the avatar reaches the tile, the correct action does not consistently execute. Tools appear to be selected properly before movement, but once the avatar arrives and run_pending_action() is called, nothing happens — no planting, watering, or tilling. If I remove the avatar movement delay and execute actions immediately on click, everything works as expected. I suspect the issue lies in how or when the tool and crop are stored, or how run_pending_action() is triggered, but haven’t been able to resolve it. I’m using Godot 4.2.1.
Scripts
``` extends Node2D var crop_id = "" var required_water = 0 var grow_time = 0 var water_count = 0 var stage = "" var is_planted = false var is_watered = false var growth_stage = 0 const MAX_GROWTH_STAGE = 3 var grow_timer = null var growth_time = 5.0 var pending_action = "" var selected_tool = "" var pending_tool = "" var pending_crop_id = "" func _ready(): $ClickArea.connect("input_event", Callable(self, "_on_click")) func _on_click(viewport, event, shape_idx): if event is InputEventMouseButton and event.pressed: var avatar = get_node("/root/Main/Avatar") avatar.move_to_tile(global_position, self) var tool = ToolManager.get_tool() var crop_id = ToolManager.get_selected_crop() if tool == "seed" and not is_planted: pending_action = "plant" pending_crop_id = crop_id elif tool == "water" and is_planted and not is_watered: pending_action = "water" elif tool == "till" and not is_planted and growth_stage == 0: pending_action = "till" elif growth_stage == MAX_GROWTH_STAGE: pending_action = "harvest" func run_pending_action(): match pending_action: "plant": var crop_data = CropRegistry.get_crop(crop_id) _plant_seed(crop_data, crop_id) "water": if ToolManager.use_water(): is_watered = true _water_tile() "till": _till_soil() "harvest": _harvest() pending_action = "" pending_crop_id = "" func _plant_seed(crop: Dictionary, crop_id_val: String): is_planted = true is_watered = false growth_stage = 0 crop_id = crop_id_val required_water = crop.get("water_needed", 1) grow_time = crop.get("grow_time", 5.0) water_count = 0 stage = "growing" _update_visual() func _water_tile(): _start_growth_timer() _update_visual() func _start_growth_timer(): grow_timer = Timer.new() grow_timer.wait_time = growth_time grow_timer.one_shot = true grow_timer.connect("timeout", Callable(self, "_on_growth_timer_timeout")) add_child(grow_timer) grow_timer.start() func _on_growth_timer_timeout(): if growth_stage < MAX_GROWTH_STAGE: growth_stage += 1 _update_visual() # Reset timer for next stage grow_timer.queue_free() _start_growth_timer() else: print("Fully grown!") func _harvest(): get_node("/root/Main/InventoryManager").add_crop(crop_id) reset_tile() print("Harvested plant at stage:", growth_stage) is_planted = false is_watered = false growth_stage = 0 if grow_timer: grow_timer.stop() grow_timer.queue_free() grow_timer = null $SoilSprite.modulate = Color(0.6, 0.6, 0.6) # Grey or dull brown func _till_soil(): print("Soil tilled and ready to plant.") is_planted = false is_watered = false growth_stage = 0 if grow_timer: grow_timer.stop() grow_timer.queue_free() grow_timer = null # Force re-visualization _update_visual() func _update_visual(): match growth_stage: 0: if is_planted and not is_watered: $SoilSprite.modulate = Color(1.0, 0.85, 0.4) # Dry golden yellow elif is_planted and is_watered: $SoilSprite.modulate = Color(0.4, 0.9, 0.4) # Healthy green else: $SoilSprite.modulate = Color(1, 1, 1) # Fresh tilled 1: $SoilSprite.modulate = Color(0.6, 1.0, 0.6) # Sprout 2: $SoilSprite.modulate = Color(1.0, 0.9, 0.6) # Bloom 3: $SoilSprite.modulate = Color(1.0, 0.8, 1.0) # Fruit func reset_tile(): var sprite = $Sprite if sprite: sprite.modulate = Color(1, 1, 1) else: print("Sprite node not found in tile.") ``` Avatar script``` extends Node2D @onready var animated_sprite = $AnimatedSprite2D var speed = 100 # pixels per second var target_tile = null func move_to_tile(target_position: Vector2, tile_ref = null): target_tile = tile_ref animated_sprite.play("walk") var tween = create_tween() tween.tween_property(self, "position", target_position, position.distance_to(target_position) / speed) tween.tween_callback(Callable(self, "_on_reach_destination")) func _on_reach_destination(): animated_sprite.play("idle") if target_tile: target_tile.run_pending_action() target_tile = null ```