This commit is contained in:
Tomasz Boruc 2026-10-11 13:26:14 +02:00
parent 6658adf389
commit 1ad22be08e
16 changed files with 187 additions and 58 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
export_presets.cfg export_presets.cfg
Randoloot.console.exe Randoloot.console.exe
Randoloot.exe Randoloot.exe
/builds/

24
EXPORT_WINDOWS.md Normal file
View File

@ -0,0 +1,24 @@
# Eksport do jednego EXE
1. W Godocie zainstaluj Export Templates zgodne z wersją edytora (obecnie 4.7.2).
2. Otwórz Project → Export i wybierz preset Randoloot / Windows Desktop.
3. Ustaw architekturę x86_64, Embed PCK włączone i Export Console Wrapper wyłączone.
4. Użyj Export Project i wyłącz Export With Debug. Docelowa ścieżka: `builds/windows/Randoloot.exe`.
Do przekazania graczowi wystarczy ten EXE. Zasoby są wbudowane. Godot nie musi być zainstalowany na komputerze gracza; potrzebny jest Windows 64-bit i sterownik graficzny obsługujący Vulkan. Projekt używa Vulkan zamiast Direct3D 12, aby eksport nie potrzebował dodatkowych bibliotek Agility SDK / PIX.
Folder `builds` jest pomijany przez Godota i Git. Lokalny `export_presets.cfg` jest już ignorowany przez Git; przy przenoszeniu projektu należy odtworzyć powyższe ustawienia eksportu.
## Ścieżki i zasoby
- W PCK wielkość liter ma znaczenie. Ścieżka musi dokładnie odpowiadać nazwie na dysku, np. `res://assets/Ships/Spaceship_0.png`.
- Importowane grafiki i dźwięki wczytuj przez `load()` / `preload()` / ResourceLoader. Surowy FileAccess nie gwarantuje dostępu do oryginalnego pliku po eksporcie.
- Dane zapisywane podczas gry powinny korzystać z `user://`, ponieważ `res://` w EXE jest tylko do odczytu.
- Po eksporcie sprawdź EXE w osobnym folderze bez plików projektu.
## Dokumentacja
- https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_windows.html
- https://docs.godotengine.org/en/stable/classes/class_editorexportplatformwindows.html
- https://docs.godotengine.org/en/stable/classes/class_fileaccess.html
- https://docs.godotengine.org/en/stable/tutorials/best_practices/project_organization.html

BIN
assets/Ships/alien_ship.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@ -15,10 +15,6 @@ run/main_scene="res://scenes/ui/main_menu.tscn"
config/features=PackedStringArray("4.7", "Forward Plus") config/features=PackedStringArray("4.7", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
[audio]
buses/default_bus_layout="res://default_bus_layout.tres"
[autoload] [autoload]
GameState="*res://scripts/game/game_state.gd" GameState="*res://scripts/game/game_state.gd"
@ -88,7 +84,3 @@ ship_fire_secondary={
3d/physics_engine="Jolt Physics" 3d/physics_engine="Jolt Physics"
common/physics_interpolation=true common/physics_interpolation=true
[rendering]
rendering_device/driver.windows="d3d12"

View File

@ -7,7 +7,7 @@ script = ExtResource("1_definition")
item_id = "machine_gun" item_id = "machine_gun"
display_name = "Działko maszynowe" display_name = "Działko maszynowe"
category = 0 category = 0
description = "Bardzo szybkostrzelna broń kinetyczna." description = "Bardzo szybkostrzelna broń kinetyczna. Zużywa 1 sztukę amunicji na strzał."
primary_stat = "damage" primary_stat = "damage"
primary_value = 4.0 primary_value = 4.0
stat_suffix = " DMG" stat_suffix = " DMG"
@ -15,7 +15,7 @@ weapon_kind = 1
fire_interval = 0.1 fire_interval = 0.1
projectile_speed = 1150.0 projectile_speed = 1150.0
weapon_range = 550.0 weapon_range = 550.0
energy_cost = 3.0 energy_cost = 0.0
damage_lower_roll_min = 2 damage_lower_roll_min = 2
damage_lower_roll_max = 4 damage_lower_roll_max = 4
damage_spread_roll_min = 1 damage_spread_roll_min = 1

View File

@ -71,10 +71,16 @@ layout_mode = 2
size_flags_vertical = 3 size_flags_vertical = 3
theme_override_colors/font_color = Color(0.59, 0.67, 0.75, 1) theme_override_colors/font_color = Color(0.59, 0.67, 0.75, 1)
theme_override_font_sizes/font_size = 14 theme_override_font_sizes/font_size = 14
text = "Usługi w przygotowaniu" text = "Kup amunicję do działka maszynowego."
horizontal_alignment = 1 horizontal_alignment = 1
vertical_alignment = 1 vertical_alignment = 1
[node name="BuyAmmunitionButton" type="Button" parent="Center/Panel/Contents"]
custom_minimum_size = Vector2(0, 44)
layout_mode = 2
theme_override_font_sizes/font_size = 15
text = "KUP 100 AMUNICJI — 100 KREDYTÓW"
[node name="NextButton" type="Button" parent="Center/Panel/Contents"] [node name="NextButton" type="Button" parent="Center/Panel/Contents"]
custom_minimum_size = Vector2(0, 44) custom_minimum_size = Vector2(0, 44)
layout_mode = 2 layout_mode = 2

View File

@ -49,6 +49,22 @@ layout_mode = 2
theme_override_font_sizes/font_size = 11 theme_override_font_sizes/font_size = 11
text = "ZAMKNIJ" text = "ZAMKNIJ"
[node name="Ammunition" type="HBoxContainer" parent="Contents"]
layout_mode = 2
theme_override_constants/separation = 12
[node name="Count" type="Label" parent="Contents/Ammunition"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 11
text = "AMUNICJA: 300"
[node name="CraftButton" type="Button" parent="Contents/Ammunition"]
layout_mode = 2
theme_override_font_sizes/font_size = 11
text = "TWÓRZ +100 AMUNICJI (50 części)"
tooltip_text = "Zamień 50 części na 100 sztuk amunicji do działka maszynowego."
[node name="CargoGrid" type="GridContainer" parent="Contents"] [node name="CargoGrid" type="GridContainer" parent="Contents"]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 4 size_flags_horizontal = 4

View File

@ -8,15 +8,11 @@ func _ready() -> void:
push_error("SceneMusicPlayer has no track_path configured.") push_error("SceneMusicPlayer has no track_path configured.")
return return
var audio_data := FileAccess.get_file_as_bytes(track_path) var music_stream := load(track_path) as AudioStreamMP3
if audio_data.is_empty():
push_error("Could not read music track: %s" % track_path)
return
var music_stream := AudioStreamMP3.load_from_buffer(audio_data)
if music_stream == null: if music_stream == null:
push_error("Could not decode music track: %s" % track_path) push_error("Could not decode music track: %s" % track_path)
return return
music_stream = music_stream.duplicate() as AudioStreamMP3
music_stream.loop = true music_stream.loop = true
stream = music_stream stream = music_stream
volume_db = playback_volume_db volume_db = playback_volume_db

View File

@ -9,15 +9,11 @@ var _ship: CharacterBody2D
func _ready() -> void: func _ready() -> void:
_ship = get_parent() as CharacterBody2D _ship = get_parent() as CharacterBody2D
var audio_data := FileAccess.get_file_as_bytes(ENGINE_SOUND_PATH) var engine_stream := load(ENGINE_SOUND_PATH) as AudioStreamMP3
if audio_data.size() <= 44:
push_error("Could not read engine sound: %s" % ENGINE_SOUND_PATH)
return
var engine_stream := AudioStreamMP3.load_from_buffer(audio_data)
if engine_stream == null: if engine_stream == null:
push_error("Could not decode engine sound: %s" % ENGINE_SOUND_PATH) push_error("Could not decode engine sound: %s" % ENGINE_SOUND_PATH)
return return
engine_stream = engine_stream.duplicate() as AudioStreamMP3
engine_stream.loop = true engine_stream.loop = true
stream = engine_stream stream = engine_stream
volume_db = SILENT_VOLUME_DB volume_db = SILENT_VOLUME_DB

View File

@ -25,7 +25,7 @@ static func play(parent: Node, source_position: Vector2, weapon_kind: int, volum
return return
if not _streams.has(weapon_kind): if not _streams.has(weapon_kind):
var sound_path: String = SOUND_PATHS[weapon_kind] var sound_path: String = SOUND_PATHS[weapon_kind]
_streams[weapon_kind] = AudioStreamMP3.load_from_buffer(FileAccess.get_file_as_bytes(sound_path)) _streams[weapon_kind] = load(sound_path) as AudioStreamMP3
var stream := _streams[weapon_kind] as AudioStreamMP3 var stream := _streams[weapon_kind] as AudioStreamMP3
if stream == null: if stream == null:
return return

View File

@ -107,7 +107,7 @@ func _physics_process(delta: float) -> void:
_fire_projectile(item) _fire_projectile(item)
_shot_timers[slot_index] = definition.weapon_cooldown _shot_timers[slot_index] = definition.weapon_cooldown
_: _:
if _shot_timers[slot_index] <= 0.0 and _consume_energy(item.get_energy_cost()): if _shot_timers[slot_index] <= 0.0 and _consume_shot_resource(item):
_fire_projectile(item) _fire_projectile(item)
_shot_timers[slot_index] = definition.fire_interval _shot_timers[slot_index] = definition.fire_interval
for slot_index in 2: for slot_index in 2:
@ -173,6 +173,11 @@ func _muzzle_position() -> Vector2:
var forward := Vector2.UP.rotated(ship.rotation) var forward := Vector2.UP.rotated(ship.rotation)
return ship.global_position + forward * 28.0 return ship.global_position + forward * 28.0
func _consume_shot_resource(item: ItemInstance) -> bool:
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
return inventory.consume_ammunition()
return _consume_energy(item.get_energy_cost())
func _consume_energy(amount: float) -> bool: func _consume_energy(amount: float) -> bool:
var current_energy := float(ship.get("energy")) var current_energy := float(ship.get("energy"))
if current_energy < amount: if current_energy < amount:

View File

@ -53,6 +53,8 @@ func roll_damage() -> float:
func get_energy_cost() -> float: func get_energy_cost() -> float:
if definition == null or definition.category != ItemDefinition.Category.WEAPON: if definition == null or definition.category != ItemDefinition.Category.WEAPON:
return 0.0 return 0.0
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
return 0.0
# Compare this weapon with its starter damage. Captain/global gear buffs are free. # Compare this weapon with its starter damage. Captain/global gear buffs are free.
var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5) var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5)
var base_spread := roundf((definition.damage_spread_roll_min + definition.damage_spread_roll_max) * 0.5) var base_spread := roundf((definition.damage_spread_roll_min + definition.damage_spread_roll_max) * 0.5)
@ -109,6 +111,9 @@ func get_tooltip_text() -> String:
lines.append("Regeneracja generatora: +%.1f /s" % (energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus)) lines.append("Regeneracja generatora: +%.1f /s" % (energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus))
if definition.category == ItemDefinition.Category.WEAPON: if definition.category == ItemDefinition.Category.WEAPON:
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.") lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
lines.append("Zasięg: %.0f px • Amunicja: 1 / strzał" % definition.weapon_range)
else:
lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()]) lines.append("Zasięg: %.0f px • Energia: %.1f / strzał" % [definition.weapon_range, get_energy_cost()])
lines.append("Odstęp strzałów: %.2f s" % maxf(definition.fire_interval, definition.weapon_cooldown)) lines.append("Odstęp strzałów: %.2f s" % maxf(definition.fire_interval, definition.weapon_cooldown))
if not additional_modifiers.is_empty(): if not additional_modifiers.is_empty():

