diff --git a/artifacts/shadow_height_errors.log b/artifacts/shadow_height_errors.log new file mode 100644 index 0000000..e69de29 diff --git a/artifacts/shadow_height_test.log b/artifacts/shadow_height_test.log new file mode 100644 index 0000000..da1dbb4 --- /dev/null +++ b/artifacts/shadow_height_test.log @@ -0,0 +1,7 @@ +Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org +OpenGL API 3.3.0 NVIDIA 610.88 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 4070 SUPER + +[MCPRuntimeServer] port: scanning 6570-6585 (default) +[MCPRuntimeServer] listening on 127.0.0.1:6570 +[MCPRegistry] runtime port 6570 registered for c:/users/tomek/documents/godot/randoloot +SURFACE WEATHER PASS: six biomes, layered shadow/weather, bounded particles, engine emission and expiry, snow wash diff --git a/artifacts/weather_biome_0.png b/artifacts/weather_biome_0.png new file mode 100644 index 0000000..d89a329 Binary files /dev/null and b/artifacts/weather_biome_0.png differ diff --git a/artifacts/weather_biome_0.png.import b/artifacts/weather_biome_0.png.import new file mode 100644 index 0000000..ca13d40 --- /dev/null +++ b/artifacts/weather_biome_0.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ru2hbrs0q665" +path="res://.godot/imported/weather_biome_0.png-56be56101fb8051111e1549c74b1d3f9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_0.png" +dest_files=["res://.godot/imported/weather_biome_0.png-56be56101fb8051111e1549c74b1d3f9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biome_1.png b/artifacts/weather_biome_1.png new file mode 100644 index 0000000..4e6800b Binary files /dev/null and b/artifacts/weather_biome_1.png differ diff --git a/artifacts/weather_biome_1.png.import b/artifacts/weather_biome_1.png.import new file mode 100644 index 0000000..8bce602 --- /dev/null +++ b/artifacts/weather_biome_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsvxij3sfl7r0" +path="res://.godot/imported/weather_biome_1.png-e23eb3ba14bf6b2312ebcd08a83a2787.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_1.png" +dest_files=["res://.godot/imported/weather_biome_1.png-e23eb3ba14bf6b2312ebcd08a83a2787.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biome_2.png b/artifacts/weather_biome_2.png new file mode 100644 index 0000000..155f2df Binary files /dev/null and b/artifacts/weather_biome_2.png differ diff --git a/artifacts/weather_biome_2.png.import b/artifacts/weather_biome_2.png.import new file mode 100644 index 0000000..ab9e496 --- /dev/null +++ b/artifacts/weather_biome_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d2dn14mo4fjsv" +path="res://.godot/imported/weather_biome_2.png-09f02d8dabe5cfaf52888c8d341cbad0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_2.png" +dest_files=["res://.godot/imported/weather_biome_2.png-09f02d8dabe5cfaf52888c8d341cbad0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biome_3.png b/artifacts/weather_biome_3.png new file mode 100644 index 0000000..0a40903 Binary files /dev/null and b/artifacts/weather_biome_3.png differ diff --git a/artifacts/weather_biome_3.png.import b/artifacts/weather_biome_3.png.import new file mode 100644 index 0000000..4c4fc37 --- /dev/null +++ b/artifacts/weather_biome_3.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cy5xf2gcagkpp" +path="res://.godot/imported/weather_biome_3.png-255dd2d621d148d1cf2834373e25c76c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_3.png" +dest_files=["res://.godot/imported/weather_biome_3.png-255dd2d621d148d1cf2834373e25c76c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biome_4.png b/artifacts/weather_biome_4.png new file mode 100644 index 0000000..160b569 Binary files /dev/null and b/artifacts/weather_biome_4.png differ diff --git a/artifacts/weather_biome_4.png.import b/artifacts/weather_biome_4.png.import new file mode 100644 index 0000000..3066a2e --- /dev/null +++ b/artifacts/weather_biome_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://de0ybcp2lacgy" +path="res://.godot/imported/weather_biome_4.png-9725c1874018ea20ca0488f51ce7ce9e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_4.png" +dest_files=["res://.godot/imported/weather_biome_4.png-9725c1874018ea20ca0488f51ce7ce9e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biome_5.png b/artifacts/weather_biome_5.png new file mode 100644 index 0000000..564c359 Binary files /dev/null and b/artifacts/weather_biome_5.png differ diff --git a/artifacts/weather_biome_5.png.import b/artifacts/weather_biome_5.png.import new file mode 100644 index 0000000..c5ff929 --- /dev/null +++ b/artifacts/weather_biome_5.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b60b8my0hbfc3" +path="res://.godot/imported/weather_biome_5.png-72690658e057b3e6edf27897d3452110.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biome_5.png" +dest_files=["res://.godot/imported/weather_biome_5.png-72690658e057b3e6edf27897d3452110.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_biomes_review.png b/artifacts/weather_biomes_review.png new file mode 100644 index 0000000..d35c510 Binary files /dev/null and b/artifacts/weather_biomes_review.png differ diff --git a/artifacts/weather_biomes_review.png.import b/artifacts/weather_biomes_review.png.import new file mode 100644 index 0000000..8ca5a2b --- /dev/null +++ b/artifacts/weather_biomes_review.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wr6q0g4mk4cv" +path="res://.godot/imported/weather_biomes_review.png-87628c1fa50d238ad7389e8e4a2b1238.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://artifacts/weather_biomes_review.png" +dest_files=["res://.godot/imported/weather_biomes_review.png-87628c1fa50d238ad7389e8e4a2b1238.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/artifacts/weather_errors.log b/artifacts/weather_errors.log new file mode 100644 index 0000000..e69de29 diff --git a/artifacts/weather_test.log b/artifacts/weather_test.log new file mode 100644 index 0000000..27ef7f9 --- /dev/null +++ b/artifacts/weather_test.log @@ -0,0 +1,7 @@ +Godot Engine v4.7.2.stable.official.ed1daf0bf - https://godotengine.org +OpenGL API 3.3.0 NVIDIA 610.88 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce RTX 4070 SUPER + +[MCPRuntimeServer] port: scanning 6570-6585 (default) +[MCPRuntimeServer] listening on 127.0.0.1:6571 +[MCPRegistry] runtime port 6571 registered for c:/users/tomek/documents/godot/randoloot +SURFACE WEATHER PASS: six biomes, layered shadow/weather, bounded particles, engine emission and expiry, snow wash diff --git a/planet_mechanic.txt b/planet_mechanic.txt index b180c44..8f94465 100644 --- a/planet_mechanic.txt +++ b/planet_mechanic.txt @@ -1138,3 +1138,11 @@ HANGAR BEZ ZAKLADEK I PRZEGLAD OKIEN — 2026-10-18 - Regresja slot_unlock_test i freighter_storage_test — PASS. - Podglad: artifacts/frigate_review_hangar_single.png. Pelny przeglad: artifacts/frigate_all_windows_review.png. + + +Efekty lotu i pogoda (18.10.2026) +- Lądownik rzuca miękki cień; podczas ciągu/hamowania wznieca lokalny pył, a na wodzie rozpryski. W tundrze unosi śnieg, przy drzewach czasem liście. Na skale pyłu jest mniej. Dopalacz wzmacnia efekt. +- Cząsteczki pozostają w przestrzeni świata i samoczynnie zanikają: maksymalnie 144 cząsteczki podmuchu oraz 190 pogodowych. +- Pogoda biomów: doliny i mokradła deszcz, jesienne lasy liście, wyżyny podmuchy, kaniony pył, tundra śnieg. Natężenie przechodzi płynnie między stanami co 38–85 sekund. +- Niska mgła nad dolinami pozostaje pod statkiem. Nad sceną są delikatne opady i smugi światła; przy silnym deszczu na mokradłach rzadki słaby błysk. Korony drzew lekko kołyszą się z wiatrem. +- Istniejący ambient wiatru reaguje na natężenie pogody. Efekty są kosmetyczne i nie zmieniają szans POI ani zagrożenia. diff --git a/resources/effects/lander_shadow.gdshader b/resources/effects/lander_shadow.gdshader new file mode 100644 index 0000000..9deca58 --- /dev/null +++ b/resources/effects/lander_shadow.gdshader @@ -0,0 +1,15 @@ +shader_type canvas_item; +render_mode unshaded; +void fragment() { + vec2 step_uv = TEXTURE_PIXEL_SIZE*12.0; + float a = texture(TEXTURE,UV).a*0.28; + a += texture(TEXTURE,UV+step_uv*vec2(1,0)).a*0.12; + a += texture(TEXTURE,UV-step_uv*vec2(1,0)).a*0.12; + a += texture(TEXTURE,UV+step_uv*vec2(0,1)).a*0.12; + a += texture(TEXTURE,UV-step_uv*vec2(0,1)).a*0.12; + a += texture(TEXTURE,UV+step_uv).a*0.06; + a += texture(TEXTURE,UV-step_uv).a*0.06; + a += texture(TEXTURE,UV+step_uv*vec2(1,-1)).a*0.06; + a += texture(TEXTURE,UV+step_uv*vec2(-1,1)).a*0.06; + COLOR = vec4(0.02,0.035,0.03,a*0.20); +} diff --git a/resources/effects/lander_shadow.gdshader.uid b/resources/effects/lander_shadow.gdshader.uid new file mode 100644 index 0000000..f4ad957 --- /dev/null +++ b/resources/effects/lander_shadow.gdshader.uid @@ -0,0 +1 @@ +uid://dh5qpdlbxshin diff --git a/resources/effects/tree_wind.gdshader b/resources/effects/tree_wind.gdshader new file mode 100644 index 0000000..30d6763 --- /dev/null +++ b/resources/effects/tree_wind.gdshader @@ -0,0 +1,8 @@ +shader_type canvas_item; +uniform vec2 wind_direction = vec2(0.95,0.3); +uniform float wind_strength = 0.6; +void vertex() { + vec2 point = (MODEL_MATRIX*vec4(VERTEX,0.0,1.0)).xy; + float sway = sin(TIME*1.35+point.x*0.012+point.y*0.006)*0.7+sin(TIME*2.1+point.y*0.014)*0.3; + VERTEX += wind_direction*sway*wind_strength*1.5; +} diff --git a/resources/effects/tree_wind.gdshader.uid b/resources/effects/tree_wind.gdshader.uid new file mode 100644 index 0000000..3ec596a --- /dev/null +++ b/resources/effects/tree_wind.gdshader.uid @@ -0,0 +1 @@ +uid://b87pkow0vmcgg diff --git a/scripts/planets/surface_audio.gd b/scripts/planets/surface_audio.gd index d05f48a..1ec4ac9 100644 --- a/scripts/planets/surface_audio.gd +++ b/scripts/planets/surface_audio.gd @@ -105,6 +105,7 @@ func _process(delta: float) -> void: _refresh_local() var gust := sin(_elapsed*0.19)*1.4+sin(_elapsed*0.073)*0.8 var engine_mask := -3.0 if source.engine_thrust_active() else 0.0 + if is_instance_valid(source.weather): gust += source.weather.intensity*2.5 _wind.volume_db = move_toward(_wind.volume_db,WIND_DB[_biome]+gust+engine_mask,delta*26) var leaf_target := -60.0 if _near_trees.is_empty() else lerpf(-34,-23,_density)+gust+engine_mask _foliage.volume_db = move_toward(_foliage.volume_db,leaf_target,delta*8) diff --git a/scripts/planets/surface_flight_effects.gd b/scripts/planets/surface_flight_effects.gd new file mode 100644 index 0000000..005dba9 --- /dev/null +++ b/scripts/planets/surface_flight_effects.gd @@ -0,0 +1,111 @@ +extends Node2D +## Ground wash stays in world space after emission, independently of the lander. +const LIMIT := 144 +var source: Control +var weather: Node2D +var _rng := RandomNumberGenerator.new() +var _particles: Array[Dictionary] = [] +var _soft: GradientTexture2D +var _shadow: Sprite2D +var _emission := 0.0 +var _tree_timer := 0.0 +var _near_trees := false +var _surface_kind := "dust" +var _ground := Color("b5a17a") + +func _ready() -> void: + name = "SurfaceFlightEffects" + z_index = 21 + texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR + _rng.seed = source.progress.visit_seed ^ 0x44555354 + _ground = (source.world.data.landscape.sand as Color).lerp(source.world.data.landscape.low,0.25) + var gradient := Gradient.new() + gradient.set_color(0,Color.WHITE) + gradient.set_color(1,Color(1,1,1,0)) + _soft = GradientTexture2D.new() + _soft.gradient = gradient + _soft.width = 64 + _soft.height = 64 + _soft.fill = GradientTexture2D.FILL_RADIAL + _soft.fill_from = Vector2(0.5,0.5) + _soft.fill_to = Vector2(0.5,1) + _shadow = Sprite2D.new() + _shadow.name = "LanderShadow" + _shadow.texture = source.lander.texture + _shadow.scale = source.lander.scale*0.76 + _shadow.z_index = -1 + var material := ShaderMaterial.new() + material.shader = preload("res://resources/effects/lander_shadow.gdshader") + _shadow.material = material + add_child(_shadow) + +func surface_kind(point: Vector2) -> String: + if int(source.world.data.landscape.index) == 5: return "snow" + var h: float = weather.terrain_height(point) + if h < 0.29 or (int(source.world.data.landscape.index) == 2 and h < 0.37): return "spray" + return "rock" if h > 0.64 else "dust" + +func _process(delta: float) -> void: + if not is_instance_valid(source): return + visible = not source._ending + if source._ending: return + var pose: Transform2D = source.lander.global_transform + pose.origin = source._previous_position.lerp(source.lander.position,Engine.get_physics_interpolation_fraction()) + var point := pose.origin + _shadow.position = point+Vector2(48,64) + _shadow.rotation = pose.get_rotation() + _shadow.visible = source.lander.visible + _tree_timer -= delta + if _tree_timer <= 0: + _tree_timer = 0.4 + _near_trees = false + for tree in source.world.data.trees: + if point.distance_squared_to(tree.position) < 170*170: + _near_trees = true + break + _surface_kind = surface_kind(point) + var thrust: bool = source.engine_thrust_active() + var brake: bool = not source._dialog.visible and Input.is_action_pressed("ship_brake") and source._motion.velocity.length() > 12 + var boost: bool = source._motion.boost_active and thrust + if thrust or brake: + var rate := 60.0 if boost else (26.0 if thrust else 18.0) + if _surface_kind == "rock": rate *= 0.3 + _emission += delta*rate + while _emission >= 1: + _emission -= 1 + if _particles.size() < LIMIT: _emit(point,pose.get_rotation(),boost) + else: _emission = 0 + for index in range(_particles.size()-1,-1,-1): + var particle: Dictionary = _particles[index] + particle.age += delta + particle.position += (particle.velocity+weather.wind*0.7)*delta + particle.velocity *= exp(-delta*1.3) + if particle.age > particle.life: _particles.remove_at(index) + queue_redraw() + +func _emit(point: Vector2, angle: float, boost: bool) -> void: + var kind := _surface_kind + if _near_trees and kind == "dust" and _rng.randf() < 0.20: kind = "leaf" + var nozzle := Vector2(_rng.randf_range(-29,29),_rng.randf_range(22,42)).rotated(angle) + var scatter := Vector2(_rng.randf_range(-32,32),_rng.randf_range(10,45)).rotated(angle) + _particles.append({"position":point+nozzle,"velocity":scatter+source._motion.velocity*0.1,"age":0.0,"life":_rng.randf_range(1.1,2.4),"size":_rng.randf_range(9,17)*(1.4 if boost else 1.0),"kind":kind,"angle":_rng.randf_range(-PI,PI)}) + +func _draw() -> void: + for particle in _particles: + var t := clampf(particle.age/particle.life,0,1) + var alpha := sin(t*PI) + var kind: String = particle.kind + var radius: float = particle.size*(1.0+t*1.9) + var color := _ground + color.a = alpha*0.14 + if kind == "rock": color = Color(0.65,0.65,0.59,alpha*0.09) + if kind == "snow": color = Color(0.88,0.95,0.95,alpha*0.25) + if kind == "spray": color = Color(0.66,0.83,0.80,alpha*0.18) + if kind == "leaf": + draw_set_transform(particle.position,particle.angle+t*2,Vector2(1,0.45)) + draw_circle(Vector2.ZERO,2.5,Color(0.49,0.35,0.17,alpha*0.7),true,-1,true) + draw_set_transform(Vector2.ZERO) + else: + draw_texture_rect(_soft,Rect2(particle.position-Vector2.ONE*radius,Vector2.ONE*radius*2),false,color) + if kind in ["spray","snow"]: + draw_circle(particle.position+Vector2(4,-7)*t,1.4,Color(color,alpha*0.38),true,-1,true) diff --git a/scripts/planets/surface_flight_effects.gd.uid b/scripts/planets/surface_flight_effects.gd.uid new file mode 100644 index 0000000..30a4e44 --- /dev/null +++ b/scripts/planets/surface_flight_effects.gd.uid @@ -0,0 +1 @@ +uid://b8fcnm6unlmjh diff --git a/scripts/planets/surface_screen.gd b/scripts/planets/surface_screen.gd index 182a15b..3181e5d 100644 --- a/scripts/planets/surface_screen.gd +++ b/scripts/planets/surface_screen.gd @@ -10,6 +10,8 @@ var progress: Resource var world: Node2D var lander: Sprite2D var camera: Camera2D +var weather: Node2D +var flight_effects: Node2D var _hp: ProgressBar var _cargo: Label var _threat: Label @@ -97,6 +99,15 @@ func _ready() -> void: atmosphere.set_script(preload("res://scripts/planets/surface_clouds.gd")) atmosphere.seed_value = progress.visit_seed viewport.add_child(atmosphere) + weather = Node2D.new() + weather.set_script(preload("res://scripts/planets/surface_weather.gd")) + weather.source = self + viewport.add_child(weather) + flight_effects = Node2D.new() + flight_effects.set_script(preload("res://scripts/planets/surface_flight_effects.gd")) + flight_effects.source = self + flight_effects.weather = weather + viewport.add_child(flight_effects) var nature_audio := Node.new() nature_audio.name = "SurfaceAudio" nature_audio.set_script(preload("res://scripts/planets/surface_audio.gd")) diff --git a/scripts/planets/surface_tree_canopy.gd b/scripts/planets/surface_tree_canopy.gd new file mode 100644 index 0000000..1257ede --- /dev/null +++ b/scripts/planets/surface_tree_canopy.gd @@ -0,0 +1,22 @@ +extends Node2D +var world: Node2D +var wind_material: ShaderMaterial +func _ready() -> void: + name = "WindCanopies" + texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS + wind_material = ShaderMaterial.new() + wind_material.shader = preload("res://resources/effects/tree_wind.gdshader") + material = wind_material + +func _draw() -> void: + for tree in world.data.trees: + var shade := float(tree.shade) + var color: Color = Color(shade,shade,shade)*world.data.landscape.trees + if int(world.data.profile.temperature) < -10: color = color.lerp(Color("d1e1da"),0.4) + elif int(world.data.profile.temperature) > 40: color = color.lerp(Color("c6a86b"),0.28) + var variant := int(tree.get("variant",-1)) + var texture: Texture2D = world._tree if variant < 0 else world._nature[variant] + var extent := Vector2(tree.size,tree.size*texture.get_height()/texture.get_width()) + draw_set_transform(tree.position,tree.rotation) + draw_texture_rect(texture,Rect2(-extent*0.5,extent),false,color) + draw_set_transform(Vector2.ZERO) diff --git a/scripts/planets/surface_tree_canopy.gd.uid b/scripts/planets/surface_tree_canopy.gd.uid new file mode 100644 index 0000000..c93880d --- /dev/null +++ b/scripts/planets/surface_tree_canopy.gd.uid @@ -0,0 +1 @@ +uid://bqq7jv5t84lsa diff --git a/scripts/planets/surface_weather.gd b/scripts/planets/surface_weather.gd new file mode 100644 index 0000000..4b8e78b --- /dev/null +++ b/scripts/planets/surface_weather.gd @@ -0,0 +1,130 @@ +extends Node2D +## World-space weather: bounded particle pool, slow transitions, no screen-space noise. +const LIMIT := 190 +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() + +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) + mode = ["rain","leaves","rain","wind","dust","snow"][_biome] + target_intensity = 0.38 if _biome in [2,5] else 0.18 + var material := ShaderMaterial.new() + material.shader = preload("res://resources/effects/cloud_mist.gdshader") + for index in 12: + var fog := Sprite2D.new() + fog.texture = preload("res://assets/planets/cloud_mist.png") + fog.material = material + fog.position = Vector2(index%4*1100-1650,index/4*1100-1100) + 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: + _transition = _rng.randf_range(38,85) + target_intensity = _rng.randf_range(0.12,0.85) if _biome in [2,4,5] else _rng.randf_range(0.0,0.58) + intensity = move_toward(intensity,target_intensity,delta*0.018) + var gust := 0.75+0.25*sin(_time*0.19)+0.12*sin(_time*0.57) + wind = Vector2(22,7).rotated(sin(_time*0.045)*0.25)*(0.7+intensity*1.4)*gust + 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) + fog.modulate.a = (0.035+intensity*0.14)*valley*(1.0 if _biome in [0,1,2,5] else 0.3) + if _biome == 4: + for fog in _fog: fog.modulate = Color(0.76,0.62,0.39,fog.modulate.a+intensity*0.04) + _emission += delta*([13,12,34,8,24,34][_biome])*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.8) + _storm_timer -= delta + if mode == "rain" and intensity > 0.66 and _storm_timer <= 0: + _storm_timer = _rng.randf_range(28,65) + _flash = 0.045 + var point := center+Vector2(_rng.randf_range(-extent.x*0.8,extent.x*0.8),-extent.y*0.7) + _bolt = PackedVector2Array([point,point+Vector2(15,30),point+Vector2(4,45),point+Vector2(31,80)]) + 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(-25,250) 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(3,6),"size":_rng.randf_range(1.1,2.8),"phase":_rng.randf_range(0,TAU)}) + +func _draw() -> void: + 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 mode: + "rain": + draw_line(point,point+Vector2(-2,12),Color(0.73,0.83,0.86,alpha*0.26),0.8,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.55),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,_flash*6),1.2,true) + +func _draw_reset() -> void: + draw_set_transform(Vector2.ZERO) diff --git a/scripts/planets/surface_weather.gd.uid b/scripts/planets/surface_weather.gd.uid new file mode 100644 index 0000000..d0cf848 --- /dev/null +++ b/scripts/planets/surface_weather.gd.uid @@ -0,0 +1 @@ +uid://d3omfrwkxohc6 diff --git a/scripts/planets/surface_world.gd b/scripts/planets/surface_world.gd index e7d489d..4f93344 100644 --- a/scripts/planets/surface_world.gd +++ b/scripts/planets/surface_world.gd @@ -4,6 +4,7 @@ const BUILDING_SCALE := preload("res://scripts/planets/surface_scenery.gd").BUIL const GENERATOR := preload("res://scripts/planets/surface_generator.gd") var data: Dictionary var terrain: ImageTexture +var tree_canopy: Node2D var _nature: Array[AtlasTexture] = [] var _tree: Texture2D var _poi_sprites: Dictionary = {} @@ -54,6 +55,10 @@ func _ready() -> void: mountains.data = data mountains.z_index = -1 add_child(mountains) + tree_canopy = Node2D.new() + tree_canopy.set_script(preload("res://scripts/planets/surface_tree_canopy.gd")) + tree_canopy.world = self + add_child(tree_canopy) for poi in data.pois: var sprite := Sprite2D.new() if poi.kind == "deposit": @@ -146,15 +151,6 @@ func _draw() -> void: _draw_prop(_nature[int(detail.variant)], detail.position, detail.size, detail.rotation, Color(shade,shade,shade)) for rock in data.rocks: _draw_prop(_nature[int(rock.get("variant",4))],rock.position,rock.size,rock.rotation,Color.WHITE) - for tree in data.trees: - var shade := float(tree.shade) - var color: Color = Color(shade,shade,shade)*data.landscape.trees - if int(data.profile.temperature) < -10: - color = color.lerp(Color("d1e1da"), 0.4) - elif int(data.profile.temperature) > 40: - color = color.lerp(Color("c6a86b"), 0.28) - var variant := int(tree.get("variant", -1)) - _draw_prop(_tree if variant < 0 else _nature[variant], tree.position, tree.size, tree.rotation, color) # Small landing beacon. draw_circle(Vector2.ZERO, 50, Color(0.5, 0.85, 0.8, 0.12)) draw_arc(Vector2.ZERO, 48, 0, TAU, 48, Color("8cd9c2"), 2, true) diff --git a/tests/previews/surface_weather_preview.gd b/tests/previews/surface_weather_preview.gd new file mode 100644 index 0000000..6cce355 --- /dev/null +++ b/tests/previews/surface_weather_preview.gd @@ -0,0 +1,52 @@ +extends Node + +func _ready() -> void: + var sheet := Image.create(1280,1080,false,Image.FORMAT_RGBA8) + var progress: Resource = GameState.captain.expedition + for biome in 6: + var profile: Dictionary = preload("res://scripts/planets/planet_profiles.gd").generate(99,biome,"Pogoda / biom %d" % (biome+1)) + profile.temperature = 75 if biome == 4 else (-35 if biome == 5 else 20) + progress.lander_hp = 100 + assert(progress.begin_visit(profile)) + progress.visit_seed = 200+biome + var surface := Control.new() + surface.set_script(preload("res://scripts/planets/surface_screen.gd")) + surface.captain = GameState.captain + surface.progress = progress + add_child(surface) + surface.set_process(false) + surface.set_physics_process(false) + surface.weather.set_process(false) + surface.flight_effects.set_process(false) + surface.weather.intensity = 0.75 + surface.weather.target_intensity = 0.75 + assert(surface.weather._biome == biome) + assert(surface.weather.z_index > surface.lander.z_index) + assert(surface.flight_effects.z_index < surface.lander.z_index) + Input.action_press("ship_thrust") + for frame in 240: + surface.weather._process(1.0/60) + surface.flight_effects._process(1.0/60) + assert(surface.weather._particles.size() > 0) + assert(surface.weather._particles.size() <= surface.weather.LIMIT) + assert(surface.flight_effects._particles.size() > 0) + assert(surface.flight_effects._particles.size() <= surface.flight_effects.LIMIT) + assert(surface.flight_effects._shadow.texture == surface.lander.texture) + for frame in 5: await get_tree().process_frame + await RenderingServer.frame_post_draw + var image: Image = get_viewport().get_texture().get_image() + assert(image.save_png("res://artifacts/weather_biome_%d.png" % biome) == OK) + image.convert(Image.FORMAT_RGBA8) + image.resize(640,360,Image.INTERPOLATE_LANCZOS) + sheet.blit_rect(image,Rect2i(0,0,640,360),Vector2i(biome%2*640,biome/2*360)) + Input.action_release("ship_thrust") + for frame in 180: surface.flight_effects._process(1.0/60) + assert(surface.flight_effects._particles.is_empty(),"Ground wash must expire after stopping engines") + if biome == 5: assert(surface.flight_effects.surface_kind(Vector2.ZERO) == "snow") + progress.finish_visit(GameState.captain,false) + surface.queue_free() + await get_tree().process_frame + await get_tree().process_frame + assert(sheet.save_png("res://artifacts/weather_biomes_review.png") == OK) + print("SURFACE WEATHER PASS: six biomes, layered shadow/weather, bounded particles, engine emission and expiry, snow wash") + get_tree().quit() diff --git a/tests/previews/surface_weather_preview.gd.uid b/tests/previews/surface_weather_preview.gd.uid new file mode 100644 index 0000000..a9d9090 --- /dev/null +++ b/tests/previews/surface_weather_preview.gd.uid @@ -0,0 +1 @@ +uid://cd8q7210dpt26 diff --git a/tests/previews/surface_weather_preview.tscn b/tests/previews/surface_weather_preview.tscn new file mode 100644 index 0000000..fc7eb63 --- /dev/null +++ b/tests/previews/surface_weather_preview.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/previews/surface_weather_preview.gd" id="1"] + +[node name="SurfaceWeatherPreview" type="Node"] +script = ExtResource("1")