Files
Trinittu/dynamic-sky/scripts/weather_controller.gd
2026-02-03 11:26:48 +01:00

279 lines
8.0 KiB
GDScript

@tool
class_name WeatherController
extends Node
signal weather_state_changed(old_state: WeatherProfile.WeatherState, new_state: WeatherProfile.WeatherState)
signal weather_transition_started(from: WeatherProfile.WeatherState, to: WeatherProfile.WeatherState)
signal weather_transition_completed(state: WeatherProfile.WeatherState)
signal weather_state_entered(state: WeatherProfile.WeatherState)
signal weather_state_exited(state: WeatherProfile.WeatherState)
@export_group("Weather Profiles")
@export var weather_profiles: Array[WeatherProfile] = []
@export var current_weather: WeatherProfile.WeatherState = WeatherProfile.WeatherState.CLEAR:
set(value):
if value != current_weather:
_request_weather_change(value)
@export_group("Transition Settings")
@export var default_transition_duration: float = 5.0
@export var transition_curve: Curve
@export_group("Random Weather")
@export var enable_random_weather: bool = false
@export var min_weather_duration: float = 60.0
@export var max_weather_duration: float = 300.0
@export var snow_enabled: bool = true
@export_group("Scripted Timeline")
@export var use_scripted_timeline: bool = false
@export var weather_timeline: Array[Dictionary] = []
var _current_profile: WeatherProfile
var _target_profile: WeatherProfile
var _transition_weight: float = 1.0
var _is_transitioning: bool = false
var _transition_duration: float = 0.0
var _transition_elapsed: float = 0.0
var _random_weather_timer: float = 0.0
var _next_weather_change: float = 0.0
var _timeline_index: int = 0
var _timeline_timer: float = 0.0
func _ready() -> void:
if weather_profiles.is_empty():
_initialize_default_profiles()
if transition_curve == null:
transition_curve = Curve.new()
transition_curve.add_point(Vector2(0.0, 0.0))
transition_curve.add_point(Vector2(0.5, 0.5))
transition_curve.add_point(Vector2(1.0, 1.0))
_current_profile = get_profile_for_state(current_weather)
_target_profile = _current_profile
_schedule_next_random_weather()
func _process(delta: float) -> void:
if _is_transitioning:
_process_transition(delta)
if enable_random_weather and not use_scripted_timeline:
_process_random_weather(delta)
if use_scripted_timeline:
_process_scripted_timeline(delta)
func _initialize_default_profiles() -> void:
weather_profiles.clear()
weather_profiles.append(WeatherProfile.create_clear())
weather_profiles.append(WeatherProfile.create_cloudy())
weather_profiles.append(WeatherProfile.create_overcast())
weather_profiles.append(WeatherProfile.create_rain())
weather_profiles.append(WeatherProfile.create_storm())
weather_profiles.append(WeatherProfile.create_snow())
func _process_transition(delta: float) -> void:
_transition_elapsed += delta
var t := clampf(_transition_elapsed / _transition_duration, 0.0, 1.0)
if transition_curve:
_transition_weight = transition_curve.sample(t)
else:
_transition_weight = t
if t >= 1.0:
_complete_transition()
func _complete_transition() -> void:
_is_transitioning = false
_transition_weight = 1.0
_current_profile = _target_profile
var new_state := _current_profile.weather_state
weather_transition_completed.emit(new_state)
weather_state_entered.emit(new_state)
func _process_random_weather(delta: float) -> void:
_random_weather_timer += delta
if _random_weather_timer >= _next_weather_change:
_random_weather_timer = 0.0
_trigger_random_weather_change()
_schedule_next_random_weather()
func _schedule_next_random_weather() -> void:
_next_weather_change = randf_range(min_weather_duration, max_weather_duration)
func _trigger_random_weather_change() -> void:
var available_states: Array[WeatherProfile.WeatherState] = []
for state in WeatherProfile.WeatherState.values():
if state == current_weather:
continue
if state == WeatherProfile.WeatherState.SNOW and not snow_enabled:
continue
available_states.append(state)
if available_states.is_empty():
return
var new_state: WeatherProfile.WeatherState = available_states.pick_random()
set_weather(new_state)
func _process_scripted_timeline(delta: float) -> void:
if weather_timeline.is_empty():
return
_timeline_timer += delta
if _timeline_index < weather_timeline.size():
var entry: Dictionary = weather_timeline[_timeline_index]
var trigger_time: float = entry.get("time", 0.0)
if _timeline_timer >= trigger_time:
var state_name: String = entry.get("state", "CLEAR")
var duration: float = entry.get("duration", default_transition_duration)
if WeatherProfile.WeatherState.has(state_name):
var state: WeatherProfile.WeatherState = WeatherProfile.WeatherState.get(state_name)
set_weather(state, duration)
_timeline_index += 1
func _request_weather_change(new_state: WeatherProfile.WeatherState) -> void:
var profile := get_profile_for_state(new_state)
if profile == null:
push_warning("WeatherController: No profile for state %s" % WeatherProfile.WeatherState.keys()[new_state])
return
set_weather(new_state, profile.default_transition_duration)
func get_profile_for_state(state: WeatherProfile.WeatherState) -> WeatherProfile:
for profile in weather_profiles:
if profile.weather_state == state:
return profile
return null
func set_weather(state: WeatherProfile.WeatherState, transition_duration: float = -1.0) -> void:
var profile := get_profile_for_state(state)
if profile == null:
push_warning("WeatherController: No profile for state %s" % WeatherProfile.WeatherState.keys()[state])
return
if _current_profile:
weather_state_exited.emit(_current_profile.weather_state)
var old_state := current_weather
current_weather = state
_target_profile = profile
if transition_duration < 0:
transition_duration = profile.default_transition_duration
_transition_duration = transition_duration
_transition_elapsed = 0.0
_is_transitioning = true
_transition_weight = 0.0
weather_transition_started.emit(old_state, state)
weather_state_changed.emit(old_state, state)
func set_weather_immediate(state: WeatherProfile.WeatherState) -> void:
var profile := get_profile_for_state(state)
if profile == null:
return
if _current_profile:
weather_state_exited.emit(_current_profile.weather_state)
var old_state := current_weather
current_weather = state
_current_profile = profile
_target_profile = profile
_is_transitioning = false
_transition_weight = 1.0
weather_state_changed.emit(old_state, state)
weather_state_entered.emit(state)
func get_current_profile() -> WeatherProfile:
if _is_transitioning and _current_profile and _target_profile:
return _current_profile.lerp_to(_target_profile, _transition_weight)
return _current_profile
func get_cloud_density() -> float:
var profile := get_current_profile()
return profile.cloud_density if profile else 0.2
func get_cloud_opacity() -> float:
var profile := get_current_profile()
return profile.cloud_opacity if profile else 0.7
func get_cloud_coverage() -> float:
var profile := get_current_profile()
return profile.cloud_coverage if profile else 0.3
func get_cloud_color() -> Color:
var profile := get_current_profile()
return profile.cloud_color if profile else Color.WHITE
func get_wind_speed() -> float:
var profile := get_current_profile()
return profile.wind_speed if profile else 1.0
func get_wind_direction() -> Vector2:
var profile := get_current_profile()
return profile.wind_direction if profile else Vector2(1.0, 0.0)
func get_rain_intensity() -> float:
var profile := get_current_profile()
return profile.rain_intensity if profile else 0.0
func get_snow_intensity() -> float:
var profile := get_current_profile()
return profile.snow_intensity if profile else 0.0
func is_lightning_active() -> bool:
var profile := get_current_profile()
return profile.lightning_enabled if profile else false
func get_lightning_frequency() -> float:
var profile := get_current_profile()
return profile.lightning_frequency if profile else 0.0
func get_transition_progress() -> float:
return _transition_weight
func is_transitioning() -> bool:
return _is_transitioning
func reset_timeline() -> void:
_timeline_index = 0
_timeline_timer = 0.0