feat: add rain puddles and stabilize shoreline water
This commit is contained in:
parent
eeef99793d
commit
3a61056ce5
49 changed files with 1033 additions and 1852 deletions
|
|
@ -505,7 +505,7 @@ func _run() -> void:
|
|||
"trailblazer",
|
||||
))
|
||||
var remote_transform: Transform3D = player.global_transform
|
||||
remote_transform.origin += Vector3(0.8, 0.0, 0.0)
|
||||
remote_transform.origin += Vector3(20.0, 0.0, 0.0)
|
||||
var remote_avatar := spawn_service.spawn_remote_player(
|
||||
NAMEPLATE_TEST_PEER_ID,
|
||||
remote_transform,
|
||||
|
|
@ -534,6 +534,29 @@ func _run() -> void:
|
|||
assert(remote_plate.visible)
|
||||
assert((remote_nameplate.get("name") as Label).text == "remote stray")
|
||||
assert((remote_nameplate.get("title") as Label).text == "trailblazer")
|
||||
chat_ui.call(
|
||||
"_show_speech_bubble",
|
||||
NAMEPLATE_TEST_PEER_ID,
|
||||
"distant hello",
|
||||
"",
|
||||
)
|
||||
await process_frame
|
||||
chat_ui.call("_update_speech")
|
||||
var remote_speech_entries: Dictionary = chat_ui.get("_speech")
|
||||
var remote_speech: Dictionary = remote_speech_entries.get(
|
||||
NAMEPLATE_TEST_PEER_ID,
|
||||
{},
|
||||
)
|
||||
var remote_speech_bubble := remote_speech.get("bubble") as PanelContainer
|
||||
assert(remote_speech_bubble != null and remote_speech_bubble.visible)
|
||||
assert(is_equal_approx(
|
||||
remote_speech_bubble.scale.x,
|
||||
ChatUI.speech_scale_for_distance(
|
||||
remote_avatar.global_position.distance_to(player.global_position)
|
||||
),
|
||||
))
|
||||
assert(remote_speech_bubble.scale.x < 1.0)
|
||||
chat_ui.call("_on_peer_removed", NAMEPLATE_TEST_PEER_ID)
|
||||
spawn_service.remove_peer(NAMEPLATE_TEST_PEER_ID)
|
||||
registry.remove_peer(NAMEPLATE_TEST_PEER_ID)
|
||||
chat_ui.call("_update_nameplates")
|
||||
|
|
|
|||
|
|
@ -524,21 +524,16 @@ func _validate_starter_water_bodies() -> void:
|
|||
assert(ocean.water_type == WaterType.Type.SALT_WATER)
|
||||
assert(pond.fish_pool == PondPool)
|
||||
assert(ocean.fish_pool == OceanPool)
|
||||
assert(region.get_node_or_null("ShorelineRibbons/Pond") == null)
|
||||
var shoreline_geometry := (
|
||||
region.get_node_or_null("ShorelineRibbons/Ocean") as MeshInstance3D
|
||||
)
|
||||
assert(shoreline_geometry != null)
|
||||
assert(not shoreline_geometry.visible)
|
||||
assert(shoreline_geometry.material_override == null)
|
||||
var pond_material := (
|
||||
region.get_node("WaterBodies/Pond/VisualWater") as MeshInstance3D
|
||||
).material_override as ShaderMaterial
|
||||
var ocean_material := (
|
||||
region.get_node("WaterBodies/Ocean/VisualWater") as MeshInstance3D
|
||||
).material_override as ShaderMaterial
|
||||
assert(not bool(pond_material.get_shader_parameter("tide_effect_enabled")))
|
||||
assert(bool(ocean_material.get_shader_parameter("tide_effect_enabled")))
|
||||
assert(pond_material != null)
|
||||
assert(ocean_material != null)
|
||||
assert(float(ocean_material.get_shader_parameter("shoreline_foam_depth")) > 0.0)
|
||||
assert(float(ocean_material.get_shader_parameter("shoreline_foam_strength")) > 0.0)
|
||||
region.free()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -131,9 +131,6 @@ func _validate_normal_profile(main: Node) -> void:
|
|||
assert((main.get_node("%TitleMusic") as AudioStreamPlayer).stream != null)
|
||||
assert((main.get_node("%NewGameMusic") as AudioStreamPlayer).stream != null)
|
||||
assert((main.get_node("%DuskMusic") as AudioStreamPlayer).stream != null)
|
||||
assert(
|
||||
(main.get_node("%WavesAudio") as AudioStreamPlayer).stream != null
|
||||
)
|
||||
assert(
|
||||
(main.get_node("RainAmbience") as AudioStreamPlayer).stream != null
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,15 @@ func _validate_voice_distance_attenuation() -> void:
|
|||
ChatUI.animalese_volume_offset_db_for_distance(24.0),
|
||||
-80.0,
|
||||
))
|
||||
assert(is_equal_approx(ChatUI.speech_scale_for_distance(0.0), 1.0))
|
||||
assert(is_equal_approx(ChatUI.speech_scale_for_distance(4.0), 1.0))
|
||||
var middle_distance_scale := ChatUI.speech_scale_for_distance(14.0)
|
||||
assert(middle_distance_scale < 1.0)
|
||||
assert(middle_distance_scale > ChatUI.SPEECH_MINIMUM_SCALE)
|
||||
assert(is_equal_approx(
|
||||
ChatUI.speech_scale_for_distance(24.0),
|
||||
ChatUI.SPEECH_MINIMUM_SCALE,
|
||||
))
|
||||
|
||||
|
||||
func _run_host() -> void:
|
||||
|
|
|
|||
84
tests/rain_puddle_runtime_validation.gd
Normal file
84
tests/rain_puddle_runtime_validation.gd
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
extends SceneTree
|
||||
|
||||
const MainScene: PackedScene = preload("res://main/main.tscn")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
root.size = Vector2i(1280, 720)
|
||||
var main: Node = MainScene.instantiate()
|
||||
root.add_child(main)
|
||||
for _frame: int in 4:
|
||||
await process_frame
|
||||
if not bool(main.get("_application_initialized")):
|
||||
main.call("_activate_selected_data_path", "", true)
|
||||
for _frame: int in 8:
|
||||
await process_frame
|
||||
assert(bool(main.get("_application_initialized")))
|
||||
assert(bool(main.call(
|
||||
"_apply_world",
|
||||
WorldLayout.GENERATED,
|
||||
PlayerSaveManager.DEFAULT_WORLD_SEED,
|
||||
true,
|
||||
)))
|
||||
var weather := main.get_node(
|
||||
"%WorldWeatherService"
|
||||
) as WorldWeatherService
|
||||
assert(weather != null)
|
||||
weather.apply_authoritative_snapshot(
|
||||
WorldWeatherService.Weather.RAINY,
|
||||
60.0,
|
||||
)
|
||||
main.call("_set_gameplay_active", true)
|
||||
await physics_frame
|
||||
await physics_frame
|
||||
|
||||
var puddles := main.get_node(
|
||||
"RainPuddlePresentation"
|
||||
) as RainPuddlePresentation
|
||||
assert(puddles != null)
|
||||
assert(not puddles.is_light_performance_profile())
|
||||
assert(puddles.is_processing())
|
||||
puddles.set_suppressed(true)
|
||||
assert(not puddles.is_processing())
|
||||
puddles.set_suppressed(false)
|
||||
assert(puddles.is_processing())
|
||||
puddles.set_active(false)
|
||||
assert(not puddles.is_processing())
|
||||
puddles.set_active(true)
|
||||
assert(puddles.is_processing())
|
||||
for _tick: int in 18:
|
||||
puddles.call("_process", 0.25)
|
||||
assert(puddles.get_active_puddle_count() > 0)
|
||||
var visual_root := puddles.get_node("PuddleVisuals") as Node3D
|
||||
assert(visual_root != null)
|
||||
for puddle_visual: Node in visual_root.get_children():
|
||||
assert(puddle_visual is MeshInstance3D)
|
||||
assert(puddle_visual.get_child_count() == 0)
|
||||
|
||||
weather.apply_authoritative_snapshot(
|
||||
WorldWeatherService.Weather.SUNNY,
|
||||
60.0,
|
||||
)
|
||||
puddles.call("_process", 30.0)
|
||||
assert(puddles.get_active_puddle_count() == 0)
|
||||
_stop_audio_players(main)
|
||||
main.queue_free()
|
||||
for _frame: int in 8:
|
||||
await process_frame
|
||||
print("Rain puddle runtime validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _stop_audio_players(root_node: Node) -> void:
|
||||
if root_node is AudioStreamPlayer:
|
||||
(root_node as AudioStreamPlayer).stop()
|
||||
elif root_node is AudioStreamPlayer2D:
|
||||
(root_node as AudioStreamPlayer2D).stop()
|
||||
elif root_node is AudioStreamPlayer3D:
|
||||
(root_node as AudioStreamPlayer3D).stop()
|
||||
for child: Node in root_node.get_children():
|
||||
_stop_audio_players(child)
|
||||
1
tests/rain_puddle_runtime_validation.gd.uid
Normal file
1
tests/rain_puddle_runtime_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://jo5lqk2n8bik
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const ShorelineAmbienceType := preload("res://world/shoreline_ambience.gd")
|
||||
const WavesStream := preload("res://audio/ambience/waves.wav")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var waves := WavesStream as AudioStreamWAV
|
||||
assert(waves != null)
|
||||
|
||||
var segments: Array[PackedVector2Array] = [
|
||||
PackedVector2Array([Vector2.ZERO, Vector2(10.0, 0.0)]),
|
||||
]
|
||||
assert(is_equal_approx(
|
||||
ShorelineAmbienceType.distance_to_segments(Vector2(5.0, 3.0), segments),
|
||||
3.0,
|
||||
))
|
||||
assert(is_equal_approx(
|
||||
ShorelineAmbienceType.distance_to_segments(Vector2(-4.0, 0.0), segments),
|
||||
4.0,
|
||||
))
|
||||
|
||||
var controller := ShorelineAmbienceType.new() as ShorelineAmbience
|
||||
var runtime_audio := AudioStreamPlayer.new()
|
||||
runtime_audio.name = "WavesAudio"
|
||||
runtime_audio.unique_name_in_owner = true
|
||||
runtime_audio.stream = WavesStream
|
||||
runtime_audio.bus = &"SFX"
|
||||
controller.add_child(runtime_audio)
|
||||
runtime_audio.owner = controller
|
||||
root.add_child(controller)
|
||||
await process_frame
|
||||
assert(runtime_audio.bus == &"Environment")
|
||||
assert(runtime_audio.stream == null)
|
||||
controller.set_audio_enabled(true)
|
||||
assert(controller.near_distance < controller.far_distance)
|
||||
assert(controller.near_volume_db > controller.far_volume_db)
|
||||
var runtime_waves := runtime_audio.stream as AudioStreamWAV
|
||||
assert(runtime_waves.loop_mode == AudioStreamWAV.LOOP_FORWARD)
|
||||
assert(runtime_waves.loop_begin == 0)
|
||||
assert(runtime_waves.loop_end == 1044956)
|
||||
controller.free()
|
||||
await process_frame
|
||||
|
||||
print("Shoreline ambience validation: PASS")
|
||||
quit()
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://bwvhgpbkb00l1
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
extends SceneTree
|
||||
|
||||
const ShorelineMesh: ArrayMesh = preload(
|
||||
"res://world/generated/shorelines/starter_ocean_shoreline.tres"
|
||||
)
|
||||
const GEOMETRY_EPSILON := 0.0001
|
||||
const CROSSING_NEIGHBORHOOD := 32
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
assert(ShorelineMesh.get_surface_count() == 1)
|
||||
var arrays := ShorelineMesh.surface_get_arrays(0)
|
||||
var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array
|
||||
var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array
|
||||
assert(not vertices.is_empty())
|
||||
assert(vertices.size() % 2 == 0)
|
||||
assert(indices.size() == vertices.size() * 3)
|
||||
var point_count: int = int(float(vertices.size()) / 2.0)
|
||||
var land_edge := PackedVector2Array()
|
||||
var water_edge := PackedVector2Array()
|
||||
for index: int in point_count:
|
||||
var land := vertices[index * 2]
|
||||
var water := vertices[index * 2 + 1]
|
||||
land_edge.append(Vector2(land.x, land.z))
|
||||
water_edge.append(Vector2(water.x, water.z))
|
||||
var ribbon_width := land.distance_to(water)
|
||||
assert(ribbon_width >= 0.34)
|
||||
assert(ribbon_width <= 1.5)
|
||||
var crossing_count := (
|
||||
_count_local_crossings(water_edge)
|
||||
+ _count_local_crossings(land_edge)
|
||||
)
|
||||
if crossing_count > 0:
|
||||
push_error(
|
||||
"Shoreline ribbon contains %d local edge crossings."
|
||||
% crossing_count
|
||||
)
|
||||
quit(1)
|
||||
return
|
||||
print("Shoreline ribbon validation: PASS")
|
||||
quit()
|
||||
|
||||
|
||||
func _count_local_crossings(points: PackedVector2Array) -> int:
|
||||
var crossing_count := 0
|
||||
for first: int in points.size():
|
||||
var first_next := (first + 1) % points.size()
|
||||
var checked_neighbors := mini(
|
||||
CROSSING_NEIGHBORHOOD,
|
||||
points.size() - 2,
|
||||
)
|
||||
for offset: int in range(2, checked_neighbors + 1):
|
||||
var second := (first + offset) % points.size()
|
||||
var second_next := (second + 1) % points.size()
|
||||
if second_next == first:
|
||||
continue
|
||||
if _segments_cross(
|
||||
points[first],
|
||||
points[first_next],
|
||||
points[second],
|
||||
points[second_next],
|
||||
):
|
||||
crossing_count += 1
|
||||
return crossing_count
|
||||
|
||||
|
||||
func _segments_cross(
|
||||
a: Vector2,
|
||||
b: Vector2,
|
||||
c: Vector2,
|
||||
d: Vector2,
|
||||
) -> bool:
|
||||
if (
|
||||
maxf(a.x, b.x) < minf(c.x, d.x) - GEOMETRY_EPSILON
|
||||
or maxf(c.x, d.x) < minf(a.x, b.x) - GEOMETRY_EPSILON
|
||||
or maxf(a.y, b.y) < minf(c.y, d.y) - GEOMETRY_EPSILON
|
||||
or maxf(c.y, d.y) < minf(a.y, b.y) - GEOMETRY_EPSILON
|
||||
):
|
||||
return false
|
||||
var first_direction := b - a
|
||||
var second_direction := d - c
|
||||
var denominator := first_direction.cross(second_direction)
|
||||
if absf(denominator) <= GEOMETRY_EPSILON:
|
||||
return false
|
||||
var offset := c - a
|
||||
var first_amount := offset.cross(second_direction) / denominator
|
||||
var second_amount := offset.cross(first_direction) / denominator
|
||||
return (
|
||||
first_amount > GEOMETRY_EPSILON
|
||||
and first_amount < 1.0 - GEOMETRY_EPSILON
|
||||
and second_amount > GEOMETRY_EPSILON
|
||||
and second_amount < 1.0 - GEOMETRY_EPSILON
|
||||
)
|
||||
|
|
@ -1 +0,0 @@
|
|||
uid://c73frygpfhred
|
||||
|
|
@ -19,6 +19,9 @@ const FishAvailabilityType = preload("res://fish/fish_availability.gd")
|
|||
const FishableWaterRegionType = preload(
|
||||
"res://world/fishable_water_region.gd"
|
||||
)
|
||||
const RainPuddlePresentationType = preload(
|
||||
"res://world/rain_puddle_presentation.gd"
|
||||
)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
|
|
@ -34,6 +37,7 @@ func _run() -> void:
|
|||
_validate_fishing_weather_seams()
|
||||
_validate_fishing_spot_context()
|
||||
_validate_weather_presentation()
|
||||
_validate_rain_puddle_surface_rules()
|
||||
print("World weather validation: PASS")
|
||||
quit()
|
||||
|
||||
|
|
@ -285,6 +289,53 @@ func _validate_fishing_spot_context() -> void:
|
|||
weather.free()
|
||||
|
||||
|
||||
func _validate_rain_puddle_surface_rules() -> void:
|
||||
var dry_flat := PackedVector3Array([
|
||||
Vector3(-1.0, 0.0, -1.0),
|
||||
Vector3(2.0, 0.0, -1.0),
|
||||
Vector3(-1.0, 0.0, 2.0),
|
||||
])
|
||||
assert(RainPuddlePresentationType.is_puddle_surface_eligible(
|
||||
dry_flat,
|
||||
-0.25,
|
||||
))
|
||||
var water_edge := PackedVector3Array([
|
||||
Vector3(-1.0, 0.0, -1.0),
|
||||
Vector3(2.0, 0.0, -1.0),
|
||||
Vector3(-1.0, -0.2, 2.0),
|
||||
])
|
||||
assert(not RainPuddlePresentationType.is_puddle_surface_eligible(
|
||||
water_edge,
|
||||
-0.25,
|
||||
))
|
||||
var steep_ground := PackedVector3Array([
|
||||
Vector3(-1.0, 0.0, -1.0),
|
||||
Vector3(2.0, 0.0, -1.0),
|
||||
Vector3(-1.0, 2.0, -0.75),
|
||||
])
|
||||
assert(not RainPuddlePresentationType.is_puddle_surface_eligible(
|
||||
steep_ground,
|
||||
-0.25,
|
||||
))
|
||||
var center := Vector3(4.0, 1.0, -3.0)
|
||||
var radii := Vector2(1.0, 0.5)
|
||||
assert(RainPuddlePresentationType.is_point_inside_puddle(
|
||||
Vector3(4.75, 1.0, -3.0),
|
||||
center,
|
||||
radii,
|
||||
))
|
||||
assert(not RainPuddlePresentationType.is_point_inside_puddle(
|
||||
Vector3(5.1, 1.0, -3.0),
|
||||
center,
|
||||
radii,
|
||||
))
|
||||
assert(not RainPuddlePresentationType.is_point_inside_puddle(
|
||||
Vector3(4.0, 2.1, -3.0),
|
||||
center,
|
||||
radii,
|
||||
))
|
||||
|
||||
|
||||
func _validate_weather_presentation() -> void:
|
||||
var world_root := Node3D.new()
|
||||
root.add_child(world_root)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue