extends RefCounted const DEFINITIONS := [ preload("res://resources/items/recycler.tres"), preload("res://resources/items/virus.tres"), preload("res://resources/items/titanium_armor.tres"), preload("res://resources/items/combat_drones.tres"), preload("res://resources/items/teleport.tres"), preload("res://resources/items/cloak.tres"), preload("res://resources/items/mine_layer.tres"), preload("res://resources/items/flares.tres"), preload("res://resources/items/solar_shield_charge.tres"), preload("res://resources/items/auxiliary_shield.tres"), preload("res://resources/items/gamma_cannon.tres"), preload("res://resources/items/plasma_cannon.tres"), preload("res://resources/items/beam_laser.tres"), preload("res://resources/items/pulse_laser.tres"), preload("res://resources/items/machine_gun.tres"), preload("res://resources/items/external_turret.tres"), preload("res://resources/items/missile_launcher.tres"), preload("res://resources/items/unguided_missile.tres"), preload("res://resources/items/reinforced_shield.tres"), preload("res://resources/items/hull_plating.tres"), preload("res://resources/items/compact_generator.tres"), preload("res://resources/items/engine_extension.tres"), preload("res://resources/items/shield_recharger.tres"), preload("res://resources/items/afterburner.tres"), preload("res://resources/items/shield_charge.tres"), preload("res://resources/items/repair_drones.tres"), preload("res://resources/items/ship_computer.tres"), ] const AFFIX_DATABASE := preload("res://resources/items/affix_database.tres") static func generate(level: int, enemy_tier: int = 0) -> ItemInstance: return generate_for_definition(available_definitions().pick_random(), level, enemy_tier) static func available_definitions() -> Array: var lab := preload("res://scripts/planets/laboratory.gd") var pool := DEFINITIONS.filter(func(definition): return definition.item_id not in lab.IDS) for id in lab.IDS: if GameState.captain.expedition.laboratory_unlocks.get(id,false): pool.append(lab.definition(id)) return pool static func generate_loot(level: int, enemy_tier: int = 0, definition: ItemDefinition = null, equipment_luck_percent: float = 0.0) -> ItemInstance: var loot_level := GameState.captain.roll_loot_level(level, -1.0, equipment_luck_percent) return generate_for_definition(definition if definition != null else available_definitions().pick_random(), loot_level, enemy_tier) static func generate_for_definition(definition: ItemDefinition, level: int, enemy_tier: int = 0) -> ItemInstance: var item := ItemInstance.new() item.definition = definition roll_base_values(item, level) if definition.category == ItemDefinition.Category.ACTIVE_EXTENSION: if item.is_passive_extension(): item.passive_value = preload("res://scripts/items/passive_extension_rolls.gd").roll_value(definition.passive_effect) else: item.extension_cooldown = ItemInstance.EXTENSION_COOLDOWNS.roll_seconds() return item var roll := randf() var count := 1 if roll < 0.5 else (2 if roll < 0.75 else (3 if roll < 0.89 else (4 if roll < 0.97 else 5))) if enemy_tier == 1: count = mini(5, count + 1) elif enemy_tier >= 2: count = maxi(4, count) item.additional_modifiers = AFFIX_DATABASE.roll_affixes(item.definition, item.item_level, count) item.rarity = clampi(item.additional_modifiers.size() - 1, 0, 4) return item static func roll_base_values(item: ItemInstance, level: int) -> void: var definition := item.definition if definition.weapon_effect == "artillery": item.artillery_balance_revision = 1 item.item_level = clampi(level, 1, 1000) var scale_factor := preload("res://scripts/levels/level_balance.gd").item_stat_scale(item.item_level) item.primary_value_override = definition.primary_value * scale_factor * randf_range(0.8, 1.2) if definition.primary_stat == "max_speed": item.primary_value_override = randf_range(1.0, 5.0) * scale_factor if definition.category == ItemDefinition.Category.WEAPON: var lower := randi_range(mini(definition.damage_lower_roll_min, definition.damage_lower_roll_max), maxi(definition.damage_lower_roll_min, definition.damage_lower_roll_max)) var spread := randi_range(mini(definition.damage_spread_roll_min, definition.damage_spread_roll_max), maxi(definition.damage_spread_roll_min, definition.damage_spread_roll_max)) item.damage_min = maxf(1.0, roundf(lower * scale_factor)) item.damage_max = maxf(item.damage_min + 1.0, roundf((lower + spread) * scale_factor)) item.primary_value_override = (item.damage_min + item.damage_max) * 0.5 item.energy_regeneration_override = definition.energy_regeneration_bonus * scale_factor * randf_range(0.8, 1.2) if definition.shield_regeneration_bonus > 0.0: item.shield_regeneration_override = definition.shield_regeneration_bonus * scale_factor * randf_range(0.8, 1.2) static func can_advance(item: ItemInstance) -> bool: return item != null and item.definition != null and item.definition.category != ItemDefinition.Category.CONSUMABLE and item.item_level >= 1 and item.item_level < 1000 static func advance(item: ItemInstance) -> bool: if not can_advance(item): return false var level := item.item_level + 1 var updated: Array[ItemStatModifier] = [] for modifier in item.additional_modifiers: if modifier == null: updated.append(null) continue # Keep shared modifiers on other items untouched. var copy := modifier.duplicate() as ItemStatModifier if copy.affix == null: for candidate in AFFIX_DATABASE.affixes: if candidate != null and candidate.stat_key == copy.stat_key and candidate.can_roll(item.definition,level): copy.affix = candidate break if copy.affix != null: copy.value = copy.affix.roll_value(level) # Fixed legacy bonuses without a defined roll range stay fixed. updated.append(copy) roll_base_values(item,level) item.additional_modifiers = updated return true