View File

@ -2,10 +2,18 @@ extends Node
class_name ShipInventory class_name ShipInventory
signal inventory_changed signal inventory_changed
signal ammunition_changed
const CARGO_SIZE := 20 const CARGO_SIZE := 20
const WEAPON_SLOT_COUNT := 2 const WEAPON_SLOT_COUNT := 2
const MODULE_SLOT_COUNT := 6 const MODULE_SLOT_COUNT := 6
const STARTING_AMMUNITION := 300
const AMMUNITION_CRAFT_AMOUNT := 100
const AMMUNITION_CRAFT_PARTS_COST := 50.0
const AMMUNITION_BUY_AMOUNT := 100
const AMMUNITION_BUY_BASE_COST := 20
var ammunition: int = STARTING_AMMUNITION
var cargo: Array[ItemInstance] = [] var cargo: Array[ItemInstance] = []
var weapon_slots: Array[ItemInstance] = [] var weapon_slots: Array[ItemInstance] = []
@ -24,6 +32,7 @@ func _ready() -> void:
cargo.assign(GameState.restart_inventory["cargo"]) cargo.assign(GameState.restart_inventory["cargo"])
weapon_slots.assign(GameState.restart_inventory["weapon"]) weapon_slots.assign(GameState.restart_inventory["weapon"])
module_slots.assign(GameState.restart_inventory["module"]) module_slots.assign(GameState.restart_inventory["module"])
ammunition = int(GameState.restart_inventory.get("ammunition", STARTING_AMMUNITION))
GameState.restart_inventory.clear() GameState.restart_inventory.clear()
_recalculate_ship_stats() _recalculate_ship_stats()
inventory_changed.emit() inventory_changed.emit()
@ -41,14 +50,49 @@ func preserve_for_restart() -> void:
"cargo": cargo.duplicate(), "cargo": cargo.duplicate(),
"weapon": weapon_slots.duplicate(), "weapon": weapon_slots.duplicate(),
"module": module_slots.duplicate(), "module": module_slots.duplicate(),
"ammunition": ammunition,
} }
func consume_ammunition() -> bool:
if ammunition <= 0:
return false
ammunition -= 1
ammunition_changed.emit()
return true
func can_craft_ammunition() -> bool:
return GameState.captain.parts >= AMMUNITION_CRAFT_PARTS_COST
func craft_ammunition() -> bool:
if not can_craft_ammunition():
return false
GameState.captain.parts -= AMMUNITION_CRAFT_PARTS_COST
ammunition += AMMUNITION_CRAFT_AMOUNT
GameState.captain.emit_changed()
ammunition_changed.emit()
return true
func get_item(slot_kind: String, index: int) -> ItemInstance: func get_item(slot_kind: String, index: int) -> ItemInstance:
var slots := _get_slots(slot_kind) var slots := _get_slots(slot_kind)
if index < 0 or index >= slots.size(): if index < 0 or index >= slots.size():
return null return null
return slots[index] return slots[index]
func get_ammunition_buy_cost() -> int:
return AMMUNITION_BUY_BASE_COST * maxi(1, GameState.system_level)
func can_buy_ammunition() -> bool:
return GameState.captain.credits >= get_ammunition_buy_cost()
func buy_ammunition() -> bool:
if not can_buy_ammunition():
return false
GameState.captain.credits -= get_ammunition_buy_cost()
ammunition += AMMUNITION_BUY_AMOUNT
GameState.captain.emit_changed()
ammunition_changed.emit()
return true
func add_item(item: ItemInstance) -> bool: func add_item(item: ItemInstance) -> bool:
if item == null or item.definition == null: if item == null or item.definition == null:
return false return false
@ -122,34 +166,11 @@ func _recalculate_ship_stats() -> void:
ship.call("apply_equipment_bonuses", bonuses) ship.call("apply_equipment_bonuses", bonuses)
func _add_starter_items() -> void: func _add_starter_items() -> void:
var starter_items := [ var definition := load("res://resources/items/plasma_cannon.tres") as ItemDefinition
"res://resources/items/plasma_cannon.tres",
"res://resources/items/beam_laser.tres",
"res://resources/items/machine_gun.tres",
"res://resources/items/missile_launcher.tres",
"res://resources/items/reinforced_shield.tres",
"res://resources/items/hull_plating.tres",
"res://resources/items/compact_generator.tres",
"res://resources/items/engine_extension.tres",
"res://resources/items/tuned_thrusters.tres",
"res://resources/items/shield_recharger.tres",
"res://resources/items/afterburner.tres",
"res://resources/items/shield_charge.tres",
"res://resources/items/repair_drones.tres",
]
var cargo_index := 0
var weapon_index := 0
for path in starter_items:
var definition := load(path) as ItemDefinition
if definition == null: if definition == null:
push_warning("Could not load starter item: %s" % path) push_warning("Could not load starter plasma cannon")
continue return
var item := ItemInstance.new() var item := ItemInstance.new()
item.definition = definition item.definition = definition
item.item_level = 1 item.item_level = 1
if definition.category == ItemDefinition.Category.WEAPON and weapon_index < WEAPON_SLOT_COUNT: weapon_slots[0] = item
weapon_slots[weapon_index] = item
weapon_index += 1
else:
cargo[cargo_index] = item
cargo_index += 1

