extends Node2D ## World-space weather: bounded particle pool, slow transitions, no screen-space noise. const LIMIT := 360 const CONDITIONS := preload("res://scripts/planets/surface_weather_conditions.gd") var source: Control var intensity := 0.15 var target_intensity := 0.2 var wind := Vector2(18,6) var mode := "wind" var _rng := RandomNumberGenerator.new() var _time := 0.0 var _transition := 36.0 var _storm_timer := 40.0 var _flash := 0.0 var _emission := 0.0 var _particles: Array[Dictionary] = [] var _fog: Array[Sprite2D] = [] var _biome := 0 var _bolt := PackedVector2Array() var condition := "clear" var _darkness := 0.0 var _clouds: Node2D var _fog_strength := 0.1 func _ready() -> void: name = "SurfaceWeather" z_index = 80 texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS _rng.seed = source.progress.visit_seed ^ 0x57454154 _biome = int(source.world.data.landscape.index) _clouds = get_parent().get_node_or_null("SurfaceAtmosphere") set_condition(CONDITIONS.pick(_biome,_rng), true) var material := ShaderMaterial.new() material.shader = preload("res://resources/effects/cloud_mist.gdshader") for index in 20: var fog := Sprite2D.new() fog.texture = preload("res://assets/planets/cloud_mist.png") fog.material = material fog.position = Vector2(index%5*950-1900,index/5*950-1425) fog.rotation = _rng.randf_range(-PI,PI) fog.scale = Vector2.ONE*_rng.randf_range(650,1100)/fog.texture.get_width() fog.z_as_relative = false fog.z_index = 18 fog.modulate = Color(0.79,0.84,0.79,0.0) add_child(fog) _fog.append(fog) func _center() -> Vector2: return source.camera.get_screen_center_position() func _extent() -> Vector2: return Vector2(get_viewport_rect().size)/source.camera.zoom*0.5+Vector2(140,140) func _process(delta: float) -> void: if not is_instance_valid(source) or source._ending: return _time += delta _transition -= delta if _transition <= 0: set_condition(CONDITIONS.pick(_biome,_rng)) intensity = move_toward(intensity,target_intensity,delta*0.028) var gust := 0.75+0.25*sin(_time*0.19)+0.12*sin(_time*0.57) var preset: Dictionary = CONDITIONS.DATA[condition] _darkness = move_toward(_darkness,float(preset.darkness),delta*0.10) _fog_strength = move_toward(_fog_strength,float(preset.fog),delta*0.08) wind = Vector2(22,7).rotated(sin(_time*0.045)*0.25)*(0.7+intensity*1.4)*gust*float(preset.wind) if is_instance_valid(_clouds): _clouds.set_weather(float(preset.clouds),_darkness,wind) if is_instance_valid(source.world.tree_canopy): source.world.tree_canopy.wind_material.set_shader_parameter("wind_direction",wind.normalized()) source.world.tree_canopy.wind_material.set_shader_parameter("wind_strength",0.5+intensity*1.4) for index in _fog.size(): var fog := _fog[index] fog.position += wind*delta*0.24 fog.position.x = wrapf(fog.position.x,-2350,2350) fog.position.y = wrapf(fog.position.y,-2100,2100) var h := terrain_height(fog.position) var valley := 1.0-smoothstep(0.42,0.65,h) var fog_color := Color(0.79,0.84,0.79).lerp(Color(0.20,0.26,0.32),_darkness*0.65) fog_color.a = (0.02+_fog_strength*0.24)*valley*(1.0 if _biome in [0,1,2,5] else 0.3) fog.modulate = fog_color if _biome == 4: for fog in _fog: fog.modulate = Color(0.76,0.62,0.39,fog.modulate.a+intensity*0.04) var emission_rate := 170.0 if mode == "rain" else (85.0 if mode in ["snow","dust"] else 16.0) _emission += delta*emission_rate*intensity while _emission >= 1: _emission -= 1 if _particles.size() < LIMIT: _spawn() var center := _center() var extent := _extent() for index in range(_particles.size()-1,-1,-1): var p: Dictionary = _particles[index] p.age += delta p.position += (p.velocity+wind)*delta if p.age > p.life or absf(p.position.x-center.x) > extent.x*1.3 or absf(p.position.y-center.y) > extent.y*1.3: _particles.remove_at(index) _flash = move_toward(_flash,0,delta*0.42) _storm_timer -= delta if condition == "storm" and intensity > 0.65 and _storm_timer <= 0: trigger_lightning() queue_redraw() func terrain_height(point: Vector2) -> float: var x := clampi(int((point.x+1920)/40),0,95) var y := clampi(int((point.y+1920)/40),0,95) return float(source.world.data.heights[y*96+x]) func _spawn() -> void: var extent := _extent() var point := _center()+Vector2(_rng.randf_range(-extent.x,extent.x),_rng.randf_range(-extent.y,extent.y)) var velocity := Vector2(-70,460) if mode == "rain" else Vector2(_rng.randf_range(-8,8),_rng.randf_range(7,22)) _particles.append({"position":point,"velocity":velocity,"age":0.0,"life":_rng.randf_range(0.8,1.6) if mode == "rain" else _rng.randf_range(3,6),"kind":mode,"size":_rng.randf_range(1.1,2.8),"phase":_rng.randf_range(0,TAU)}) func _draw() -> void: var tint := Color(0.05,0.08,0.14,_darkness*0.12) if condition == "dust_storm": tint = Color(0.46,0.30,0.12,intensity*0.10) draw_rect(Rect2(_center()-_extent(),_extent()*2),tint) for p in _particles: var alpha := sin(clampf(p.age/p.life,0,1)*PI) var point: Vector2 = p.position+Vector2(sin(_time*1.6+p.phase)*3,0) match str(p.get("kind",mode)): "rain": draw_line(point,point+Vector2(-4,18),Color(0.73,0.83,0.90,alpha*0.48),0.9,true) if terrain_height(point) < 0.32: draw_arc(point+Vector2(-2,12),p.size*1.5,0,TAU,12,Color(0.73,0.83,0.86,alpha*0.10),0.7,true) "snow": draw_circle(point,p.size,Color(0.91,0.96,0.97,alpha*0.65),true,-1,true) "leaves": draw_set_transform(point,sin(_time+p.phase),Vector2(1,0.45)) draw_circle(Vector2.ZERO,p.size,Color(0.61,0.43,0.19,alpha*0.55),true,-1,true) _draw_reset() _: draw_line(point,point+wind.normalized()*p.size*7,Color(0.80,0.72,0.53,alpha*0.11),1,true) var center := _center() var extent := _extent() # Very faint broad sun shafts, confined to damp wooded climates. if _biome in [0,1,2]: for index in 2: var start := center+Vector2(-extent.x*0.7+index*extent.x*0.9,-extent.y) draw_colored_polygon(PackedVector2Array([start,start+Vector2(50,0),start+Vector2(380,extent.y*2),start+Vector2(210,extent.y*2)]),Color(0.95,0.87,0.62,0.012*(1.0-intensity))) if _flash > 0: draw_rect(Rect2(center-extent,extent*2),Color(0.78,0.84,1.0,_flash)) draw_polyline(_bolt,Color(0.83,0.90,1.0,minf(_flash*7,0.85)),2.0,true) func _draw_reset() -> void: draw_set_transform(Vector2.ZERO) func set_condition(next: String, immediate := false) -> bool: if not CONDITIONS.DATA.has(next): return false condition = next var preset: Dictionary = CONDITIONS.DATA[condition] mode = str(preset.mode) if mode == "wind": mode = "leaves" if _biome == 1 else ("dust" if _biome == 4 else "wind") target_intensity = float(preset.intensity) _transition = _rng.randf_range(45,95) _storm_timer = _rng.randf_range(5,14) if immediate: intensity = target_intensity _darkness = float(preset.darkness) _fog_strength = float(preset.fog) if is_instance_valid(_clouds): _clouds.set_weather(float(preset.clouds),float(preset.darkness),wind) return true func display_name() -> String: return str(CONDITIONS.DATA[condition].name) func trigger_lightning() -> void: _storm_timer = _rng.randf_range(7,18) _flash = 0.10 var center := _center() var extent := _extent() var point := center+Vector2(_rng.randf_range(-extent.x*0.65,extent.x*0.65),-extent.y*0.85) _bolt = PackedVector2Array([point]) for step in range(1,9): _bolt.append(point+Vector2(_rng.randf_range(-35,35)+step*8,step*30))