From 8dbeb06e73d93a0eef0d774a2cefbc75358b6827 Mon Sep 17 00:00:00 2001 From: Voyager Date: Sun, 23 Aug 2026 00:50:45 -0400 Subject: [PATCH] fix: recover players from shallow freshwater --- tests/fishing_surface_validation.gd | 14 +++++++++++++- tests/generated_world_runtime_validation.gd | 14 ++++++++++++++ world/generation/generated_world_region.gd | 4 ++++ world/player_water_trigger.gd | 19 ++++++++++++++----- world/regions/starter_island_region.tscn | 2 ++ world/water_body_authoring.gd | 19 ++++++++++++++++++- 6 files changed, 65 insertions(+), 7 deletions(-) diff --git a/tests/fishing_surface_validation.gd b/tests/fishing_surface_validation.gd index 842b3e8..a6724a8 100644 --- a/tests/fishing_surface_validation.gd +++ b/tests/fishing_surface_validation.gd @@ -151,6 +151,11 @@ func _validate_portable_water_body() -> void: water_body.set("surface_size", Vector2(12.0, 8.0)) water_body.set("fishing_depth", 6.0) water_body.set("recovery_depth", 7.0) + water_body.set( + "recovery_entry_height_reference", + PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN, + ) + water_body.set("recovery_entry_depth_threshold", 0.1) water_body.set("fish_pool", PondPool) root.add_child(water_body) await process_frame @@ -169,8 +174,15 @@ func _validate_portable_water_body() -> void: assert(is_equal_approx(fishing_region.get_surface_height(), 7.0)) assert(is_equal_approx(fishing_shape_node.position.y, -3.0)) assert(fishing_shape.size.is_equal_approx(Vector3(12.0, 6.0, 8.0))) - var recovery_region := water_body.get_node("RecoveryRegion") as Area3D + var recovery_region := ( + water_body.get_node("RecoveryRegion") as PlayerWaterTrigger + ) assert(is_equal_approx(recovery_region.position.y, -3.5)) + assert( + recovery_region.entry_height_reference + == PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN + ) + assert(is_equal_approx(recovery_region.entry_depth_threshold, 0.1)) water_body.queue_free() await process_frame diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index 82627a5..d97be79 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -377,6 +377,12 @@ func _validate_generated_region( assert(ocean != null and fresh_root != null) assert(ocean.water_type == WaterType.Type.SALT_WATER) assert(is_equal_approx(ocean.position.y, GeneratedWorldRegion.WATER_HEIGHT)) + var ocean_recovery := ocean.get_node("RecoveryRegion") as PlayerWaterTrigger + assert( + ocean_recovery.entry_height_reference + == PlayerWaterTrigger.EntryHeightReference.BODY_CENTER + ) + assert(is_equal_approx(ocean_recovery.entry_depth_threshold, 0.35)) var fresh_placement_count := 0 var river_placement_count := 0 var polygon_sizes_by_coordinate: Dictionary[Vector2i, int] = {} @@ -415,6 +421,14 @@ func _validate_generated_region( assert(fresh.surface_size.x <= 10.0 and fresh.surface_size.y <= 10.0) assert(not fresh.visual_surface_enabled) assert(not (fresh.get_node("VisualWater") as MeshInstance3D).visible) + var fresh_recovery := ( + fresh.get_node("RecoveryRegion") as PlayerWaterTrigger + ) + assert( + fresh_recovery.entry_height_reference + == PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN + ) + assert(is_equal_approx(fresh_recovery.entry_depth_threshold, 0.1)) var coordinate_tokens := child.name.trim_prefix("FreshWater_").split("_") assert(coordinate_tokens.size() == 2) var coordinate := Vector2i( diff --git a/world/generation/generated_world_region.gd b/world/generation/generated_world_region.gd index c226610..95cf6d3 100644 --- a/world/generation/generated_world_region.gd +++ b/world/generation/generated_world_region.gd @@ -393,6 +393,10 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void: body.visual_surface_enabled = false body.water_material = _fresh_water_material() body.water_type = WaterType.Type.FRESH_WATER + body.recovery_entry_height_reference = ( + PlayerWaterTrigger.EntryHeightReference.PLAYER_ORIGIN + ) + body.recovery_entry_depth_threshold = 0.1 body.fish_pool = _fresh_water_pool(tags) body.location_tags = _fresh_water_location_tags(tags) body.selection_priority = 10 diff --git a/world/player_water_trigger.gd b/world/player_water_trigger.gd index fbd84ba..93096dd 100644 --- a/world/player_water_trigger.gd +++ b/world/player_water_trigger.gd @@ -9,6 +9,11 @@ enum SurfaceHeightMode { PARENT_GLOBAL_Y, } +enum EntryHeightReference { + BODY_CENTER, + PLAYER_ORIGIN, +} + @export_group("Surface") @export var surface_height_mode: SurfaceHeightMode = SurfaceHeightMode.EXPLICIT: set(value): @@ -18,7 +23,11 @@ enum SurfaceHeightMode { ## Legacy explicit height used when Surface Height Mode is Explicit. @export var surface_height: float = 0.2 @export_group("Recovery Trigger") -## Recovery begins when the player's body center reaches this depth. +## Point on the player used to measure entry depth below the water surface. +@export var entry_height_reference: EntryHeightReference = ( + EntryHeightReference.BODY_CENTER +) +## Recovery begins when the selected reference reaches this depth. @export_range(0.0, 2.0, 0.05) var entry_depth_threshold: float = 0.35 var _tracked_players: Array[Player] = [] @@ -42,10 +51,10 @@ func _physics_process(_delta: float) -> void: var player_key: StringName = StringName(str(player.get_instance_id())) if _triggered_players.has(player_key): continue - if ( - player.get_body_center_position().y - <= active_surface_height - entry_depth_threshold - ): + var entry_height := player.get_body_center_position().y + if entry_height_reference == EntryHeightReference.PLAYER_ORIGIN: + entry_height = player.global_position.y + if entry_height <= active_surface_height - entry_depth_threshold: _triggered_players[player_key] = true recovery_requested.emit(player, active_surface_height) diff --git a/world/regions/starter_island_region.tscn b/world/regions/starter_island_region.tscn index e8bb51e..b52b0ea 100644 --- a/world/regions/starter_island_region.tscn +++ b/world/regions/starter_island_region.tscn @@ -161,6 +161,8 @@ water_material = ExtResource("19_fresh_water") fish_pool = ExtResource("12_pond_pool") location_tags = Array[StringName]([&"starter_pond", &"pond"]) selection_priority = 1 +recovery_entry_height_reference = 1 +recovery_entry_depth_threshold = 0.1 [node name="VisualWater" type="MeshInstance3D" parent="WaterBodies/Pond" unique_id=1034265960] material_override = ExtResource("19_fresh_water") diff --git a/world/water_body_authoring.gd b/world/water_body_authoring.gd index 2430be0..a272d90 100644 --- a/world/water_body_authoring.gd +++ b/world/water_body_authoring.gd @@ -69,6 +69,17 @@ var fishing_depth: float = 4.0: set(value): manage_recovery_coverage = value _sync_owned_nodes() +@export var recovery_entry_height_reference: PlayerWaterTrigger.EntryHeightReference = ( + PlayerWaterTrigger.EntryHeightReference.BODY_CENTER +): + set(value): + recovery_entry_height_reference = value + _sync_owned_nodes() +@export_range(0.0, 2.0, 0.05) +var recovery_entry_depth_threshold: float = 0.35: + set(value): + recovery_entry_depth_threshold = clampf(value, 0.0, 2.0) + _sync_owned_nodes() @export_range(0.1, 20.0, 0.1, "or_greater", "suffix:m") var recovery_depth: float = 4.8: set(value): @@ -150,12 +161,18 @@ func _sync_owned_nodes() -> void: if manage_recovery_coverage: var recovery_region := ( - get_node_or_null(recovery_region_path) as Area3D + get_node_or_null(recovery_region_path) as PlayerWaterTrigger ) var recovery_shape_node := ( get_node_or_null(recovery_shape_path) as CollisionShape3D ) if recovery_region != null and recovery_shape_node != null: + recovery_region.entry_height_reference = ( + recovery_entry_height_reference + ) + recovery_region.entry_depth_threshold = ( + recovery_entry_depth_threshold + ) if uses_polygon and _sync_polygon_collision_shapes( recovery_region, recovery_shape_node,