Stacje górnicze

This commit is contained in:
Tomasz Boruc 2026-10-19 22:58:37 +02:00
parent af91c9023d
commit ed26b0974d
45 changed files with 993 additions and 33 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c7urmci7jx376"
path="res://.godot/imported/mining_built_outpost.png-77e746170a9aec36f9fee12b78634737.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_built_outpost.png"
dest_files=["res://.godot/imported/mining_built_outpost.png-77e746170a9aec36f9fee12b78634737.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

BIN
artifacts/mining_cabin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 KiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d1spksp3e0m2s"
path="res://.godot/imported/mining_cabin.png-706b1ec26fcc87cd347620ae43590b0c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_cabin.png"
dest_files=["res://.godot/imported/mining_cabin.png-706b1ec26fcc87cd347620ae43590b0c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 KiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dqvq68kdagb24"
path="res://.godot/imported/mining_command.png-7caa01b95f77da3d680ffba6796dafb3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_command.png"
dest_files=["res://.godot/imported/mining_command.png-7caa01b95f77da3d680ffba6796dafb3.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dr1rcr822mjvh"
path="res://.godot/imported/mining_construction.png-91bcdc8e846b46c629cc595f3cbf4668.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_construction.png"
dest_files=["res://.godot/imported/mining_construction.png-91bcdc8e846b46c629cc595f3cbf4668.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://nanieob3htt"
path="res://.godot/imported/mining_recruitment.png-bfd8a737935c7b3b4da967f155357ff7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_recruitment.png"
dest_files=["res://.godot/imported/mining_recruitment.png-bfd8a737935c7b3b4da967f155357ff7.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://j54irqsrl1fo"
path="res://.godot/imported/mining_storage.png-d886989878e8d040e73eac66bd0fc46b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://artifacts/mining_storage.png"
dest_files=["res://.godot/imported/mining_storage.png-d886989878e8d040e73eac66bd0fc46b.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

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><g fill="none" stroke="#81d9e9" stroke-width="2"><circle cx="24" cy="24" r="17" opacity=".25"/><ellipse cx="24" cy="24" rx="20" ry="8" transform="rotate(-35 24 24)"/><ellipse cx="24" cy="24" rx="20" ry="8" transform="rotate(35 24 24)"/></g><circle cx="21" cy="24" r="5" fill="#b6f4ff"/><circle cx="28" cy="24" r="5" fill="#4e9fce"/><circle cx="39" cy="13" r="3" fill="#e1ffff"/></svg>

After

Width:  |  Height:  |  Size: 468 B

View File

@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://h3b6hffd0r28"
path="res://.godot/imported/deuter.svg-eb6e41c97050cdbb566835b47bc2c3cf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/icons/resources/deuter.svg"
dest_files=["res://.godot/imported/deuter.svg-eb6e41c97050cdbb566835b47bc2c3cf.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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1 @@
Use case: stylized-concept. Asset type: transparent game sprite for a planetary deuterium mining outpost in a top-down space survival RPG. Reference image is the existing planetary warehouse building, match its painted realistic hard-surface game-art style and camera angle. One compact complete modular deep-drilling mining facility: central robust drilling well with extraction derrick, attached low white-gray industrial control hut with orange stripes, two pale blue cylindrical deuterium tanks connected by visible steel pipes, small radiator and power equipment, work platform with hazard markings and service lights. Practical weathered off-world technology, recognizable silhouette at 160 pixels, detailed but uncluttered. Camera nearly overhead with slight view of front walls exactly like reference, isolated whole structure, no large terrain base. All parts connected as one game prop. Fully transparent background, small natural contact shadow only, no landscape, no sky, no people, no text, no letters, no logos. Entire building visible with generous transparent margin and no cropping.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cnpk68uwjsb35"
path="res://.godot/imported/deuterium_outpost.png-8c0051818e09799eaa0a4237c6a5c8e9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/planets/props/deuterium_outpost.png"
dest_files=["res://.godot/imported/deuterium_outpost.png-8c0051818e09799eaa0a4237c6a5c8e9.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

View File

@ -30,6 +30,7 @@ func _ready() -> void:
GameState.system_level = clampi(GameState.system_level, 1, LEVEL_BALANCE.MAX_LEVEL) GameState.system_level = clampi(GameState.system_level, 1, LEVEL_BALANCE.MAX_LEVEL)
if GameState.system_seed == 0: if GameState.system_seed == 0:
GameState.system_seed = GameState.create_system_seed() GameState.system_seed = GameState.create_system_seed()
GameState.remember_system()
system_data = SYSTEM_GENERATOR.generate(GameState.system_level, GameState.system_seed) system_data = SYSTEM_GENERATOR.generate(GameState.system_level, GameState.system_seed)
player.position = system_data["player_start"] player.position = system_data["player_start"]
var camera := player.get_node_or_null("Camera2D") as Camera2D var camera := player.get_node_or_null("Camera2D") as Camera2D
@ -95,6 +96,11 @@ func _spawn_celestial_bodies() -> void:
protected_planet = planet protected_planet = planet
func _spawn_enemies() -> void: func _spawn_enemies() -> void:
if bool(GameState.cleared_systems.get(GameState.system_level, false)):
system_cleared = true
_boss_spawned = true
protected_planet.call("set_safe", true)
return
for data in system_data["enemies"]: for data in system_data["enemies"]:
if int(data["tier"]) == 2: if int(data["tier"]) == 2:
_pending_boss = data.duplicate() _pending_boss = data.duplicate()
@ -159,6 +165,7 @@ func _physics_process(_delta: float) -> void:
_spawn_boss() _spawn_boss()
else: else:
system_cleared = true system_cleared = true
GameState.cleared_systems[GameState.system_level] = true
protected_planet.call("set_safe", true) protected_planet.call("set_safe", true)
if system_cleared and is_near_protected_planet(): if system_cleared and is_near_protected_planet():
if not services_menu.visible: if not services_menu.visible:
@ -218,7 +225,7 @@ func _load_next_level() -> void:
var previous_seed := GameState.system_seed var previous_seed := GameState.system_seed
player.get_node("ShipInventory").call("preserve_for_restart") player.get_node("ShipInventory").call("preserve_for_restart")
GameState.system_level += 1 GameState.system_level += 1
GameState.system_seed = GameState.create_system_seed() GameState.system_seed = GameState.seed_for_system(GameState.system_level)
var error := get_tree().reload_current_scene() var error := get_tree().reload_current_scene()
if error != OK: if error != OK:
GameState.system_level = previous_level GameState.system_level = previous_level
@ -228,3 +235,25 @@ func _load_next_level() -> void:
if is_near_protected_planet(): if is_near_protected_planet():
services_menu.call("show_services", false, true) services_menu.call("show_services", false, true)
push_error("Could not enter next system: %s" % error_string(error)) push_error("Could not enter next system: %s" % error_string(error))
func travel_to_system(level: int) -> bool:
if _transitioning or bool(player.get("_is_destroyed")) or level == GameState.system_level or not GameState.can_visit_system(level): return false
_transitioning = true
_load_known_system.call_deferred(level)
return true
func _load_known_system(level: int) -> void:
var previous_level := GameState.system_level
var previous_seed := GameState.system_seed
player.get_node("ShipInventory").preserve_for_restart()
GameState.remember_system()
GameState.system_level = level
GameState.system_seed = GameState.seed_for_system(level)
get_tree().paused = false
var error := get_tree().reload_current_scene()
if error != OK:
GameState.system_level = previous_level
GameState.system_seed = previous_seed
GameState.restart_inventory.clear()
_transitioning = false
push_error("Nie udało się przenieść do układu: %s" % error_string(error))

View File

@ -7,6 +7,10 @@ var selected_ship_path: String = ""
var restart_inventory: Dictionary = {} var restart_inventory: Dictionary = {}
var system_level: int = 1 var system_level: int = 1
var system_seed: int = 0 var system_seed: int = 0
var highest_system := 1
var system_seeds: Dictionary = {}
var cleared_systems: Dictionary = {}
var _production_signal_time := 0.0
var explored_pois: Dictionary = {} var explored_pois: Dictionary = {}
var asteroid_retry_seeds: Dictionary = {} var asteroid_retry_seeds: Dictionary = {}
var exploration_wreck_bonuses: Dictionary = {} var exploration_wreck_bonuses: Dictionary = {}
@ -32,6 +36,10 @@ func reset_current_system_exploration() -> void:
func start_new_run() -> void: func start_new_run() -> void:
captain = CAPTAIN_PROGRESS.new() captain = CAPTAIN_PROGRESS.new()
system_level = 1 system_level = 1
highest_system = 1
system_seeds.clear()
cleared_systems.clear()
_production_signal_time = 0
system_seed = create_system_seed() system_seed = create_system_seed()
restart_inventory.clear() restart_inventory.clear()
explored_pois.clear() explored_pois.clear()
@ -40,7 +48,35 @@ func start_new_run() -> void:
hud_panel_state.clear() hud_panel_state.clear()
func _ready() -> void: func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
var game_window := get_tree().root var game_window := get_tree().root
game_window.content_scale_size = Vector2i(1280, 720) game_window.content_scale_size = Vector2i(1280, 720)
game_window.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS game_window.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
game_window.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND game_window.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
func remember_system() -> void:
highest_system = maxi(highest_system, system_level)
system_seeds[system_level] = system_seed
func can_visit_system(level: int) -> bool:
return level >= 1 and level <= highest_system and level <= 120
func seed_for_system(level: int) -> int:
if not system_seeds.has(level): system_seeds[level] = create_system_seed()
return int(system_seeds[level])
func _process(delta: float) -> void:
advance_play_time(delta, get_tree().current_scene)
func advance_play_time(delta: float, scene: Node) -> void:
if scene == null or not scene.has_node("PlayerShip"): return
if bool(scene.get_node("PlayerShip").get("_is_destroyed")): return
var expedition := scene.get_node_or_null("PlanetExpeditions")
var location_open := expedition != null and is_instance_valid(expedition.screen)
if get_tree().paused and not location_open: return
var network := preload("res://scripts/planets/mining_network.gd")
if network.advance(captain.expedition, delta) <= 0: return
_production_signal_time += delta
if _production_signal_time >= 1.0:
_production_signal_time = fmod(_production_signal_time, 1.0)
captain.expedition.emit_changed()

View File

@ -268,3 +268,9 @@ func _exit_tree() -> void:
if is_instance_valid(screen): if is_instance_valid(screen):
get_tree().paused = _previous_pause get_tree().paused = _previous_pause
get_tree().auto_accept_quit = true get_tree().auto_accept_quit = true
func request_system_travel(level: int) -> bool:
if not is_instance_valid(screen) or GameState.captain.expedition.active or level == GameState.system_level or not GameState.can_visit_system(level): return false
if not game.travel_to_system(level): return false
_leave()
return true

View File

@ -0,0 +1,73 @@
extends RefCounted
const MENU := preload("res://scripts/planets/freighter_menu.gd")
const NETWORK := preload("res://scripts/planets/mining_network.gd")
static func build(screen: Control) -> void:
var progress: Resource = screen.captain.expedition
var navigation := MENU.card(screen._content)
MENU.label(navigation, "NAWIGACJA / ODKRYTE UKŁADY", 14, MENU.GOLD)
MENU.label(navigation, "Obecny: System %d · Najdalszy odkryty: %d" % [GameState.system_level, GameState.highest_system], 12)
var row := MENU.row(navigation)
var destination := OptionButton.new()
destination.name = "SystemDestination"
destination.size_flags_horizontal = Control.SIZE_EXPAND_FILL
destination.add_theme_font_size_override("font_size", 11)
destination.add_theme_font_override("font", ThemeDB.fallback_font)
for level in range(1, GameState.highest_system + 1):
destination.add_item("System %d%s" % [level, " · HARD" if level > 60 else ""], level)
destination.select(GameState.system_level - 1)
row.add_child(destination)
var travel := MENU.button(row, "LEĆ DO UKŁADU", func(): screen._travel_to_system(destination.get_selected_id()), true)
travel.name = "TravelToSystem"
travel.disabled = true
destination.item_selected.connect(func(_index): travel.disabled = destination.get_selected_id() == GameState.system_level)
MENU.label(navigation, "Nowe układy odblokujesz, przechodząc dalej przez tunel po pokonaniu przeciwników.", 11, MENU.MUTED)
var summary := MENU.card(screen._content)
MENU.label(summary, "SIEĆ WYDOBYWCZA", 14, MENU.GOLD)
var stock := MENU.label(summary, "", 13, Color("95dce8"))
screen._economy_labels["network"] = stock
MENU.label(summary, "Obsadzona placówka: 1 deuter/min + punkty zarządzania. Dostawy automatyczne do magazynu.", 11, MENU.MUTED)
MENU.label(summary, "Jeden załogant obsługuje jedną placówkę. Nowy przydział przenosi go z poprzedniej.", 11, MENU.MUTED)
if progress.mining_stations.is_empty():
MENU.label(summary, "Brak placówek. Szukaj głębokich złóż na planetach (szansa 60%). Budowa: 200 części.", 12)
for station_id in progress.mining_stations:
var station: Dictionary = progress.mining_stations[station_id]
var card := MENU.card(screen._content)
MENU.label(card, "%s · SYSTEM %d" % [station.planet_name, station.system], 13, MENU.GOLD)
var member: Dictionary = progress.crew.get(str(station.get("crew_id", "")), {})
MENU.label(card, "Czeka na załoganta" if member.is_empty() else "%s · zarządzanie %d/100 · %.0f deuteru/min" % [member.name, member.management, NETWORK.station_rate(progress, station)], 12)
var assign_row := MENU.row(card)
var option := OptionButton.new()
option.name = "StationCrew_" + str(station_id).md5_text().substr(0, 8)
option.size_flags_horizontal = Control.SIZE_EXPAND_FILL
option.fit_to_longest_item = false
option.add_theme_font_size_override("font_size", 11)
option.add_theme_font_override("font", ThemeDB.fallback_font)
option.add_item("Bez przydziału")
option.set_item_metadata(0, "")
for crew_id in progress.crew:
var person: Dictionary = progress.crew[crew_id]
option.add_item("%s · %d/100" % [person.name, person.management])
option.set_item_metadata(option.item_count - 1, crew_id)
if str(station.get("crew_id", "")) == str(crew_id): option.select(option.item_count - 1)
assign_row.add_child(option)
var apply := MENU.button(assign_row, "PRZYDZIEL", screen._assign_station.bind(str(station_id), option))
apply.disabled = progress.crew.is_empty() and member.is_empty()
if progress.crew.is_empty(): MENU.label(screen._content, "Brak załogantów. Przygotuj kajutę i zatrudnij specjalistę spotkanego na planecie.", 12, MENU.MUTED)
screen._refresh_live_labels()
static func cabin(screen: Control) -> void:
var progress: Resource = screen.captain.expedition
var member := NETWORK.cabin_crew(progress, screen._tab)
var card := MENU.card(screen._content)
if member.is_empty():
MENU.label(card, "WOLNA KAJUTA", 14, MENU.GOLD)
MENU.label(card, "Przygotowana dla jednego załoganta. Specjalistów możesz zatrudniać na planetach.", 12)
return
MENU.label(card, str(member.name), 15, MENU.GOLD)
MENU.label(card, "Zarządzanie: %d / 100\nPremia do wydobycia: +%d deuteru/min\nPochodzenie: %s" % [member.management, member.management, member.planet_name], 12)
var assignment := "Bez przydziału"
for station in progress.mining_stations.values():
if str(station.get("crew_id", "")) == str(member.id): assignment = "%s · System %d" % [station.planet_name, station.system]
MENU.label(card, "Placówka: " + assignment, 12, Color("95dce8"))
MENU.button(card, "ZWOLNIJ ZAŁOGANTA", screen._dismiss_crew.bind(str(member.id)))

View File

@ -0,0 +1 @@
uid://cl4wrga1rbm0p

View File

@ -3,7 +3,7 @@ signal section_selected(section: String)
signal section_hovered(section: String) signal section_hovered(section: String)
const DESIGN_SIZE := Vector2(1792,768) const DESIGN_SIZE := Vector2(1792,768)
const ROOMS := [ const ROOMS := [
{"id":"command","title":"CENTRUM DOWODZENIA","status":"MOSTEK","rect":Rect2(25,165,315,435),"tag":Vector2(88,68),"active":false}, {"id":"command","title":"CENTRUM DOWODZENIA","status":"NAWIGACJA / PLACÓWKI","rect":Rect2(25,165,315,435),"tag":Vector2(88,68),"active":false},
{"id":"workshop","title":"WARSZTAT","status":"OPERACYJNY","rect":Rect2(360,120,255,205),"tag":Vector2(360,58),"active":true}, {"id":"workshop","title":"WARSZTAT","status":"OPERACYJNY","rect":Rect2(360,120,255,205),"tag":Vector2(360,58),"active":true},
{"id":"expansion_1","title":"SEKCJA 02","status":"W REMONCIE","rect":Rect2(632,120,200,205),"tag":Vector2(632,58),"active":false}, {"id":"expansion_1","title":"SEKCJA 02","status":"W REMONCIE","rect":Rect2(632,120,200,205),"tag":Vector2(632,58),"active":false},
{"id":"expansion_2","title":"SEKCJA 03","status":"W REMONCIE","rect":Rect2(848,120,214,205),"tag":Vector2(848,58),"active":false}, {"id":"expansion_2","title":"SEKCJA 03","status":"W REMONCIE","rect":Rect2(848,120,214,205),"tag":Vector2(848,58),"active":false},
@ -20,7 +20,7 @@ const ROOM_SCENE := preload("res://scenes/planets/freighter_room.tscn")
const ROOM_PLANS := { const ROOM_PLANS := {
"expansion_1":{"kind":"laboratory","title":"LABORATORIUM","description":"Stanowiska analityczne, aparatura i stół do doświadczeń."}, "expansion_1":{"kind":"laboratory","title":"LABORATORIUM","description":"Stanowiska analityczne, aparatura i stół do doświadczeń."},
"expansion_2":{"kind":"alien_research","title":"BADANIA OBCYCH","description":"Izolowane próbki i komora do badań obcych organizmów."}, "expansion_2":{"kind":"alien_research","title":"BADANIA OBCYCH","description":"Izolowane próbki i komora do badań obcych organizmów."},
"expansion_3":{"kind":"module_storage","title":"SKŁAD MODUŁÓW","description":"Regały, pojemniki i stanowiska do przechowywania modułów."} "expansion_3":{"kind":"module_storage","title":"MAGAZYN","description":"Regały, pojemniki i stanowiska do przechowywania modułów."}
} }
var room_states: Dictionary = {} var room_states: Dictionary = {}
var room_nodes: Dictionary = {} var room_nodes: Dictionary = {}

View File

@ -16,12 +16,15 @@ var _hint: Label
var _owned_open := false var _owned_open := false
var _storage_columns: GridContainer var _storage_columns: GridContainer
var _storage_notice := "" var _storage_notice := ""
var _economy_labels: Dictionary = {}
@onready var deck: Control = $Deck @onready var deck: Control = $Deck
@onready var _audio: Node = $FreighterAudio @onready var _audio: Node = $FreighterAudio
func _ready() -> void: func _ready() -> void:
if captain == null: if captain == null:
captain = GameState.captain captain = GameState.captain
captain.expedition.freighter_rooms["expansion_3"] = "module_storage"
captain.expedition.changed.connect(_refresh_live_labels)
deck.sync_rooms(captain.expedition.freighter_rooms) deck.sync_rooms(captain.expedition.freighter_rooms)
deck.section_selected.connect(_select_section) deck.section_selected.connect(_select_section)
deck.section_hovered.connect(_hover_section) deck.section_hovered.connect(_hover_section)
@ -111,7 +114,7 @@ func _layout() -> void:
var research: bool = _tab == "expansion_2" and deck.is_built(_tab) var research: bool = _tab == "expansion_2" and deck.is_built(_tab)
var width := minf(490 if research else (700 if storage else (440 if _tab == "hangar" else 490)),viewport_size.x-32) var width := minf(490 if research else (700 if storage else (440 if _tab == "hangar" else 490)),viewport_size.x-32)
var construction: bool = deck.is_buildable(_tab) and not deck.is_built(_tab) var construction: bool = deck.is_buildable(_tab) and not deck.is_built(_tab)
var target_height := 390 if construction else 430 if research else 420 if _tab == "hangar" else (520 if _tab == "workshop" else (600 if storage else (300 if _tab == "command" else 260))) var target_height := 390 if construction else 430 if research else 420 if _tab == "hangar" else (520 if _tab == "workshop" else (600 if storage else (540 if _tab == "command" else (340 if _tab.begins_with("cabin_") else 260))))
var height := minf(target_height,maxf(240,viewport_size.y-140)) var height := minf(target_height,maxf(240,viewport_size.y-140))
_drawer.position = Vector2(16 if _tab == "hangar" else viewport_size.x-width-16,maxf(90,(viewport_size.y-height)*0.5)) _drawer.position = Vector2(16 if _tab == "hangar" else viewport_size.x-width-16,maxf(90,(viewport_size.y-height)*0.5))
_drawer.size = Vector2(width,height) _drawer.size = Vector2(width,height)
@ -162,6 +165,7 @@ func _label(text: String, size_value := 14, color := Color("c5d3d7")) -> Label:
return node return node
func _refresh() -> void: func _refresh() -> void:
_economy_labels.clear()
_storage_columns = null _storage_columns = null
_audio.set_maintenance_needed(captain.expedition.lander_hp < 30) _audio.set_maintenance_needed(captain.expedition.lander_hp < 30)
for child in _content.get_children(): for child in _content.get_children():
@ -199,6 +203,9 @@ func _build_room() -> void:
if str(plan.kind) == "alien_research" and deck.is_built(_tab): if str(plan.kind) == "alien_research" and deck.is_built(_tab):
_build_alien_research() _build_alien_research()
return return
if _tab.begins_with("cabin_") and deck.is_built(_tab):
preload("res://scripts/planets/freighter_command.gd").cabin(self)
return
_label("ZBUDOWANO" if deck.is_built(_tab) else "W REMONCIE",21,UI.CYAN if deck.is_built(_tab) else Color("e0b16a")) _label("ZBUDOWANO" if deck.is_built(_tab) else "W REMONCIE",21,UI.CYAN if deck.is_built(_tab) else Color("e0b16a"))
_label(str(plan.description)) _label(str(plan.description))
if deck.is_built(_tab): if deck.is_built(_tab):
@ -359,6 +366,18 @@ func _unlock_ship_slot(kind: String) -> void:
func _build_storage() -> void: func _build_storage() -> void:
MENU.hero(_content,"storage","MAGAZYN / ZASOBY I WYPOSAŻENIE") MENU.hero(_content,"storage","MAGAZYN / ZASOBY I WYPOSAŻENIE")
var deuter_card := MENU.card(_content)
var deuter_row := MENU.row(deuter_card)
MENU.icon(deuter_row, load("res://assets/icons/resources/deuter.svg"), 26)
var deuter_title := MENU.label(deuter_row, "DEUTER / PALIWO FREGATY", 13, Color("95dce8"))
deuter_title.autowrap_mode = TextServer.AUTOWRAP_OFF
deuter_title.size_flags_horizontal = Control.SIZE_EXPAND_FILL
var deuter_amount := MENU.label(deuter_row, "%.1f" % captain.expedition.deuter, 15, MENU.GOLD)
deuter_amount.autowrap_mode = TextServer.AUTOWRAP_OFF
deuter_amount.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
deuter_amount.custom_minimum_size.x = 80
_economy_labels["stock_header"] = deuter_amount
MENU.label(deuter_card, "Automatyczne dostawy z obsadzonych placówek · %.0f / min" % preload("res://scripts/planets/mining_network.gd").total_rate(captain.expedition), 11, MENU.MUTED)
if not deck.is_built("expansion_3"): if not deck.is_built("expansion_3"):
_build_resource_overview() _build_resource_overview()
var construction := MENU.card(_content) var construction := MENU.card(_content)
@ -437,21 +456,23 @@ func _build_resource_overview() -> void:
if is_instance_valid(inventory): if is_instance_valid(inventory):
for key in ["ammunition","missile","irradiated_ammunition","energy_bank"]: values[key] = inventory.get_crafted_stock(key) for key in ["ammunition","missile","irradiated_ammunition","energy_bank"]: values[key] = inventory.get_crafted_stock(key)
values.samples = captain.expedition.analysis_samples values.samples = captain.expedition.analysis_samples
values.deuter = captain.expedition.deuter
var names: Dictionary = RECIPES.NAMES.duplicate() var names: Dictionary = RECIPES.NAMES.duplicate()
names.merge(GEAR.MATERIAL_NAMES) names.merge(GEAR.MATERIAL_NAMES)
names.merge({"credits":"Kredyty","parts":"Części","ammunition":"Amunicja","missile":"Rakiety","irradiated_ammunition":"Radiacyjna","energy_bank":"Powerbanki","samples":"Próbki"}) names.merge({"credits":"Kredyty","parts":"Części","ammunition":"Amunicja","missile":"Rakiety","irradiated_ammunition":"Radiacyjna","energy_bank":"Powerbanki","samples":"Próbki","deuter":"Deuter"})
for key in values: for key in values:
var tile := MENU.card(grid) var tile := MENU.card(grid)
tile.get_parent().name = "Stock_"+key tile.get_parent().name = "Stock_"+key
var row := MENU.row(tile) var row := MENU.row(tile)
var path := "res://assets/icons/resources/%s.%s" % [key,"svg" if key in ["credits","energy_bank"] else "png"] var path := "res://assets/icons/resources/%s.%s" % [key,"svg" if key in ["credits","energy_bank","deuter"] else "png"]
if ResourceLoader.exists(path): MENU.icon(row,load(path),22) if ResourceLoader.exists(path): MENU.icon(row,load(path),22)
var labels := VBoxContainer.new() var labels := VBoxContainer.new()
labels.size_flags_horizontal = Control.SIZE_EXPAND_FILL labels.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_child(labels) row.add_child(labels)
MENU.label(labels,str(names.get(key,key)),11,MENU.MUTED) MENU.label(labels,str(names.get(key,key)),11,MENU.MUTED)
MENU.label(labels,str(values[key]),13,MENU.GOLD) var amount_label := MENU.label(labels,"%.1f" % values[key] if key == "deuter" else str(values[key]),13,MENU.GOLD)
tile.get_parent().tooltip_text = "%s: %d" % [names.get(key,key),values[key]] if key == "deuter": _economy_labels["stock"] = amount_label
tile.get_parent().tooltip_text = "%s: %.1f" % [names.get(key,key),values[key]] if key == "deuter" else "%s: %d" % [names.get(key,key),values[key]]
func _transfer_all(retrieve: bool) -> void: func _transfer_all(retrieve: bool) -> void:
if not is_instance_valid(inventory): return if not is_instance_valid(inventory): return
@ -547,17 +568,25 @@ func _build_workshop() -> void:
button.disabled = equipped button.disabled = equipped
func _build_command() -> void: func _build_command() -> void:
var progress: Resource = captain.expedition preload("res://scripts/planets/freighter_command.gd").build(self)
_label("MOSTEK / SYSTEMY NAWIGACYJNE",13,Color("8bd9d0"))
_label("Łączność z twoim statkiem jest stabilna. Fregata pozostaje gotowa do przejścia do kolejnego układu.") func _travel_to_system(level: int) -> void:
_label("Zeskanowane planety: %d" % progress.scans.size(),16) var controller := get_parent().get_parent()
_label("Próbki do analizy w dziale badań obcych: %d" % progress.analysis_samples,15,UI.CYAN) if controller.has_method("request_system_travel"): controller.request_system_travel(level)
_label("ODZYSKANE ARCHIWA",15,UI.CYAN)
if progress.research_records.is_empty(): func _assign_station(station_id: String, option: OptionButton) -> void:
_label("Brak odzyskanych zapisów.",13,Color("819ca5")) preload("res://scripts/planets/mining_network.gd").assign(captain.expedition, station_id, str(option.get_selected_metadata()))
for key in progress.research_records: _refresh()
_label("%s ×%d" % [key,progress.research_records[key]],13)
_label("Rozbudowa centrum dowodzenia oraz pozostałych sekcji będzie dostępna później.",13,Color("bc9e72")) func _dismiss_crew(member_id: String) -> void:
preload("res://scripts/planets/mining_network.gd").dismiss(captain.expedition, member_id)
_refresh()
func _refresh_live_labels() -> void:
for key in _economy_labels:
var label: Label = _economy_labels[key]
if not is_instance_valid(label): continue
label.text = "Deuter: %.1f · Produkcja: %.0f/min" % [captain.expedition.deuter, preload("res://scripts/planets/mining_network.gd").total_rate(captain.expedition)] if key == "network" else "%.1f" % captain.expedition.deuter
func _craft(kind: String) -> void: func _craft(kind: String) -> void:
if _tab != "workshop": return if _tab != "workshop": return

View File

@ -0,0 +1,92 @@
extends RefCounted
## The network belongs to expedition progress, so it survives visits and system travel.
const BUILD_COST := 200
const DEPOSIT_CHANCE := 0.60
const CREW_CHANCE := 0.40
const CABINS := ["cabin_1", "cabin_2", "cabin_3", "cabin_4", "cabin_5"]
const FIRST_NAMES := ["Ada", "Mira", "Jan", "Leon", "Iga", "Oskar", "Nina", "Tomasz", "Sara", "Emil"]
const LAST_NAMES := ["Nowak", "Voss", "Kowalski", "Orion", "Lis", "Ward", "Keller", "Wilk", "Stern", "Vale"]
static func planet_roll(profile: Dictionary, salt: int) -> RandomNumberGenerator:
var rng := RandomNumberGenerator.new()
rng.seed = int(profile.get("seed", 0)) ^ str(profile.get("id", "planet")).hash() ^ salt
return rng
static func has_deep_deposit(profile: Dictionary) -> bool:
return planet_roll(profile, 0x44454550).randf() < DEPOSIT_CHANCE
static func has_recruit(profile: Dictionary) -> bool:
return planet_roll(profile, 0x43524557).randf() < CREW_CHANCE
static func recruit(profile: Dictionary) -> Dictionary:
var rng := planet_roll(profile, 0x50455253)
var rating := clampi(1 + roundi(clampf(float(profile.get("difficulty", 0)), 0, 1) * 80) + rng.randi_range(0, 19), 1, 100)
return {"id":str(profile.id)+":crew", "name":FIRST_NAMES[rng.randi_range(0,9)]+" "+LAST_NAMES[rng.randi_range(0,9)], "management":rating, "planet_id":str(profile.id), "planet_name":str(profile.name), "cabin":""}
static func free_cabin(progress: Resource) -> String:
for cabin in CABINS:
if progress.freighter_rooms.get(cabin, "") != "crew_cabin": continue
if cabin_crew(progress, cabin).is_empty(): return cabin
return ""
static func cabin_crew(progress: Resource, cabin: String) -> Dictionary:
for member in progress.crew.values():
if str(member.cabin) == cabin: return member
return {}
static func hire(progress: Resource, profile: Dictionary) -> bool:
var candidate := recruit(profile)
var cabin := free_cabin(progress)
if not has_recruit(profile) or progress.recruited_planets.has(str(profile.id)) or cabin.is_empty(): return false
candidate.cabin = cabin
progress.crew[candidate.id] = candidate
progress.recruited_planets[str(profile.id)] = true
progress.emit_changed()
return true
static func dismiss(progress: Resource, member_id: String) -> bool:
if not progress.crew.has(member_id): return false
for station in progress.mining_stations.values():
if str(station.get("crew_id", "")) == member_id: station.crew_id = ""
progress.crew.erase(member_id)
progress.emit_changed()
return true
static func build(progress: Resource, captain: Resource, profile: Dictionary, system: int) -> bool:
var id := str(profile.id)
if not has_deep_deposit(profile) or progress.mining_stations.has(id) or captain.parts < BUILD_COST: return false
captain.parts -= BUILD_COST
progress.mining_stations[id] = {"id":id, "planet_name":str(profile.name), "system":system, "crew_id":"", "produced":0.0}
captain.emit_changed()
progress.emit_changed()
return true
static func assign(progress: Resource, station_id: String, member_id: String) -> bool:
if not progress.mining_stations.has(station_id): return false
if not member_id.is_empty() and not progress.crew.has(member_id): return false
if not member_id.is_empty():
for station in progress.mining_stations.values():
if str(station.get("crew_id", "")) == member_id: station.crew_id = ""
progress.mining_stations[station_id].crew_id = member_id
progress.emit_changed()
return true
static func station_rate(progress: Resource, station: Dictionary) -> float:
var id := str(station.get("crew_id", ""))
if not progress.crew.has(id): return 0.0
return 1.0 + clampi(int(progress.crew[id].management), 1, 100)
static func total_rate(progress: Resource) -> float:
var total := 0.0
for station in progress.mining_stations.values(): total += station_rate(progress, station)
return total
static func advance(progress: Resource, seconds: float) -> float:
if seconds <= 0: return 0
var gained := 0.0
for station in progress.mining_stations.values():
var amount := station_rate(progress, station) * seconds / 60.0
station.produced = float(station.get("produced", 0)) + amount
gained += amount
progress.deuter += gained
return gained

View File

@ -0,0 +1 @@
uid://bt35et03ccbgg

View File

@ -8,7 +8,11 @@ const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
@export var delivered_modules: Array[Resource] = [] @export var delivered_modules: Array[Resource] = []
@export var research_records: Dictionary = {} @export var research_records: Dictionary = {}
@export var alien_skills: Dictionary = {} @export var alien_skills: Dictionary = {}
@export var freighter_rooms: Dictionary = {} @export var freighter_rooms: Dictionary = {"expansion_3":"module_storage"}
@export var mining_stations: Dictionary = {}
@export var crew: Dictionary = {}
@export var recruited_planets: Dictionary = {}
@export var deuter := 0.0
@export var freighter_storage: Array[Resource] = [] @export var freighter_storage: Array[Resource] = []
@export var delivered_missiles := 0 @export var delivered_missiles := 0
@export var analysis_samples := 0 @export var analysis_samples := 0
@ -70,7 +74,7 @@ func add_material(key: String, amount: int) -> void:
emit_changed() emit_changed()
func resolve_poi(captain: Resource, poi: Dictionary, rng: RandomNumberGenerator) -> Dictionary: func resolve_poi(captain: Resource, poi: Dictionary, rng: RandomNumberGenerator) -> Dictionary:
if not active or bool(poi.get("complete", false)): if not active or bool(poi.get("complete", false)) or not preload("res://scripts/planets/poi_rules.gd").STAGES.has(str(poi.get("kind", ""))):
return {} return {}
var stage := int(poi.get("stage", 0)) var stage := int(poi.get("stage", 0))
var chance := PROFILES.success_chance(active_profile, captain, stage) var chance := PROFILES.success_chance(active_profile, captain, stage)

View File

@ -1,5 +1,5 @@
extends RefCounted extends RefCounted
const NAMES := {"deposit":"Złoże surowców", "ruins":"Starożytna struktura", "warehouse":"Opuszczony magazyn", "village":"Opuszczona wioska", "cave":"Jaskinia"} const NAMES := {"deposit":"Złoże surowców", "ruins":"Starożytna struktura", "warehouse":"Opuszczony magazyn", "village":"Opuszczona wioska", "cave":"Jaskinia", "deep_deposit":"Głębokie złoże deuteru", "crew":"Obóz specjalisty"}
const STAGES := { const STAGES := {
"deposit":["Zbierz odsłonięte minerały", "Wykonaj odwiert", "Wydobądź głęboką żyłę"], "deposit":["Zbierz odsłonięte minerały", "Wykonaj odwiert", "Wydobądź głęboką żyłę"],
"ruins":["Przeszukaj przedsionek", "Otwórz komorę", "Zbadaj rdzeń"], "ruins":["Przeszukaj przedsionek", "Otwórz komorę", "Zbadaj rdzeń"],

View File

@ -58,6 +58,9 @@ static func generate(seed_value: int, profile: Dictionary) -> Dictionary:
var natural_count := rng.randi_range(MIN_NATURAL_POIS, MAX_NATURAL_POIS) var natural_count := rng.randi_range(MIN_NATURAL_POIS, MAX_NATURAL_POIS)
for extra in range(natural_count - 4): for extra in range(natural_count - 4):
kinds.append("cave" if rng.randf() < 0.5 else "deposit") kinds.append("cave" if rng.randf() < 0.5 else "deposit")
var network := preload("res://scripts/planets/mining_network.gd")
if network.has_deep_deposit(profile): kinds.append("deep_deposit")
if network.has_recruit(profile): kinds.append("crew")
for index in kinds.size(): for index in kinds.size():
var kind: String = kinds[index] var kind: String = kinds[index]
var chosen := Vector2.ZERO var chosen := Vector2.ZERO

View File

@ -20,7 +20,10 @@ func _draw() -> void:
for poi in world.data.pois: for poi in world.data.pois:
if poi.discovered: if poi.discovered:
var at: Vector2 = (poi.position / total + Vector2.ONE * 0.5) * size var at: Vector2 = (poi.position / total + Vector2.ONE * 0.5) * size
draw_circle(at, 2.5, Color("708377") if poi.complete else Color("f4cb80")) var color := Color("708377") if poi.complete else Color("f4cb80")
if poi.kind == "deep_deposit": color = Color("84dff3")
elif poi.kind == "crew" and not poi.complete: color = Color("a7e5a2")
draw_circle(at, 3.0 if poi.kind == "deep_deposit" else 2.5, color)
for event in world.data.events: for event in world.data.events:
if not event.complete: if not event.complete:
var at: Vector2 = (event.position/total+Vector2.ONE*0.5)*size var at: Vector2 = (event.position/total+Vector2.ONE*0.5)*size

View File

@ -58,7 +58,9 @@ func _ready() -> void:
container.texture = viewport.get_texture() container.texture = viewport.get_texture()
world = Node2D.new() world = Node2D.new()
world.set_script(preload("res://scripts/planets/surface_world.gd")) world.set_script(preload("res://scripts/planets/surface_world.gd"))
world.data = GENERATOR.generate(progress.visit_seed, progress.active_profile) world.data = GENERATOR.generate(int(progress.active_profile.seed), progress.active_profile)
for poi in world.data.pois:
if poi.kind == "crew" and progress.recruited_planets.has(str(progress.active_profile.id)): poi.complete = true
viewport.add_child(world) viewport.add_child(world)
lander = Sprite2D.new() lander = Sprite2D.new()
lander.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON lander.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_ON
@ -226,6 +228,9 @@ func _show_poi(message := "") -> void:
POI_CARD.panel_style(_dialog) POI_CARD.panel_style(_dialog)
_choices.add_theme_constant_override("separation",8) _choices.add_theme_constant_override("separation",8)
_layout_dialog() _layout_dialog()
if _poi.kind in ["deep_deposit", "crew"]:
_show_colony_poi(message)
return
var stage := clampi(int(_poi.stage),0,2) var stage := clampi(int(_poi.stage),0,2)
POI_CARD.header(_choices,str(_poi.kind),stage,bool(_poi.complete)) POI_CARD.header(_choices,str(_poi.kind),stage,bool(_poi.complete))
if not message.is_empty(): if not message.is_empty():
@ -400,3 +405,54 @@ func _resize_surface_viewport() -> void:
_layout_dialog() _layout_dialog()
if is_instance_valid(_surface_viewport): if is_instance_valid(_surface_viewport):
_surface_viewport.size = Vector2i(Vector2(maxf(1,size.x),maxf(1,size.y))*SURFACE_RENDER_SCALE) _surface_viewport.size = Vector2i(Vector2(maxf(1,size.x),maxf(1,size.y))*SURFACE_RENDER_SCALE)
func _show_colony_poi(message: String) -> void:
var network := preload("res://scripts/planets/mining_network.gd")
var is_deposit: bool = _poi.kind == "deep_deposit"
var art := TextureRect.new()
art.texture = load("res://assets/planets/props/deuterium_outpost.png" if is_deposit else "res://assets/planets/props/engineer_camp.png")
art.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
art.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
art.custom_minimum_size.y = 170
_choices.add_child(art)
POI_CARD.text(_choices, "GŁĘBOKIE ZŁOŻE / DEUTER" if is_deposit else "SPECJALISTA / REKRUTACJA", 15, POI_CARD.GOLD)
if not message.is_empty(): POI_CARD.text(_choices, message, 13, POI_CARD.GOLD)
if is_deposit:
var id := str(progress.active_profile.id)
if progress.mining_stations.has(id):
var station: Dictionary = progress.mining_stations[id]
POI_CARD.text(_choices, "Placówka zbudowana · %.0f deuteru/min" % network.station_rate(progress, station), 13)
POI_CARD.text(_choices, "Przydziel załoganta w centrum dowodzenia fregaty. Deuter trafia automatycznie do magazynu, również gdy jesteś poza tą planetą.", 12)
else:
POI_CARD.text(_choices, "Pod powierzchnią znajduje się głęboki pokład deuteru. Placówka wymaga 200 części i załoganta do zarządzania.", 13)
POI_CARD.text(_choices, "Bazowo 1 deuter/min + punkty zarządzania załoganta. Części: %d / 200" % int(captain.parts), 12)
var build := POI_CARD.button(_choices, "ZBUDUJ PLACÓWKĘ · 200 CZĘŚCI", _build_outpost, true)
build.name = "BuildMiningOutpost"
build.disabled = captain.parts < network.BUILD_COST
else:
var candidate: Dictionary = network.recruit(progress.active_profile)
POI_CARD.text(_choices, "%s\nZarządzanie: %d / 100\nPremia do wydobycia: +%d deuteru/min" % [candidate.name, candidate.management, candidate.management], 14)
if progress.recruited_planets.has(str(progress.active_profile.id)):
POI_CARD.text(_choices, "Specjalista został już zatrudniony.", 12)
else:
var cabin: String = network.free_cabin(progress)
POI_CARD.text(_choices, "Brak wolnej, zbudowanej kajuty. Przygotuj pokój na fregacie." if cabin.is_empty() else "Zakwaterowanie: Kajuta %02d" % int(cabin.trim_prefix("cabin_")), 12)
var hire := POI_CARD.button(_choices, "ZATRUDNIJ ZAŁOGANTA", _hire_crew, true)
hire.name = "HireCrew"
hire.disabled = cabin.is_empty()
POI_CARD.button(_choices, "Wróć do wyprawy", func(): _dialog.hide())
func _build_outpost() -> void:
if _ending or not progress.active or _poi.kind != "deep_deposit": return
var network := preload("res://scripts/planets/mining_network.gd")
if network.build(progress, captain, progress.active_profile, GameState.system_level):
world.refresh_pois(lander.position)
_show_poi("Placówka gotowa. Przydziel operatora w centrum dowodzenia.")
else: _show_poi("Nie można zbudować placówki. Sprawdź liczbę części.")
func _hire_crew() -> void:
if _ending or not progress.active or _poi.kind != "crew": return
if preload("res://scripts/planets/mining_network.gd").hire(progress, progress.active_profile):
_poi.complete = true
_show_poi("Załogant zajął wolną kajutę na fregacie.")
else: _show_poi("Zatrudnienie wymaga wolnej, zbudowanej kajuty.")

View File

@ -61,7 +61,7 @@ func _ready() -> void:
add_child(tree_canopy) add_child(tree_canopy)
for poi in data.pois: for poi in data.pois:
var sprite := Sprite2D.new() var sprite := Sprite2D.new()
if poi.kind == "deposit": if poi.kind in ["deposit", "deep_deposit"]:
sprite.set_script(preload("res://scripts/planets/surface_deposit.gd")) sprite.set_script(preload("res://scripts/planets/surface_deposit.gd"))
sprite.configure(data,poi) sprite.configure(data,poi)
else: else:
@ -69,7 +69,7 @@ func _ready() -> void:
sprite.set_script(preload("res://scripts/planets/surface_cave.gd")) sprite.set_script(preload("res://scripts/planets/surface_cave.gd"))
sprite.configure(data,poi) sprite.configure(data,poi)
else: else:
var textures := {"ruins":"ruins", "warehouse":"props/warehouse", "village":"landmarks/abandoned_hamlet"} var textures := {"ruins":"ruins", "warehouse":"props/warehouse", "village":"landmarks/abandoned_hamlet", "crew":"props/engineer_camp"}
sprite.texture = load("res://assets/planets/%s.png" % textures[poi.kind]) sprite.texture = load("res://assets/planets/%s.png" % textures[poi.kind])
sprite.position = poi.position sprite.position = poi.position
if sprite.texture != null and poi.kind != "cave": if sprite.texture != null and poi.kind != "cave":
@ -140,8 +140,19 @@ func refresh_pois(lander_position: Vector2) -> void:
sprite.modulate = Color(0.5, 0.55, 0.5) if poi.complete else Color.WHITE sprite.modulate = Color(0.5, 0.55, 0.5) if poi.complete else Color.WHITE
var label: Label = _poi_labels[poi.id] var label: Label = _poi_labels[poi.id]
label.visible = bool(poi.discovered) label.visible = bool(poi.discovered)
if poi.kind == "deep_deposit":
var built: bool = GameState.captain.expedition.mining_stations.has(str(data.profile.id))
if built and not bool(sprite.get_meta("outpost", false)):
sprite.texture = load("res://assets/planets/props/deuterium_outpost.png")
sprite.material = null
sprite.rotation = 0
sprite.scale = Vector2.ONE * 185.0 / maxf(sprite.texture.get_width(), sprite.texture.get_height())
sprite.set_meta("outpost", true)
label.text = "Placówka górnicza" if built else "Głębokie złoże deuteru"
if lander_position.distance_to(poi.position) <= 150: label.text += "\n[F] Placówka" if built else "\n[F] Buduj"
continue
var title: String = preload("res://scripts/planets/poi_rules.gd").NAMES[poi.kind] var title: String = preload("res://scripts/planets/poi_rules.gd").NAMES[poi.kind]
label.text = title + (" · zbadano" if poi.complete else ("\n[F] Zbadaj" if lander_position.distance_to(poi.position) <= 150 else "")) label.text = title + ((" · zatrudniono" if poi.kind == "crew" else " · zbadano") if poi.complete else ("\n[F] Zbadaj" if lander_position.distance_to(poi.position) <= 150 else ""))
func _draw() -> void: func _draw() -> void:
if terrain == null: if terrain == null:

View File

@ -0,0 +1,84 @@
extends Node
const NETWORK := preload("res://scripts/planets/mining_network.gd")
const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
func settle() -> void:
for frame in 20: await get_tree().process_frame
func stop_game(game: Node) -> void:
game.set_physics_process(false)
game.player.set_physics_process(false)
game.player.get_node("WeaponController").set_physics_process(false)
for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false)
func _ready() -> void:
GameState.start_new_run()
GameState.set_process(false)
GameState.system_seed = 12345
GameState.remember_system()
GameState.system_level = 3
GameState.system_seed = 54321
GameState.remember_system()
GameState.system_level = 1
GameState.system_seed = 12345
GameState.cleared_systems[1] = true
var captain: Resource = GameState.captain
var profile: Dictionary
for index in 100:
var candidate := PROFILES.generate(12345, index, "Placówka testowa")
if NETWORK.has_recruit(candidate) and NETWORK.has_deep_deposit(candidate):
profile = candidate
break
captain.expedition.freighter_rooms.cabin_1 = "crew_cabin"
captain.parts = 200
assert(NETWORK.hire(captain.expedition, profile))
assert(NETWORK.build(captain.expedition, captain, profile, 1))
var member: Dictionary = NETWORK.cabin_crew(captain.expedition, "cabin_1")
NETWORK.assign(captain.expedition, profile.id, member.id)
NETWORK.advance(captain.expedition, 60)
var stock: float = captain.expedition.deuter
get_tree().current_scene = null
var game := preload("res://scenes/game/game.tscn").instantiate()
get_tree().root.add_child.call_deferred(game)
await settle()
get_tree().current_scene = game
stop_game(game)
assert(game.system_cleared and game.get_remaining_enemies() == 0)
var layout: Dictionary = game.system_data
var inventory = game.player.get_node("ShipInventory")
inventory.ammunition = 321
var manager = game.get_node("PlanetExpeditions")
manager.open_freighter()
manager.screen._select_section("command")
assert(manager.screen._content.find_child("SystemDestination", true, false).item_count == 3)
assert(not manager.request_system_travel(4))
assert(is_instance_valid(manager.screen))
var destination: OptionButton = manager.screen._content.find_child("SystemDestination", true, false)
destination.select(2)
destination.item_selected.emit(2)
var travel: Button = manager.screen._content.find_child("TravelToSystem", true, false)
assert(not travel.disabled)
travel.pressed.emit()
await settle()
game = get_tree().current_scene
stop_game(game)
assert(GameState.system_level == 3 and GameState.system_seed == 54321 and GameState.highest_system == 3)
assert(GameState.captain == captain and captain.expedition.deuter == stock)
assert(game.player.get_node("ShipInventory").ammunition == 321)
assert(captain.expedition.mining_stations[profile.id].crew_id == member.id)
manager = game.get_node("PlanetExpeditions")
manager.open_freighter()
assert(manager.request_system_travel(1))
await settle()
game = get_tree().current_scene
stop_game(game)
assert(GameState.system_level == 1 and GameState.system_seed == 12345)
assert(game.system_cleared and game.get_remaining_enemies() == 0)
assert(game.system_data.star_name == layout.star_name)
assert(game.system_data.planets[0].profile == layout.planets[0].profile)
assert(captain.expedition.deuter == stock and captain.expedition.crew.size() == 1)
assert(not game.travel_to_system(4))
get_tree().current_scene = self
game.free()
print("FREIGHTER_NAVIGATION_TEST_OK: real scene reload 1 -> 3 -> 1, unlocked cap, original seeds and planet identities, cleared systems, inventory, crew, stations and deuter retained")
get_tree().quit()

View File

@ -0,0 +1 @@
uid://w7bjpxclh38j

View File

@ -0,0 +1,5 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/levels/freighter_navigation_test.gd" id="1"]
[node name="FreighterNavigationTest" type="Node"]
process_mode = 3
script = ExtResource("1")

View File

@ -0,0 +1,87 @@
extends Node
const NETWORK := preload("res://scripts/planets/mining_network.gd")
const PROGRESS := preload("res://scripts/planets/planet_progress.gd")
const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
func _ready() -> void:
GameState.start_new_run()
GameState.set_process(false)
var captain: Resource = GameState.captain
var progress: Resource = captain.expedition
assert(progress.freighter_rooms.expansion_3 == "module_storage")
var profiles: Array[Dictionary] = []
var deposits := 0
for index in 1000:
var profile := PROFILES.generate(87654, index, "Planeta %d" % index)
profiles.append(profile)
deposits += int(NETWORK.has_deep_deposit(profile))
assert(NETWORK.has_deep_deposit(profile) == NETWORK.has_deep_deposit(profile.duplicate(true)))
var easy: Dictionary = profile.duplicate(true)
var hard: Dictionary = profile.duplicate(true)
easy.difficulty = 0
hard.difficulty = 1
assert(NETWORK.recruit(easy).management in range(1, 21))
assert(NETWORK.recruit(hard).management in range(81, 101))
assert(deposits > 540 and deposits < 660)
var source: Dictionary = profiles.filter(func(p): return NETWORK.has_recruit(p) and NETWORK.has_deep_deposit(p))[0]
assert(not NETWORK.hire(progress, source))
progress.freighter_rooms.cabin_1 = "crew_cabin"
assert(NETWORK.hire(progress, source))
assert(not NETWORK.hire(progress, source))
var member: Dictionary = NETWORK.cabin_crew(progress, "cabin_1")
assert(not member.is_empty() and NETWORK.free_cabin(progress).is_empty())
captain.parts = 199
assert(not NETWORK.build(progress, captain, source, 1) and captain.parts == 199)
captain.parts = 400
assert(NETWORK.build(progress, captain, source, 1) and captain.parts == 200)
assert(not NETWORK.build(progress, captain, source, 1) and captain.parts == 200)
assert(NETWORK.advance(progress, 600) == 0 and progress.deuter == 0)
assert(NETWORK.assign(progress, source.id, member.id))
var expected: float = 1 + int(member.management)
NETWORK.advance(progress, 30)
assert(is_equal_approx(progress.deuter, expected * 0.5))
NETWORK.advance(progress, 30)
assert(is_equal_approx(progress.deuter, expected))
var second: Dictionary = profiles.filter(func(p): return NETWORK.has_deep_deposit(p) and p.id != source.id)[0]
assert(NETWORK.build(progress, captain, second, 2) and captain.parts == 0)
assert(NETWORK.assign(progress, second.id, member.id))
assert(progress.mining_stations[source.id].crew_id == "")
assert(NETWORK.total_rate(progress) == expected)
assert(not NETWORK.assign(progress, source.id, "unknown"))
var path := "res://tmp/mining_roundtrip.tres"
assert(ResourceSaver.save(progress, path) == OK)
var restored: Resource = ResourceLoader.load(path, "", ResourceLoader.CACHE_MODE_IGNORE)
assert(restored.deuter == progress.deuter and restored.crew == progress.crew and restored.mining_stations == progress.mining_stations)
var game := preload("res://scenes/game/game.tscn").instantiate()
add_child(game)
game.set_physics_process(false)
game.player.set_physics_process(false)
for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false)
var before: float = progress.deuter
GameState.advance_play_time(60, game)
assert(is_equal_approx(progress.deuter - before, expected))
get_tree().paused = true
before = progress.deuter
GameState.advance_play_time(60, game)
assert(progress.deuter == before)
var expeditions := game.get_node("PlanetExpeditions")
expeditions.open_freighter()
GameState.advance_play_time(60, game)
assert(is_equal_approx(progress.deuter - before, expected))
expeditions._leave()
assert(progress.begin_visit(source))
expeditions._enter()
expeditions.screen = Control.new()
expeditions._layer.add_child(expeditions.screen)
before = progress.deuter
GameState.advance_play_time(60, game)
assert(is_equal_approx(progress.deuter - before, expected))
progress.finish_visit(captain, true)
assert(NETWORK.dismiss(progress, member.id))
assert(NETWORK.total_rate(progress) == 0 and NETWORK.free_cabin(progress) == "cabin_1")
assert(not NETWORK.hire(progress, source))
expeditions._leave()
get_tree().paused = false
game.free()
print("MINING_NETWORK_TEST_OK: 60%% stable sites (%d/1000), rating 1-100 by biome, cabins, hiring, 200 parts, assignment, 1+rating/min, game-time clock and pause, dismissal, resource persistence" % deposits)
get_tree().quit()

View File

@ -0,0 +1 @@
uid://bm0jfqrvvtnnk

View File

@ -0,0 +1,5 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/levels/mining_network_test.gd" id="1"]
[node name="MiningNetworkTest" type="Node"]
process_mode = 3
script = ExtResource("1")

View File

@ -49,7 +49,7 @@ func _ready() -> void:
assert(GEAR.equip(captain,"breathing",crafted.uid)) assert(GEAR.equip(captain,"breathing",crafted.uid))
assert(GEAR.level(captain,"breathing") == 2) assert(GEAR.level(captain,"breathing") == 2)
var world: Dictionary = SURFACE.generate(11447,profile) var world: Dictionary = SURFACE.generate(11447,profile)
assert(world.pois.size() >= 6 and world.pois.size() <= 7 and world.trees.size() > 0 and world.mountains.size() > 0 and world.structures.size() > 0) assert(world.pois.size() >= 8 and world.pois.size() <= 16 and world.trees.size() > 0 and world.mountains.size() > 0 and world.structures.size() > 0)
assert(SURFACE.generate(11447,profile).pois == world.pois) assert(SURFACE.generate(11447,profile).pois == world.pois)
assert(SURFACE.generate(11448,profile).pois != world.pois) assert(SURFACE.generate(11448,profile).pois != world.pois)
for seed_value in 100: for seed_value in 100:
@ -119,7 +119,7 @@ func _ready() -> void:
manager.scan_nearby() manager.scan_nearby()
manager.land_nearby() manager.land_nearby()
assert(get_tree().paused and GameState.captain.expedition.active) assert(get_tree().paused and GameState.captain.expedition.active)
assert(manager.screen.world.data.pois.size() >= 6 and manager.screen.world.data.pois.size() <= 7) assert(manager.screen.world.data.pois.size() >= 8 and manager.screen.world.data.pois.size() <= 16)
assert(manager.screen.planet_texture == manager._near_planet.body_texture) assert(manager.screen.planet_texture == manager._near_planet.body_texture)
assert(manager.screen._telemetry.portrait.texture == manager._near_planet.body_texture) assert(manager.screen._telemetry.portrait.texture == manager._near_planet.body_texture)
for poi in manager.screen.world.data.pois: for poi in manager.screen.world.data.pois:

View File

@ -11,7 +11,7 @@ func _ready() -> void:
GameState.system_level = 20 GameState.system_level = 20
assert(state.begin_visit(profile)) assert(state.begin_visit(profile))
var samples_before := 0 var samples_before := 0
for kind in RULES.NAMES: for kind in RULES.STAGES:
for stage in 3: for stage in 3:
var success := false var success := false
for attempt in 100: for attempt in 100:

View File

@ -9,7 +9,7 @@ func _ready() -> void:
var profile: Dictionary = preload("res://scripts/planets/planet_profiles.gd").generate(711,index,"Test") var profile: Dictionary = preload("res://scripts/planets/planet_profiles.gd").generate(711,index,"Test")
var data: Dictionary = GENERATOR.generate(500+index,profile) var data: Dictionary = GENERATOR.generate(500+index,profile)
var counts := {"warehouse":0,"village":0,"cave":0,"ruins":0,"deposit":0} var counts := {"warehouse":0,"village":0,"cave":0,"ruins":0,"deposit":0}
for poi in data.pois: counts[poi.kind] += 1 for poi in data.pois: counts[poi.kind] = int(counts.get(poi.kind, 0)) + 1
for poi in data.pois: for poi in data.pois:
if poi.kind != "cave": continue if poi.kind != "cave": continue
assert(poi.has("mountain_position")) assert(poi.has("mountain_position"))

View File

@ -10,7 +10,8 @@ func _ready() -> void:
assert(manager.freighter.find_children("*","Label",true,false).is_empty()) assert(manager.freighter.find_children("*","Label",true,false).is_empty())
manager.open_freighter() manager.open_freighter()
var screen: Control = manager.screen var screen: Control = manager.screen
for section in ["expansion_1","expansion_2","expansion_3","cabin_1","cabin_2","cabin_3","cabin_4","cabin_5"]: assert(screen.deck.is_built("expansion_3"))
for section in ["expansion_1","expansion_2","cabin_1","cabin_2","cabin_3","cabin_4","cabin_5"]:
screen._select_section(section) screen._select_section(section)
for frame in 8: await get_tree().process_frame for frame in 8: await get_tree().process_frame
for mineral in preload("res://scripts/items/crafting_recipes.gd").MINERALS: for mineral in preload("res://scripts/items/crafting_recipes.gd").MINERALS:
@ -26,5 +27,5 @@ func _ready() -> void:
manager.screen.closed.emit() manager.screen.closed.emit()
game.free() game.free()
await get_tree().process_frame await get_tree().process_frame
print("CONSTRUCTION_LAYOUT_OK: no freighter label, all eight rooms, five single-line costs, full cost table visible") print("CONSTRUCTION_LAYOUT_OK: no freighter label, seven unbuilt rooms and default storage, five single-line costs, full cost table visible")
get_tree().quit() get_tree().quit()

View File

@ -0,0 +1,101 @@
extends Node
const NETWORK := preload("res://scripts/planets/mining_network.gd")
const PROFILES := preload("res://scripts/planets/planet_profiles.gd")
func settle() -> void:
for frame in 12: await get_tree().process_frame
func shot(file: String) -> void:
await settle()
await RenderingServer.frame_post_draw
get_viewport().get_texture().get_image().save_png("res://artifacts/mining_%s.png" % file)
func _ready() -> void:
GameState.start_new_run()
GameState.set_process(false)
var captain: Resource = GameState.captain
var progress: Resource = captain.expedition
var profile: Dictionary
for index in 100:
var p := PROFILES.generate(654321, index, "Asterion")
if NETWORK.has_recruit(p) and NETWORK.has_deep_deposit(p):
profile = p
break
var game := preload("res://scenes/game/game.tscn").instantiate()
add_child(game)
game.set_physics_process(false)
game.player.set_physics_process(false)
game.player.get_node("WeaponController").set_physics_process(false)
for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false)
var controller := game.get_node("PlanetExpeditions")
assert(progress.begin_visit(profile))
controller._enter()
var surface := Control.new()
surface.set_script(preload("res://scripts/planets/surface_screen.gd"))
surface.captain = captain
surface.progress = progress
surface.flight_ship = game.player
surface.finished.connect(controller.finish_surface)
controller.screen = surface
controller._layer.add_child(surface)
await settle()
surface.weather.set_condition("clear", true)
var deposit: Dictionary = surface.world.data.pois.filter(func(p): return p.kind == "deep_deposit")[0]
var camp: Dictionary = surface.world.data.pois.filter(func(p): return p.kind == "crew")[0]
surface._poi = deposit
surface.lander.position = deposit.position
surface._previous_position = deposit.position
surface.camera.position = deposit.position
surface.camera.reset_smoothing()
captain.parts = 199
surface._show_poi()
assert(surface._choices.find_child("BuildMiningOutpost", true, false).disabled)
captain.parts = 200
surface._show_poi()
await shot("construction")
surface._choices.find_child("BuildMiningOutpost", true, false).pressed.emit()
assert(progress.mining_stations.size() == 1 and captain.parts == 0)
assert(surface.world._poi_sprites[deposit.id].get_meta("outpost", false))
await shot("built_outpost")
surface._poi = camp
surface._show_poi()
assert(surface._choices.find_child("HireCrew", true, false).disabled)
progress.freighter_rooms.cabin_1 = "crew_cabin"
surface._show_poi()
await shot("recruitment")
surface._choices.find_child("HireCrew", true, false).pressed.emit()
assert(progress.crew.size() == 1 and NETWORK.free_cabin(progress).is_empty())
surface._return(true)
await settle()
controller.open_freighter()
var screen: Control = controller.screen
screen._select_section("command")
await settle()
var member: Dictionary = NETWORK.cabin_crew(progress, "cabin_1")
var option: OptionButton = screen._content.find_child("StationCrew_"+str(profile.id).md5_text().substr(0, 8), true, false)
assert(option.item_count == 2)
option.select(1)
screen._assign_station(profile.id, option)
NETWORK.advance(progress, 60)
progress.emit_changed()
assert(progress.deuter == 1 + member.management)
await shot("command")
assert(screen._drawer.size.x <= 490 and screen._drawer.get_global_rect().end.y <= 721)
screen._select_section("storage")
await settle()
assert(screen._economy_labels.has("stock_header"))
var before: String = screen._economy_labels.stock_header.text
NETWORK.advance(progress, 60)
progress.emit_changed()
assert(screen._economy_labels.stock_header.text != before)
await shot("storage")
screen._select_section("cabin_1")
await shot("cabin")
assert(screen._content.find_children("*", "Button", true, false).size() == 1)
screen._dismiss_crew(member.id)
assert(progress.crew.is_empty() and NETWORK.total_rate(progress) == 0)
controller._leave()
game.free()
get_tree().paused = false
print("MINING_COLONY_REVIEW_OK: surface construction gate and sprite, hiring gate and cabin, station assignment, live storage, cabin dismissal and compact layout")
get_tree().quit()

View File

@ -0,0 +1 @@
uid://ndvsst8pe2wb

View File

@ -0,0 +1,5 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/previews/mining_colony_review.gd" id="1"]
[node name="MiningColonyReview" type="Node"]
process_mode = 3
script = ExtResource("1")