@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