@@ -80,6 +80,12 @@ def _load_blessing_relevance() -> list:
8080 return raw ["blessings" ]
8181
8282
83+ def _load_valid_sets () -> set :
84+ raw = json .loads ((STATIC / "artifact_sets.json" ).read_text (encoding = "utf-8" ))
85+ rows = next ((v for k , v in raw .items () if k != "_meta" and isinstance (v , list )), [])
86+ return {r ["set" ] for r in rows }
87+
88+
8389_COMPUTED_COLS = ["base_computed" , "artifact_bonus" , "mastery_bonus" ,
8490 "affinity_bonus" , "classic_arena_bonus" , "blessing_bonus" ,
8591 "empower_bonus" , "relic_bonus" , "faction_guardians_bonus" ]
@@ -195,6 +201,57 @@ def relevant(mid: int) -> bool:
195201 return by_tree
196202
197203
204+ # Artifact set recommendations by kit role. The set BONUSES are game-truth
205+ # (data/static/artifact_sets.json); the role->set mapping is standard
206+ # team-building heuristic. Internal set code -> UI name for readability
207+ # (set names differ from UI, like blessings — see artifact_relic_mechanics).
208+ SET_UI_NAME = {
209+ "CriticalDamage" : "Savage/Cruel (C.DMG)" , "CriticalChance" : "Crit Rate" ,
210+ "AttackPower" : "Offense (ATK%)" , "IgnoreDefense" : "Stalwart-ish / IgnoreDef" ,
211+ "LifeDrain" : "Lifesteal" , "AttackAndCritRate" : "Atk+CR" ,
212+ "AccuracyAndSpeed" : "Perception (ACC+SPD)" , "Accuracy" : "Accuracy" ,
213+ "AttackSpeed" : "Speed" , "SpeedAndResistance" : "Speed+RES" ,
214+ "Hp" : "Life (HP%)" , "Defense" : "Defense" , "Shield" : "Shield" ,
215+ "Resistance" : "Resistance" , "HpAndHeal" : "Immortal" ,
216+ "Counterattack" : "Retaliation" , "Stamina" : "Relentless (TM)" ,
217+ "DotRate" : "Toxic" , "BlockDebuff" : "Immunity" ,
218+ "ResistanceAndBlockDebuff" : "Resist+Immunity" ,
219+ "UnkillableAndSpdAndCrDmg" : "Frenzy/Unkillable-ish" ,
220+ }
221+
222+
223+ def recommend_sets (hero : dict , location : str , valid_sets : set ) -> list :
224+ provides = set (hero .get ("provides" , []))
225+ role = hero .get ("synergy_role" , "" )
226+ game_role = hero .get ("game_role" , "" )
227+ is_attacker = game_role == "Attack" or role in ("attacker" , "dot" )
228+ is_debuffer = any (p .startswith ("enemy_debuff:" ) for p in provides ) or "dot" in role
229+ is_support = role in ("support" , "healer/support" ) or game_role == "Support"
230+ is_tank = game_role in ("Defense" , "Health" )
231+
232+ picks : list [str ] = []
233+ # Debuffers/DoT carries lead with Speed + Accuracy (cadence + landing
234+ # matter most), then damage. Pure attackers lead with damage sets.
235+ if is_debuffer :
236+ picks += ["AttackSpeed" , "AccuracyAndSpeed" , "Accuracy" ]
237+ if is_attacker :
238+ picks += ["CriticalDamage" , "AttackPower" , "CriticalChance" ,
239+ "IgnoreDefense" , "LifeDrain" ]
240+ if is_support or is_tank :
241+ picks += ["AttackSpeed" , "SpeedAndResistance" , "Hp" , "Defense" ,
242+ "Resistance" , "HpAndHeal" , "Shield" ]
243+ if location in ("arena" , "tt" ):
244+ picks = ["AttackSpeed" , "CriticalDamage" , "CriticalChance" ] + picks
245+ # Dedup preserving order, keep only sets that exist in game data.
246+ seen , out = set (), []
247+ for s in picks :
248+ if s in seen or s not in valid_sets :
249+ continue
250+ seen .add (s )
251+ out .append (SET_UI_NAME .get (s , s ))
252+ return out [:5 ]
253+
254+
198255def recommend_blessing (hero : dict , location : str , brel : list ) -> list :
199256 provides = set (hero .get ("provides" , []))
200257 role = hero .get ("synergy_role" , "" )
@@ -277,6 +334,9 @@ def main() -> None:
277334 names = ", " .join (p ["name" ] for p in picks )
278335 print (f" { tree } : { names } " )
279336 print ()
337+ sets = recommend_sets (hero , args .location , _load_valid_sets ())
338+ print (f" Artifact sets (role-fit): { ', ' .join (sets ) if sets else '(role-standard)' } " )
339+ print ()
280340 bl = recommend_blessing (hero , args .location , brel )
281341 print (f" Blessing (relevant + role-fit): { ', ' .join (bl ) if bl else '(role-standard)' } " )
282342 print ()
0 commit comments