View File

@ -5,16 +5,37 @@ signal closed
@onready var next_button: Button = $Center/Panel/Contents/NextButton @onready var next_button: Button = $Center/Panel/Contents/NextButton
@onready var close_button: Button = $Center/Panel/Contents/CloseButton @onready var close_button: Button = $Center/Panel/Contents/CloseButton
@onready var buy_ammunition_button: Button = $Center/Panel/Contents/BuyAmmunitionButton
@onready var services_label: Label = $Center/Panel/Contents/Services
var _inventory: ShipInventory
func _ready() -> void: func _ready() -> void:
next_button.pressed.connect(func() -> void: next_level_requested.emit()) next_button.pressed.connect(func() -> void: next_level_requested.emit())
close_button.pressed.connect(_close) close_button.pressed.connect(_close)
buy_ammunition_button.pressed.connect(_buy_ammunition)
_inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory
if _inventory:
_inventory.ammunition_changed.connect(_refresh_ammunition_service)
GameState.captain.changed.connect(_refresh_ammunition_service)
_refresh_ammunition_service()
func show_services(at_level_cap: bool) -> void: func show_services(at_level_cap: bool) -> void:
_refresh_ammunition_service()
next_button.disabled = at_level_cap next_button.disabled = at_level_cap
next_button.text = "OSIĄGNIĘTO OSTATNI UKŁAD" if at_level_cap else "PRZEJDŹ DO NASTĘPNEGO UKŁADU" next_button.text = "OSIĄGNIĘTO OSTATNI UKŁAD" if at_level_cap else "PRZEJDŹ DO NASTĘPNEGO UKŁADU"
show() show()
func _refresh_ammunition_service() -> void:
buy_ammunition_button.disabled = _inventory == null or not _inventory.can_buy_ammunition()
if _inventory:
buy_ammunition_button.text = "KUP %d AMUNICJI — %d KREDYTÓW" % [ShipInventory.AMMUNITION_BUY_AMOUNT, _inventory.get_ammunition_buy_cost()]
services_label.text = "Amunicja: %d • Kredyty: %d\nCena: 100 kredytów × poziom układu (%d)" % [_inventory.ammunition, GameState.captain.credits, GameState.system_level]
func _buy_ammunition() -> void:
if visible and _inventory:
_inventory.buy_ammunition()
func _close() -> void: func _close() -> void:
hide() hide()
closed.emit() closed.emit()

