extends Node class_name ShipInventory signal inventory_changed signal ammunition_changed const CARGO_SIZE := 20 const WEAPON_SLOT_COUNT := 2 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 weapon_slots: Array[ItemInstance] = [] var module_slots: Array[ItemInstance] = [] @onready var ship: CharacterBody2D = get_parent() func _ready() -> void: add_to_group("ship_inventory") cargo.resize(CARGO_SIZE) weapon_slots.resize(WEAPON_SLOT_COUNT) module_slots.resize(MODULE_SLOT_COUNT) if GameState.restart_inventory.is_empty(): _add_starter_items() else: cargo.assign(GameState.restart_inventory["cargo"]) weapon_slots.assign(GameState.restart_inventory["weapon"]) module_slots.assign(GameState.restart_inventory["module"]) ammunition = int(GameState.restart_inventory.get("ammunition", STARTING_AMMUNITION)) GameState.restart_inventory.clear() _recalculate_ship_stats() inventory_changed.emit() func get_cargo_used() -> int: var used := 0 for item in cargo: if item != null: used += 1 return used func preserve_for_restart() -> void: # Copy the slot arrays; resources retain levels and individual modifiers. GameState.restart_inventory = { "cargo": cargo.duplicate(), "weapon": weapon_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: var slots := _get_slots(slot_kind) if index < 0 or index >= slots.size(): return null 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: if item == null or item.definition == null: return false for index in cargo.size(): if cargo[index] == null: cargo[index] = item inventory_changed.emit() return true return false func delete_cargo_item(index: int) -> bool: if index < 0 or index >= cargo.size() or cargo[index] == null: return false cargo[index] = null GameState.captain.parts += 10.0 GameState.captain.emit_changed() inventory_changed.emit() return true func can_move_item(from_kind: String, from_index: int, to_kind: String, to_index: int) -> bool: var source := _get_slots(from_kind) var destination := _get_slots(to_kind) if from_index < 0 or from_index >= source.size() or to_index < 0 or to_index >= destination.size(): return false if from_kind == to_kind and from_index == to_index: return false var item := source[from_index] if item == null or item.definition == null: return false if to_kind == "weapon" and item.definition.category != ItemDefinition.Category.WEAPON: return false if to_kind == "module" and item.definition.category == ItemDefinition.Category.WEAPON: return false var displaced := destination[to_index] if displaced != null and displaced.definition != null: if from_kind == "weapon" and displaced.definition.category != ItemDefinition.Category.WEAPON: return false if from_kind == "module" and displaced.definition.category == ItemDefinition.Category.WEAPON: return false return true func move_item(from_kind: String, from_index: int, to_kind: String, to_index: int) -> bool: if not can_move_item(from_kind, from_index, to_kind, to_index): return false var source := _get_slots(from_kind) var destination := _get_slots(to_kind) var displaced := destination[to_index] destination[to_index] = source[from_index] source[from_index] = displaced _recalculate_ship_stats() inventory_changed.emit() return true func _get_slots(slot_kind: String) -> Array[ItemInstance]: match slot_kind: "cargo": return cargo "weapon": return weapon_slots "module": return module_slots return [] func _recalculate_ship_stats() -> void: var bonuses := {} for item in weapon_slots + module_slots: if item == null or item.definition == null: continue for stat_key in ["max_hull", "max_shields", "max_energy", "max_speed", "energy_regeneration", "shield_regeneration", "hull_percent", "shield_percent", "damage_percent", "speed_percent", "experience_percent", "credits_percent", "critical_chance_percent", "critical_damage_percent"]: bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key) ship.call("apply_equipment_bonuses", bonuses) func _add_starter_items() -> void: var definition := load("res://resources/items/plasma_cannon.tres") as ItemDefinition if definition == null: push_warning("Could not load starter plasma cannon") return var item := ItemInstance.new() item.definition = definition item.item_level = 1 weapon_slots[0] = item