extends RefCounted const MAX_RANK := 3 static var _icon_cache: Dictionary = {} const COOLDOWN := 30.0 const DURATION := 5.0 const IDS := ["business_shark", "reactor_overload", "tactical_maneuver", "efficient_magazine", "miner", "power_redirect", "lucky", "explorer", "electrician"] const DATA := { "electrician": {"name": "Elektryk", "active": false, "values": [0.15, 0.20, 0.25], "color": "70f5df"}, "lucky": {"name": "Farciarz", "active": false, "values": [0.05, 0.10, 0.25], "color": "e8c76f"}, "explorer": {"name": "Eksplorator", "active": false, "values": [1, 3, 6], "color": "76d9cc"}, "business_shark": {"name": "Rekin biznesu", "active": false, "values": [0.10, 0.30, 0.60], "color": "efb568"}, "reactor_overload": {"name": "Przeciążenie reaktora", "active": true, "values": [0.28, 0.70, 1.40], "color": "63d9f5"}, "tactical_maneuver": {"name": "Manewr taktyczny", "active": true, "values": [0.30, 0.60, 1.00], "color": "b9a0ef"}, "efficient_magazine": {"name": "Oszczędny magazynek", "active": false, "values": [0.10, 0.20, 0.50], "color": "a9d69a"}, "miner": {"name": "Górnik", "active": false, "values": [3, 6, 10], "color": "e5ac76"}, "power_redirect": {"name": "Przekierowanie zasilania", "active": true, "values": [1.00, 1.80, 3.00], "color": "f28b6e"}, } static func value(skill: String, rank: int) -> float: if not DATA.has(skill) or rank <= 0: return 0.0 return float(DATA[skill]["values"][clampi(rank, 1, MAX_RANK) - 1]) static func duration(skill: String) -> float: return 15.0 if skill == "reactor_overload" else (7.0 if skill == "power_redirect" else DURATION) static func icon(skill: String) -> Texture2D: if skill not in IDS: return null if not _icon_cache.has(skill): var atlas: Texture2D = load("res://assets/icons/captain/unified_skills.png") var cell := atlas.get_size()/3.0 var index := IDS.find(skill) var row := index/3 var rows := [0.0,0.313,0.635,1.0] var region := Rect2i(Vector2(index%3*cell.x+4,rows[row]*atlas.get_height()+4),Vector2(cell.x-8,(rows[row+1]-rows[row])*atlas.get_height()-8)) var image := atlas.get_image().get_region(region) image.resize(384,384,Image.INTERPOLATE_LANCZOS) image.generate_mipmaps() # Separate cached textures keep UV-based cooldown/rounded-corner shaders local. _icon_cache[skill] = ImageTexture.create_from_image(image) return _icon_cache[skill] static func description(skill: String, rank: int) -> String: var amount := roundi(value(skill, rank) * 100.0) match skill: "electrician": return "Powerbank przywraca %d%% maksymalnej energii zamiast 10%%. Umiejętność pasywna; premie rang nie sumują się." % amount "lucky": return "Szczęście: %d%% szans na przedmiot o 1 poziom wyższy niż bazowy loot planszy. Dotyczy lootu z wrogów i znalezisk. Bazowo: 0%%. Sumuje się ze sprzętem do limitu 60%%. Premie rang nie sumują się." % amount "explorer": return "+%d wraków na nowej planszy. Premia ustalana przy rozpoczęciu systemu; nie zmienia bieżącej planszy. Premie rang nie sumują się." % roundi(value(skill, rank)) "business_shark": return "Zakupy za kredyty tańsze o %d%%." % amount "reactor_overload": return "Regeneracja energii +%d%% przez 15 sekund." % amount "tactical_maneuver": return "Szansa uniku wynosi %d%% przez 5 sekund. Unik anuluje całe trafienie." % amount "efficient_magazine": return "Zużycie amunicji mniejsze o %d%%. Obejmuje zwykłą i napromieniowaną amunicję, bez rakiet." % amount "miner": return "+%d szt. surowca z każdej zniszczonej asteroidy." % roundi(value(skill, rank)) "power_redirect": return "Regeneracja energii +%d%% przez 7 sekund. Zużywa wszystkie tarcze i blokuje ich odzysk na czas działania. Wymaga min. 25%% tarcz." % amount return ""