View File

@ -6,6 +6,8 @@ const CARGO_SIZE := 20
@onready var cargo_grid: GridContainer = $Contents/CargoGrid @onready var cargo_grid: GridContainer = $Contents/CargoGrid
@onready var close_button: Button = $Contents/Header/CloseButton @onready var close_button: Button = $Contents/Header/CloseButton
@onready var title_label: Label = $Contents/Header/Title @onready var title_label: Label = $Contents/Header/Title
@onready var ammunition_label: Label = $Contents/Ammunition/Count
@onready var craft_button: Button = $Contents/Ammunition/CraftButton
var _inventory: ShipInventory var _inventory: ShipInventory
var _drawer_open := false var _drawer_open := false
@ -14,9 +16,12 @@ var drawer_width := 780.0
func _ready() -> void: func _ready() -> void:
close_button.pressed.connect(close_drawer) close_button.pressed.connect(close_drawer)
craft_button.pressed.connect(_craft_ammunition)
craft_button.text = "TWÓRZ +%d AMUNICJI (%.0f części)" % [ShipInventory.AMMUNITION_CRAFT_AMOUNT, ShipInventory.AMMUNITION_CRAFT_PARTS_COST]
_inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory _inventory = get_tree().get_first_node_in_group("ship_inventory") as ShipInventory
if _inventory: if _inventory:
_inventory.inventory_changed.connect(_refresh_title) _inventory.inventory_changed.connect(_refresh_title)
_inventory.ammunition_changed.connect(_refresh_title)
GameState.captain.changed.connect(_refresh_title) GameState.captain.changed.connect(_refresh_title)
for index in CARGO_SIZE: for index in CARGO_SIZE:
var slot := SLOT_SCENE.instantiate() var slot := SLOT_SCENE.instantiate()
@ -26,8 +31,14 @@ func _ready() -> void:
_refresh_title() _refresh_title()
func _refresh_title() -> void: func _refresh_title() -> void:
craft_button.disabled = _inventory == null or not _inventory.can_craft_ammunition()
if _inventory: if _inventory:
title_label.text = "MAGAZYN (%d/%d) · Części: %.1f" % [_inventory.get_cargo_used(), CARGO_SIZE, GameState.captain.parts] title_label.text = "MAGAZYN (%d/%d) · Części: %.1f" % [_inventory.get_cargo_used(), CARGO_SIZE, GameState.captain.parts]
ammunition_label.text = "AMUNICJA: %d" % _inventory.ammunition
func _craft_ammunition() -> void:
if _inventory:
_inventory.craft_ammunition()
func toggle_drawer() -> void: func toggle_drawer() -> void:
_set_drawer_open(not _drawer_open) _set_drawer_open(not _drawer_open)

