Add weather presentation and gameplay UI improvements

This commit is contained in:
Alexander Sellite 2026-08-13 01:31:05 -04:00
parent db6074ddfa
commit 51fa6535b6
28 changed files with 1067 additions and 61 deletions

View file

@ -21,7 +21,8 @@ independent team Woofmeow.
arms, ears and tails, and multiple world props and decorative assets, arms, ears and tails, and multiple world props and decorative assets,
including trees and bridges. The contributor requested to be credited as including trees and bridges. The contributor requested to be credited as
`chillnfill`. `chillnfill`.
- Tekgator: creator of the cooler-capacity and barrier-power shop icons. - Tekgator: creator of the cooler-capacity, reel-speed, and barrier-power shop
icons.
Voyager is also the original catalog porter credit in PortMaster metadata. Voyager is also the original catalog porter credit in PortMaster metadata.
Third-party creators are credited with their individual records below. Those Third-party creators are credited with their individual records below. Those
@ -124,6 +125,7 @@ locations only and must never appear in scenes or resources.
| Fish art and portraits | `fish/species/`, `art/exported/fish/` | Original 2D artwork by co-owner Endeavour. | | Fish art and portraits | `fish/species/`, `art/exported/fish/` | Original 2D artwork by co-owner Endeavour. |
| Item icons | `items/icons/` | Original 2D artwork by co-owner Endeavour except where separately credited below. | | Item icons | `items/icons/` | Original 2D artwork by co-owner Endeavour except where separately credited below. |
| Cooler-capacity shop icon | `items/icons/equipment/64_cooler_plus.png` | Original artwork by Tekgator. Repository SHA-256: `b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`. | | Cooler-capacity shop icon | `items/icons/equipment/64_cooler_plus.png` | Original artwork by Tekgator. Repository SHA-256: `b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`. |
| Reel-speed shop icon | `items/icons/shop/64_speed_plus.png` | Original artwork by Tekgator. Repository SHA-256: `938452798f2be505e06ce3fbef178f52459331fd3bced8763fbc2a80157125ca`. |
| Barrier-power shop icon | `items/icons/shop/64_power_plus.png` | Original artwork by Tekgator. Repository SHA-256: `164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`. | | Barrier-power shop icon | `items/icons/shop/64_power_plus.png` | Original artwork by Tekgator. Repository SHA-256: `164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`. |
| Inventory notepad | `art/ui/ui_notepad.png` | Original 2D artwork by co-owner Endeavour; integrated without a runtime external path. | | Inventory notepad | `art/ui/ui_notepad.png` | Original 2D artwork by co-owner Endeavour; integrated without a runtime external path. |
| Environment textures | `art/exported/environment/textures/` | Original 2D artwork by co-owner Endeavour. | | Environment textures | `art/exported/environment/textures/` | Original 2D artwork by co-owner Endeavour. |
@ -182,10 +184,13 @@ authored `mesh_style.png` filenames.
### Tekgator shop icon contribution record ### Tekgator shop icon contribution record
- Requested credit: `Tekgator`. - Requested credit: `Tekgator`.
- Original contributions: cooler-capacity and barrier-power shop icons. - Original contributions: cooler-capacity, reel-speed, and barrier-power shop
icons.
- Repository files and SHA-256: - Repository files and SHA-256:
- `items/icons/equipment/64_cooler_plus.png`: - `items/icons/equipment/64_cooler_plus.png`:
`b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7` `b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`
- `items/icons/shop/64_speed_plus.png`:
`938452798f2be505e06ce3fbef178f52459331fd3bced8763fbc2a80157125ca`
- `items/icons/shop/64_power_plus.png`: - `items/icons/shop/64_power_plus.png`:
`164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7` `164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`
- Permission record: retain the original permission record or a later written - Permission record: retain the original permission record or a later written

View file

@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<g fill="none" stroke="#17243a" stroke-width="5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="25" cy="34" r="14" fill="#54c7c0"/>
<circle cx="25" cy="34" r="4"/>
<path d="M36 24 l8-8"/>
<path d="M43 16 l7 7"/>
<path d="M40 38 h16"/>
<path d="M50 31 l7 7 -7 7"/>
<path d="M9 14 h13"/>
<path d="M6 23 h11"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -2,16 +2,16 @@
importer="texture" importer="texture"
type="CompressedTexture2D" type="CompressedTexture2D"
uid="uid://c2hal1gkmnuqa" uid="uid://dk6yx5cbpblo6"
path="res://.godot/imported/reel_speed_upgrade.svg-6bce0cfdbf18895938d0bcb4850467c8.ctex" path="res://.godot/imported/64_speed_plus.png-c695fb4021c17dfa94ed7aa48ce262c1.ctex"
metadata={ metadata={
"vram_texture": false "vram_texture": false
} }
[deps] [deps]
source_file="res://items/icons/placeholder/reel_speed_upgrade.svg" source_file="res://items/icons/shop/64_speed_plus.png"
dest_files=["res://.godot/imported/reel_speed_upgrade.svg-6bce0cfdbf18895938d0bcb4850467c8.ctex"] dest_files=["res://.godot/imported/64_speed_plus.png-c695fb4021c17dfa94ed7aa48ce262c1.ctex"]
[params] [params]
@ -38,6 +38,3 @@ process/hdr_as_srgb=false
process/hdr_clamp_exposure=false process/hdr_clamp_exposure=false
process/size_limit=0 process/size_limit=0
detect_3d/compress_to=1 detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -420,6 +420,7 @@ func _initialize_application(dedicated: bool) -> void:
_test_world.get_sun(), _test_world.get_sun(),
_world_weather, _world_weather,
_player, _player,
Callable(_player, "get_active_gameplay_camera"),
) )
if not _world_time.natural_time_advanced.is_connected( if not _world_time.natural_time_advanced.is_connected(
_on_natural_time_advanced _on_natural_time_advanced

View file

@ -1005,7 +1005,7 @@ func _unhandled_input(event: InputEvent) -> void:
if not local_control_enabled or not _is_camera_input_enabled(): if not local_control_enabled or not _is_camera_input_enabled():
return return
if event.is_action_pressed("toggle_free_camera"): if event.is_action_pressed("toggle_free_camera"):
_set_free_camera_active(not _free_camera_active) toggle_free_camera()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
return return
@ -1140,6 +1140,16 @@ func _set_free_camera_active(active: bool) -> void:
_camera.current = local_control_enabled _camera.current = local_control_enabled
func toggle_free_camera() -> void:
if not local_control_enabled:
return
_set_free_camera_active(not _free_camera_active)
func is_free_camera_active() -> bool:
return _free_camera_active
func _update_free_camera_physics() -> void: func _update_free_camera_physics() -> void:
if _free_camera == null or _free_camera_body == null: if _free_camera == null or _free_camera_body == null:
_set_free_camera_active(false) _set_free_camera_active(false)

View file

@ -85,6 +85,14 @@ func _run() -> void:
assert(not toolbar.is_docked_right()) assert(not toolbar.is_docked_right())
var chat_panel := chat_ui.get_node("ChatPanel") as PanelContainer var chat_panel := chat_ui.get_node("ChatPanel") as PanelContainer
var chat_height_button := chat_ui.get_node("ChatHeightButton") as Button var chat_height_button := chat_ui.get_node("ChatHeightButton") as Button
var chat_status := chat_ui.find_child(
"ChatStatus", true, false
) as Label
assert(chat_status != null)
assert(
chat_status.autowrap_mode
== TextServer.AUTOWRAP_WORD_SMART
)
assert(is_zero_approx(chat_panel.position.y)) assert(is_zero_approx(chat_panel.position.y))
assert(is_equal_approx(chat_panel.size.x, ChatUI.MOBILE_COMPACT_WIDTH)) assert(is_equal_approx(chat_panel.size.x, ChatUI.MOBILE_COMPACT_WIDTH))
assert(not chat_height_button.visible) assert(not chat_height_button.visible)
@ -101,6 +109,17 @@ func _run() -> void:
default_docks.paint_dock_right = true default_docks.paint_dock_right = true
assert(settings_manager.apply_settings(default_docks)) assert(settings_manager.apply_settings(default_docks))
await process_frame await process_frame
chat_ui.call(
"_set_status",
(
"Unknown command: /this-command-name-is-intentionally-long-"
+ "enough-to-require-smart-character-wrapping"
),
)
await process_frame
assert(is_equal_approx(chat_panel.size.x, ChatUI.PANEL_WIDTH))
assert(chat_status.size.x <= ChatUI.PANEL_WIDTH)
chat_ui.call("_set_status", "")
assert(not chat_ui.is_docked_right()) assert(not chat_ui.is_docked_right())
assert(not chat_ui.is_mobile_mode()) assert(not chat_ui.is_mobile_mode())
assert(toolbar.is_docked_right()) assert(toolbar.is_docked_right())
@ -140,6 +159,42 @@ func _run() -> void:
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button))) assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
assert(chat_ui.is_open()) assert(chat_ui.is_open())
chat_ui.refocus_gameplay() chat_ui.refocus_gameplay()
var quick_menu := game_ui.get_node(
"%QuickRadialMenu"
) as QuickRadialMenu
var emote_menu := game_ui.get_node(
"%EmoteRadialMenu"
) as EmoteRadialMenu
assert(quick_menu != null and emote_menu != null)
assert(QuickRadialMenu.ACTIONS.size() == QuickRadialMenu.SECTOR_COUNT)
assert(QuickRadialMenu.ACTIONS.has(&"freecam"))
assert(QuickRadialMenu.ACTIONS.has(&"hud"))
game_ui.call("_on_quick_action_selected", &"freecam")
assert(player.is_free_camera_active())
assert(player.get_active_gameplay_camera().name == &"FreeCamera")
game_ui.call("_on_quick_action_selected", &"freecam")
assert(not player.is_free_camera_active())
game_ui.call("_on_quick_action_selected", &"hud")
await process_frame
assert(game_ui.is_gameplay_hud_hidden())
assert(chat_ui.is_hud_hidden())
assert(not (
game_ui.get_node("%GameplayTransientHUD") as Control
).visible)
assert(not (
game_ui.get_node("%ExperiencePresentation") as Control
).visible)
quick_menu.open_menu(true)
assert(quick_menu.visible and quick_menu.is_open())
quick_menu.close_menu()
emote_menu.open_menu(true)
assert(emote_menu.visible and emote_menu.is_open())
emote_menu.close_menu()
chat_ui.open_chat()
await process_frame
assert(chat_ui.is_open() and chat_panel.visible)
chat_ui.refocus_gameplay()
assert(chat_ui.is_hud_hidden())
var cancel_button := InputEventJoypadButton.new() var cancel_button := InputEventJoypadButton.new()
cancel_button.button_index = JOY_BUTTON_B cancel_button.button_index = JOY_BUTTON_B
cancel_button.pressed = true cancel_button.pressed = true
@ -152,10 +207,16 @@ func _run() -> void:
player_menu.open_section(PlayerMenu.Section.PROFILE) player_menu.open_section(PlayerMenu.Section.PROFILE)
for _frame: int in 12: for _frame: int in 12:
await process_frame await process_frame
assert(game_ui.is_gameplay_hud_hidden())
assert(player_menu.visible)
assert(root.gui_get_focus_owner() is not LineEdit) assert(root.gui_get_focus_owner() is not LineEdit)
player_menu.close_menu() player_menu.close_menu()
for _frame: int in 12: for _frame: int in 12:
await process_frame await process_frame
game_ui.call("_on_quick_action_selected", &"hud")
await process_frame
assert(not game_ui.is_gameplay_hud_hidden())
assert(not chat_ui.is_hud_hidden())
assert(not service.can_activate()) assert(not service.can_activate())
assert(not service.is_active() and not toolbar.visible) assert(not service.is_active() and not toolbar.visible)
assert(player.bag.add_item(ArtShopStock.ART_KIT_ITEM_ID, 1)) assert(player.bag.add_item(ArtShopStock.ART_KIT_ITEM_ID, 1))

