Improve nearby water recovery and camera prompts

Recover players on generated worlds to the nearest safe grass or sand surface. Use the active gameplay camera when positioning shop and storage prompts.
This commit is contained in:
Alexander Sellite 2026-08-29 17:43:11 -04:00
parent ae802493a0
commit 4575830857
10 changed files with 396 additions and 25 deletions

View file

@ -416,6 +416,12 @@ func _run() -> void:
)
assert(normal_camera.is_position_behind(chat_anchor))
assert(not free_camera.is_position_behind(chat_anchor))
var shop_prompt := game_ui.get_node("%ShopPrompt") as Control
var storage_prompt := game_ui.get_node("%StoragePrompt") as Control
game_ui.set_shop_prompt_visible(true, chat_anchor)
game_ui.set_storage_prompt_visible(true, chat_anchor)
assert(shop_prompt.visible)
assert(storage_prompt.visible)
chat_ui.show_local_speech("hello there friend")
await process_frame
assert(bool(player.get("_speech_mouth_active")))

View file

@ -265,6 +265,15 @@ func _run() -> void:
assert(bool(water_recovery.get("_prior_movement_enabled")))
assert(bool(water_recovery.get("_prior_camera_input_enabled")))
assert(not pause_menu.visible)
var test_world := main.get_node("TestWorld") as TestWorld
assert(test_world != null)
var recovery_entry := water_recovery.get("_entry_position") as Vector3
var resolved_recovery := water_recovery.call(
"_resolve_recovery_position"
) as Vector3
assert(resolved_recovery.is_equal_approx(
test_world.get_water_recovery_position(recovery_entry)
))
water_recovery.call("_finish_recovery")
assert(player.is_movement_enabled())

View file

@ -536,6 +536,11 @@ func _validate_generated_region(
-INF,
0.35,
)
_validate_water_recovery_positions(
region,
grass_prop_surface_triangles,
sand_prop_surface_triangles,
)
var anchors := region.get_node(
"GatherableAnchors/ReachableTreeTrunks"
) as GatherableAnchorSet3D
@ -1268,6 +1273,76 @@ func _triangle_surface_height_at(
return highest
func _validate_water_recovery_positions(
region: GeneratedWorldRegion,
grass_triangles: Array[PackedVector3Array],
sand_triangles: Array[PackedVector3Array],
) -> void:
var fresh_water_root := region.get_node(
"WaterBodies/FreshWaterBodies"
) as Node3D
assert(fresh_water_root != null and fresh_water_root.get_child_count() > 0)
var fresh_water := fresh_water_root.get_child(0) as WaterBodyAuthoring
assert(fresh_water != null)
var entry_position := fresh_water.global_position
var fallback_position := region.get_player_spawn_transform().origin
var recovery_position := region.get_water_recovery_position(
entry_position,
fallback_position,
)
assert(recovery_position.is_finite())
assert(
_horizontal_distance_squared(entry_position, recovery_position)
< _horizontal_distance_squared(entry_position, fallback_position),
"Fresh-water recovery returned the distant generated-world spawn.",
)
assert(
recovery_position.y
> GeneratedWorldRegion.WATER_HEIGHT
+ GeneratedWorldRegion.WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE
)
var recovery_triangles: Array[PackedVector3Array] = []
recovery_triangles.append_array(grass_triangles)
recovery_triangles.append_array(sand_triangles)
var center_height := _triangle_surface_height_at(
recovery_position,
recovery_triangles,
)
assert(center_height > -INF)
assert(absf(center_height - recovery_position.y) <= 0.01)
for direction: Vector2 in GeneratedWorldRegion.PROP_SURFACE_SAMPLE_DIRECTIONS:
var footprint_position := recovery_position + Vector3(
direction.x * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS,
0.0,
direction.y * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS,
)
var footprint_height := _triangle_surface_height_at(
footprint_position,
recovery_triangles,
)
assert(footprint_height > -INF)
assert(
absf(footprint_height - recovery_position.y)
<= GeneratedWorldRegion.PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE
)
assert(
region.get_water_recovery_position(
entry_position,
fallback_position,
).is_equal_approx(recovery_position)
)
assert(
region.get_water_recovery_position(
Vector3(INF, 0.0, 0.0),
fallback_position,
).is_equal_approx(fallback_position)
)
func _horizontal_distance_squared(a: Vector3, b: Vector3) -> float:
return Vector2(a.x - b.x, a.z - b.z).length_squared()
func _validate_tree_gatherable_anchors(
region: GeneratedWorldRegion,
decorations: Node3D,

View file

@ -100,6 +100,14 @@ func _validate_world_switching() -> void:
assert(world.get_fishing_shop() != null)
assert(world.get_player_storage() != null)
assert(not world.get_fishable_water_regions().is_empty())
var starter_safe_points := world.get_safe_respawn_points()
assert(not starter_safe_points.is_empty())
var authored_safe_point: SafeRespawnPoint = starter_safe_points.back()
assert(
world.get_water_recovery_position(
authored_safe_point.global_position
).is_equal_approx(authored_safe_point.global_position)
)
assert(world.activate_world(WorldLayout.GENERATED, 24680))
assert(world.get_world_layout() == WorldLayout.GENERATED)
assert(world.get_generation_seed() == 24680)