From 1163cef320e006ac09256bd815a09fc337a57662 Mon Sep 17 00:00:00 2001 From: Voyager Date: Wed, 2 Sep 2026 17:20:05 -0400 Subject: [PATCH] Fix rain puddle splash lifecycle --- inventory/player_bag.gd | 13 +++--- tests/rain_puddle_runtime_validation.gd | 13 ++++++ world/rain_puddle_presentation.gd | 57 +++++++++++++++---------- 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/inventory/player_bag.gd b/inventory/player_bag.gd index 89feff4..1a5f5b0 100644 --- a/inventory/player_bag.gd +++ b/inventory/player_bag.gd @@ -83,11 +83,14 @@ func add_item_to_storage(item_id: StringName, quantity: int = 1) -> bool: if existing != null: existing.quantity += quantity else: - var owned := OwnedItemType.new() - owned.item_id = item_id - owned.quantity = quantity - owned.storage_slot = _first_available_storage_slot(item, _items) - _items.append(owned) + var new_owned_item := OwnedItemType.new() + new_owned_item.item_id = item_id + new_owned_item.quantity = quantity + new_owned_item.storage_slot = _first_available_storage_slot( + item, + _items, + ) + _items.append(new_owned_item) contents_changed.emit() return true if existing != null: diff --git a/tests/rain_puddle_runtime_validation.gd b/tests/rain_puddle_runtime_validation.gd index 6456276..f5d937c 100644 --- a/tests/rain_puddle_runtime_validation.gd +++ b/tests/rain_puddle_runtime_validation.gd @@ -50,6 +50,19 @@ func _run() -> void: assert(not puddles.is_processing()) puddles.set_active(true) assert(puddles.is_processing()) + var splash_root := puddles.get_node("PuddleSplashes") as Node3D + assert(splash_root != null) + splash_root.position = Vector3(5.0, 1.0, -4.0) + var expected_splash_position := Vector3(1.5, 2.04, -3.0) + puddles.call("_emit_splash", Vector3(1.5, 0.0, -3.0), 2.0) + assert(splash_root.get_child_count() == 1) + var splash := splash_root.get_child(0) as CPUParticles3D + assert(splash != null and splash.is_inside_tree()) + assert(splash.global_position.is_equal_approx(expected_splash_position)) + await create_timer(0.55).timeout + await process_frame + assert(not is_instance_valid(splash)) + splash_root.position = Vector3.ZERO for _tick: int in 18: puddles.call("_process", 0.25) assert(puddles.get_active_puddle_count() > 0) diff --git a/world/rain_puddle_presentation.gd b/world/rain_puddle_presentation.gd index 82c2a39..a7da0b5 100644 --- a/world/rain_puddle_presentation.gd +++ b/world/rain_puddle_presentation.gd @@ -286,21 +286,21 @@ func _spawn_puddle_near(player_position: Vector3) -> bool: if triangle_index < 0 or triangle_index >= _surface_triangles.size(): return false var triangle: PackedVector3Array = _surface_triangles[triangle_index] - var position: Vector3 = _random_point_in_triangle(triangle) + var candidate_position: Vector3 = _random_point_in_triangle(triangle) var horizontal_distance: float = Vector2( - position.x, - position.z, + candidate_position.x, + candidate_position.z, ).distance_to(Vector2(player_position.x, player_position.z)) if ( horizontal_distance < MINIMUM_PLAYER_DISTANCE or horizontal_distance > MAXIMUM_PLAYER_DISTANCE - or _is_candidate_blocked(position) - or _is_near_existing_puddle(position) + or _is_candidate_blocked(candidate_position) + or _is_near_existing_puddle(candidate_position) ): continue var maximum_radius: float = minf( MAXIMUM_PUDDLE_RADIUS, - _minimum_triangle_edge_distance(position, triangle) * 0.65, + _minimum_triangle_edge_distance(candidate_position, triangle) * 0.65, ) if maximum_radius < MINIMUM_PUDDLE_RADIUS: continue @@ -311,7 +311,7 @@ func _spawn_puddle_near(player_position: Vector3) -> bool: var radius_z: float = radius_x * _random.randf_range(0.58, 0.86) _activate_puddle( puddle, - position, + candidate_position, Vector2(radius_x, radius_z), _random.randf_range(0.0, TAU), ) @@ -440,15 +440,15 @@ func _apply_puddle_alpha(puddle: PuddleState) -> void: puddle.material.albedo_color = color -func _is_candidate_blocked(position: Vector3) -> bool: +func _is_candidate_blocked(candidate_position: Vector3) -> bool: if not is_inside_tree(): return false var world: World3D = get_world_3d() if world == null: return false var query := PhysicsRayQueryParameters3D.create( - position + Vector3.UP * 0.05, - position + Vector3.UP * 1.75, + candidate_position + Vector3.UP * 0.05, + candidate_position + Vector3.UP * 1.75, TERRAIN_COLLISION_MASK, ) query.collide_with_areas = false @@ -456,8 +456,11 @@ func _is_candidate_blocked(position: Vector3) -> bool: return not world.direct_space_state.intersect_ray(query).is_empty() -func _is_near_existing_puddle(position: Vector3) -> bool: - var horizontal_position := Vector2(position.x, position.z) +func _is_near_existing_puddle(candidate_position: Vector3) -> bool: + var horizontal_position := Vector2( + candidate_position.x, + candidate_position.z, + ) for puddle: PuddleState in _puddles: if not puddle.active: continue @@ -505,15 +508,14 @@ func _update_player_splashes( func _emit_splash(player_position: Vector3, surface_height: float) -> void: - if _splash_root == null or not _splash_root.visible: + if ( + _splash_root == null + or not _splash_root.is_inside_tree() + or not _splash_root.visible + ): return var particles := CPUParticles3D.new() particles.name = "RainPuddleSplash" - particles.global_position = Vector3( - player_position.x, - surface_height + 0.04, - player_position.z, - ) particles.amount = 7 particles.lifetime = 0.38 particles.one_shot = true @@ -535,7 +537,13 @@ func _emit_splash(player_position: Vector3, surface_height: float) -> void: droplet_mesh.size = Vector3(0.028, 0.04, 0.028) droplet_mesh.material = material particles.mesh = droplet_mesh + particles.emitting = false _splash_root.add_child(particles) + particles.global_position = Vector3( + player_position.x, + surface_height + 0.04, + player_position.z, + ) particles.emitting = true var cleanup := create_tween() cleanup.tween_interval(0.5) @@ -548,10 +556,10 @@ func _seed_for_world() -> int: return max(_test_world.get_generation_seed(), 1) ^ 0x5261696e -func _surface_cell_for(position: Vector3) -> Vector2i: +func _surface_cell_for(surface_position: Vector3) -> Vector2i: return Vector2i( - floori(position.x / SURFACE_CELL_SIZE), - floori(position.z / SURFACE_CELL_SIZE), + floori(surface_position.x / SURFACE_CELL_SIZE), + floori(surface_position.z / SURFACE_CELL_SIZE), ) @@ -567,10 +575,13 @@ func _random_point_in_triangle(triangle: PackedVector3Array) -> Vector3: func _minimum_triangle_edge_distance( - position: Vector3, + candidate_position: Vector3, triangle: PackedVector3Array, ) -> float: - var horizontal_position := Vector2(position.x, position.z) + var horizontal_position := Vector2( + candidate_position.x, + candidate_position.z, + ) return minf( _horizontal_distance_to_segment( horizontal_position,