View file

@ -31,13 +31,15 @@ func _run_host() -> void:
main.get_node("%WorldWeatherService") as WorldWeatherService main.get_node("%WorldWeatherService") as WorldWeatherService
) )
assert(session.start_private_host(TEST_PORT)) assert(session.start_private_host(TEST_PORT))
var game_ui := main.get_node("%GameUI") as CanvasLayer
var chat_ui := game_ui.get_node("%ChatUI") as ChatUI
assert(chat_ui.call("_handle_chat_command", "/weather clear"))
assert(world_weather.get_weather() == WorldWeatherService.Weather.SUNNY)
world_time.synchronize_time(INITIAL_HOST_TIME) world_time.synchronize_time(INITIAL_HOST_TIME)
assert(is_equal_approx( assert(is_equal_approx(
world_time.get_persistent_time_hours(), INITIAL_HOST_TIME world_time.get_persistent_time_hours(), INITIAL_HOST_TIME
)) ))
world_weather.apply_authoritative_snapshot( assert(chat_ui.call("_handle_chat_command", "/weather rainy"))
WorldWeatherService.Weather.RAINY, 300.0
)
assert( assert(
world_weather.get_persistent_weather() world_weather.get_persistent_weather()
== WorldWeatherService.Weather.RAINY == WorldWeatherService.Weather.RAINY
@ -66,9 +68,7 @@ func _run_host() -> void:
assert(is_equal_approx( assert(is_equal_approx(
world_time.get_persistent_time_hours(), UPDATED_HOST_TIME world_time.get_persistent_time_hours(), UPDATED_HOST_TIME
)) ))
world_weather.apply_authoritative_snapshot( assert(chat_ui.call("_handle_chat_command", "/weather foggy"))
WorldWeatherService.Weather.FOGGY, 300.0
)
assert( assert(
world_weather.get_persistent_weather() world_weather.get_persistent_weather()
== WorldWeatherService.Weather.FOGGY == WorldWeatherService.Weather.FOGGY

View file

@ -249,6 +249,15 @@ func _validate_environment_presentation() -> void:
< 0.01 < 0.01
) )
var day_ambient_energy: float = runtime_environment.ambient_light_energy var day_ambient_energy: float = runtime_environment.ambient_light_energy
assert(not runtime_environment.fog_enabled)
assert(is_equal_approx(runtime_environment.fog_sky_affect, 0.35))
var water_shader: Shader = preload(
"res://world/materials/stylized_water.gdshader"
)
assert("fog_disabled" in water_shader.code)
assert(is_zero_approx(float(
SaltWaterMaterial.get_shader_parameter("weather_fog_amount")
)))
var day_water_brightness := float( var day_water_brightness := float(
SaltWaterMaterial.get_shader_parameter("environment_brightness") SaltWaterMaterial.get_shader_parameter("environment_brightness")
) )
@ -292,17 +301,24 @@ func _validate_environment_presentation() -> void:
) )
assert(night_water_tint.get_luminance() < 0.5) assert(night_water_tint.get_luminance() < 0.5)
var night_background_energy := runtime_environment.background_energy_multiplier var night_background_energy := runtime_environment.background_energy_multiplier
var night_ambient_energy := runtime_environment.ambient_light_energy
var night_fog_light_energy := runtime_environment.fog_light_energy
var night_adjustment_brightness := runtime_environment.adjustment_brightness var night_adjustment_brightness := runtime_environment.adjustment_brightness
var night_adjustment_saturation := runtime_environment.adjustment_saturation var night_adjustment_saturation := runtime_environment.adjustment_saturation
visuals.apply_weather_immediately(WorldWeatherService.Weather.FOGGY) visuals.apply_weather_immediately(WorldWeatherService.Weather.FOGGY)
visuals.apply_time_immediately(0.0) visuals.apply_time_immediately(0.0)
assert(runtime_environment.fog_enabled)
assert(float(
SaltWaterMaterial.get_shader_parameter("weather_fog_amount")
) > 0.99)
var foggy_sky_horizon := ( var foggy_sky_horizon := (
runtime_sky_material.get_shader_parameter("sky_horizon_color") as Color runtime_sky_material.get_shader_parameter("sky_horizon_color") as Color
) )
assert(foggy_sky_horizon.is_equal_approx(night_horizon)) assert(foggy_sky_horizon.is_equal_approx(night_horizon))
assert(is_zero_approx(runtime_environment.fog_sky_affect)) assert(is_equal_approx(runtime_environment.fog_sky_affect, 1.0))
assert(is_zero_approx(runtime_environment.fog_aerial_perspective))
assert(is_equal_approx(runtime_environment.fog_depth_begin, 3.0)) assert(is_equal_approx(runtime_environment.fog_depth_begin, 3.0))
assert(is_equal_approx(runtime_environment.fog_depth_end, 28.0)) assert(is_equal_approx(runtime_environment.fog_depth_end, 18.0))
assert(is_equal_approx( assert(is_equal_approx(
float(SaltWaterMaterial.get_shader_parameter("environment_brightness")), float(SaltWaterMaterial.get_shader_parameter("environment_brightness")),
night_water_brightness, night_water_brightness,
@ -311,6 +327,14 @@ func _validate_environment_presentation() -> void:
runtime_environment.background_energy_multiplier, runtime_environment.background_energy_multiplier,
night_background_energy, night_background_energy,
)) ))
assert(is_equal_approx(
runtime_environment.ambient_light_energy,
night_ambient_energy,
))
assert(is_equal_approx(
runtime_environment.fog_light_energy,
night_fog_light_energy,
))
assert(is_equal_approx( assert(is_equal_approx(
runtime_environment.adjustment_brightness, runtime_environment.adjustment_brightness,
night_adjustment_brightness, night_adjustment_brightness,
@ -321,6 +345,10 @@ func _validate_environment_presentation() -> void:
)) ))
visuals.apply_weather_immediately(WorldWeatherService.Weather.SUNNY) visuals.apply_weather_immediately(WorldWeatherService.Weather.SUNNY)
visuals.apply_time_immediately(20.0) visuals.apply_time_immediately(20.0)
assert(not runtime_environment.fog_enabled)
assert(is_zero_approx(float(
SaltWaterMaterial.get_shader_parameter("weather_fog_amount")
)))
var dusk_horizon: Color = runtime_sky_material.get_shader_parameter( var dusk_horizon: Color = runtime_sky_material.get_shader_parameter(
"sky_horizon_color" "sky_horizon_color"
) as Color ) as Color

View file

