extends CanvasLayer ## Exposes high-level controls for overarching visual and audio effects. ## Default weather intensity transition time. const DEFAULT_WEATHER_FADE: float = 10.0 ## Default screen fade transition time. const DEFAULT_FADE: float = 1.0 ## Default tint transition time. const DEFAULT_TINT: float = 1.0 ## Default flash duration. const DEFAULT_FLASH: float = 1.0 ## Max turbulence influence at peak snow magnitude. const SNOW_MAX_MAX_TURBULENCE_INFLUENCE: float = 0.375 ## Max turbulence influence at zero snow magnitude. const SNOW_MIN_MAX_TURBULENCE_INFLUENCE: float = 0.125 ## Minimum necessary rain magnitude for lightning. const LIGHTNING_RAIN_THRESHOLD: float = 0.8 ## Probability of a lightning strike at any given frame at peak rain magnitude. const LIGHTNING_MAX_PROBABILITY_PER_FRAME: float = 0.005 ## Maximum duration of random lighning. const LIGHTNING_MAX_FADE: float = 1.0 ## Active configuration. @export var config: FXConfig ## Audio fader for playing music. @onready var bgm_fader := $BGM as AudioFader ## Audio fader for playing general-purpose continuous ambience. @onready var bgs_fader := $BGS as AudioFader ## Ambience interpolator for playing continuous ambience related to rain. @onready var rain_bgs_interpolator := $RainBGS as AmbienceInterpolator ## Ambience interpolator for playing continuous ambience related to snow. @onready var snow_bgs_interpolator := $SnowBGS as AmbienceInterpolator ## Having a single universal environment allows high-level manipulation. @onready var environment := $WorldEnvironment.environment as Environment ## Tween channel for changing fog color. @onready var fog_color_tween_channel := TweenChannel.make_replacing() ## Tween channel for changing fog density. @onready var fog_density_tween_channel := TweenChannel.make_replacing() ## Tween channel for changing fog sky affect. @onready var fog_sky_affect_tween_channel := TweenChannel.make_replacing() ## Sky resource for use with environment. @onready var sky := $WorldEnvironment.environment.sky as Sky ## Tween channels for shader globals. @onready var shader_global_tween_channels := {} ## Having a single universal light source allows high-level manipulation. @onready var light := $DirectionalLight3D as DirectionalLight3D ## Tween channel for moving light source. @onready var light_transform_tween_channel := TweenChannel.make_replacing() ## Tween channel for changing light source energy. @onready var light_energy_tween_channel := TweenChannel.make_replacing() ## Particle system for rendering rain. @onready var rain_particles := $RainParticles as WeatherParticles ## Tween channel for changing rain particle amount ratio. @onready var rain_amount_tween_channel := TweenChannel.make_replacing() ## Particle system for rendering snow. @onready var snow_particles := $SnowParticles as WeatherParticles ## Tween channel for changing snow particle amount ratio. @onready var snow_amount_tween_channel := TweenChannel.make_replacing() ## Tween channel for changing snow turbulence. @onready var snow_turbulence_tween_channel := TweenChannel.make_replacing() ## Multiplicative solid-color overlay primarily for ambient effects. @onready var tint_rect := $Tint as ColorRect ## Tween channel for changing tint color. @onready var tint_tween_channel := TweenChannel.make_replacing() ## Alpha-blended solid-color overlay primarily for screen transitions. @onready var fade_rect := $Fade as ColorRect ## Tween channel for changing fade color. @onready var fade_tween_channel := TweenChannel.make_replacing() ## Alpha-blended solid-color overlay primarily for dramatic effect. @onready var flash_rect := $Flash as ColorRect ## Tween channel for changing flash color. @onready var flash_tween_channel := TweenChannel.make_replacing() ## Sets the environment to use canvas layer 1 (and below) as the background. func enable_canvas_background() -> void: environment.background_mode = Environment.BG_CANVAS environment.background_canvas_max_layer = 1 ## Sets the environment to use the sky as the background. func disable_canvas_background() -> void: environment.background_mode = Environment.BG_SKY environment.sky = sky ## Applies the background mode associated with the active config. func restore_background_mode() -> void: if config.canvas: enable_canvas_background() else: disable_canvas_background() ## Fades out any current music and plays the given music instead. func play_bgm(stream: AudioStream, fade: float = 0.0) -> void: if fade > 0.0: await bgm_fader.fade_in(stream, fade) else: await bgm_fader.play(stream) ## Fades out any current music. func stop_bgm(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: await bgm_fader.fade_out(fade) ## Plays the music specified by the active config. func restore_bgm(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: await play_bgm(config.bgm, fade) ## Fades out any current general-purpose ambience and plays the given instead. func play_bgs( stream: AudioStream, fade: float = AudioFader.DEFAULT_AUDIO_FADE ) -> void: await bgs_fader.crossfade(stream, fade) ## Fades out any current general-purpose ambience. func stop_bgs(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: await bgs_fader.fade_out(fade) ## Plays the ambience specified by the active config. func restore_bgs(fade: float = AudioFader.DEFAULT_AUDIO_FADE) -> void: await play_bgs(config.bgs, fade) ## Sets fog color. Alpha is used for density and sky affect. func set_fog(color: Color, fade: float = DEFAULT_WEATHER_FADE) -> void: var opaque := color opaque.a = 1.0 var set_fog_color := func() -> void: var tween := await fog_color_tween_channel.create_tween(self) tween.tween_property(environment, ^'fog_light_color', opaque, fade) await fog_color_tween_channel.sync(tween) var set_fog_density := func() -> void: var tween := await fog_density_tween_channel.create_tween(self) tween.tween_property(environment, ^'fog_density', color.a, fade) await fog_density_tween_channel.sync(tween) var set_fog_sky_affect := func() -> void: var tween := ( await fog_sky_affect_tween_channel.create_tween(self) ) tween.tween_property(environment, ^'fog_sky_affect', color.a, fade) await fog_sky_affect_tween_channel.sync(tween) await Task.group([ Task.new(set_fog_color), Task.new(set_fog_density), Task.new(set_fog_sky_affect) ]).sync() ## Sets fog density and sky affect without changing color. func set_fog_strength( strength: float, fade: float = DEFAULT_WEATHER_FADE ) -> void: var set_fog_density := func() -> void: var tween := await fog_density_tween_channel.create_tween(self) tween.tween_property(environment, ^'fog_density', strength, fade) await fog_density_tween_channel.sync(tween) var set_fog_sky_affect := func() -> void: var tween := ( await fog_sky_affect_tween_channel.create_tween(self) ) tween.tween_property(environment, ^'fog_sky_affect', strength, fade) await fog_sky_affect_tween_channel.sync(tween) await Task.group([ Task.new(set_fog_density), Task.new(set_fog_sky_affect) ]).sync() ## Applies the fog settings specified by the active config. func restore_fog(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_fog(config.fog, fade) ## Sets global shader uniform. func set_shader_global( what: StringName, value: float, fade: float = DEFAULT_WEATHER_FADE ) -> void: if not shader_global_tween_channels.has(what): shader_global_tween_channels[what] = TweenChannel.make_replacing() var tween_channel := shader_global_tween_channels[what] as TweenChannel var tween := await tween_channel.create_tween(self) tween.tween_method( RenderingServer.global_shader_parameter_set.bind(what), RenderingServer.global_shader_parameter_get(what), value, fade ) await tween_channel.sync(tween) ## Sets closeness to midnight. func set_night(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await set_shader_global(&'night', value, fade) ## Applies the night value specified by the active config. func restore_night(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_night(config.night, fade) ## Sets sky cloudiness. func set_overcast(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await set_shader_global(&'overcast', value, fade) ## Applies the overcast value specified by the active config. func restore_overcast(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_overcast(config.overcast, fade) ## Sets environment wetness. func set_wet(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await set_shader_global(&'wet', value, fade) ## Applies the wet value specified by the active config. func restore_wet(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_wet(config.wet, fade) ## Sets wind speed. func set_wind(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await set_shader_global(&'wind', value, fade) ## Applies the wind value specified by the active config. func restore_wind(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_wind(config.wind, fade) ## Sets light source's position (i.e. the opposite of its facing direction). func set_light_source( value: Vector3, fade: float = DEFAULT_WEATHER_FADE ) -> void: var from := light.basis var to := Basis.looking_at(-value) var setter := func(weight: float) -> void: light.basis = from.slerp(to, weight) var tween := await light_transform_tween_channel.create_tween(self) tween.tween_method(setter, 0.0, 1.0, fade) await light_transform_tween_channel.sync(tween) ## Applies the light source position specified by the active config. func restore_light_source(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_light_source(config.light_source, fade) ## Sets light source's energy. func set_light_energy( value: float, fade: float = DEFAULT_WEATHER_FADE ) -> void: var tween := await light_energy_tween_channel.create_tween(self) tween.tween_property(light, ^'light_energy', value, fade) await light_energy_tween_channel.sync(tween) ## Applies the light source energy specified by the active config. func restore_light_energy(fade: float = DEFAULT_WEATHER_FADE) -> void: await set_light_energy(config.light_energy, fade) ## Sets the weather to rainfall at the given magnitude. func set_rain(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await _set_weather(value, 0.0, fade) ## Sets the weather to snowfall at the given magnitude. func set_snow(value: float, fade: float = DEFAULT_WEATHER_FADE) -> void: await _set_weather(0.0, value, fade) ## Clears any active weather. func stop_weather(fade: float = DEFAULT_WEATHER_FADE) -> void: await _set_weather(0.0, 0.0, fade) ## Applies the weather specified by the active config. func restore_weather(fade: float = DEFAULT_WEATHER_FADE) -> void: match config.weather_type: FXConfig.WeatherType.NONE: await stop_weather(fade) FXConfig.WeatherType.RAIN: await set_rain(config.weather_magnitude, fade) FXConfig.WeatherType.SNOW: await set_snow(config.weather_magnitude, fade) ## Sets screen tint. func tint(color: Color, fade: float = DEFAULT_TINT) -> void: var tween := await tint_tween_channel.create_tween(self) tween.tween_property(tint_rect, ^'color', color, fade) await tint_tween_channel.sync(tween) ## Clears screen tint. func clear_tint(fade: float = DEFAULT_TINT) -> void: await tint(Color.WHITE, fade) ## Applies the screen tint specified by the active config. func restore_tint(fade: float = DEFAULT_TINT) -> void: await tint(config.tint, fade) ## Fades the screen out to the given color. func fade_out(color: Color = Color.BLACK, fade: float = DEFAULT_FADE) -> void: var tween := await fade_tween_channel.create_tween(self) tween.tween_property(fade_rect, ^'color', color, fade) await fade_tween_channel.sync(tween) ## Fades the screen in from any current screen fade state. func fade_in(fade: float = DEFAULT_FADE) -> void: var transparent_equiv := fade_rect.color transparent_equiv.a = 0.0 await fade_out(transparent_equiv, fade) ## Flashes the screen the given color. func flash(color: Color, fade: float = DEFAULT_FLASH) -> void: var transparent_equiv := color transparent_equiv.a = 0.0 var tween := await flash_tween_channel.create_tween(self) flash_rect.color = color tween.tween_property(flash_rect, ^'color', transparent_equiv, fade) await flash_tween_channel.sync(tween) ## Simulates lightning by rapidly shifting fog, brightness, and time of day. func lightning(value: float = 1.0, fade: float = DEFAULT_FLASH) -> void: var night_task := func() -> void: RenderingServer.global_shader_parameter_set( &'night', lerpf(config.night, 0.0, value) ) await restore_night(fade) var fog_task := func() -> void: environment.fog_density = lerpf(config.fog.a, 0.0, value) environment.fog_sky_affect = environment.fog_density await set_fog_strength(config.fog.a, fade) var light_task := func() -> void: light.light_energy = lerpf(config.light_energy, 1.0, value) await set_light_energy(config.light_energy, fade) await Task.group([ Task.new(night_task), Task.new(fog_task), Task.new(light_task) ]).sync() ## Applies given as active config. Restores all associated effects. ## ## If config to apply is null or not given, restores all effects ## associated with the current active config. func apply_conifg( new_config: FXConfig = null, fade: float = DEFAULT_WEATHER_FADE ) -> void: if new_config: config = new_config restore_background_mode() var bgm_task := func() -> void: await restore_bgm(fade) var bgs_task := func() -> void: await restore_bgs(fade) var fog_task := func() -> void: await restore_fog(fade) var night_task := func() -> void: await restore_night(fade) var overcast_task := func() -> void: await restore_overcast(fade) var wet_task := func() -> void: await restore_wet(fade) var wind_task := func() -> void: await restore_wind(fade) var light_source_task := func() -> void: await restore_light_source(fade) var light_energy_task := func() -> void: await restore_light_energy(fade) var weather_task := func() -> void: await restore_weather(fade) var tint_task := func() -> void: await restore_tint(fade) await Task.group([ Task.new(bgm_task), Task.new(bgs_task), Task.new(fog_task), Task.new(night_task), Task.new(overcast_task), Task.new(wet_task), Task.new(wind_task), Task.new(light_source_task), Task.new(light_energy_task), Task.new(weather_task), Task.new(tint_task) ]).sync() ## Changes the weather to an arbitrary and possibly chimeric configuration. func _set_weather( rain: float, snow: float, fade: float = DEFAULT_WEATHER_FADE ) -> void: var rain_audio_task := func() -> void: await rain_bgs_interpolator.interpolate(rain, fade) var rain_amount_task := func() -> void: var tween := await rain_amount_tween_channel.create_tween(self) tween.tween_property(rain_particles, ^'amount_ratio', rain, fade) await rain_amount_tween_channel.sync(tween) var snow_audio_task := func() -> void: await snow_bgs_interpolator.interpolate(snow, fade) var snow_amount_task := func() -> void: var tween := await snow_amount_tween_channel.create_tween(self) tween.tween_property(snow_particles, ^'amount_ratio', snow, fade) await snow_amount_tween_channel.sync(tween) var max_turbulence: float = lerpf( SNOW_MIN_MAX_TURBULENCE_INFLUENCE, SNOW_MAX_MAX_TURBULENCE_INFLUENCE, snow ) var min_turbulence: float = max_turbulence/2.0 var snow_max_turbulence_task := func() -> void: var tween := await snow_turbulence_tween_channel.create_tween(self) tween.tween_property( snow_particles.process_material, ^'turbulence_influence_max', max_turbulence, fade ) await snow_turbulence_tween_channel.sync(tween) var snow_min_turbulence_task := func() -> void: var tween := await snow_turbulence_tween_channel.create_tween(self) tween.tween_property( snow_particles.process_material, ^'turbulence_influence_min', min_turbulence, fade ) await snow_turbulence_tween_channel.sync(tween) await Task.group([ Task.new(rain_audio_task), Task.new(rain_amount_task), Task.new(snow_audio_task), Task.new(snow_amount_task), Task.new(snow_max_turbulence_task), Task.new(snow_min_turbulence_task) ]).sync() ## Lightning handling for _process. func _do_lightning() -> void: if rain_particles.amount_ratio > LIGHTNING_RAIN_THRESHOLD: var roll: float = randf() var max_power: float = ( (rain_particles.amount_ratio - LIGHTNING_RAIN_THRESHOLD)/ (1.0 - LIGHTNING_RAIN_THRESHOLD) ) var check: float = max_power*LIGHTNING_MAX_PROBABILITY_PER_FRAME if roll <= check: var power: float = max_power*roll/check # Intentionally not awaited. lightning(power, power*LIGHTNING_MAX_FADE) func _process(_delta: float) -> void: _do_lightning()