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

@ -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_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

View file

@ -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;
}

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 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);

View file

@ -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:

View file

@ -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