@ -24,6 +24,7 @@ func _initialize() -> void:
func _run() -> void: func _run() -> void:
_validate_weather_scheduler() _validate_weather_scheduler()
_validate_authoritative_weather_override()
_validate_weather_persistence() _validate_weather_persistence()
_validate_snapshot_bounds() _validate_snapshot_bounds()
_validate_fishing_weather_seams() _validate_fishing_weather_seams()
@ -74,6 +75,54 @@ func _duration_is_valid(weather: WorldWeatherServiceType) -> bool:
return false return false
func _validate_authoritative_weather_override() -> void:
var weather := WorldWeatherServiceType.new()
root.add_child(weather)
assert(not weather.set_authoritative_weather(
WorldWeatherServiceType.Weather.FOGGY
))
weather.begin_authoritative_session(20260812)
assert(weather.set_authoritative_weather(
WorldWeatherServiceType.Weather.FOGGY
))
assert(weather.is_foggy())
assert(_duration_is_valid(weather))
weather.end_session()
assert(not weather.set_authoritative_weather(
WorldWeatherServiceType.Weather.RAINY
))
var clock := WorldTimeServiceType.new()
root.add_child(clock)
clock.begin_session(WorldTimeServiceType.DAY_START_HOUR)
var schedule: Array[Dictionary] = []
for index: int in WorldWeatherServiceType.DAILY_PLAN_SEGMENT_COUNT:
schedule.append({
"start_hour": fposmod(
WorldTimeServiceType.DAY_START_HOUR
+ float(index)
* WorldWeatherServiceType.DAILY_PLAN_SEGMENT_HOURS,
WorldTimeServiceType.HOURS_PER_DAY,
),
"weather": int(WorldWeatherServiceType.Weather.SUNNY),
})
assert(weather.configure_daily_plan("command-test", schedule, clock))
weather.begin_authoritative_session(20260812)
assert(weather.set_authoritative_weather(
WorldWeatherServiceType.Weather.RAINY
))
weather.advance_weather(1.0)
assert(weather.is_raining())
clock.synchronize_time(
WorldTimeServiceType.DAY_START_HOUR
+ WorldWeatherServiceType.DAILY_PLAN_SEGMENT_HOURS
)
weather.advance_weather(0.01)
assert(weather.get_weather() == WorldWeatherServiceType.Weather.SUNNY)
weather.queue_free()
clock.queue_free()
func _validate_weather_persistence() -> void: func _validate_weather_persistence() -> void:
var weather := WorldWeatherServiceType.new() var weather := WorldWeatherServiceType.new()
root.add_child(weather) root.add_child(weather)
@ -212,20 +261,118 @@ func _validate_weather_presentation() -> void:
weather.begin_remote_session() weather.begin_remote_session()
var rain_target := Node3D.new() var rain_target := Node3D.new()
world_root.add_child(rain_target) world_root.add_child(rain_target)
var rain_camera := Camera3D.new()
world_root.add_child(rain_camera)
rain_target.global_position = Vector3(-18.0, 2.0, 14.0)
rain_camera.global_position = Vector3(24.0, 9.0, -18.0)
var visuals := WorldTimeVisualControllerType.new() var visuals := WorldTimeVisualControllerType.new()
world_root.add_child(visuals) world_root.add_child(visuals)
visuals.setup( visuals.setup(
clock, world_environment, sun, weather, rain_target clock,
world_environment,
sun,
weather,
rain_target,
func() -> Camera3D: return rain_camera,
) )
var runtime_environment: Environment = world_environment.environment var runtime_environment: Environment = world_environment.environment
var runtime_sky_material := (
runtime_environment.sky.sky_material as ShaderMaterial
)
var clear_background_energy: float = (
runtime_environment.background_energy_multiplier
)
var clear_ambient_energy: float = runtime_environment.ambient_light_energy
var clear_fog_light_energy: float = runtime_environment.fog_light_energy
var clear_brightness: float = runtime_environment.adjustment_brightness
var clear_saturation: float = runtime_environment.adjustment_saturation var clear_saturation: float = runtime_environment.adjustment_saturation
var clear_sun_energy: float = sun.light_energy
var clear_water_brightness: float = float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"environment_brightness"
)
)
assert(not runtime_environment.fog_enabled)
assert(is_zero_approx(float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
)))
assert(is_equal_approx(runtime_environment.fog_sky_affect, 0.35))
assert(is_zero_approx(float(
runtime_sky_material.get_shader_parameter("cloud_opacity")
)))
var storm_clouds := visuals.get_node("LocalStormClouds") as Node3D
assert(storm_clouds != null)
assert(storm_clouds.call("get_patch_count") == 81)
var cloud_field := storm_clouds.get_node("CloudField") as MultiMeshInstance3D
assert(cloud_field != null)
assert(cloud_field.multimesh != null)
assert(cloud_field.multimesh.instance_count == 81)
assert(cloud_field.multimesh.mesh is ArrayMesh)
var cloud_shader: Shader = preload(
"res://world/environment/local_storm_cloud.gdshader"
)
assert("cull_disabled" in cloud_shader.code)
assert(LocalStormCloudLayer.WRAP_RADIUS >= 315.0)
assert(is_equal_approx(LocalStormCloudLayer.MAXIMUM_OPACITY, 1.0))
assert(
LocalStormCloudLayer.DISTANCE_FADE_END
< LocalStormCloudLayer.WRAP_RADIUS
)
assert(is_zero_approx(float(storm_clouds.call("get_storm_amount"))))
visuals.apply_weather_immediately( visuals.apply_weather_immediately(
WorldWeatherServiceType.Weather.FOGGY WorldWeatherServiceType.Weather.FOGGY
) )
assert(runtime_environment.fog_enabled)
assert(float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
) > 0.99)
var water_fog_color := (
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_color"
) as Color
)
assert(water_fog_color.get_luminance() < 0.05)
assert(runtime_environment.fog_depth_begin <= 4.01) assert(runtime_environment.fog_depth_begin <= 4.01)
assert(runtime_environment.fog_depth_end <= 42.01) assert(is_equal_approx(runtime_environment.fog_depth_end, 18.0))
assert(is_zero_approx(runtime_environment.fog_sky_affect)) assert(is_equal_approx(runtime_environment.fog_sky_affect, 1.0))
assert(runtime_environment.fog_aerial_perspective >= 0.91) assert(is_zero_approx(runtime_environment.fog_aerial_perspective))
assert(is_zero_approx(float(
runtime_sky_material.get_shader_parameter("cloud_opacity")
)))
assert(is_equal_approx(
runtime_environment.background_energy_multiplier,
clear_background_energy
* WorldTimeVisualControllerType.FOG_DAYLIGHT_SCENE_BRIGHTNESS,
))
assert(is_equal_approx(
runtime_environment.ambient_light_energy,
clear_ambient_energy
* WorldTimeVisualControllerType.FOG_DAYLIGHT_SCENE_BRIGHTNESS,
))
assert(is_equal_approx(
runtime_environment.fog_light_energy,
clear_fog_light_energy
* WorldTimeVisualControllerType.FOG_DAYLIGHT_FOG_LIGHT_BRIGHTNESS,
))
assert(is_equal_approx(
runtime_environment.adjustment_brightness,
clear_brightness
* WorldTimeVisualControllerType.FOG_DAYLIGHT_POST_BRIGHTNESS,
))
assert(is_equal_approx(sun.light_energy, clear_sun_energy * 0.30))
assert(is_equal_approx(
float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"environment_brightness"
)
),
clear_water_brightness
* WorldTimeVisualControllerType.FOG_DAYLIGHT_WATER_BRIGHTNESS,
))
assert(is_equal_approx( assert(is_equal_approx(
runtime_environment.adjustment_saturation, runtime_environment.adjustment_saturation,
clear_saturation, clear_saturation,
@ -233,8 +380,41 @@ func _validate_weather_presentation() -> void:
visuals.apply_weather_immediately( visuals.apply_weather_immediately(
WorldWeatherServiceType.Weather.RAINY WorldWeatherServiceType.Weather.RAINY
) )
assert(runtime_environment.fog_enabled)
assert(float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
) > 0.99)
assert(is_equal_approx(runtime_environment.fog_sky_affect, 0.68))
assert(
float(runtime_sky_material.get_shader_parameter("cloud_coverage"))
> 0.98
)
assert(
float(runtime_sky_material.get_shader_parameter("cloud_opacity"))
> 0.99
)
assert(float(storm_clouds.call("get_storm_amount")) > 0.87)
assert(storm_clouds.visible)
assert(is_equal_approx(
runtime_environment.adjustment_brightness,
clear_brightness
* 0.92
* WorldTimeVisualControllerType.RAIN_DAYLIGHT_POST_BRIGHTNESS,
))
var rain := visuals.get_node("LocalRain") as GPUParticles3D var rain := visuals.get_node("LocalRain") as GPUParticles3D
assert(rain != null) assert(rain != null)
assert(rain.global_position.is_equal_approx(
rain_camera.global_position
+ WorldTimeVisualControllerType.RAIN_EMITTER_OFFSET
))
rain_camera.global_position = Vector3(-31.0, 15.0, 42.0)
visuals.call("_update_rain_position")
assert(rain.global_position.is_equal_approx(
rain_camera.global_position
+ WorldTimeVisualControllerType.RAIN_EMITTER_OFFSET
))
assert(rain.emitting) assert(rain.emitting)
assert(rain.amount_ratio > 0.99) assert(rain.amount_ratio > 0.99)
assert(rain.amount == WorldTimeVisualControllerType.RAIN_PARTICLE_AMOUNT) assert(rain.amount == WorldTimeVisualControllerType.RAIN_PARTICLE_AMOUNT)
@ -253,8 +433,52 @@ func _validate_weather_presentation() -> void:
assert(rain_mesh.size.is_equal_approx( assert(rain_mesh.size.is_equal_approx(
WorldTimeVisualControllerType.RAIN_DROP_SIZE WorldTimeVisualControllerType.RAIN_DROP_SIZE
)) ))
visuals.call(
"_on_weather_changed",
WorldWeatherServiceType.Weather.SUNNY,
60.0,
)
visuals.call(
"_process",
WorldTimeVisualControllerType.WEATHER_TRANSITION_SECONDS * 0.5,
)
var halfway_water_fog: float = float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
)
assert(halfway_water_fog > 0.49 and halfway_water_fog < 0.51)
assert(is_equal_approx(
float(
WorldTimeVisualControllerType.FRESH_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
),
halfway_water_fog,
))
assert(runtime_environment.fog_enabled)
visuals.call(
"_process",
WorldTimeVisualControllerType.WEATHER_TRANSITION_SECONDS * 0.5,
)
assert(is_zero_approx(float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
)))
assert(not runtime_environment.fog_enabled)
visuals.apply_weather_immediately(
WorldWeatherServiceType.Weather.CLOUDY
)
assert(not runtime_environment.fog_enabled)
assert(is_zero_approx(float(
WorldTimeVisualControllerType.SALT_WATER_MATERIAL.get_shader_parameter(
"weather_fog_amount"
)
)))
visuals.apply_weather_immediately( visuals.apply_weather_immediately(
WorldWeatherServiceType.Weather.SUNNY WorldWeatherServiceType.Weather.SUNNY
) )
assert(not runtime_environment.fog_enabled)
assert(not rain.emitting) assert(not rain.emitting)
world_root.queue_free() world_root.queue_free()

View file

@ -99,6 +99,7 @@ var _opacity_tween: Tween
var _height_tween: Tween var _height_tween: Tween
var _opened: bool = false var _opened: bool = false
var _available: bool = false var _available: bool = false
var _hud_hidden: bool = false
var _presentation_state := PresentationState.COMPACT var _presentation_state := PresentationState.COMPACT
var _visible_state_before_collapse := PresentationState.COMPACT var _visible_state_before_collapse := PresentationState.COMPACT
var _panel_hovered: bool = false var _panel_hovered: bool = false
@ -435,7 +436,10 @@ func _build_ui() -> void:
_history.add_theme_stylebox_override("normal", _borderless_style(Color.TRANSPARENT)) _history.add_theme_stylebox_override("normal", _borderless_style(Color.TRANSPARENT))
box.add_child(_history) box.add_child(_history)
_status = Label.new() _status = Label.new()
_status.name = "ChatStatus"
_status.custom_minimum_size = Vector2(0, 20) _status.custom_minimum_size = Vector2(0, 20)
_status.size_flags_horizontal = Control.SIZE_EXPAND_FILL
_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
_status.hide() _status.hide()
_status.mouse_filter = Control.MOUSE_FILTER_IGNORE _status.mouse_filter = Control.MOUSE_FILTER_IGNORE
_status.add_theme_font_override("font", UtilityPageStyle.TuffyFont) _status.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
@ -770,6 +774,8 @@ func _handle_chat_command(body: String) -> bool:
match command: match command:
"time": "time":
_handle_time_command(parts) _handle_time_command(parts)
"weather":
_handle_weather_command(parts)
"": "":
_set_status("Enter a command after /.") _set_status("Enter a command after /.")
_: _:
@ -814,6 +820,41 @@ func _handle_time_command(parts: PackedStringArray) -> void:
close_chat() close_chat()
func _handle_weather_command(parts: PackedStringArray) -> void:
if parts.size() != 2:
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
return
if _session == null or not _session.is_host():
_set_status("Only the host can change world weather.")
return
if _world_weather == null:
_set_status("World weather is unavailable.")
return
var weather_name := String(parts[1]).to_lower()
var target_weather: WorldWeatherServiceType.Weather
match weather_name:
"clear", "sunny":
target_weather = WorldWeatherServiceType.Weather.SUNNY
weather_name = "clear"
"cloudy":
target_weather = WorldWeatherServiceType.Weather.CLOUDY
"rainy":
target_weather = WorldWeatherServiceType.Weather.RAINY
"foggy":
target_weather = WorldWeatherServiceType.Weather.FOGGY
_:
_set_status("Usage: /weather [clear, cloudy, rainy, foggy]")
return
if not _world_weather.set_authoritative_weather(target_weather):
_set_status("World weather could not be changed.")
return
_set_status("")
_service.broadcast_system_message(
"World weather set to %s." % weather_name
)
close_chat()
func _on_local_message_confirmed(message: Dictionary) -> void: func _on_local_message_confirmed(message: Dictionary) -> void:
if ( if (
not _send_pending not _send_pending
@ -956,6 +997,16 @@ func _refresh_visibility() -> void:
_unread_indicator.hide() _unread_indicator.hide()
_hint.hide() _hint.hide()
return return
if _hud_hidden and not _opened:
_panel.hide()
_clock_panel.hide()
_weather_icon.hide()
_fish_finder_effect_icon.hide()
_collapse_button.hide()
_height_button.hide()
_unread_indicator.hide()
_hint.hide()
return
_clock_panel.show() _clock_panel.show()
_weather_icon.show() _weather_icon.show()
_fish_finder_effect_icon.visible = ( _fish_finder_effect_icon.visible = (
@ -1044,6 +1095,15 @@ func is_mobile_mode() -> bool:
return _mobile_mode return _mobile_mode
func set_hud_hidden(hidden: bool) -> void:
_hud_hidden = hidden
_refresh_visibility()
func is_hud_hidden() -> bool:
return _hud_hidden
func _refresh_handle_labels(state: PresentationState) -> void: func _refresh_handle_labels(state: PresentationState) -> void:
var collapsed := state == PresentationState.COLLAPSED var collapsed := state == PresentationState.COLLAPSED
var height_state := _visible_state_before_collapse if collapsed else state var height_state := _visible_state_before_collapse if collapsed else state

View file

@ -2,7 +2,7 @@
[ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"] [ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"]
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/reel_speed_upgrade.svg" id="3_reel"] [ext_resource type="Texture2D" path="res://items/icons/shop/64_speed_plus.png" id="3_reel"]
[ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"] [ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"]
[ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"] [ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"]
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"] [ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]

View file

@ -167,6 +167,7 @@ var _bag: PlayerBagType
var _item_catalog: ItemCatalogType var _item_catalog: ItemCatalogType
var _player_menu_open: bool = false var _player_menu_open: bool = false
var _gameplay_ui_enabled: bool = false var _gameplay_ui_enabled: bool = false
var _gameplay_hud_hidden: bool = false
var _fishing_spot: FishingSpotType var _fishing_spot: FishingSpotType
var _system_menu_open: bool = false var _system_menu_open: bool = false
var _shop_open: bool = false var _shop_open: bool = false
@ -428,6 +429,14 @@ func _input(event: InputEvent) -> void:
if _handle_controller_chat_controls(event): if _handle_controller_chat_controls(event):
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
return return
if (
event.is_action_pressed("toggle_hud")
and not (event is InputEventKey and event.echo)
and _can_toggle_gameplay_hud()
):
set_gameplay_hud_hidden(not _gameplay_hud_hidden)
get_viewport().set_input_as_handled()
return
if ( if (
_surface_drawing_toolbar != null _surface_drawing_toolbar != null
and _surface_drawing_toolbar.owns_pointer_event(event) and _surface_drawing_toolbar.owns_pointer_event(event)
@ -624,6 +633,11 @@ func _on_quick_action_selected(action_id: StringName) -> void:
_toggle_surface_drawing() _toggle_surface_drawing()
&"chat": &"chat":
_chat_ui.open_chat() _chat_ui.open_chat()
&"freecam":
if _player != null:
_player.toggle_free_camera()
&"hud":
set_gameplay_hud_hidden(not _gameplay_hud_hidden)
func _handle_virtual_mouse_input(event: InputEvent) -> bool: func _handle_virtual_mouse_input(event: InputEvent) -> bool:
@ -1401,6 +1415,7 @@ func _on_hud_active_bait_changed(_item_id: StringName) -> void:
func _refresh_active_bait_indicator_visibility() -> void: func _refresh_active_bait_indicator_visibility() -> void:
_active_bait_indicator.visible = ( _active_bait_indicator.visible = (
_gameplay_ui_enabled _gameplay_ui_enabled
and not _gameplay_hud_hidden
and not _system_menu_open and not _system_menu_open
and not _player_menu_open and not _player_menu_open
and not _shop_open and not _shop_open
@ -1413,11 +1428,12 @@ func _on_hud_bait_inventory_changed() -> void:
func set_gameplay_ui_enabled(enabled: bool) -> void: func set_gameplay_ui_enabled(enabled: bool) -> void:
_gameplay_ui_enabled = enabled _gameplay_ui_enabled = enabled
if not enabled:
_gameplay_hud_hidden = false
_refresh_active_bait_indicator_visibility() _refresh_active_bait_indicator_visibility()
if enabled: if enabled:
_try_start_shop_npc_speech() _try_start_shop_npc_speech()
_gameplay_transient_hud.visible = enabled and not _player_menu_open _refresh_gameplay_hud_visibility()
_experience_presentation.visible = enabled
_refresh_chat_availability() _refresh_chat_availability()
if not enabled: if not enabled:
_end_virtual_mouse() _end_virtual_mouse()
@ -1448,10 +1464,50 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
call_deferred("_start_next_experience_animation") call_deferred("_start_next_experience_animation")
func set_gameplay_hud_hidden(hidden: bool) -> void:
_gameplay_hud_hidden = hidden
if _quick_radial_menu != null:
_quick_radial_menu.set_hud_hidden(hidden)
_refresh_gameplay_hud_visibility()
_refresh_active_bait_indicator_visibility()
_refresh_hotbar_visibility()
_refresh_chat_availability()
func is_gameplay_hud_hidden() -> bool:
return _gameplay_hud_hidden
func _can_toggle_gameplay_hud() -> bool:
return (
_gameplay_ui_enabled
and not _system_menu_open
and not _player_menu_open
and not _shop_open
and not _chat_input_open
and not _showcase_active
and not _emote_radial_menu.is_open()
and not _quick_radial_menu.is_open()
)
func _refresh_gameplay_hud_visibility() -> void:
var show_world_hud: bool = (
_gameplay_ui_enabled
and not _gameplay_hud_hidden
and not _system_menu_open
and not _player_menu_open
and not _shop_open
)
_gameplay_transient_hud.visible = show_world_hud
_experience_presentation.visible = show_world_hud
func set_system_menu_open(is_open: bool) -> void: func set_system_menu_open(is_open: bool) -> void:
_system_menu_open = is_open _system_menu_open = is_open
if is_open and _surface_drawing != null: if is_open and _surface_drawing != null:
_surface_drawing.deactivate() _surface_drawing.deactivate()
_refresh_gameplay_hud_visibility()
_refresh_chat_availability() _refresh_chat_availability()
_refresh_hotbar_visibility() _refresh_hotbar_visibility()
_hotbar_ui.set_gameplay_input_enabled( _hotbar_ui.set_gameplay_input_enabled(
@ -2049,7 +2105,7 @@ func _on_player_menu_visibility_changed(is_open: bool) -> void:
_end_virtual_mouse() _end_virtual_mouse()
if is_open and _surface_drawing != null: if is_open and _surface_drawing != null:
_surface_drawing.deactivate() _surface_drawing.deactivate()
_gameplay_transient_hud.visible = _gameplay_ui_enabled and not is_open _refresh_gameplay_hud_visibility()
if is_open: if is_open:
_hotbar_ui.set_drag_enabled(false) _hotbar_ui.set_drag_enabled(false)
_hotbar_ui.set_presentation_visible(false, false) _hotbar_ui.set_presentation_visible(false, false)
@ -2106,6 +2162,7 @@ func _on_shop_visibility_changed(is_open: bool) -> void:
shop_backdrop_visibility_changed.emit(true) shop_backdrop_visibility_changed.emit(true)
if not is_open and _player_menu.is_shop_cooler_mounted(): if not is_open and _player_menu.is_shop_cooler_mounted():
_player_menu.unmount_shop_cooler() _player_menu.unmount_shop_cooler()
_refresh_gameplay_hud_visibility()
_refresh_chat_availability() _refresh_chat_availability()
_refresh_hotbar_visibility() _refresh_hotbar_visibility()
_hotbar_ui.set_gameplay_input_enabled( _hotbar_ui.set_gameplay_input_enabled(
@ -2229,6 +2286,10 @@ func _on_hotbar_presentation_transition_finished(
func _refresh_hotbar_visibility() -> void: func _refresh_hotbar_visibility() -> void:
_hotbar_ui.set_presentation_visible( _hotbar_ui.set_presentation_visible(
_gameplay_ui_enabled _gameplay_ui_enabled
and (
not _gameplay_hud_hidden
or (_player_menu_open and _player_menu_hotbar_visible)
)
and not _system_menu_open and not _system_menu_open
and not _shop_open and not _shop_open
and ( and (
@ -2250,6 +2311,7 @@ func _on_chat_text_entry_ownership_changed(active: bool) -> void:
_chat_input_open = active _chat_input_open = active
if active and _surface_drawing != null: if active and _surface_drawing != null:
_surface_drawing.deactivate() _surface_drawing.deactivate()
_refresh_chat_availability()
_emit_interactive_pointer_ui_changed() _emit_interactive_pointer_ui_changed()
@ -2263,4 +2325,5 @@ func _refresh_chat_availability() -> void:
and not _shop_open and not _shop_open
) )
_chat_ui.set_available(chat_available) _chat_ui.set_available(chat_available)
_chat_ui.set_hud_hidden(_gameplay_hud_hidden and not _chat_input_open)
passive_pointer_ui_changed.emit(chat_available) passive_pointer_ui_changed.emit(chat_available)

View file

@ -9,7 +9,6 @@ const BubbleButtonScene: PackedScene = preload(
const BubbleProfile: BubbleMenuProfile = preload( const BubbleProfile: BubbleMenuProfile = preload(
"res://ui/components/bubble_menu/bubble_menu_profile.tres" "res://ui/components/bubble_menu/bubble_menu_profile.tres"
) )
const SECTOR_COUNT: int = 8
const RING_RADIUS: float = 172.0 const RING_RADIUS: float = 172.0
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0) const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
const CONTROLLER_SELECTION_DEADZONE: float = 0.35 const CONTROLLER_SELECTION_DEADZONE: float = 0.35
@ -25,6 +24,8 @@ const ACTIONS: Array[StringName] = [
&"online", &"online",
&"paint", &"paint",
&"chat", &"chat",
&"freecam",
&"hud",
] ]
const LABELS: Array[String] = [ const LABELS: Array[String] = [
"stuff", "stuff",
@ -35,13 +36,17 @@ const LABELS: Array[String] = [
"online", "online",
"paint", "paint",
"chat", "chat",
"freecam",
"hide hud",
] ]
const SECTOR_COUNT: int = 10
var _buttons: Array[BubbleButton] = [] var _buttons: Array[BubbleButton] = []
var _is_open: bool = false var _is_open: bool = false
var _selected_sector: int = 0 var _selected_sector: int = 0
var _controller_selection_mode: bool = false var _controller_selection_mode: bool = false
var _controller_mapping_manager: ControllerMappingManagerType var _controller_mapping_manager: ControllerMappingManagerType
var _hud_hidden: bool = false
func setup_controller_mapping( func setup_controller_mapping(
@ -58,7 +63,7 @@ func _ready() -> void:
bubble.profile = BubbleProfile bubble.profile = BubbleProfile
bubble.focus_mode = Control.FOCUS_NONE bubble.focus_mode = Control.FOCUS_NONE
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
bubble.text = LABELS[sector] bubble.text = _label_for_sector(sector)
add_child(bubble) add_child(bubble)
_buttons.append(bubble) _buttons.append(bubble)
_apply_selection_styles() _apply_selection_styles()
@ -88,6 +93,7 @@ func open_menu(controller_selection: bool = false) -> void:
_selected_sector = 0 _selected_sector = 0
_controller_selection_mode = controller_selection _controller_selection_mode = controller_selection
visible = true visible = true
_refresh_labels()
_layout_bubbles() _layout_bubbles()
_apply_selection_styles() _apply_selection_styles()
@ -101,6 +107,22 @@ func is_open() -> bool:
return _is_open return _is_open
func set_hud_hidden(hidden: bool) -> void:
_hud_hidden = hidden
_refresh_labels()
func _refresh_labels() -> void:
for sector: int in _buttons.size():
_buttons[sector].text = _label_for_sector(sector)
func _label_for_sector(sector: int) -> String:
if ACTIONS[sector] == &"hud":
return "show hud" if _hud_hidden else "hide hud"
return LABELS[sector]
func _emit_selected_action(action_id: StringName) -> void: func _emit_selected_action(action_id: StringName) -> void:
action_selected.emit(action_id) action_selected.emit(action_id)
@ -157,7 +179,8 @@ func _update_mouse_selection() -> void:
func _select_sector_from_offset(offset: Vector2) -> void: func _select_sector_from_offset(offset: Vector2) -> void:
var angle: float = fposmod(offset.angle() + PI * 0.5 + PI / 8.0, TAU) var half_sector: float = TAU / (float(SECTOR_COUNT) * 2.0)
var angle: float = fposmod(offset.angle() + PI * 0.5 + half_sector, TAU)
var sector: int = int(floor(angle / (TAU / float(SECTOR_COUNT)))) var sector: int = int(floor(angle / (TAU / float(SECTOR_COUNT))))
if sector == _selected_sector: if sector == _selected_sector:
return return

View file

@ -124,7 +124,7 @@ text = "Tekgator"
layout_mode = 2 layout_mode = 2
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1) theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
theme_override_font_sizes/font_size = 15 theme_override_font_sizes/font_size = 15
text = "cooler-capacity and barrier-power shop icon artwork" text = "cooler-capacity, reel-speed, and barrier-power shop icon artwork"
autowrap_mode = 2 autowrap_mode = 2
[node name="AdditionalCredits" type="VBoxContainer" parent="Paper/Margin/Layout/Columns"] [node name="AdditionalCredits" type="VBoxContainer" parent="Paper/Margin/Layout/Columns"]

View file

@ -0,0 +1,37 @@
shader_type spatial;
render_mode blend_mix, depth_prepass_alpha, cull_disabled, unshaded, fog_disabled, shadows_disabled;
uniform vec4 cloud_color : source_color = vec4(0.07, 0.10, 0.12, 1.0);
uniform float weather_opacity : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform float maximum_opacity : hint_range(0.0, 1.0, 0.01) = 0.5;
uniform float distance_fade_start : hint_range(50.0, 500.0, 1.0) = 240.0;
uniform float distance_fade_end : hint_range(50.0, 500.0, 1.0) = 300.0;
varying float world_normal_height;
varying float camera_distance;
void vertex() {
world_normal_height = normalize(MODEL_NORMAL_MATRIX * NORMAL).y;
vec3 world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
camera_distance = distance(
world_position.xz,
CAMERA_POSITION_WORLD.xz
);
}
void fragment() {
float facet_light = mix(
0.68,
1.06,
clamp(world_normal_height * 0.5 + 0.5, 0.0, 1.0)
);
ALBEDO = cloud_color.rgb * facet_light;
float distance_fade = 1.0 - smoothstep(
distance_fade_start,
distance_fade_end,
camera_distance
);
ALPHA = weather_opacity * maximum_opacity * distance_fade * cloud_color.a;
}

View file

@ -0,0 +1 @@
uid://c20wlon7hc48g

View file

@ -0,0 +1,228 @@
class_name LocalStormCloudLayer
extends Node3D
const CLOUD_SHADER: Shader = preload(
"res://world/environment/local_storm_cloud.gdshader"
)
const MIN_CLOUD_ALTITUDE: float = 15.0
const MAX_CLOUD_ALTITUDE: float = 23.0
const GRID_AXIS_COUNT: int = 9
const GRID_SPACING: float = 70.0
const FIELD_HALF_EXTENT: float = GRID_AXIS_COUNT * GRID_SPACING * 0.5
const DISTANCE_FADE_START: float = 240.0
const DISTANCE_FADE_END: float = 300.0
const WRAP_RADIUS: float = FIELD_HALF_EXTENT
const MAXIMUM_OPACITY: float = 1.0
const BASE_VELOCITY := Vector2(0.825, -0.45)
const BODY_OUTLINE: Array[Vector2] = [
Vector2(-6.9, -0.8),
Vector2(-6.6, -2.0),
Vector2(-5.7, -3.0),
Vector2(-4.5, -3.5),
Vector2(-3.0, -3.7),
Vector2(-1.8, -4.2),
Vector2(-0.5, -4.3),
Vector2(0.7, -4.0),
Vector2(2.0, -4.1),
Vector2(3.0, -3.5),
Vector2(4.6, -3.3),
Vector2(5.8, -2.5),
Vector2(6.6, -1.5),
Vector2(7.0, -0.2),
Vector2(6.8, 1.0),
Vector2(6.1, 1.9),
Vector2(6.0, 2.9),
Vector2(5.1, 3.7),
Vector2(3.8, 4.0),
Vector2(2.7, 4.7),
Vector2(1.4, 4.9),
Vector2(0.2, 5.3),
Vector2(-1.2, 5.0),
Vector2(-2.1, 4.4),
Vector2(-3.6, 4.2),
Vector2(-4.9, 3.5),
Vector2(-5.4, 2.5),
Vector2(-6.3, 1.7),
Vector2(-6.8, 0.6),
]
var _target: Node3D
var _material: ShaderMaterial
var _multimesh: MultiMesh
var _cloud_mesh: MultiMeshInstance3D
var _drift_offset := Vector2.ZERO
var _storm_amount: float = 0.0
func setup(target: Node3D) -> void:
_target = target
_build_cloud_field()
_update_cloud_transforms()
set_process(true)
func set_storm_amount(amount: float, color: Color) -> void:
_storm_amount = clampf(amount, 0.0, 1.0)
var should_be_visible: bool = _storm_amount > 0.001
if should_be_visible and not visible:
_update_cloud_transforms()
visible = should_be_visible
if _material == null:
return
_material.set_shader_parameter("weather_opacity", _storm_amount)
_material.set_shader_parameter("cloud_color", color)
func get_storm_amount() -> float:
return _storm_amount
func get_patch_count() -> int:
return GRID_AXIS_COUNT * GRID_AXIS_COUNT
func _process(delta: float) -> void:
if not visible or _target == null or not is_instance_valid(_target):
return
_drift_offset += BASE_VELOCITY * delta
_drift_offset = Vector2(
_wrap_axis(_drift_offset.x),
_wrap_axis(_drift_offset.y),
)
_update_cloud_transforms()
func _build_cloud_field() -> void:
_material = ShaderMaterial.new()
_material.shader = CLOUD_SHADER
_material.set_shader_parameter("weather_opacity", 0.0)
_material.set_shader_parameter("maximum_opacity", MAXIMUM_OPACITY)
_material.set_shader_parameter("distance_fade_start", DISTANCE_FADE_START)
_material.set_shader_parameter("distance_fade_end", DISTANCE_FADE_END)
var cloud_body_mesh: ArrayMesh = _build_cloud_body_mesh()
cloud_body_mesh.surface_set_material(0, _material)
_multimesh = MultiMesh.new()
_multimesh.transform_format = MultiMesh.TRANSFORM_3D
_multimesh.mesh = cloud_body_mesh
_multimesh.instance_count = get_patch_count()
_multimesh.custom_aabb = AABB(
Vector3(
-FIELD_HALF_EXTENT - 60.0,
MIN_CLOUD_ALTITUDE - 5.0,
-FIELD_HALF_EXTENT - 60.0,
),
Vector3(
(FIELD_HALF_EXTENT + 60.0) * 2.0,
MAX_CLOUD_ALTITUDE - MIN_CLOUD_ALTITUDE + 10.0,
(FIELD_HALF_EXTENT + 60.0) * 2.0,
),
)
_cloud_mesh = MultiMeshInstance3D.new()
_cloud_mesh.name = "CloudField"
_cloud_mesh.multimesh = _multimesh
_cloud_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
add_child(_cloud_mesh)
visible = false
func _build_cloud_body_mesh() -> ArrayMesh:
var outline := PackedVector2Array(BODY_OUTLINE)
var surface_tool := SurfaceTool.new()
surface_tool.begin(Mesh.PRIMITIVE_TRIANGLES)
var face_indices: PackedInt32Array = Geometry2D.triangulate_polygon(
outline
)
for triangle_offset: int in range(0, face_indices.size(), 3):
var point_a: Vector2 = outline[face_indices[triangle_offset]]
var point_b: Vector2 = outline[face_indices[triangle_offset + 1]]
var point_c: Vector2 = outline[face_indices[triangle_offset + 2]]
_add_oriented_triangle(
surface_tool,
_to_cloud_vertex(point_a),
_to_cloud_vertex(point_b),
_to_cloud_vertex(point_c),
Vector3.DOWN,
)
surface_tool.index()
return surface_tool.commit()
func _to_cloud_vertex(point: Vector2) -> Vector3:
return Vector3(point.x, 0.0, point.y)
func _add_oriented_triangle(
surface_tool: SurfaceTool,
point_a: Vector3,
point_b: Vector3,
point_c: Vector3,
expected_normal: Vector3,
) -> void:
if (point_b - point_a).cross(point_c - point_a).dot(expected_normal) < 0.0:
var swap: Vector3 = point_b
point_b = point_c
point_c = swap
for point: Vector3 in [point_a, point_b, point_c]:
surface_tool.set_normal(expected_normal)
surface_tool.add_vertex(point)
func _update_cloud_transforms() -> void:
if _target == null or _multimesh == null:
return
global_position = _target.global_position
var target_xz := Vector2(
_target.global_position.x,
_target.global_position.z,
)
var grid_center: float = float(GRID_AXIS_COUNT - 1) * 0.5
var instance_index: int = 0
for grid_z: int in GRID_AXIS_COUNT:
for grid_x: int in GRID_AXIS_COUNT:
var patch_index: int = grid_z * GRID_AXIS_COUNT + grid_x
var world_offset := Vector2(
(float(grid_x) - grid_center) * GRID_SPACING,
(float(grid_z) - grid_center) * GRID_SPACING,
) + _drift_offset
var relative := Vector2(
_wrap_axis(world_offset.x - target_xz.x),
_wrap_axis(world_offset.y - target_xz.y),
)
var altitude: float = lerpf(
MIN_CLOUD_ALTITUDE,
MAX_CLOUD_ALTITUDE,
float(patch_index % 4) / 3.0,
)
var patch_scale: float = 2.20 + float(
(patch_index * 7) % 10
) * 0.10
var width_variation: float = 0.90 + float(
(patch_index * 5) % 7
) * 0.04
var depth_variation: float = 0.90 + float(
(patch_index * 3 + 2) % 7
) * 0.04
var patch_basis := Basis(
Vector3.UP,
deg_to_rad(float(patch_index * 17 % 41) - 20.0),
).scaled(Vector3(
patch_scale * 2.35 * width_variation,
patch_scale * 0.34,
patch_scale * 2.65 * depth_variation,
))
_multimesh.set_instance_transform(
instance_index,
Transform3D(
patch_basis,
Vector3(relative.x, altitude, relative.y),
),
)
instance_index += 1
func _wrap_axis(value: float) -> float:
return fposmod(value + FIELD_HALF_EXTENT, FIELD_HALF_EXTENT * 2.0) \
- FIELD_HALF_EXTENT

View file

@ -0,0 +1 @@
uid://krinusur24lh

View file

@ -10,7 +10,7 @@ ambient_light_source = 2
ambient_light_color = Color(0.82, 0.9, 0.92, 1) ambient_light_color = Color(0.82, 0.9, 0.92, 1)
ambient_light_energy = 1.08 ambient_light_energy = 1.08
reflected_light_source = 2 reflected_light_source = 2
fog_enabled = true fog_enabled = false
fog_mode = 1 fog_mode = 1
fog_light_color = Color(0.54902, 0.74902, 0.764706, 1) fog_light_color = Color(0.54902, 0.74902, 0.764706, 1)
fog_light_energy = 0.82 fog_light_energy = 0.82

View file

@ -1,5 +1,7 @@
shader_type sky; shader_type sky;
#include "res://world/environment/netfishing_sky_clouds.gdshaderinc"
uniform vec4 sky_top_color : source_color = vec4(0.204, 0.498, 0.643, 1.0); uniform vec4 sky_top_color : source_color = vec4(0.204, 0.498, 0.643, 1.0);
uniform vec4 sky_horizon_color : source_color = vec4(0.663, 0.843, 0.847, 1.0); uniform vec4 sky_horizon_color : source_color = vec4(0.663, 0.843, 0.847, 1.0);
uniform vec4 ground_bottom_color : source_color = vec4(0.157, 0.361, 0.439, 1.0); uniform vec4 ground_bottom_color : source_color = vec4(0.157, 0.361, 0.439, 1.0);
@ -83,6 +85,7 @@ void sky() {
base_color = mix( base_color = mix(
base_color, moon_color.rgb, moon_disc * moon_visible_strength base_color, moon_color.rgb, moon_disc * moon_visible_strength
); );
base_color = apply_weather_clouds(base_color, view_direction);
COLOR = base_color; COLOR = base_color;
} }

View file

@ -0,0 +1,70 @@
uniform float cloud_coverage : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform float cloud_opacity : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform vec4 cloud_light_color : source_color = vec4(0.70, 0.76, 0.77, 1.0);
uniform vec4 cloud_shadow_color : source_color = vec4(0.31, 0.38, 0.40, 1.0);
uniform vec2 cloud_wind = vec2(0.010, -0.006);
uniform float cloud_scale : hint_range(0.1, 3.0, 0.05) = 0.72;
float cloud_hash(vec2 point) {
return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453);
}
float cloud_value_noise(vec2 point) {
vec2 cell = floor(point);
vec2 local = fract(point);
vec2 blend = local * local * (3.0 - 2.0 * local);
float bottom = mix(
cloud_hash(cell),
cloud_hash(cell + vec2(1.0, 0.0)),
blend.x
);
float top = mix(
cloud_hash(cell + vec2(0.0, 1.0)),
cloud_hash(cell + vec2(1.0, 1.0)),
blend.x
);
return mix(bottom, top, blend.y);
}
float cloud_shape(vec2 point) {
vec2 broad_point = floor(point * 5.0) / 5.0;
vec2 detail_point = floor(point * 13.0) / 13.0;
float broad = cloud_value_noise(broad_point * 0.62);
float detail = cloud_value_noise(detail_point * 1.55 + vec2(17.3, 9.1));
float combined = broad * 0.72 + detail * 0.28;
return floor(combined * 7.0 + 0.5) / 7.0;
}
vec3 apply_weather_clouds(vec3 base_color, vec3 view_direction) {
if (cloud_opacity <= 0.001 || view_direction.y <= 0.0) {
return base_color;
}
float safe_height = max(view_direction.y, 0.08);
vec2 cloud_plane = view_direction.xz / safe_height;
vec2 cloud_point = (
cloud_plane + cloud_wind * TIME
) * cloud_scale;
float shape = cloud_shape(cloud_point);
float threshold = mix(0.82, 0.27, cloud_coverage);
float mask = smoothstep(threshold - 0.08, threshold + 0.08, shape);
mask = floor(mask * 4.0 + 0.5) / 4.0;
mask *= smoothstep(0.025, 0.14, view_direction.y);
mask *= cloud_opacity;
float body_depth = smoothstep(threshold, 1.0, shape);
float underside = clamp(
(1.0 - smoothstep(0.08, 0.72, view_direction.y)) * 0.35
+ body_depth * 0.65,
0.0,
1.0
);
vec3 cloud_color = mix(
cloud_light_color.rgb,
cloud_shadow_color.rgb,
underside
);
return mix(base_color, cloud_color, mask);
}

View file

@ -0,0 +1 @@
uid://cuqo54hgbjcrf

View file

@ -57,6 +57,11 @@ uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0; uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
uniform vec4 environment_tint : source_color = vec4(1.0); uniform vec4 environment_tint : source_color = vec4(1.0);
uniform float environment_brightness : hint_range(0.1, 1.2, 0.01) = 1.0; uniform float environment_brightness : hint_range(0.1, 1.2, 0.01) = 1.0;
uniform vec4 weather_fog_color : source_color = vec4(0.025, 0.038, 0.045, 1.0);
uniform float weather_fog_amount : hint_range(0.0, 1.0, 0.01) = 0.0;
uniform float weather_fog_begin : hint_range(0.0, 500.0, 0.5) = 20.0;
uniform float weather_fog_end : hint_range(1.0, 1000.0, 0.5) = 95.0;
uniform float weather_fog_curve : hint_range(0.1, 4.0, 0.01) = 1.4;
varying vec3 world_position; varying vec3 world_position;
varying float surface_view_depth; varying float surface_view_depth;
@ -569,6 +574,22 @@ void fragment() {
visible_highlight_color, visible_highlight_color,
clamp(visible_highlight, 0.0, 1.0) clamp(visible_highlight, 0.0, 1.0)
); );
float weather_fog_distance = smoothstep(
weather_fog_begin,
max(weather_fog_end, weather_fog_begin + 1.0),
surface_view_depth
);
weather_fog_distance = pow(
clamp(weather_fog_distance, 0.0, 1.0),
weather_fog_curve
);
float weather_fog_mix = weather_fog_amount * weather_fog_distance;
water_color = mix(
water_color,
weather_fog_color.rgb,
weather_fog_mix
);
water_alpha = mix(water_alpha, 0.96, weather_fog_mix);
ALBEDO = water_color; ALBEDO = water_color;
ALPHA = clamp(water_alpha, 0.1, 0.96); ALPHA = clamp(water_alpha, 0.1, 0.96);

View file

@ -9,12 +9,21 @@ const RAIN_PARTICLE_AMOUNT: int = 560
const RAIN_VELOCITY_MIN: float = 16.0 const RAIN_VELOCITY_MIN: float = 16.0
const RAIN_VELOCITY_MAX: float = 20.0 const RAIN_VELOCITY_MAX: float = 20.0
const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014) const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014)
const FOG_DAYLIGHT_SCENE_BRIGHTNESS: float = 0.42
const FOG_DAYLIGHT_FOG_LIGHT_BRIGHTNESS: float = 0.42
const FOG_DAYLIGHT_WATER_BRIGHTNESS: float = 0.42
const FOG_DAYLIGHT_POST_BRIGHTNESS: float = 0.50
const RAIN_DAYLIGHT_POST_BRIGHTNESS: float = 0.52
const WATER_FOG_COLOR := Color(0.025, 0.038, 0.045)
const SALT_WATER_MATERIAL: ShaderMaterial = preload( const SALT_WATER_MATERIAL: ShaderMaterial = preload(
"res://world/materials/stylized_water.tres" "res://world/materials/stylized_water.tres"
) )
const FRESH_WATER_MATERIAL: ShaderMaterial = preload( const FRESH_WATER_MATERIAL: ShaderMaterial = preload(
"res://world/materials/stylized_water_fresh.tres" "res://world/materials/stylized_water_fresh.tres"
) )
const LocalStormCloudLayerType = preload(
"res://world/environment/local_storm_cloud_layer.gd"
)
const DAY_SKY_TOP := Color(0.204, 0.498, 0.643) const DAY_SKY_TOP := Color(0.204, 0.498, 0.643)
const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847) const DAY_SKY_HORIZON := Color(0.663, 0.843, 0.847)
@ -44,15 +53,27 @@ const WARM_SUN := Color(1.0, 0.58, 0.30)
const WARM_WATER_TINT := Color(0.78, 0.62, 0.58) const WARM_WATER_TINT := Color(0.78, 0.62, 0.58)
const WARM_SUN_DISC := Color(1.0, 0.45, 0.16) const WARM_SUN_DISC := Color(1.0, 0.45, 0.16)
const MOON_DISC := Color(0.78, 0.88, 1.0) const MOON_DISC := Color(0.78, 0.88, 1.0)
const DAY_CLOUD_LIGHT := Color(0.70, 0.76, 0.77)
const DAY_CLOUD_SHADOW := Color(0.31, 0.38, 0.40)
const NIGHT_CLOUD_LIGHT := Color(0.18, 0.22, 0.30)
const NIGHT_CLOUD_SHADOW := Color(0.06, 0.09, 0.14)
const WARM_CLOUD_LIGHT := Color(0.76, 0.55, 0.45)
const WARM_CLOUD_SHADOW := Color(0.31, 0.23, 0.24)
const STORM_CLOUD_SHADOW := Color(0.08, 0.12, 0.14)
const DAY_LOWER_CLOUD := Color(0.22, 0.25, 0.26)
const NIGHT_LOWER_CLOUD := Color(0.08, 0.10, 0.14)
const WARM_LOWER_CLOUD := Color(0.22, 0.16, 0.16)
var _time_service: WorldTimeService var _time_service: WorldTimeService
var _weather_service: WorldWeatherService var _weather_service: WorldWeatherService
var _world_environment: WorldEnvironment var _world_environment: WorldEnvironment
var _sun: DirectionalLight3D var _sun: DirectionalLight3D
var _rain_target: Node3D var _rain_target: Node3D
var _rain_camera_provider: Callable
var _environment: Environment var _environment: Environment
var _sky_material: ShaderMaterial var _sky_material: ShaderMaterial
var _rain: GPUParticles3D var _rain: GPUParticles3D
var _storm_clouds: LocalStormCloudLayer
var _elapsed: float = 0.0 var _elapsed: float = 0.0
var _weather_from: WorldWeatherService.Weather = ( var _weather_from: WorldWeatherService.Weather = (
WorldWeatherService.Weather.SUNNY WorldWeatherService.Weather.SUNNY
@ -69,16 +90,19 @@ func setup(
sun: DirectionalLight3D, sun: DirectionalLight3D,
weather_service: WorldWeatherService = null, weather_service: WorldWeatherService = null,
rain_target: Node3D = null, rain_target: Node3D = null,
rain_camera_provider: Callable = Callable(),
) -> void: ) -> void:
_time_service = time_service _time_service = time_service
_weather_service = weather_service _weather_service = weather_service
_world_environment = world_environment _world_environment = world_environment
_sun = sun _sun = sun
_rain_target = rain_target _rain_target = rain_target
_rain_camera_provider = rain_camera_provider
if not _prepare_runtime_environment(): if not _prepare_runtime_environment():
set_process(false) set_process(false)
return return
_prepare_rain() _prepare_rain()
_prepare_storm_clouds()
if _weather_service != null: if _weather_service != null:
_weather_from = _weather_service.get_weather() _weather_from = _weather_service.get_weather()
_weather_to = _weather_from _weather_to = _weather_from
@ -184,6 +208,13 @@ func _prepare_rain() -> void:
_update_rain_position() _update_rain_position()
func _prepare_storm_clouds() -> void:
_storm_clouds = LocalStormCloudLayerType.new()
_storm_clouds.name = "LocalStormClouds"
add_child(_storm_clouds)
_storm_clouds.setup(_rain_target)
func _apply_time(time_hours: float) -> void: func _apply_time(time_hours: float) -> void:
if _environment == null or _sky_material == null or _sun == null: if _environment == null or _sky_material == null or _sun == null:
return return
@ -220,6 +251,19 @@ func _apply_time(time_hours: float) -> void:
var fog_color: Color = _blended_color( var fog_color: Color = _blended_color(
NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth
) )
var fog_daylight_amount: float = (
daylight * _weather_value(0.0, 0.0, 0.0, 1.0)
)
var rain_daylight_amount: float = (
daylight * _weather_value(0.0, 0.0, 1.0, 0.0)
)
var weather_fog_amount: float = _weather_value(0.0, 0.0, 1.0, 1.0)
_environment.fog_enabled = weather_fog_amount > 0.001
var fog_scene_brightness: float = lerpf(
1.0,
FOG_DAYLIGHT_SCENE_BRIGHTNESS,
fog_daylight_amount,
)
_sky_material.set_shader_parameter( _sky_material.set_shader_parameter(
"sky_top_color", sky_top "sky_top_color", sky_top
) )
@ -235,24 +279,34 @@ func _apply_time(time_hours: float) -> void:
"ground_horizon_color", "ground_horizon_color",
ground_horizon, ground_horizon,
) )
_apply_sky_clouds(daylight, warmth)
_environment.background_energy_multiplier = ( _environment.background_energy_multiplier = (
lerpf(0.52, 0.85, daylight) lerpf(0.52, 0.85, daylight)
* _weather_value(1.0, 0.82, 0.66, 1.0) * _weather_value(1.0, 0.82, 0.66, 1.0)
* fog_scene_brightness
) )
_environment.ambient_light_color = ambient _environment.ambient_light_color = ambient
_environment.ambient_light_energy = ( _environment.ambient_light_energy = (
lerpf(0.66, 1.08, daylight) lerpf(0.66, 1.08, daylight)
* _weather_value(1.0, 0.88, 0.76, 0.82) * _weather_value(1.0, 0.88, 0.76, 1.0)
* fog_scene_brightness
) )
_environment.fog_light_color = fog_color _environment.fog_light_color = fog_color
_environment.fog_light_energy = ( _environment.fog_light_energy = (
lerpf(0.56, 0.82, daylight) lerpf(0.56, 0.82, daylight)
* _weather_value(1.0, 0.92, 0.82, 1.05) * _weather_value(1.0, 0.92, 0.82, 1.0)
* lerpf(
1.0,
FOG_DAYLIGHT_FOG_LIGHT_BRIGHTNESS,
fog_daylight_amount,
)
) )
_environment.fog_aerial_perspective = _weather_value( _environment.fog_aerial_perspective = _weather_value(
0.35, 0.48, 0.62, 0.92 0.35, 0.48, 0.62, 0.0
)
_environment.fog_sky_affect = _weather_value(
0.35, 0.48, 0.68, 1.0
) )
_environment.fog_sky_affect = 0.0
_environment.fog_depth_curve = _weather_value( _environment.fog_depth_curve = _weather_value(
1.6, 1.5, 1.4, 1.18 1.6, 1.5, 1.4, 1.18
) )
@ -260,12 +314,22 @@ func _apply_time(time_hours: float) -> void:
42.0, 32.0, 20.0, 3.0 42.0, 32.0, 20.0, 3.0
) )
_environment.fog_depth_end = _weather_value( _environment.fog_depth_end = _weather_value(
170.0, 140.0, 95.0, 28.0 170.0, 140.0, 95.0, 18.0
) )
_apply_water_environment(daylight, warmth) _apply_water_environment(daylight, warmth, weather_fog_amount)
_environment.adjustment_brightness = ( _environment.adjustment_brightness = (
lerpf(0.93, 0.98, daylight) lerpf(0.93, 0.98, daylight)
* _weather_value(1.0, 0.97, 0.92, 1.0) * _weather_value(1.0, 0.97, 0.92, 1.0)
* lerpf(
1.0,
FOG_DAYLIGHT_POST_BRIGHTNESS,
fog_daylight_amount,
)
* lerpf(
1.0,
RAIN_DAYLIGHT_POST_BRIGHTNESS,
rain_daylight_amount,
)
) )
_environment.adjustment_saturation = ( _environment.adjustment_saturation = (
lerpf(0.82, 0.91, daylight) lerpf(0.82, 0.91, daylight)
@ -299,7 +363,7 @@ func _apply_time(time_hours: float) -> void:
) )
_sun.light_energy = ( _sun.light_energy = (
lerpf(0.0, 0.10, daylight) lerpf(0.0, 0.10, daylight)
* _weather_value(1.0, 0.55, 0.25, 0.35) * _weather_value(1.0, 0.55, 0.25, 0.30)
) )
_update_rain_amount() _update_rain_amount()
@ -307,6 +371,7 @@ func _apply_time(time_hours: float) -> void:
func _apply_water_environment( func _apply_water_environment(
daylight: float, daylight: float,
warmth: float, warmth: float,
weather_fog_amount: float,
) -> void: ) -> void:
var water_tint := _blended_color( var water_tint := _blended_color(
NIGHT_WATER_TINT, NIGHT_WATER_TINT,
@ -318,6 +383,11 @@ func _apply_water_environment(
var water_brightness := ( var water_brightness := (
lerpf(0.34, 1.0, daylight) lerpf(0.34, 1.0, daylight)
* _weather_value(1.0, 0.90, 0.78, 1.0) * _weather_value(1.0, 0.90, 0.78, 1.0)
* lerpf(
1.0,
FOG_DAYLIGHT_WATER_BRIGHTNESS,
daylight * _weather_value(0.0, 0.0, 0.0, 1.0),
)
) )
for material: ShaderMaterial in [ for material: ShaderMaterial in [
SALT_WATER_MATERIAL, SALT_WATER_MATERIAL,
@ -328,6 +398,68 @@ func _apply_water_environment(
"environment_brightness", "environment_brightness",
water_brightness, water_brightness,
) )
material.set_shader_parameter("weather_fog_color", WATER_FOG_COLOR)
material.set_shader_parameter(
"weather_fog_amount",
weather_fog_amount,
)
material.set_shader_parameter(
"weather_fog_begin",
_weather_value(42.0, 32.0, 20.0, 3.0),
)
material.set_shader_parameter(
"weather_fog_end",
_weather_value(170.0, 140.0, 95.0, 18.0),
)
material.set_shader_parameter(
"weather_fog_curve",
_weather_value(1.6, 1.5, 1.4, 1.18),
)
func _apply_sky_clouds(daylight: float, warmth: float) -> void:
var cloud_light := _blended_color(
NIGHT_CLOUD_LIGHT,
DAY_CLOUD_LIGHT,
WARM_CLOUD_LIGHT,
daylight,
warmth,
)
var cloud_shadow := _blended_color(
NIGHT_CLOUD_SHADOW,
DAY_CLOUD_SHADOW,
WARM_CLOUD_SHADOW,
daylight,
warmth,
)
var storm_amount: float = _weather_value(0.0, 0.18, 0.55, 0.0)
cloud_light = cloud_light.lerp(cloud_shadow, storm_amount * 0.35)
cloud_shadow = cloud_shadow.lerp(
STORM_CLOUD_SHADOW,
storm_amount * 0.10,
)
var lower_cloud_color := _blended_color(
NIGHT_LOWER_CLOUD,
DAY_LOWER_CLOUD,
WARM_LOWER_CLOUD,
daylight,
warmth,
)
_sky_material.set_shader_parameter(
"cloud_coverage",
_weather_value(0.0, 0.58, 0.985, 0.0),
)
_sky_material.set_shader_parameter(
"cloud_opacity",
_weather_value(0.0, 0.76, 1.0, 0.0),
)
_sky_material.set_shader_parameter("cloud_light_color", cloud_light)
_sky_material.set_shader_parameter("cloud_shadow_color", cloud_shadow)
if _storm_clouds != null:
_storm_clouds.set_storm_amount(
_weather_value(0.0, 0.0, 1.0, 0.0),
lower_cloud_color,
)
func _on_weather_changed( func _on_weather_changed(
@ -382,7 +514,14 @@ func _update_rain_amount() -> void:
func _update_rain_position() -> void: func _update_rain_position() -> void:
if _rain == null or _rain_target == null: if _rain == null or _rain_target == null:
return return
_rain.global_position = _rain_target.global_position + RAIN_EMITTER_OFFSET var anchor_position: Vector3 = _rain_target.global_position
if _rain_camera_provider.is_valid():
var camera_candidate: Variant = _rain_camera_provider.call()
if camera_candidate is Camera3D:
var active_camera := camera_candidate as Camera3D
if is_instance_valid(active_camera):
anchor_position = active_camera.global_position
_rain.global_position = anchor_position + RAIN_EMITTER_OFFSET
func _daylight_amount(hour: float) -> float: func _daylight_amount(hour: float) -> float:

View file

@ -30,6 +30,8 @@ var _rng := RandomNumberGenerator.new()
var _daily_plan_id: String = "" var _daily_plan_id: String = ""
var _daily_schedule: Array[Dictionary] = [] var _daily_schedule: Array[Dictionary] = []
var _world_time: WorldTimeService var _world_time: WorldTimeService
var _manual_override_weather: Weather = DEFAULT_WEATHER
var _manual_override_segment_index: int = -1
func _ready() -> void: func _ready() -> void:
@ -44,6 +46,7 @@ func begin_authoritative_session(seed_value: int) -> void:
_rng.seed = seed_value _rng.seed = seed_value
_running_authority = true _running_authority = true
set_process(true) set_process(true)
_clear_manual_override()
if _daily_schedule.is_empty() or _world_time == null: if _daily_schedule.is_empty() or _world_time == null:
if _has_persistent_state: if _has_persistent_state:
_set_weather( _set_weather(
@ -64,12 +67,14 @@ func begin_authoritative_session(seed_value: int) -> void:
func begin_remote_session() -> void: func begin_remote_session() -> void:
_running_authority = false _running_authority = false
set_process(false) set_process(false)
_clear_manual_override()
_set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true) _set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true)
func end_session() -> void: func end_session() -> void:
_running_authority = false _running_authority = false
set_process(false) set_process(false)
_clear_manual_override()
_set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true) _set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true)
@ -98,6 +103,22 @@ func apply_authoritative_snapshot(
_set_weather(weather, maxf(seconds_remaining, 0.0), false) _set_weather(weather, maxf(seconds_remaining, 0.0), false)
func set_authoritative_weather(weather: Weather) -> bool:
if not _running_authority or not is_valid_weather(int(weather)):
return false
var duration: float = _roll_duration(weather)
if not _daily_schedule.is_empty() and _world_time != null:
_manual_override_weather = weather
_manual_override_segment_index = _current_daily_segment_index()
duration = _current_daily_segment_seconds_remaining(
_manual_override_segment_index
)
else:
_clear_manual_override()
_set_weather(weather, duration, true)
return true
func set_persistence_tracking_enabled(enabled: bool) -> void: func set_persistence_tracking_enabled(enabled: bool) -> void:
if _persistence_tracking_enabled == enabled: if _persistence_tracking_enabled == enabled:
return return
@ -181,6 +202,7 @@ func configure_daily_plan(
return false return false
_daily_plan_id = plan_id _daily_plan_id = plan_id
_daily_schedule.clear() _daily_schedule.clear()
_clear_manual_override()
for value: Variant in schedule: for value: Variant in schedule:
_daily_schedule.append((value as Dictionary).duplicate(true)) _daily_schedule.append((value as Dictionary).duplicate(true))
_world_time = world_time _world_time = world_time
@ -193,6 +215,7 @@ func clear_daily_plan() -> void:
_daily_plan_id = "" _daily_plan_id = ""
_daily_schedule.clear() _daily_schedule.clear()
_world_time = null _world_time = null
_clear_manual_override()
func get_daily_plan_id() -> String: func get_daily_plan_id() -> String:
@ -327,21 +350,43 @@ func _duration_range(weather: Weather) -> Vector2:
func _update_scheduled_weather(force_emit: bool) -> void: func _update_scheduled_weather(force_emit: bool) -> void:
if _daily_schedule.is_empty() or _world_time == null: if _daily_schedule.is_empty() or _world_time == null:
return return
var segment_index: int = _current_daily_segment_index()
var entry: Dictionary = _daily_schedule[segment_index]
var weather: Weather = int(entry.get("weather", DEFAULT_WEATHER)) as Weather
if _manual_override_segment_index == segment_index:
weather = _manual_override_weather
elif _manual_override_segment_index >= 0:
_clear_manual_override()
var seconds_remaining: float = _current_daily_segment_seconds_remaining(
segment_index
)
_set_weather(weather, seconds_remaining, force_emit)
func _current_daily_segment_index() -> int:
var elapsed_hours: float = fposmod( var elapsed_hours: float = fposmod(
_world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR, _world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
WorldTimeService.HOURS_PER_DAY, WorldTimeService.HOURS_PER_DAY,
) )
var segment_hours: float = DAILY_PLAN_SEGMENT_HOURS return clampi(
var segment_index: int = clampi( floori(elapsed_hours / DAILY_PLAN_SEGMENT_HOURS),
floori(elapsed_hours / segment_hours),
0, 0,
_daily_schedule.size() - 1, _daily_schedule.size() - 1,
) )
var entry: Dictionary = _daily_schedule[segment_index]
var weather: Weather = int(entry.get("weather", DEFAULT_WEATHER)) as Weather
var next_boundary: float = float(segment_index + 1) * segment_hours func _current_daily_segment_seconds_remaining(segment_index: int) -> float:
var hours_remaining: float = maxf(next_boundary - elapsed_hours, 0.0) var elapsed_hours: float = fposmod(
var seconds_remaining: float = ( _world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
hours_remaining / WorldTimeService.HOURS_PER_REAL_SECOND WorldTimeService.HOURS_PER_DAY,
) )
_set_weather(weather, seconds_remaining, force_emit) var next_boundary: float = (
float(segment_index + 1) * DAILY_PLAN_SEGMENT_HOURS
)
var hours_remaining: float = maxf(next_boundary - elapsed_hours, 0.0)
return hours_remaining / WorldTimeService.HOURS_PER_REAL_SECOND
func _clear_manual_override() -> void:
_manual_override_weather = DEFAULT_WEATHER
_manual_override_segment_index = -1