diff --git a/artifacts/passive_extensions_preview.png b/artifacts/passive_extensions_preview.png index f4ca79f..f292010 100644 Binary files a/artifacts/passive_extensions_preview.png and b/artifacts/passive_extensions_preview.png differ diff --git a/assets/icons/captain/ARTWORK.md b/assets/icons/captain/ARTWORK.md index 8e3323b..d3ee4d5 100644 --- a/assets/icons/captain/ARTWORK.md +++ b/assets/icons/captain/ARTWORK.md @@ -25,3 +25,12 @@ Use case: stylized-concept. Final production square ability icon for a realistic ## power_redirect Use case: stylized-concept. Final production square ability icon for a realistic painterly space RPG. Highly polished hand-painted digital game art, cinematic material detail, strong simple silhouette readable at 40 pixels, centered composition with 12% safe margin. Dark navy outer corners, restrained luminous accent, no text, no letters, no numbers, no interface, no border. One iconic subject, not a scene. Power Redirect active skill: a heavy metallic spaceship shield emitter on the left splitting open and releasing a powerful curved orange-gold energy stream into a bright compact cyan reactor core on the right. A bold luminous flow of power visually connects the two components. Burnt orange, pale gold, electric teal highlights, realistic sci-fi machinery, strong simple two-part silhouette. + + +## lucky (2026-10-17) +Built-in image_gen, transparent background. Saved as assets/icons/captain/lucky.png. +Final prompt: Use case: stylized-concept. Asset: single premium science fiction game captain skill inventory icon, square, transparent background. Skill LUCK: sculptural futuristic four-leaf clover made from luminous emerald crystal and brushed gold metal, with a small gold orbital ring and a few tiny golden sparks. Centered bold compact silhouette readable at 40 pixels, exquisite painted 3D game item rendering, dramatic highlights, rich gold and green, isolated cutout, no text, no letters, no frame, no backdrop. Save resulting asset for game integration. + +## explorer (2026-10-17) +Built-in image_gen, transparent background. Saved as assets/icons/captain/explorer.png. +Final prompt: Use case: stylized-concept. Single premium science fiction game captain skill icon EXPLORER. Centered bold compact 3D sculptural deep space navigation compass, weathered titanium and gold ring surrounding luminous turquoise holographic miniature planet and three-point star, subtle emerald cyan trails suggesting discovery, exquisitely detailed painterly game item artwork with dramatic highlights, readable small silhouette, isolated on genuinely transparent background. Square composition, generous transparent margins, no text no numbers no frame no scenery. Match premium sci-fi inventory artwork. diff --git a/assets/icons/captain/explorer.png b/assets/icons/captain/explorer.png new file mode 100644 index 0000000..a16792a Binary files /dev/null and b/assets/icons/captain/explorer.png differ diff --git a/assets/icons/captain/explorer.png.import b/assets/icons/captain/explorer.png.import new file mode 100644 index 0000000..fae1ce5 --- /dev/null +++ b/assets/icons/captain/explorer.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btk4ywrx737fw" +path="res://.godot/imported/explorer.png-5f686e01e5775b401dc40513f1c279ea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/captain/explorer.png" +dest_files=["res://.godot/imported/explorer.png-5f686e01e5775b401dc40513f1c279ea.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/assets/icons/captain/lucky.png b/assets/icons/captain/lucky.png new file mode 100644 index 0000000..4eb9f04 Binary files /dev/null and b/assets/icons/captain/lucky.png differ diff --git a/assets/icons/captain/lucky.png.import b/assets/icons/captain/lucky.png.import new file mode 100644 index 0000000..076e59a --- /dev/null +++ b/assets/icons/captain/lucky.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddyvf2s8nmvma" +path="res://.godot/imported/lucky.png-8c04401bc3d2099ccc953cd3aeeed557.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/icons/captain/lucky.png" +dest_files=["res://.godot/imported/lucky.png-8c04401bc3d2099ccc953cd3aeeed557.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/scenes/ui/ship_equipment_panel.tscn b/scenes/ui/ship_equipment_panel.tscn index cddb05c..9d117d1 100644 --- a/scenes/ui/ship_equipment_panel.tscn +++ b/scenes/ui/ship_equipment_panel.tscn @@ -43,7 +43,7 @@ text = "SYSTEMY POKŁADOWE" horizontal_alignment = 1 [node name="EquipmentLayout" type="Control" parent="Contents"] -custom_minimum_size = Vector2(284, 552) +custom_minimum_size = Vector2(264, 432) layout_mode = 2 mouse_filter = 2 @@ -59,130 +59,130 @@ script = ExtResource("1_schematic") [node name="Weapon1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 22.0 -offset_right = 78.0 -offset_bottom = 86.0 +offset_left = 12.0 +offset_top = 20.0 +offset_right = 68.0 +offset_bottom = 76.0 slot_kind = "weapon" slot_index = 0 overlay_slot = true [node name="Weapon2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 22.0 -offset_right = 270.0 -offset_bottom = 86.0 +offset_left = 196.0 +offset_top = 20.0 +offset_right = 252.0 +offset_bottom = 76.0 slot_kind = "weapon" slot_index = 1 overlay_slot = true [node name="Weapon3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 110.0 -offset_top = 22.0 -offset_right = 174.0 -offset_bottom = 86.0 +offset_left = 104.0 +offset_top = 20.0 +offset_right = 160.0 +offset_bottom = 76.0 slot_kind = "weapon" slot_index = 2 overlay_slot = true [node name="Module1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 108.0 -offset_right = 78.0 -offset_bottom = 172.0 +offset_left = 12.0 +offset_top = 96.0 +offset_right = 68.0 +offset_bottom = 152.0 slot_kind = "module" slot_index = 0 overlay_slot = true [node name="Module2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 200.0 -offset_right = 78.0 -offset_bottom = 264.0 +offset_left = 12.0 +offset_top = 164.0 +offset_right = 68.0 +offset_bottom = 220.0 slot_kind = "module" slot_index = 1 overlay_slot = true [node name="Module3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 292.0 -offset_right = 78.0 -offset_bottom = 356.0 +offset_left = 12.0 +offset_top = 232.0 +offset_right = 68.0 +offset_bottom = 288.0 slot_kind = "module" slot_index = 2 overlay_slot = true [node name="Module4" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 384.0 -offset_right = 78.0 -offset_bottom = 448.0 +offset_left = 12.0 +offset_top = 300.0 +offset_right = 68.0 +offset_bottom = 356.0 slot_kind = "module" slot_index = 3 overlay_slot = true [node name="Module5" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 14.0 -offset_top = 476.0 -offset_right = 78.0 -offset_bottom = 540.0 +offset_left = 12.0 +offset_top = 368.0 +offset_right = 68.0 +offset_bottom = 424.0 slot_kind = "module" slot_index = 4 overlay_slot = true [node name="Extension1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 108.0 -offset_right = 270.0 -offset_bottom = 172.0 +offset_left = 196.0 +offset_top = 96.0 +offset_right = 252.0 +offset_bottom = 152.0 slot_kind = "extension" slot_index = 0 overlay_slot = true [node name="Extension2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 200.0 -offset_right = 270.0 -offset_bottom = 264.0 +offset_left = 196.0 +offset_top = 164.0 +offset_right = 252.0 +offset_bottom = 220.0 slot_kind = "extension" slot_index = 1 overlay_slot = true [node name="Extension3" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 292.0 -offset_right = 270.0 -offset_bottom = 356.0 +offset_left = 196.0 +offset_top = 232.0 +offset_right = 252.0 +offset_bottom = 288.0 slot_kind = "extension" slot_index = 2 overlay_slot = true [node name="Extension4" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 384.0 -offset_right = 270.0 -offset_bottom = 448.0 +offset_left = 196.0 +offset_top = 300.0 +offset_right = 252.0 +offset_bottom = 356.0 slot_kind = "extension" slot_index = 3 overlay_slot = true [node name="Extension5" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")] layout_mode = 0 -offset_left = 206.0 -offset_top = 476.0 -offset_right = 270.0 -offset_bottom = 540.0 +offset_left = 196.0 +offset_top = 368.0 +offset_right = 252.0 +offset_bottom = 424.0 slot_kind = "extension" slot_index = 4 overlay_slot = true diff --git a/scripts/captain/captain_skills.gd b/scripts/captain/captain_skills.gd index fafa7a0..0bfb0f0 100644 --- a/scripts/captain/captain_skills.gd +++ b/scripts/captain/captain_skills.gd @@ -22,8 +22,7 @@ static func value(skill: String, rank: int) -> float: return float(DATA[skill]["values"][clampi(rank, 1, MAX_RANK) - 1]) static func icon(skill: String) -> Texture2D: - var extension := "svg" if skill in ["lucky", "explorer"] else "png" - return load("res://assets/icons/captain/%s.%s" % [skill, extension]) as Texture2D + return load("res://assets/icons/captain/%s.png" % skill) as Texture2D static func description(skill: String, rank: int) -> String: var amount := roundi(value(skill, rank) * 100.0) diff --git a/scripts/game/enemy_ship.gd b/scripts/game/enemy_ship.gd index 5dcee52..3b83dc5 100644 --- a/scripts/game/enemy_ship.gd +++ b/scripts/game/enemy_ship.gd @@ -131,7 +131,16 @@ func get_tier_tint() -> Color: func get_visual_size() -> float: return 112.0 if loot_tier == 2 else (68.0 if loot_tier == 1 else 56.0) +func regenerate_defenses(delta: float) -> void: + if hull <= 0.0 or is_queued_for_deletion(): + return + hull = minf(max_hull, hull + max_hull * 0.01 * delta) + shields = minf(max_shields, shields + max_shields * 0.01 * delta) + if is_instance_valid(_shield_visual): + _shield_visual.set_shield_values(shields, max_shields) + func _physics_process(delta: float) -> void: + regenerate_defenses(delta) _slow_remaining = maxf(0.0, _slow_remaining - delta) _weapon_timer = maxf(0.0, _weapon_timer - delta) if not is_instance_valid(_player) or bool(_player.get("_is_destroyed")): diff --git a/scripts/game/flight_hud.gd b/scripts/game/flight_hud.gd index 7d469e1..7584f49 100644 --- a/scripts/game/flight_hud.gd +++ b/scripts/game/flight_hud.gd @@ -24,7 +24,7 @@ var _sidebar: PanelContainer var _sidebar_toggle: Button var _sidebar_open := false var _sidebar_tween: Tween -const SIDEBAR_WIDTH := 306.0 +const SIDEBAR_WIDTH := 286.0 const EQUIPMENT_UI_SCALE := 0.8 var _debug_scroll: ScrollContainer var _debug_actions: VBoxContainer @@ -327,8 +327,13 @@ func _layout_sidebar() -> void: return if _sidebar_tween != null and _sidebar_tween.is_running(): _sidebar_tween.kill() - _sidebar.scale = Vector2.ONE * EQUIPMENT_UI_SCALE - _sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 148.0 - _ship_passive_reserved_height) / EQUIPMENT_UI_SCALE))) + var scroll := _sidebar.get_node("Scroll") as ScrollContainer + scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + var content_height: float = scroll.get_node("Contents").get_combined_minimum_size().y + 16.0 + var available_height := maxf(100.0, $HUDRoot.size.y - 148.0 - _ship_passive_reserved_height) + var panel_scale := minf(EQUIPMENT_UI_SCALE, available_height / content_height) + _sidebar.scale = Vector2.ONE * panel_scale + _sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), content_height) _cockpit.scale = Vector2.ONE * 0.85 _cockpit.size = Vector2(minf(850.0, ($HUDRoot.size.x - 24) / 0.85), 120) _cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12) @@ -341,7 +346,7 @@ func _layout_sidebar() -> void: _captain_skills_ui.call_deferred("_layout") _layout_debug() if not _sidebar.has_meta("floating_window_dragged"): - _sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8, 136) + _sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * _sidebar.scale.x - 8, 136) else: _window_manager.call("clamp_window", _sidebar) _sidebar.visible = _sidebar_open diff --git a/scripts/game/game_state.gd b/scripts/game/game_state.gd index 9126695..1de1a5c 100644 --- a/scripts/game/game_state.gd +++ b/scripts/game/game_state.gd @@ -8,6 +8,7 @@ var restart_inventory: Dictionary = {} var system_level: int = 1 var system_seed: int = 0 var explored_pois: Dictionary = {} +var asteroid_retry_seeds: Dictionary = {} var exploration_wreck_bonuses: Dictionary = {} var hud_panel_state: Dictionary = {} @@ -17,7 +18,12 @@ func create_system_seed() -> int: return maxi(1, rng.randi()) func reset_current_system_exploration() -> void: - # Retrying after death starts a fresh attempt at this board while keeping rewards. + # Keep the system and rewards, but regenerate asteroid placement and minerals. + var previous: int = asteroid_retry_seeds.get(system_seed, system_seed) + var next_seed := create_system_seed() + while next_seed == previous: + next_seed = create_system_seed() + asteroid_retry_seeds[system_seed] = next_seed var prefix := "%d:" % system_seed for id in explored_pois.keys(): if str(id).begins_with(prefix): @@ -29,6 +35,7 @@ func start_new_run() -> void: system_seed = create_system_seed() restart_inventory.clear() explored_pois.clear() + asteroid_retry_seeds.clear() exploration_wreck_bonuses.clear() hud_panel_state.clear() diff --git a/scripts/items/ship_inventory.gd b/scripts/items/ship_inventory.gd index 0509ac8..6d72e4b 100644 --- a/scripts/items/ship_inventory.gd +++ b/scripts/items/ship_inventory.gd @@ -210,6 +210,20 @@ func can_move_item(from_kind: String, from_index: int, to_kind: String, to_index if displaced != null and displaced.definition != null: if not _accepts_item(from_kind, from_index, displaced): return false + if from_kind == "extension" or to_kind == "extension": + var projected := extension_slots.duplicate() + if from_kind == "extension": + projected[from_index] = displaced + if to_kind == "extension": + projected[to_index] = item + var equipped_types := {} + for extension in projected: + if extension == null or extension.definition == null: + continue + var type_id: String = extension.definition.item_id + if equipped_types.has(type_id): + return false + equipped_types[type_id] = true return true func _accepts_item(kind: String, index: int, item: ItemInstance) -> bool: diff --git a/scripts/levels/exploration_generator.gd b/scripts/levels/exploration_generator.gd index 0e71308..d4db1ce 100644 --- a/scripts/levels/exploration_generator.gd +++ b/scripts/levels/exploration_generator.gd @@ -9,14 +9,17 @@ static func spawn(game: Node2D, data: Dictionary, player: CharacterBody2D) -> vo var wreck_bonus := int(GameState.exploration_wreck_bonuses[GameState.system_seed]) var rng := RandomNumberGenerator.new() rng.seed = GameState.system_seed ^ 0x504f49 + var asteroid_rng := RandomNumberGenerator.new() + asteroid_rng.seed = int(GameState.asteroid_retry_seeds.get(GameState.system_seed, GameState.system_seed)) ^ 0x41535452 var occupied: Array[Vector2] = [] var wreck_count := rng.randi_range(2, 4) for index in wreck_count + rng.randi_range(6, 10): var kind := "wreck" if index < wreck_count else "asteroid" + var point_rng := asteroid_rng if kind == "asteroid" else rng var candidate := Vector2.ZERO var found := false for attempt in 200: - candidate = Vector2(rng.randf_range(-2300, 2300), rng.randf_range(-1500, 1500)) + candidate = Vector2(point_rng.randf_range(-2300, 2300), point_rng.randf_range(-1500, 1500)) if candidate.length() < 650 or candidate.distance_to(player.position) < 450: continue var clear := true @@ -31,7 +34,7 @@ static func spawn(game: Node2D, data: Dictionary, player: CharacterBody2D) -> vo break if found: occupied.append(candidate) - _add(game, player, kind, candidate, index, rng.randi()) + _add(game, player, kind, candidate, index, point_rng.randi()) # Rare per system, at most one ruin on an uninhabited planet. if rng.randf() < 0.08: var candidates: Array[int] = [] diff --git a/scripts/ui/equipment_slot_view.gd b/scripts/ui/equipment_slot_view.gd index fe183fb..0c8099d 100644 --- a/scripts/ui/equipment_slot_view.gd +++ b/scripts/ui/equipment_slot_view.gd @@ -9,6 +9,7 @@ extends PanelContainer var _inventory: ShipInventory var _icon: TextureRect var _delete_button: Button +var _drop_highlight := false func _ready() -> void: if slot_kind == "cargo": @@ -46,7 +47,7 @@ func _ready() -> void: if _inventory: _inventory.inventory_changed.connect(_refresh) _inventory.ammunition_changed.connect(_refresh) - custom_minimum_size = Vector2(72, 72) if slot_kind == "cargo" else Vector2(64, 64) + custom_minimum_size = Vector2(72, 72) if slot_kind == "cargo" else Vector2(56, 56) slot_label.custom_minimum_size = Vector2.ZERO slot_label.add_theme_font_size_override("font_size", 9) _icon = TextureRect.new() @@ -55,10 +56,10 @@ func _ready() -> void: _icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED _icon.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(_icon) - if overlay_slot: + if overlay_slot and slot_kind == "weapon": var binding := Label.new() binding.mouse_filter = Control.MOUSE_FILTER_IGNORE - binding.text = (["LPM", "PPM", "ŚPM"][slot_index]) if slot_kind == "weapon" else (str(slot_index + 1) if slot_kind == "extension" else ShipInventory.MODULE_NAMES[slot_index]) + binding.text = ["LPM", "PPM", "ŚPM"][slot_index] binding.position = Vector2(2, -24) binding.add_theme_font_size_override("font_size", 9) binding.add_theme_color_override("font_color", Color("a0b2c4")) @@ -70,6 +71,10 @@ func _ready() -> void: _refresh() func _process(_delta: float) -> void: + var valid_drop := get_viewport().gui_is_dragging() and _can_drop_data(Vector2.ZERO, get_viewport().gui_get_drag_data()) + if valid_drop != _drop_highlight: + _drop_highlight = valid_drop + _apply_slot_style(_inventory.get_item(slot_kind, slot_index) if _inventory != null else null) _update_salvage_visibility() if slot_kind == "weapon" and _has_missile(): queue_redraw() @@ -126,6 +131,12 @@ func _apply_slot_style(item: ItemInstance) -> void: style.set_border_width_all(2) if overlay_slot: style.bg_color.a = 0.86 if item != null else 0.48 + if _drop_highlight: + style.border_color = Color("75ffd0") + style.set_border_width_all(3) + style.bg_color = Color(0.08, 0.3, 0.25, 0.85) + style.shadow_color = Color(0.2, 1.0, 0.72, 0.3) + style.shadow_size = 5 add_theme_stylebox_override("panel", style) func _delete_item() -> void: diff --git a/scripts/ui/navigation_button.gd b/scripts/ui/navigation_button.gd index 48b5cbe..0bdc8f9 100644 --- a/scripts/ui/navigation_button.gd +++ b/scripts/ui/navigation_button.gd @@ -1,5 +1,33 @@ extends "res://scripts/ui/captain_skill_button.gd" +var _skill_notification := false +var _pulse_time := 0.0 +var _pulse_frames: Dictionary = {} + +func _process(delta: float) -> void: + if name != "OpenCaptainSkills": + return + var available := GameState.captain.learning_points > 0 + if available and _pulse_frames.is_empty(): + for state in ["normal", "hover", "pressed"]: + _pulse_frames[state] = get_theme_stylebox(state).duplicate() + if available: + _pulse_time += delta + var pulse := 0.5 + 0.5 * sin(_pulse_time * TAU / 1.6) + for state in _pulse_frames: + var frame := get_theme_stylebox(state) as StyleBoxFlat + frame.border_color = Color("b9a0ef").lerp(Color("ffe2a1"), pulse) + frame.shadow_color = Color(0.72, 0.5, 1.0, 0.25 + pulse * 0.4) + frame.shadow_size = int(7 + pulse * 9) + tooltip_text = "Umiejętności kapitana · punkty do wydania: %d" % GameState.captain.learning_points + elif _skill_notification: + for state in _pulse_frames: + add_theme_stylebox_override(state, _pulse_frames[state].duplicate()) + _pulse_frames.clear() + _pulse_time = 0.0 + tooltip_text = "Umiejętności kapitana" + _skill_notification = available + func configure_navigation(artwork: Texture2D, accent: Color, caption: String) -> void: configure(artwork, accent) custom_minimum_size = Vector2(64, 80) diff --git a/tests/abilities/passive_extensions_test.gd b/tests/abilities/passive_extensions_test.gd index 0fb56f3..7e5b998 100644 --- a/tests/abilities/passive_extensions_test.gd +++ b/tests/abilities/passive_extensions_test.gd @@ -70,9 +70,21 @@ func _ready() -> void: assert(int(GameState.captain.minerals.get("iron", 0)) == before + 1) ore.call("_physics_process", 1.0) assert(int(GameState.captain.minerals.get("iron", 0)) == before + 1) - # Best duplicate wins; restoration is capped, not additive. + # Duplicate extensions stay in cargo; replacing and moving equipped items is allowed. recycler.passive_value = 40 - equip("recycler", 3, 10) + var duplicate := GENERATOR.generate_for_definition(load("res://resources/items/recycler.tres"), 1) + assert(inventory.add_item(duplicate)) + var cargo_index := inventory.cargo.find(duplicate) + assert(not inventory.can_move_item("cargo", cargo_index, "extension", 3)) + assert(not inventory.move_item("cargo", cargo_index, "extension", 3)) + assert(inventory.extension_slots[3] == null and inventory.cargo[cargo_index] == duplicate) + assert(inventory.move_item("extension", 0, "extension", 3)) + assert(inventory.move_item("extension", 3, "extension", 0)) + assert(inventory.move_item("cargo", cargo_index, "extension", 0)) + assert(inventory.extension_slots[0] == duplicate and inventory.cargo[cargo_index] == recycler) + assert(inventory.move_item("cargo", cargo_index, "extension", 0)) + assert(inventory.move_item("extension", 0, "extension", 1)) + assert(inventory.move_item("extension", 1, "extension", 0)) ship.energy = ship.max_energy * 0.9 ship.shields = ship.max_shields * 0.9 ship.recycle_asteroid_fragment() diff --git a/tests/levels/enemy_repair_notification_test.gd b/tests/levels/enemy_repair_notification_test.gd new file mode 100644 index 0000000..344acca --- /dev/null +++ b/tests/levels/enemy_repair_notification_test.gd @@ -0,0 +1,36 @@ +extends Node + +func _ready() -> void: + GameState.start_new_run() + var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate() + add_child(enemy) + enemy.set_physics_process(false) + enemy.max_hull = 100.0 + enemy.max_shields = 200.0 + enemy.hull = 9.0 + enemy.shields = 0.0 + assert(enemy.is_retreating()) + enemy.regenerate_defenses(0.5) + assert(is_equal_approx(enemy.hull, 9.5) and is_equal_approx(enemy.shields, 1.0)) + assert(enemy.is_retreating()) + enemy.regenerate_defenses(0.5) + assert(is_equal_approx(enemy.hull, 10.0) and not enemy.is_retreating()) + enemy.regenerate_defenses(1000.0) + assert(enemy.hull == enemy.max_hull and enemy.shields == enemy.max_shields) + enemy.hull = 0.0 + enemy.regenerate_defenses(10.0) + assert(enemy.hull == 0.0) + var button := Button.new() + button.set_script(preload("res://scripts/ui/navigation_button.gd")) + button.configure_navigation(null, Color("b9a0ef"), "KAPITAN") + button.name = "OpenCaptainSkills" + add_child(button) + var original := (button.get_theme_stylebox("normal") as StyleBoxFlat).shadow_size + GameState.captain.learning_points = 1 + button._process(0.4) + assert((button.get_theme_stylebox("normal") as StyleBoxFlat).shadow_size > original) + GameState.captain.learning_points = 0 + button._process(0.1) + assert((button.get_theme_stylebox("normal") as StyleBoxFlat).shadow_size == original) + print("ENEMY_REPAIR_NOTIFICATION_TEST: PASS 1%/s, retreat threshold, caps, dead enemy, notification reset") + get_tree().quit() diff --git a/tests/levels/enemy_repair_notification_test.gd.uid b/tests/levels/enemy_repair_notification_test.gd.uid new file mode 100644 index 0000000..4967055 --- /dev/null +++ b/tests/levels/enemy_repair_notification_test.gd.uid @@ -0,0 +1 @@ +uid://c8qee631np6rq diff --git a/tests/levels/enemy_repair_notification_test.tscn b/tests/levels/enemy_repair_notification_test.tscn new file mode 100644 index 0000000..5efc468 --- /dev/null +++ b/tests/levels/enemy_repair_notification_test.tscn @@ -0,0 +1,4 @@ +[gd_scene load_steps=2 format=3] +[ext_resource type="Script" path="res://tests/levels/enemy_repair_notification_test.gd" id="1"] +[node name="EnemyRepairNotificationTest" type="Node"] +script = ExtResource("1") \ No newline at end of file diff --git a/tests/levels/exploration_death_restart_test.gd b/tests/levels/exploration_death_restart_test.gd index 155d452..c17552e 100644 --- a/tests/levels/exploration_death_restart_test.gd +++ b/tests/levels/exploration_death_restart_test.gd @@ -36,8 +36,6 @@ func _ready() -> void: for poi in pois(game): if poi.get("kind") in ["asteroid", "wreck"]: # Test reward retention independently of the debris renderer. - if poi.get("kind") == "asteroid": - poi.set("_asteroid_sprite", null) assert(poi.call("claim")) var minerals_before := GameState.captain.minerals.duplicate() var seed_before := GameState.system_seed @@ -59,8 +57,11 @@ func _ready() -> void: for poi in after: var original: Dictionary = before[poi.get("poi_id")] assert(original["kind"] == poi.get("kind")) - assert(original["position"] == poi.position) - assert(original["mineral"] == poi.get("_mineral")) + if poi.get("kind") == "asteroid": + assert(original["position"] != poi.position) + elif int(str(poi.get("poi_id")).get_slice(":", 1)) < 1000: + assert(original["position"] == poi.position) + assert(GameState.asteroid_retry_seeds.has(seed_before)) assert(GameState.captain.minerals == minerals_before) assert(GameState.captain.get_skill_rank("miner") == rank) assert(GameState.system_seed == seed_before) @@ -70,5 +71,5 @@ func _ready() -> void: get_tree().current_scene = self restarted.queue_free() await get_tree().process_frame - print("EXPLORATION_DEATH_RESTART_TEST: PASS (actual death and scene reload at miner ranks 0/1/2/3, exhausted boards restored, stable positions/minerals, explorer snapshot, rewards/equipment retained)") + print("EXPLORATION_DEATH_RESTART_TEST: PASS (actual death and scene reload at miner ranks 0/1/2/3, exhausted boards restored, rerolled asteroid positions/minerals, explorer snapshot, rewards/equipment retained)") get_tree().quit() diff --git a/tests/previews/passive_extensions_preview.gd b/tests/previews/passive_extensions_preview.gd index 89b25be..4436fb4 100644 --- a/tests/previews/passive_extensions_preview.gd +++ b/tests/previews/passive_extensions_preview.gd @@ -15,6 +15,8 @@ func _ready() -> void: for skill in skills.IDS: if not bool(skills.DATA[skill]["active"]): GameState.captain.skills[skill] = 1 + if "--skill-points" in OS.get_cmdline_user_args(): + GameState.captain.learning_points = 1 GameState.captain.emit_changed() inventory.inventory_changed.emit() var enemies := get_tree().get_nodes_in_group("enemies") diff --git a/tests/ships/drag_cursor_test.gd b/tests/ships/drag_cursor_test.gd index 8c1b625..1e4d0ba 100644 --- a/tests/ships/drag_cursor_test.gd +++ b/tests/ships/drag_cursor_test.gd @@ -93,6 +93,13 @@ func _ready() -> void: motion(origin + Vector2(24, 6)) await get_tree().process_frame var target: Control = hud.get("_sidebar").get_node("Scroll/Contents/ShipEquipmentPanel/Contents/EquipmentLayout/Module2") + await get_tree().process_frame + assert(target.get("_drop_highlight")) + assert(not target.get_parent().get_node("Module1").get("_drop_highlight")) + assert(not target.get_parent().get_node("Extension1").get("_drop_highlight")) + var sidebar: Control = hud.get("_sidebar") + assert(not sidebar.get_node("Scroll").get_v_scroll_bar().visible) + assert(sidebar.position.y + sidebar.size.y * sidebar.scale.y <= 720.0) var destination := target.get_global_rect().get_center() motion(destination) await get_tree().process_frame