Add weather presentation and gameplay UI improvements
This commit is contained in:
parent
db6074ddfa
commit
51fa6535b6
28 changed files with 1067 additions and 61 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -21,7 +21,8 @@ independent team Woofmeow.
|
|||
arms, ears and tails, and multiple world props and decorative assets,
|
||||
including trees and bridges. The contributor requested to be credited as
|
||||
`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.
|
||||
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. |
|
||||
| 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`. |
|
||||
| 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`. |
|
||||
| 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. |
|
||||
|
|
@ -182,10 +184,13 @@ authored `mesh_style.png` filenames.
|
|||
### Tekgator shop icon contribution record
|
||||
|
||||
- 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:
|
||||
- `items/icons/equipment/64_cooler_plus.png`:
|
||||
`b63705be5b78915753c068170323781a90e22b555b57a5b2ca9466e2726539f7`
|
||||
- `items/icons/shop/64_speed_plus.png`:
|
||||
`938452798f2be505e06ce3fbef178f52459331fd3bced8763fbc2a80157125ca`
|
||||
- `items/icons/shop/64_power_plus.png`:
|
||||
`164d34ce152aa6d75de79b69f0be2d9074f7891057de9636678889028ff98ae7`
|
||||
- Permission record: retain the original permission record or a later written
|
||||
|
|
|
|||
|
|
@ -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 |
BIN
items/icons/shop/64_speed_plus.png
Normal file
BIN
items/icons/shop/64_speed_plus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c2hal1gkmnuqa"
|
||||
path="res://.godot/imported/reel_speed_upgrade.svg-6bce0cfdbf18895938d0bcb4850467c8.ctex"
|
||||
uid="uid://dk6yx5cbpblo6"
|
||||
path="res://.godot/imported/64_speed_plus.png-c695fb4021c17dfa94ed7aa48ce262c1.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://items/icons/placeholder/reel_speed_upgrade.svg"
|
||||
dest_files=["res://.godot/imported/reel_speed_upgrade.svg-6bce0cfdbf18895938d0bcb4850467c8.ctex"]
|
||||
source_file="res://items/icons/shop/64_speed_plus.png"
|
||||
dest_files=["res://.godot/imported/64_speed_plus.png-c695fb4021c17dfa94ed7aa48ce262c1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
|
|
@ -38,6 +38,3 @@ process/hdr_as_srgb=false
|
|||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
|
|
@ -420,6 +420,7 @@ func _initialize_application(dedicated: bool) -> void:
|
|||
_test_world.get_sun(),
|
||||
_world_weather,
|
||||
_player,
|
||||
Callable(_player, "get_active_gameplay_camera"),
|
||||
)
|
||||
if not _world_time.natural_time_advanced.is_connected(
|
||||
_on_natural_time_advanced
|
||||
|
|
|
|||
|
|
@ -1005,7 +1005,7 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
if not local_control_enabled or not _is_camera_input_enabled():
|
||||
return
|
||||
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()
|
||||
return
|
||||
|
||||
|
|
@ -1140,6 +1140,16 @@ func _set_free_camera_active(active: bool) -> void:
|
|||
_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:
|
||||
if _free_camera == null or _free_camera_body == null:
|
||||
_set_free_camera_active(false)
|
||||
|
|
|
|||
|
|
@ -85,6 +85,14 @@ func _run() -> void:
|
|||
assert(not toolbar.is_docked_right())
|
||||
var chat_panel := chat_ui.get_node("ChatPanel") as PanelContainer
|
||||
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_equal_approx(chat_panel.size.x, ChatUI.MOBILE_COMPACT_WIDTH))
|
||||
assert(not chat_height_button.visible)
|
||||
|
|
@ -101,6 +109,17 @@ func _run() -> void:
|
|||
default_docks.paint_dock_right = true
|
||||
assert(settings_manager.apply_settings(default_docks))
|
||||
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_mobile_mode())
|
||||
assert(toolbar.is_docked_right())
|
||||
|
|
@ -140,6 +159,42 @@ func _run() -> void:
|
|||
assert(bool(game_ui.call("_handle_controller_chat_controls", select_button)))
|
||||
assert(chat_ui.is_open())
|
||||
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()
|
||||
cancel_button.button_index = JOY_BUTTON_B
|
||||
cancel_button.pressed = true
|
||||
|
|
@ -152,10 +207,16 @@ func _run() -> void:
|
|||
player_menu.open_section(PlayerMenu.Section.PROFILE)
|
||||
for _frame: int in 12:
|
||||
await process_frame
|
||||
assert(game_ui.is_gameplay_hud_hidden())
|
||||
assert(player_menu.visible)
|
||||
assert(root.gui_get_focus_owner() is not LineEdit)
|
||||
player_menu.close_menu()
|
||||
for _frame: int in 12:
|
||||
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.is_active() and not toolbar.visible)
|
||||
assert(player.bag.add_item(ArtShopStock.ART_KIT_ITEM_ID, 1))
|
||||
|
|
|
|||
|
|
@ -31,13 +31,15 @@ func _run_host() -> void:
|
|||
main.get_node("%WorldWeatherService") as WorldWeatherService
|
||||
)
|
||||
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)
|
||||
assert(is_equal_approx(
|
||||
world_time.get_persistent_time_hours(), INITIAL_HOST_TIME
|
||||
))
|
||||
world_weather.apply_authoritative_snapshot(
|
||||
WorldWeatherService.Weather.RAINY, 300.0
|
||||
)
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather rainy"))
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.RAINY
|
||||
|
|
@ -66,9 +68,7 @@ func _run_host() -> void:
|
|||
assert(is_equal_approx(
|
||||
world_time.get_persistent_time_hours(), UPDATED_HOST_TIME
|
||||
))
|
||||
world_weather.apply_authoritative_snapshot(
|
||||
WorldWeatherService.Weather.FOGGY, 300.0
|
||||
)
|
||||
assert(chat_ui.call("_handle_chat_command", "/weather foggy"))
|
||||
assert(
|
||||
world_weather.get_persistent_weather()
|
||||
== WorldWeatherService.Weather.FOGGY
|
||||
|
|
|
|||
|
|
@ -249,6 +249,15 @@ func _validate_environment_presentation() -> void:
|
|||
< 0.01
|
||||
)
|
||||
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(
|
||||
SaltWaterMaterial.get_shader_parameter("environment_brightness")
|
||||
)
|
||||
|
|
@ -292,17 +301,24 @@ func _validate_environment_presentation() -> void:
|
|||
)
|
||||
assert(night_water_tint.get_luminance() < 0.5)
|
||||
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_saturation := runtime_environment.adjustment_saturation
|
||||
visuals.apply_weather_immediately(WorldWeatherService.Weather.FOGGY)
|
||||
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 := (
|
||||
runtime_sky_material.get_shader_parameter("sky_horizon_color") as Color
|
||||
)
|
||||
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_end, 28.0))
|
||||
assert(is_equal_approx(runtime_environment.fog_depth_end, 18.0))
|
||||
assert(is_equal_approx(
|
||||
float(SaltWaterMaterial.get_shader_parameter("environment_brightness")),
|
||||
night_water_brightness,
|
||||
|
|
@ -311,6 +327,14 @@ func _validate_environment_presentation() -> void:
|
|||
runtime_environment.background_energy_multiplier,
|
||||
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(
|
||||
runtime_environment.adjustment_brightness,
|
||||
night_adjustment_brightness,
|
||||
|
|
@ -321,6 +345,10 @@ func _validate_environment_presentation() -> void:
|
|||
))
|
||||
visuals.apply_weather_immediately(WorldWeatherService.Weather.SUNNY)
|
||||
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(
|
||||
"sky_horizon_color"
|
||||
) as Color
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ func _initialize() -> void:
|
|||
|
||||
func _run() -> void:
|
||||
_validate_weather_scheduler()
|
||||
_validate_authoritative_weather_override()
|
||||
_validate_weather_persistence()
|
||||
_validate_snapshot_bounds()
|
||||
_validate_fishing_weather_seams()
|
||||
|
|
@ -74,6 +75,54 @@ func _duration_is_valid(weather: WorldWeatherServiceType) -> bool:
|
|||
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:
|
||||
var weather := WorldWeatherServiceType.new()
|
||||
root.add_child(weather)
|
||||
|
|
@ -212,20 +261,118 @@ func _validate_weather_presentation() -> void:
|
|||
weather.begin_remote_session()
|
||||
var rain_target := Node3D.new()
|
||||
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()
|
||||
world_root.add_child(visuals)
|
||||
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_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_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(
|
||||
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_end <= 42.01)
|
||||
assert(is_zero_approx(runtime_environment.fog_sky_affect))
|
||||
assert(runtime_environment.fog_aerial_perspective >= 0.91)
|
||||
assert(is_equal_approx(runtime_environment.fog_depth_end, 18.0))
|
||||
assert(is_equal_approx(runtime_environment.fog_sky_affect, 1.0))
|
||||
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(
|
||||
runtime_environment.adjustment_saturation,
|
||||
clear_saturation,
|
||||
|
|
@ -233,8 +380,41 @@ func _validate_weather_presentation() -> void:
|
|||
visuals.apply_weather_immediately(
|
||||
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
|
||||
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.amount_ratio > 0.99)
|
||||
assert(rain.amount == WorldTimeVisualControllerType.RAIN_PARTICLE_AMOUNT)
|
||||
|
|
@ -253,8 +433,52 @@ func _validate_weather_presentation() -> void:
|
|||
assert(rain_mesh.size.is_equal_approx(
|
||||
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(
|
||||
WorldWeatherServiceType.Weather.SUNNY
|
||||
)
|
||||
assert(not runtime_environment.fog_enabled)
|
||||
assert(not rain.emitting)
|
||||
world_root.queue_free()
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ var _opacity_tween: Tween
|
|||
var _height_tween: Tween
|
||||
var _opened: bool = false
|
||||
var _available: bool = false
|
||||
var _hud_hidden: bool = false
|
||||
var _presentation_state := PresentationState.COMPACT
|
||||
var _visible_state_before_collapse := PresentationState.COMPACT
|
||||
var _panel_hovered: bool = false
|
||||
|
|
@ -435,7 +436,10 @@ func _build_ui() -> void:
|
|||
_history.add_theme_stylebox_override("normal", _borderless_style(Color.TRANSPARENT))
|
||||
box.add_child(_history)
|
||||
_status = Label.new()
|
||||
_status.name = "ChatStatus"
|
||||
_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.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_status.add_theme_font_override("font", UtilityPageStyle.TuffyFont)
|
||||
|
|
@ -770,6 +774,8 @@ func _handle_chat_command(body: String) -> bool:
|
|||
match command:
|
||||
"time":
|
||||
_handle_time_command(parts)
|
||||
"weather":
|
||||
_handle_weather_command(parts)
|
||||
"":
|
||||
_set_status("Enter a command after /.")
|
||||
_:
|
||||
|
|
@ -814,6 +820,41 @@ func _handle_time_command(parts: PackedStringArray) -> void:
|
|||
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:
|
||||
if (
|
||||
not _send_pending
|
||||
|
|
@ -956,6 +997,16 @@ func _refresh_visibility() -> void:
|
|||
_unread_indicator.hide()
|
||||
_hint.hide()
|
||||
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()
|
||||
_weather_icon.show()
|
||||
_fish_finder_effect_icon.visible = (
|
||||
|
|
@ -1044,6 +1095,15 @@ func is_mobile_mode() -> bool:
|
|||
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:
|
||||
var collapsed := state == PresentationState.COLLAPSED
|
||||
var height_state := _visible_state_before_collapse if collapsed else state
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[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="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/placeholder/fish_coin.svg" id="5_coin"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ var _bag: PlayerBagType
|
|||
var _item_catalog: ItemCatalogType
|
||||
var _player_menu_open: bool = false
|
||||
var _gameplay_ui_enabled: bool = false
|
||||
var _gameplay_hud_hidden: bool = false
|
||||
var _fishing_spot: FishingSpotType
|
||||
var _system_menu_open: bool = false
|
||||
var _shop_open: bool = false
|
||||
|
|
@ -428,6 +429,14 @@ func _input(event: InputEvent) -> void:
|
|||
if _handle_controller_chat_controls(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
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 (
|
||||
_surface_drawing_toolbar != null
|
||||
and _surface_drawing_toolbar.owns_pointer_event(event)
|
||||
|
|
@ -624,6 +633,11 @@ func _on_quick_action_selected(action_id: StringName) -> void:
|
|||
_toggle_surface_drawing()
|
||||
&"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:
|
||||
|
|
@ -1401,6 +1415,7 @@ func _on_hud_active_bait_changed(_item_id: StringName) -> void:
|
|||
func _refresh_active_bait_indicator_visibility() -> void:
|
||||
_active_bait_indicator.visible = (
|
||||
_gameplay_ui_enabled
|
||||
and not _gameplay_hud_hidden
|
||||
and not _system_menu_open
|
||||
and not _player_menu_open
|
||||
and not _shop_open
|
||||
|
|
@ -1413,11 +1428,12 @@ func _on_hud_bait_inventory_changed() -> void:
|
|||
|
||||
func set_gameplay_ui_enabled(enabled: bool) -> void:
|
||||
_gameplay_ui_enabled = enabled
|
||||
if not enabled:
|
||||
_gameplay_hud_hidden = false
|
||||
_refresh_active_bait_indicator_visibility()
|
||||
if enabled:
|
||||
_try_start_shop_npc_speech()
|
||||
_gameplay_transient_hud.visible = enabled and not _player_menu_open
|
||||
_experience_presentation.visible = enabled
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
if not enabled:
|
||||
_end_virtual_mouse()
|
||||
|
|
@ -1448,10 +1464,50 @@ func set_gameplay_ui_enabled(enabled: bool) -> void:
|
|||
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:
|
||||
_system_menu_open = is_open
|
||||
if is_open and _surface_drawing != null:
|
||||
_surface_drawing.deactivate()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
_refresh_hotbar_visibility()
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
|
|
@ -2049,7 +2105,7 @@ func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
|||
_end_virtual_mouse()
|
||||
if is_open and _surface_drawing != null:
|
||||
_surface_drawing.deactivate()
|
||||
_gameplay_transient_hud.visible = _gameplay_ui_enabled and not is_open
|
||||
_refresh_gameplay_hud_visibility()
|
||||
if is_open:
|
||||
_hotbar_ui.set_drag_enabled(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)
|
||||
if not is_open and _player_menu.is_shop_cooler_mounted():
|
||||
_player_menu.unmount_shop_cooler()
|
||||
_refresh_gameplay_hud_visibility()
|
||||
_refresh_chat_availability()
|
||||
_refresh_hotbar_visibility()
|
||||
_hotbar_ui.set_gameplay_input_enabled(
|
||||
|
|
@ -2229,6 +2286,10 @@ func _on_hotbar_presentation_transition_finished(
|
|||
func _refresh_hotbar_visibility() -> void:
|
||||
_hotbar_ui.set_presentation_visible(
|
||||
_gameplay_ui_enabled
|
||||
and (
|
||||
not _gameplay_hud_hidden
|
||||
or (_player_menu_open and _player_menu_hotbar_visible)
|
||||
)
|
||||
and not _system_menu_open
|
||||
and not _shop_open
|
||||
and (
|
||||
|
|
@ -2250,6 +2311,7 @@ func _on_chat_text_entry_ownership_changed(active: bool) -> void:
|
|||
_chat_input_open = active
|
||||
if active and _surface_drawing != null:
|
||||
_surface_drawing.deactivate()
|
||||
_refresh_chat_availability()
|
||||
_emit_interactive_pointer_ui_changed()
|
||||
|
||||
|
||||
|
|
@ -2263,4 +2325,5 @@ func _refresh_chat_availability() -> void:
|
|||
and not _shop_open
|
||||
)
|
||||
_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)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const BubbleButtonScene: PackedScene = preload(
|
|||
const BubbleProfile: BubbleMenuProfile = preload(
|
||||
"res://ui/components/bubble_menu/bubble_menu_profile.tres"
|
||||
)
|
||||
const SECTOR_COUNT: int = 8
|
||||
const RING_RADIUS: float = 172.0
|
||||
const BUBBLE_SIZE: Vector2 = Vector2(92.0, 88.0)
|
||||
const CONTROLLER_SELECTION_DEADZONE: float = 0.35
|
||||
|
|
@ -25,6 +24,8 @@ const ACTIONS: Array[StringName] = [
|
|||
&"online",
|
||||
&"paint",
|
||||
&"chat",
|
||||
&"freecam",
|
||||
&"hud",
|
||||
]
|
||||
const LABELS: Array[String] = [
|
||||
"stuff",
|
||||
|
|
@ -35,13 +36,17 @@ const LABELS: Array[String] = [
|
|||
"online",
|
||||
"paint",
|
||||
"chat",
|
||||
"freecam",
|
||||
"hide hud",
|
||||
]
|
||||
const SECTOR_COUNT: int = 10
|
||||
|
||||
var _buttons: Array[BubbleButton] = []
|
||||
var _is_open: bool = false
|
||||
var _selected_sector: int = 0
|
||||
var _controller_selection_mode: bool = false
|
||||
var _controller_mapping_manager: ControllerMappingManagerType
|
||||
var _hud_hidden: bool = false
|
||||
|
||||
|
||||
func setup_controller_mapping(
|
||||
|
|
@ -58,7 +63,7 @@ func _ready() -> void:
|
|||
bubble.profile = BubbleProfile
|
||||
bubble.focus_mode = Control.FOCUS_NONE
|
||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
bubble.text = LABELS[sector]
|
||||
bubble.text = _label_for_sector(sector)
|
||||
add_child(bubble)
|
||||
_buttons.append(bubble)
|
||||
_apply_selection_styles()
|
||||
|
|
@ -88,6 +93,7 @@ func open_menu(controller_selection: bool = false) -> void:
|
|||
_selected_sector = 0
|
||||
_controller_selection_mode = controller_selection
|
||||
visible = true
|
||||
_refresh_labels()
|
||||
_layout_bubbles()
|
||||
_apply_selection_styles()
|
||||
|
||||
|
|
@ -101,6 +107,22 @@ func is_open() -> bool:
|
|||
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:
|
||||
action_selected.emit(action_id)
|
||||
|
||||
|
|
@ -157,7 +179,8 @@ func _update_mouse_selection() -> 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))))
|
||||
if sector == _selected_sector:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ text = "Tekgator"
|
|||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.624, 0.812, 0.824, 1)
|
||||
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
|
||||
|
||||
[node name="AdditionalCredits" type="VBoxContainer" parent="Paper/Margin/Layout/Columns"]
|
||||
|
|
|
|||
37
world/environment/local_storm_cloud.gdshader
Normal file
37
world/environment/local_storm_cloud.gdshader
Normal 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;
|
||||
}
|
||||
1
world/environment/local_storm_cloud.gdshader.uid
Normal file
1
world/environment/local_storm_cloud.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c20wlon7hc48g
|
||||
228
world/environment/local_storm_cloud_layer.gd
Normal file
228
world/environment/local_storm_cloud_layer.gd
Normal 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
|
||||
1
world/environment/local_storm_cloud_layer.gd.uid
Normal file
1
world/environment/local_storm_cloud_layer.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://krinusur24lh
|
||||
|
|
@ -10,7 +10,7 @@ ambient_light_source = 2
|
|||
ambient_light_color = Color(0.82, 0.9, 0.92, 1)
|
||||
ambient_light_energy = 1.08
|
||||
reflected_light_source = 2
|
||||
fog_enabled = true
|
||||
fog_enabled = false
|
||||
fog_mode = 1
|
||||
fog_light_color = Color(0.54902, 0.74902, 0.764706, 1)
|
||||
fog_light_energy = 0.82
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
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_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);
|
||||
|
|
@ -83,6 +85,7 @@ void sky() {
|
|||
base_color = mix(
|
||||
base_color, moon_color.rgb, moon_disc * moon_visible_strength
|
||||
);
|
||||
base_color = apply_weather_clouds(base_color, view_direction);
|
||||
|
||||
COLOR = base_color;
|
||||
}
|
||||
|
|
|
|||
70
world/environment/netfishing_sky_clouds.gdshaderinc
Normal file
70
world/environment/netfishing_sky_clouds.gdshaderinc
Normal 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);
|
||||
}
|
||||
1
world/environment/netfishing_sky_clouds.gdshaderinc.uid
Normal file
1
world/environment/netfishing_sky_clouds.gdshaderinc.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cuqo54hgbjcrf
|
||||
|
|
@ -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 vec4 environment_tint : source_color = vec4(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 float surface_view_depth;
|
||||
|
|
@ -569,6 +574,22 @@ void fragment() {
|
|||
visible_highlight_color,
|
||||
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;
|
||||
ALPHA = clamp(water_alpha, 0.1, 0.96);
|
||||
|
|
|
|||
|
|
@ -9,12 +9,21 @@ const RAIN_PARTICLE_AMOUNT: int = 560
|
|||
const RAIN_VELOCITY_MIN: float = 16.0
|
||||
const RAIN_VELOCITY_MAX: float = 20.0
|
||||
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(
|
||||
"res://world/materials/stylized_water.tres"
|
||||
)
|
||||
const FRESH_WATER_MATERIAL: ShaderMaterial = preload(
|
||||
"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_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_SUN_DISC := Color(1.0, 0.45, 0.16)
|
||||
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 _weather_service: WorldWeatherService
|
||||
var _world_environment: WorldEnvironment
|
||||
var _sun: DirectionalLight3D
|
||||
var _rain_target: Node3D
|
||||
var _rain_camera_provider: Callable
|
||||
var _environment: Environment
|
||||
var _sky_material: ShaderMaterial
|
||||
var _rain: GPUParticles3D
|
||||
var _storm_clouds: LocalStormCloudLayer
|
||||
var _elapsed: float = 0.0
|
||||
var _weather_from: WorldWeatherService.Weather = (
|
||||
WorldWeatherService.Weather.SUNNY
|
||||
|
|
@ -69,16 +90,19 @@ func setup(
|
|||
sun: DirectionalLight3D,
|
||||
weather_service: WorldWeatherService = null,
|
||||
rain_target: Node3D = null,
|
||||
rain_camera_provider: Callable = Callable(),
|
||||
) -> void:
|
||||
_time_service = time_service
|
||||
_weather_service = weather_service
|
||||
_world_environment = world_environment
|
||||
_sun = sun
|
||||
_rain_target = rain_target
|
||||
_rain_camera_provider = rain_camera_provider
|
||||
if not _prepare_runtime_environment():
|
||||
set_process(false)
|
||||
return
|
||||
_prepare_rain()
|
||||
_prepare_storm_clouds()
|
||||
if _weather_service != null:
|
||||
_weather_from = _weather_service.get_weather()
|
||||
_weather_to = _weather_from
|
||||
|
|
@ -184,6 +208,13 @@ func _prepare_rain() -> void:
|
|||
_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:
|
||||
if _environment == null or _sky_material == null or _sun == null:
|
||||
return
|
||||
|
|
@ -220,6 +251,19 @@ func _apply_time(time_hours: float) -> void:
|
|||
var fog_color: Color = _blended_color(
|
||||
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_top_color", sky_top
|
||||
)
|
||||
|
|
@ -235,24 +279,34 @@ func _apply_time(time_hours: float) -> void:
|
|||
"ground_horizon_color",
|
||||
ground_horizon,
|
||||
)
|
||||
_apply_sky_clouds(daylight, warmth)
|
||||
_environment.background_energy_multiplier = (
|
||||
lerpf(0.52, 0.85, daylight)
|
||||
* _weather_value(1.0, 0.82, 0.66, 1.0)
|
||||
* fog_scene_brightness
|
||||
)
|
||||
_environment.ambient_light_color = ambient
|
||||
_environment.ambient_light_energy = (
|
||||
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_energy = (
|
||||
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(
|
||||
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(
|
||||
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
|
||||
)
|
||||
_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 = (
|
||||
lerpf(0.93, 0.98, daylight)
|
||||
* _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 = (
|
||||
lerpf(0.82, 0.91, daylight)
|
||||
|
|
@ -299,7 +363,7 @@ func _apply_time(time_hours: float) -> void:
|
|||
)
|
||||
_sun.light_energy = (
|
||||
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()
|
||||
|
||||
|
|
@ -307,6 +371,7 @@ func _apply_time(time_hours: float) -> void:
|
|||
func _apply_water_environment(
|
||||
daylight: float,
|
||||
warmth: float,
|
||||
weather_fog_amount: float,
|
||||
) -> void:
|
||||
var water_tint := _blended_color(
|
||||
NIGHT_WATER_TINT,
|
||||
|
|
@ -318,6 +383,11 @@ func _apply_water_environment(
|
|||
var water_brightness := (
|
||||
lerpf(0.34, 1.0, daylight)
|
||||
* _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 [
|
||||
SALT_WATER_MATERIAL,
|
||||
|
|
@ -328,6 +398,68 @@ func _apply_water_environment(
|
|||
"environment_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(
|
||||
|
|
@ -382,7 +514,14 @@ func _update_rain_amount() -> void:
|
|||
func _update_rain_position() -> void:
|
||||
if _rain == null or _rain_target == null:
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ var _rng := RandomNumberGenerator.new()
|
|||
var _daily_plan_id: String = ""
|
||||
var _daily_schedule: Array[Dictionary] = []
|
||||
var _world_time: WorldTimeService
|
||||
var _manual_override_weather: Weather = DEFAULT_WEATHER
|
||||
var _manual_override_segment_index: int = -1
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -44,6 +46,7 @@ func begin_authoritative_session(seed_value: int) -> void:
|
|||
_rng.seed = seed_value
|
||||
_running_authority = true
|
||||
set_process(true)
|
||||
_clear_manual_override()
|
||||
if _daily_schedule.is_empty() or _world_time == null:
|
||||
if _has_persistent_state:
|
||||
_set_weather(
|
||||
|
|
@ -64,12 +67,14 @@ func begin_authoritative_session(seed_value: int) -> void:
|
|||
func begin_remote_session() -> void:
|
||||
_running_authority = false
|
||||
set_process(false)
|
||||
_clear_manual_override()
|
||||
_set_weather(DEFAULT_WEATHER, SUNNY_DURATION_RANGE.x, true)
|
||||
|
||||
|
||||
func end_session() -> void:
|
||||
_running_authority = false
|
||||
set_process(false)
|
||||
_clear_manual_override()
|
||||
_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)
|
||||
|
||||
|
||||
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:
|
||||
if _persistence_tracking_enabled == enabled:
|
||||
return
|
||||
|
|
@ -181,6 +202,7 @@ func configure_daily_plan(
|
|||
return false
|
||||
_daily_plan_id = plan_id
|
||||
_daily_schedule.clear()
|
||||
_clear_manual_override()
|
||||
for value: Variant in schedule:
|
||||
_daily_schedule.append((value as Dictionary).duplicate(true))
|
||||
_world_time = world_time
|
||||
|
|
@ -193,6 +215,7 @@ func clear_daily_plan() -> void:
|
|||
_daily_plan_id = ""
|
||||
_daily_schedule.clear()
|
||||
_world_time = null
|
||||
_clear_manual_override()
|
||||
|
||||
|
||||
func get_daily_plan_id() -> String:
|
||||
|
|
@ -327,21 +350,43 @@ func _duration_range(weather: Weather) -> Vector2:
|
|||
func _update_scheduled_weather(force_emit: bool) -> void:
|
||||
if _daily_schedule.is_empty() or _world_time == null:
|
||||
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(
|
||||
_world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
|
||||
WorldTimeService.HOURS_PER_DAY,
|
||||
)
|
||||
var segment_hours: float = DAILY_PLAN_SEGMENT_HOURS
|
||||
var segment_index: int = clampi(
|
||||
floori(elapsed_hours / segment_hours),
|
||||
return clampi(
|
||||
floori(elapsed_hours / DAILY_PLAN_SEGMENT_HOURS),
|
||||
0,
|
||||
_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
|
||||
var hours_remaining: float = maxf(next_boundary - elapsed_hours, 0.0)
|
||||
var seconds_remaining: float = (
|
||||
hours_remaining / WorldTimeService.HOURS_PER_REAL_SECOND
|
||||
|
||||
|
||||
func _current_daily_segment_seconds_remaining(segment_index: int) -> float:
|
||||
var elapsed_hours: float = fposmod(
|
||||
_world_time.get_time_hours() - WorldTimeService.DAY_START_HOUR,
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue