diff --git a/assets/background/space/VARIANTS.md b/assets/background/space/VARIANTS.md new file mode 100644 index 0000000..4f7f7b3 --- /dev/null +++ b/assets/background/space/VARIANTS.md @@ -0,0 +1,23 @@ +# Space background variants + +Generated with built-in imagegen. The original deep_space.png remains the first background. New artwork cycles through systems 2–6, then repeats every six systems. Native Parallax2D motion, dust and star layers are unchanged. + +## teal_veil.png + +Use case: stylized-concept. Final production landscape 3:2 background texture for a top-down space RPG, at least 1536 by 1024 pixels. Realistic painterly astronomical space art, extremely fine sharp dust filaments and delicate stars. Preserve dark near-black navy central negative space for readable spaceship combat. Rich detail at outer regions, restrained low-key luminosity, no overexposed masses, no planets, no ships, no text, no interface, no border. Entire image is deep space. Variant: turquoise veil. Vast elegant teal and cyan nebula curtains drifting diagonally from the lower left to the upper right, deep blue dark dust channels, a very small distant warm spiral galaxy near the upper left. Dense intricate wisps but broad dark center, subdued silver starlight. + +## crimson_rift.png + +Use case: stylized-concept. Final production landscape 3:2 background texture for a top-down space RPG, at least 1536 by 1024 pixels. Realistic painterly astronomical space art, extremely fine sharp dust filaments and delicate stars. Preserve dark near-black navy central negative space for readable spaceship combat. Rich detail at outer regions, restrained low-key luminosity, no overexposed masses, no planets, no ships, no text, no interface, no border. Entire image is deep space. Variant: crimson rift. Deep ruby and burgundy emission nebula filaments around a sweeping dark interstellar dust rift. Muted copper edges and faint magenta distant star clusters. A tiny faraway edge-on galaxy in one outer quadrant. Mysterious deep space, asymmetric painted composition, dark broad center, no giant bright object. + +## amber_expanse.png + +Use case: stylized-concept. Final production landscape 3:2 background texture for a top-down space RPG, at least 1536 by 1024 pixels. Realistic painterly astronomical space art, extremely fine sharp dust filaments and delicate stars. Preserve dark near-black navy central negative space for readable spaceship combat. Rich detail at outer regions, restrained low-key luminosity, no overexposed masses, no planets, no ships, no text, no interface, no border. Entire image is deep space. Variant: amber expanse. Immense fine warm gold and bronze interstellar clouds sweeping around a dark navy central void, delicate smoky copper filaments, subtle deep blue counterlight and sparse pale gold stars, a small distant galaxy embedded near the outer right edge. Atmospheric and ancient, luminosity restrained, richly layered natural astrophotographic structure. + +## violet_spiral.png + +Use case: stylized-concept. Final production landscape 3:2 background texture for a top-down space RPG, at least 1536 by 1024 pixels. Realistic painterly astronomical space art, extremely fine sharp dust filaments and delicate stars. Preserve dark near-black navy central negative space for readable spaceship combat. Rich detail at outer regions, restrained low-key luminosity, no overexposed masses, no planets, no ships, no text, no interface, no border. Entire image is deep space. Variant: violet spiral. A distant gently inclined spiral galaxy with violet and lilac arms in the far upper-right quadrant, moderate small size and dim warm center, vast muted plum and indigo wisps curving along the outer image edges, exceptionally fine cosmic dust lanes, soft blue distant star clusters. Majority central space remains dark, mysterious and spacious, no huge galactic core. + +## frozen_void.png + +Use case: stylized-concept. Final production landscape 3:2 background texture for a top-down space RPG, at least 1536 by 1024 pixels. Realistic painterly astronomical space art, extremely fine sharp dust filaments and delicate stars. Preserve dark near-black navy central negative space for readable spaceship combat. Rich detail at outer regions, restrained low-key luminosity, no overexposed masses, no planets, no ships, no text, no interface, no border. Entire image is deep space. Variant: frozen void. Vast nearly black deep intergalactic space, cold slate-blue and silver-white cirrus-like interstellar dust along the left and lower outer regions, very faint icy cyan tendrils, two tiny remote galaxies and sparse crisp distant stars. Quiet, austere and spacious. Broad clean near-black central combat area, high-detail subtle cloud structure, low saturation and low brightness. diff --git a/assets/background/space/amber_expanse.png b/assets/background/space/amber_expanse.png new file mode 100644 index 0000000..c725320 Binary files /dev/null and b/assets/background/space/amber_expanse.png differ diff --git a/assets/background/space/amber_expanse.png.import b/assets/background/space/amber_expanse.png.import new file mode 100644 index 0000000..a03bda2 --- /dev/null +++ b/assets/background/space/amber_expanse.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ds3otmcvnigjl" +path="res://.godot/imported/amber_expanse.png-3f0d05b567168730e5a7f3165b1f792e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/amber_expanse.png" +dest_files=["res://.godot/imported/amber_expanse.png-3f0d05b567168730e5a7f3165b1f792e.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=true +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/assets/background/space/crimson_rift.png b/assets/background/space/crimson_rift.png new file mode 100644 index 0000000..063499f Binary files /dev/null and b/assets/background/space/crimson_rift.png differ diff --git a/assets/background/space/crimson_rift.png.import b/assets/background/space/crimson_rift.png.import new file mode 100644 index 0000000..05d25c0 --- /dev/null +++ b/assets/background/space/crimson_rift.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://4le8obqsnyls" +path="res://.godot/imported/crimson_rift.png-64a6106d291393c89fe92f833ccc307a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/crimson_rift.png" +dest_files=["res://.godot/imported/crimson_rift.png-64a6106d291393c89fe92f833ccc307a.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=true +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/assets/background/space/frozen_void.png b/assets/background/space/frozen_void.png new file mode 100644 index 0000000..51d4392 Binary files /dev/null and b/assets/background/space/frozen_void.png differ diff --git a/assets/background/space/frozen_void.png.import b/assets/background/space/frozen_void.png.import new file mode 100644 index 0000000..d4ec77c --- /dev/null +++ b/assets/background/space/frozen_void.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ctuoqueipre6u" +path="res://.godot/imported/frozen_void.png-27db06ffcfc6a21d856449611e6a0f6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/frozen_void.png" +dest_files=["res://.godot/imported/frozen_void.png-27db06ffcfc6a21d856449611e6a0f6a.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=true +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/assets/background/space/teal_veil.png b/assets/background/space/teal_veil.png new file mode 100644 index 0000000..6d16956 Binary files /dev/null and b/assets/background/space/teal_veil.png differ diff --git a/assets/background/space/teal_veil.png.import b/assets/background/space/teal_veil.png.import new file mode 100644 index 0000000..935f68b --- /dev/null +++ b/assets/background/space/teal_veil.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ca7inlb4051jg" +path="res://.godot/imported/teal_veil.png-c156d68322d84804e2034f3ec932d377.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/teal_veil.png" +dest_files=["res://.godot/imported/teal_veil.png-c156d68322d84804e2034f3ec932d377.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=true +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/assets/background/space/violet_spiral.png b/assets/background/space/violet_spiral.png new file mode 100644 index 0000000..b811fb1 Binary files /dev/null and b/assets/background/space/violet_spiral.png differ diff --git a/assets/background/space/violet_spiral.png.import b/assets/background/space/violet_spiral.png.import new file mode 100644 index 0000000..1ce6fd9 --- /dev/null +++ b/assets/background/space/violet_spiral.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ba4150mb4y0ks" +path="res://.godot/imported/violet_spiral.png-353286ec4f5d83fdeddcc8a4d50b4cbc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/background/space/violet_spiral.png" +dest_files=["res://.godot/imported/violet_spiral.png-353286ec4f5d83fdeddcc8a4d50b4cbc.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=true +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/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index 0485af1..4608dc3 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -242,7 +242,7 @@ func _layout_debug() -> void: var top := _captain_panel.position.y + _captain_panel.size.y + 6.0 if is_instance_valid(_passive_skills): _passive_skills.position = Vector2(_captain_panel.position.x, top) - _passive_skills.size = Vector2(320, _passive_skills.get_combined_minimum_size().y) + _passive_skills.size = Vector2(36, _passive_skills.get_combined_minimum_size().y) if _passive_skills.visible: top += _passive_skills.size.y + 8.0 debug_toggle.position = Vector2(12, top) @@ -463,53 +463,13 @@ func _refresh_passive_skills() -> void: var rank := GameState.captain.get_skill_rank(skill) if rank <= 0 or bool(catalog.DATA[skill]["active"]): continue - var accent := Color(catalog.DATA[skill]["color"]) - var card := PanelContainer.new() - card.name = skill - card.custom_minimum_size = Vector2(320, 54) - card.tooltip_text = catalog.description(skill, rank) - var style := StyleBoxFlat.new() - style.bg_color = Color(0.025, 0.042, 0.064, 0.94) - style.border_color = accent.darkened(0.6) - style.set_border_width_all(1) - style.border_width_left = 2 - style.set_corner_radius_all(7) - style.set_content_margin_all(8) - card.add_theme_stylebox_override("panel", style) - _passive_skills.add_child(card) - var row := HBoxContainer.new() - row.add_theme_constant_override("separation", 10) - card.add_child(row) var icon := Button.new() + icon.name = skill icon.set_script(preload("res://scripts/ui/captain_skill_button.gd")) - icon.call("configure", catalog.icon(skill), accent) - icon.mouse_filter = Control.MOUSE_FILTER_IGNORE + icon.call("configure", catalog.icon(skill), Color(catalog.DATA[skill]["color"])) icon.disabled = true - row.add_child(icon) - var contents := VBoxContainer.new() - contents.size_flags_horizontal = Control.SIZE_EXPAND_FILL - contents.add_theme_constant_override("separation", 3) - row.add_child(contents) - var header := HBoxContainer.new() - contents.add_child(header) - var title := _passive_label(catalog.DATA[skill]["name"], 14, Color("dfedf4")) - title.size_flags_horizontal = Control.SIZE_EXPAND_FILL - header.add_child(title) - header.add_child(_passive_label("●".repeat(rank) + "○".repeat(3 - rank), 10, accent)) - var value := roundi(GameState.captain.get_skill_bonus(skill) * 100) - var summary := "" - match skill: - "business_shark": summary = "−%d%% kosztów zakupów" % value - "efficient_magazine": summary = "−%d%% zużycia amunicji" % value - "miner": summary = "+%d surowca / asteroidę" % roundi(GameState.captain.get_skill_bonus(skill)) - contents.add_child(_passive_label(summary, 12, accent)) + icon.mouse_default_cursor_shape = Control.CURSOR_HELP + icon.tooltip_text = "%s · poziom %d / 3\n%s" % [catalog.DATA[skill]["name"], rank, catalog.description(skill, rank)] + _passive_skills.add_child(icon) _passive_skills.visible = _passive_skills.get_child_count() > 0 _layout_debug.call_deferred() - -func _passive_label(text: String, font_size: int, color: Color) -> Label: - var label := Label.new() - label.text = text - label.add_theme_font_override("font", ThemeDB.fallback_font) - label.add_theme_font_size_override("font_size", font_size) - label.add_theme_color_override("font_color", color) - return label diff --git a/scripts/game/space_level_background.gd b/scripts/game/space_level_background.gd index c88613e..91f0428 100644 --- a/scripts/game/space_level_background.gd +++ b/scripts/game/space_level_background.gd @@ -1,7 +1,14 @@ extends Node2D const WORLD_SIZE := Vector2(5200.0, 3600.0) -const FAR_TEXTURE := preload("res://assets/background/space/deep_space.png") +const BACKGROUND_PATHS := [ + "res://assets/background/space/deep_space.png", + "res://assets/background/space/teal_veil.png", + "res://assets/background/space/crimson_rift.png", + "res://assets/background/space/amber_expanse.png", + "res://assets/background/space/violet_spiral.png", + "res://assets/background/space/frozen_void.png", +] const DUST_TEXTURE := preload("res://assets/background/space/nebula_dust.png") const STAR_TILE := Vector2(2048.0, 1536.0) @@ -13,7 +20,9 @@ func _ready() -> void: texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS var distant := _make_layer("DistantGalaxies", 0.04) _far_sprite = Sprite2D.new() - _far_sprite.texture = FAR_TEXTURE + # Only the artwork changes between systems; parallax layers stay identical. + var background_index := posmod(GameState.system_level - 1, BACKGROUND_PATHS.size()) + _far_sprite.texture = load(BACKGROUND_PATHS[background_index]) as Texture2D _far_sprite.modulate = Color(0.8, 0.85, 0.95) distant.add_child(_far_sprite) _resize_background() @@ -45,7 +54,7 @@ func _resize_background() -> void: # Native resolution at 720p; retain aspect ratio and cover camera travel. _far_sprite.position = view_size * 0.5 var required := view_size + WORLD_SIZE * 0.04 + Vector2(32.0, 32.0) - var ratio := required / FAR_TEXTURE.get_size() + var ratio := required / _far_sprite.texture.get_size() _far_sprite.scale = Vector2.ONE * maxf(1.0, maxf(ratio.x, ratio.y)) func _make_stars(layer_name: String, speed: float, count: int, seed_offset: int) -> void: diff --git a/scripts/game/weapon_projectile.gd b/scripts/game/weapon_projectile.gd index a39b572..05eb538 100644 --- a/scripts/game/weapon_projectile.gd +++ b/scripts/game/weapon_projectile.gd @@ -13,9 +13,9 @@ var max_range := 800.0 var weapon_kind := ItemDefinition.WeaponKind.PLASMA var homing_target: Node2D var missile_homing := true -var homing_turn_acceleration := 2.3 -var homing_max_turn_speed := 1.7 -var missile_steering_acceleration := 520.0 +var homing_turn_acceleration := 5.0 +var homing_max_turn_speed := 3.0 +var missile_steering_acceleration := 1400.0 var target_acquisition_range := 360.0 var seeker_arm_delay := 0.35 var _flight_velocity := Vector2.ZERO @@ -83,7 +83,7 @@ func _physics_process(delta: float) -> void: PI ) desired_turn_velocity = clampf( - angle_error * 2.0, + angle_error * 4.0, -homing_max_turn_speed, homing_max_turn_speed ) diff --git a/tests/abilities/missile_guidance_test.gd b/tests/abilities/missile_guidance_test.gd new file mode 100644 index 0000000..522c0b4 --- /dev/null +++ b/tests/abilities/missile_guidance_test.gd @@ -0,0 +1,72 @@ +extends Node2D + +const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn") + +func _ready() -> void: + seed(1234) + # Both stationary and crossing targets should be intercepted on the first pass. + for target_velocity in [Vector2.ZERO, Vector2(0, 55)]: + var target := CharacterBody2D.new() + target.position = Vector2(350, 70) + target.velocity = target_velocity + add_child(target) + var missile := _missile(target, 1200.0) + var closest := INF + for step in 240: + var previous: Vector2 = missile.position + target.position += target_velocity / 120.0 + missile._physics_process(1.0 / 120.0) + var near := Geometry2D.get_closest_point_to_segment(target.position, previous, missile.position) + closest = minf(closest, near.distance_to(target.position)) + assert(absf(missile._turn_velocity) <= missile.homing_max_turn_speed + 0.001) + if closest < 18.0 or missile.is_queued_for_deletion(): + break + assert(closest < 18.0, "Missile missed a reachable intercept: %.2f" % closest) + missile.free() + target.free() + + # Compare actual flight direction against the previous turn limits. + var side_target := Node2D.new() + side_target.position = Vector2(100, 200) + add_child(side_target) + var faster := _missile(side_target, 600.0) + var previous := _missile(side_target, 600.0) + previous.homing_turn_acceleration = 2.3 + previous.homing_max_turn_speed = 1.7 + previous.missile_steering_acceleration = 520.0 + for step in 48: + faster._physics_process(1.0 / 120.0) + previous._physics_process(1.0 / 120.0) + assert(faster._heading > previous._heading * 1.5) + assert(faster._flight_velocity.angle() > previous._flight_velocity.angle() * 1.5) + assert(faster.homing_target == side_target) + faster.free() + previous.free() + side_target.free() + + # Preserve the original pursuit behavior beyond acquisition range. + var far_target := Node2D.new() + far_target.position = Vector2(5000, 0) + far_target.add_to_group("enemies") + add_child(far_target) + var limited := _missile(far_target, 200.0) + limited._has_locked_target = true + for step in 120: + limited._physics_process(1.0 / 120.0) + if limited.is_queued_for_deletion(): + break + assert(not limited.is_queued_for_deletion() and limited.homing_target == far_target) + assert(limited._has_locked_target) + limited.free() + far_target.free() + print("MISSILE_GUIDANCE_TEST: PASS (stationary and moving intercepts, faster heading and actual flight turn, original target pursuit)") + get_tree().quit() + +func _missile(target: Node2D, flight_range: float) -> Area2D: + var missile := PROJECTILE.instantiate() as Area2D + missile.configure(Vector2.ZERO, Vector2.RIGHT, ItemDefinition.WeaponKind.MISSILE, 400.0, 10.0, flight_range, target) + missile.collision_mask = 0 + add_child(missile) + missile.set_physics_process(false) + missile.set_process(false) + return missile diff --git a/tests/abilities/missile_guidance_test.gd.uid b/tests/abilities/missile_guidance_test.gd.uid new file mode 100644 index 0000000..91c5e4f --- /dev/null +++ b/tests/abilities/missile_guidance_test.gd.uid @@ -0,0 +1 @@ +uid://2y4073uore6t diff --git a/tests/abilities/missile_guidance_test.tscn b/tests/abilities/missile_guidance_test.tscn new file mode 100644 index 0000000..b7a018a --- /dev/null +++ b/tests/abilities/missile_guidance_test.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://tests/abilities/missile_guidance_test.gd" id="1"] + +[node name="MissileGuidanceTest" type="Node2D"] +script = ExtResource("1")