View File

@ -32,6 +32,9 @@ func _ready() -> void:
addon.stat_key = "damage_percent" addon.stat_key = "damage_percent"
addon.value = 10.0 addon.value = 10.0
stronger.additional_modifiers.append(addon) stronger.additional_modifiers.append(addon)
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
assert(stronger.get_energy_cost() == 0.0)
else:
assert(stronger.get_energy_cost() > cost_before_addon) assert(stronger.get_energy_cost() > cost_before_addon)
for level in [1, 50, 1000]: for level in [1, 50, 1000]:
for sample in 100: for sample in 100:
@ -73,6 +76,38 @@ func _ready() -> void:
var probe := LaserProbe.new() var probe := LaserProbe.new()
player.add_child(probe) player.add_child(probe)
probe.set_physics_process(false) probe.set_physics_process(false)
var inventory := player.get_node("ShipInventory") as ShipInventory
assert(inventory.ammunition == 300)
var machine_gun := ItemInstance.new()
machine_gun.definition = preload("res://resources/items/machine_gun.tres")
player.set("energy", 0.0)
inventory.ammunition = 1
assert(probe._consume_shot_resource(machine_gun))
assert(inventory.ammunition == 0 and player.get("energy") == 0.0)
assert(not probe._consume_shot_resource(machine_gun))
GameState.captain.parts = 49.0
assert(not inventory.craft_ammunition())
assert(inventory.ammunition == 0 and GameState.captain.parts == 49.0)
GameState.captain.parts = 50.0
assert(inventory.craft_ammunition())
assert(inventory.ammunition == 100 and GameState.captain.parts == 0.0)
GameState.system_level = 3
assert(inventory.get_ammunition_buy_cost() == 300)
GameState.captain.credits = 299
assert(not inventory.buy_ammunition())
assert(inventory.ammunition == 100 and GameState.captain.credits == 299)
GameState.captain.credits = 600
assert(inventory.buy_ammunition())
assert(inventory.ammunition == 200 and GameState.captain.credits == 300)
assert(inventory.buy_ammunition())
assert(inventory.ammunition == 300 and GameState.captain.credits == 0)
assert(not inventory.buy_ammunition())
GameState.system_level = 1
inventory.preserve_for_restart()
var restored_player := PLAYER_SCENE.instantiate() as CharacterBody2D
add_child(restored_player)
assert(restored_player.get_node("ShipInventory").get("ammunition") == 300)
restored_player.queue_free()
var target := StaticBody2D.new() var target := StaticBody2D.new()
target.position = Vector2(200, -28) target.position = Vector2(200, -28)
target.collision_layer = 4 target.collision_layer = 4