extends Resource class_name ItemInstance @export var definition: ItemDefinition @export_range(1, 1000, 1) var item_level: int = 1 @export var additional_modifiers: Array[ItemStatModifier] = [] @export var primary_value_override: float = -1.0 @export var energy_regeneration_override: float = -1.0 @export_range(0, 4) var rarity: int = 0 @export var damage_min: float = -1.0 @export var damage_max: float = -1.0 func get_stat_total(stat_key: String) -> float: if stat_key == "damage" and definition and definition.category == ItemDefinition.Category.WEAPON: var damage_range := get_damage_range() return (damage_range.x + damage_range.y) * 0.5 var total := 0.0 if definition and definition.primary_stat == stat_key: total += primary_value_override if primary_value_override >= 0.0 else definition.primary_value if definition and stat_key == "energy_regeneration": total += energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus for modifier in additional_modifiers: if modifier and modifier.stat_key == stat_key: total += modifier.value return total func get_damage_range() -> Vector2: if definition == null or definition.category != ItemDefinition.Category.WEAPON: return Vector2.ZERO var lower := damage_min var upper := damage_max if lower < 0.0 or upper < 0.0: if primary_value_override >= 0.0: # Older item instances retain their previously rolled damage. lower = primary_value_override upper = primary_value_override else: # Starter weapons use the middle of the definition's roll settings. lower = roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5) upper = lower + roundf((definition.damage_spread_roll_min + definition.damage_spread_roll_max) * 0.5) var flat_bonus := 0.0 for modifier in additional_modifiers: if modifier and modifier.stat_key == "damage": flat_bonus += modifier.value return Vector2(maxf(0.0, minf(lower, upper) + flat_bonus), maxf(0.0, maxf(lower, upper) + flat_bonus)) func roll_damage() -> float: var damage_range := get_damage_range() # Inclusive integer rolls, preserving fractional flat bonuses on both bounds. var whole_span := floori(damage_range.y - damage_range.x) return damage_range.x + randi_range(0, whole_span) func get_energy_cost() -> float: if definition == null or definition.category != ItemDefinition.Category.WEAPON: 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. 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_average := maxf(1.0, base_lower + base_spread * 0.5) var damage_range := get_damage_range() var average := maxf(0.1, (damage_range.x + damage_range.y) * 0.5) var damage_factor := pow(average / base_average, definition.energy_damage_exponent) var addon_factor := 1.0 + get_addon_count() * definition.energy_per_addon return snappedf(maxf(0.0, definition.energy_cost * damage_factor * addon_factor), 0.1) func get_damage_range_text() -> String: var damage_range := get_damage_range() return "%s–%s DMG" % [_format_damage(damage_range.x), _format_damage(damage_range.y)] static func _format_damage(value: float) -> String: return str(roundi(value)) if is_equal_approx(value, roundf(value)) else "%.1f" % value func get_rarity_name() -> String: return ["Zwykły", "Niepospolity", "Rzadki", "Epicki", "Legendarny"][clampi(rarity, 0, 4)] func get_rarity_color() -> Color: return get_background_color().lightened(0.4) func get_addon_count() -> int: var count := 0 for modifier in additional_modifiers: if modifier != null: count += 1 return count func get_background_color() -> Color: return [Color("101925"), Color("414b58"), Color("65432d"), Color("244c79"), Color("796025")][mini(get_addon_count(), 4)] func get_type_color() -> Color: if definition == null: return Color("354455") match definition.category: ItemDefinition.Category.WEAPON: return Color("ae74ef") ItemDefinition.Category.ACTIVE_EXTENSION: return Color("f39c4d") return Color("65ce8b") func get_tooltip_text() -> String: if definition == null: return "" var primary_text := "%s: +%.1f%s" % [stat_name(definition.primary_stat), get_stat_total(definition.primary_stat), definition.stat_suffix] if definition.category == ItemDefinition.Category.WEAPON: primary_text = "Obrażenia: %s" % get_damage_range_text() elif definition.active_ability != null: primary_text = definition.active_ability.description var lines: Array[String] = [definition.display_name, "%s • Poziom %d" % [get_rarity_name(), item_level], "", definition.description, "", "CECHA GŁÓWNA", primary_text] if definition.energy_regeneration_bonus > 0.0: 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: 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("Odstęp strzałów: %.2f s" % maxf(definition.fire_interval, definition.weapon_cooldown)) if not additional_modifiers.is_empty(): lines.append("\nADDONY (aktywne po zamontowaniu)") for modifier in additional_modifiers: if modifier == null: continue lines.append(modifier.get_display_text()) return "\n".join(lines) static func stat_name(key: String) -> String: return str({"damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key))