extends StaticBody2D const GENERATOR := preload("res://scripts/items/item_generator.gd") const ASTEROID_TEXTURES := [preload("res://assets/asteroids/asteroid_rock.png"), preload("res://assets/asteroids/asteroid_metal.png"), preload("res://assets/asteroids/asteroid_ice.png")] const WRECK_TEXTURES := [preload("res://assets/wrecks/wreck_frigate.png"), preload("res://assets/wrecks/wreck_freighter.png"), preload("res://assets/wrecks/wreck_interceptor.png")] const LOOT := preload("res://scripts/items/loot_pickup.gd") var kind := "wreck" var poi_id := "" var reward_seed := 1 var player: CharacterBody2D var approach_radius := 115.0 var hull := 20.0 var _claimed := false var _module_left_outside := false var _label: Label var _rng := RandomNumberGenerator.new() var _mineral := "iron" var _points := PackedVector2Array() var _asteroid_sprite: Sprite2D var _asteroid_spin := 0.0 func _ready() -> void: add_to_group("exploration_pois") _rng.seed = reward_seed _mineral = ["iron", "aluminium", "titanium"][_rng.randi_range(0, 2)] hull = 20.0 * reward_scale() collision_layer = 4 if kind == "asteroid" else 0 collision_mask = 0 if kind == "asteroid": var shape := CollisionShape2D.new() var circle := CircleShape2D.new() circle.radius = 30 shape.shape = circle add_child(shape) for index in 10: _points.append(Vector2.RIGHT.rotated(TAU * index / 10) * _rng.randf_range(23, 35)) _asteroid_sprite = Sprite2D.new() _asteroid_sprite.name = "AsteroidSprite" _asteroid_sprite.texture = ASTEROID_TEXTURES[posmod(reward_seed, ASTEROID_TEXTURES.size())] var texture_size := _asteroid_sprite.texture.get_size() _asteroid_sprite.scale = Vector2.ONE * (78.0 / maxf(texture_size.x, texture_size.y)) _asteroid_sprite.rotation = deg_to_rad(posmod(reward_seed, 360)) _asteroid_spin = (0.025 + posmod(reward_seed, 12) * 0.005) * (1.0 if reward_seed % 2 == 0 else -1.0) add_child(_asteroid_sprite) if kind == "wreck": var sprite := Sprite2D.new() sprite.name = "WreckSprite" sprite.texture = WRECK_TEXTURES[posmod(reward_seed, WRECK_TEXTURES.size())] var texture_size := sprite.texture.get_size() sprite.scale = Vector2.ONE * (92.0 / maxf(texture_size.x, texture_size.y)) sprite.rotation = deg_to_rad(posmod(reward_seed, 360)) add_child(sprite) _label = Label.new() _label.light_mask = 4 _label.position = Vector2(-160, 40) _label.size = Vector2(320, 70) _label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _label.add_theme_font_size_override("font_size", 12) if kind == "ruin": z_index = 20 _label.position = Vector2(-40, -35) _label.size = Vector2(80, 70) _label.add_theme_font_size_override("font_size", 52) _label.add_theme_color_override("font_color", Color("ffd166")) _label.add_theme_color_override("font_outline_color", Color("151625")) _label.add_theme_constant_override("outline_size", 8) add_child(_label) _update_label(false) queue_redraw() func _process(delta: float) -> void: if is_instance_valid(_asteroid_sprite) and not _claimed: _asteroid_sprite.rotation += _asteroid_spin * delta func reward_scale() -> float: return 1.0 + 0.05 * (clampi(GameState.system_level, 1, 1000) - 1) func _update_label(near: bool) -> void: if kind == "ruin": _label.text = "?" elif kind == "asteroid": _label.text = "Asteroida · %s\nHP: %.0f" % [{"iron": "Żelazo", "aluminium": "Aluminium", "titanium": "Tytan"}[_mineral], hull] else: _label.text = ("Wrak statku" if kind == "wreck" else "Antyczne znalezisko") + ("\n[E] Przeszukaj" if near else "\nPodleć, aby przeszukać") func _physics_process(_delta: float) -> void: if _claimed or not is_instance_valid(player) or bool(player.get("_is_destroyed")): return var near := global_position.distance_to(player.global_position) <= approach_radius _update_label(near) if near and (kind == "ruin" or (kind == "wreck" and Input.is_physical_key_pressed(KEY_E))): claim() func take_weapon_hit(amount: float, _critical: bool = false) -> void: take_damage(amount) func take_damage(amount: float) -> void: if kind != "asteroid" or _claimed: return hull -= maxf(0, amount) if hull <= 0: claim() else: _update_label(false) func claim() -> bool: if _claimed or not is_instance_valid(player) or bool(player.get("_is_destroyed")): return false _claimed = true GameState.explored_pois[poi_id] = true var inventory := player.get_node("ShipInventory") as ShipInventory var summary := "" var scale := reward_scale() if kind == "asteroid": var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale)) GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu"}[_mineral]] elif kind == "ruin": var xp := roundi(_rng.randi_range(10, 20) * scale) GameState.captain.add_rewards(xp, 0) var module := _drop_module(true) summary = "%d XP oraz %s (poziom %d)" % [xp, module.definition.display_name, module.item_level] _show_discovery(summary) else: match _rng.randi_range(0, 3): 0: var amount := roundi(_rng.randi_range(3, 8) * scale) GameState.captain.parts += amount summary = "+%d części" % amount 1: _drop_module(false) summary = "Znaleziono moduł" 2: var amount := maxi(1, roundi(scale)) inventory.missile_ammunition += amount summary = "+%d rakiet" % amount 3: var amount := roundi(_rng.randi_range(5, 15) * scale) inventory.ammunition += amount summary = "+%d amunicji" % amount inventory.ammunition_changed.emit() GameState.captain.emit_changed() if kind != "ruin": var popup := preload("res://scenes/game/damage_popup.tscn").instantiate() as Label popup.text = summary popup.set("text_color", Color("c9e8ff")) popup.position = position + Vector2(-80, -65) get_parent().add_child.call_deferred(popup) set_physics_process(false) queue_free() return true func _show_discovery(reward: String) -> void: var layer := CanvasLayer.new() layer.layer = 30 layer.name = "AncientDiscoveryMessage" get_parent().add_child(layer) var panel := PanelContainer.new() panel.mouse_filter = Control.MOUSE_FILTER_IGNORE layer.add_child(panel) panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP) panel.offset_left = -300 panel.offset_right = 300 panel.offset_top = 70 var label := Label.new() label.custom_minimum_size = Vector2(600, 95) label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER label.mouse_filter = Control.MOUSE_FILTER_IGNORE label.add_theme_font_size_override("font_size", 18) label.add_theme_color_override("font_color", Color("ffd166")) label.text = "Znalazłeś ślady starożytnej upadłej cywilizacji!\nZdobyłeś: " + reward if _module_left_outside: label.text += "\nMagazyn pełny — moduł czeka obok planety." panel.add_child(label) var tween := layer.create_tween() tween.tween_interval(7.0) tween.tween_property(panel, "modulate:a", 0.0, 0.5) tween.tween_callback(layer.queue_free) func _drop_module(ancient: bool) -> ItemInstance: var definitions: Array[ItemDefinition] = [] for definition in GENERATOR.DEFINITIONS: if definition.category != ItemDefinition.Category.WEAPON: definitions.append(definition) var item := GENERATOR.generate_for_definition(definitions[_rng.randi_range(0, definitions.size() - 1)], mini(1000, GameState.system_level + (1 if ancient else 0)), 2 if ancient else 0) if ancient: for modifier in item.additional_modifiers: var bounds := modifier.affix.get_value_range(item.item_level) modifier.value = lerpf(bounds.x, bounds.y, _rng.randf_range(0.9, 1.0)) if player.get_node("ShipInventory").add_item(item): return item _module_left_outside = true var loot := Node2D.new() loot.set_script(LOOT) loot.set("item", item) loot.position = position + Vector2(0, 70) get_parent().add_child.call_deferred(loot) return item