from collections import defaultdict import logs_base from h_debug import running_time from h_other import sort_dict_by_value DEFAULT_DURATION = 60 ROOM_DURATION = 50 ROOM_AURA_ID = "74297" SERVERS_NO_ICC_BUFF = { "Lordaeron", } AURAS_SELF = { "53908": 15, # Potion of Speed "53909": 15, # Potion of Wild Magic "28494": 15, # Insane Strength Potion "28507": 15, # Haste Potion "53762": 120, # Indestructible Potion "28714": 60, # Flame Cap "54758": 12, # Hyperspeed Acceleration "26297": 10, # Berserking "20572": 15, # Blood Fury "72416": 10, # Frostforged Sage "72412": 10, # Frostforged Champion "55637": 15, # Lightweave "73422": 10, # Chaos Bane "71561": 30, # Strength of the Taunka "71484": 30, # Strength of the Taunka "71558": 30, # Power of the Taunka "71486": 30, # Power of the Taunka "71557": 30, # Precision of the Iron Dwarves "71487": 30, # Precision of the Iron Dwarves "71559": 30, # Aim of the Iron Dwarves "71491": 30, # Aim of the Iron Dwarves "71560": 30, # Speed of the Vrykul "71492": 30, # Speed of the Vrykul "71556": 30, # Agility of the Vrykul "71485": 30, # Agility of the Vrykul "75473": 15, # Charred Twilight Scale "75466": 15, "71605": 20, # Phylactery of the Nameless Lich "71636": 20, "75456": 20, # Sharpened Twilight Scale "75458": 20, "71644": 20, # Dislodged Foreign Object "71601": 20, "71541": 15, # Whispering Fanged Skull "71401": 15, "67772": 15, # Death's Choice "67773": 15, "67703": 15, "67708": 15, "64713": 10, # Flare of the Heavens "71564": 20, # Nevermelting Ice Crystal } AURAS_EXTERNAL = { "73822": 60*30, # Hellscream's Warsong "73828": 60*30, # Strength of Wrynn "2825": 40, # Bloodlust "32182": 40, # Heroism "57933": 10, # Tricks of the Trade "10060": 15, # Power Infusion "49016": 30, # Hysteria "23060": 60*4, # Battle Squawk "54646": 60*30, # Focus Magic "29166": 10, # Innervate "19753": 180, # Divine Intervention "72553": 100, # Gastric Bloat "70227": 30, # Empowered Blood "71532": 75, # Essence of the Blood Queen "71533": 60, # Essence of the Blood Queen "67108": 30, # Nether Power "67215": 20, # Empowered Darkness "67218": 20, # Empowered Light "63848": 50, # Hunger For Blood "14177": 60*10, # Cold Blood "51800": 60*2, # Might of Malygos "51777": 60*2, # Arcane Focus "51605": 60*2, # Zeal "44335": 30, # Energy Feedback } AURAS_BOSS_MECHANICS = { "71289": 12, # Dominate Mind "71237": 15, # Curse of Torpor "69279": 12, # Gas Spore "72550": 20, # Malleable Goo "73020": 6, # Vile Gas "73023": 12, # Mutated Infection "72838": 30, # Volatile Ooze Adhesive "72833": 20, # Gaseous Bloat "72856": 60, # Unbound Plague "72620": 20, # Choking Gas "71265": 6, # Swarming Shadows "73070": 4, # Incite Terror "71340": 30, # Pact of the Darkfallen "69762": 30, # Unchained Magic "67907": 15, # Mistress' Kiss "68125": 8, # Legion Flame "66283": 3, # Spinning Pain Spike "74509": 3, # Repelling Wave "74456": 5, # Conflagration "74384": 4, # Intimidating Roar "74531": 2, # Tail Lash "74118": DEFAULT_DURATION, # Ooze Variable "74119": DEFAULT_DURATION, # Gas Variable "70157": DEFAULT_DURATION, # Ice Tomb "69065": DEFAULT_DURATION, # Impaled "74795": DEFAULT_DURATION, # Mark of Consumption "74567": DEFAULT_DURATION, # Mark of Combustion ROOM_AURA_ID: ROOM_DURATION, # Harvest Souls } AURAS_SPEC = { "47241": 36, # Metamorphosis "70840": 10, # Devious Minds "63167": 10, # Decimation "64371": 10, # Eradication "48108": 10, # Hot Streak "70753": 5, # Pushing the Limit "70747": 30, # Quad Core "12292": 30, # Death Wish "46916": 5, # Slam! "14203": 12, # Enrage "70855": 10, # Blood Drinker "52437": 10, # Sudden Death "51690": 2, # Killing Spree "13877": 15, # Blade Flurry "53434": 20, # Call of the Wild "70728": 10, # Exploit Weakness "71007": 10, # Stinger "53220": 12, # Improved Steady Shot "48518": 15, # Eclipse (Lunar) "48517": 15, # Eclipse (Solar) "70721": 6, # Omen of Doom "16886": 3, # Nature's Grace "50334": 15, # Berserk "50213": 6, # Tiger's Fury "16870": 15, # Clearcasting "31884": 20, # Avenging Wrath "14751": DEFAULT_DURATION, # Inner Focus # "61792": 10, # Shadowy Insight "53365": 15, # Unholy Strength # "70657": 15, # Advantage } MULTISPELLS = { # Essence of the Blood Queen "71533": [ "71533", "71531", "71525", "71473", ], "71532": [ "71532", "70867", "70879", "71530", ], # Malleable Goo "72550": [ "72297", "72549", "72548", "72550", "70853", "72873", "72458", "72874", ], # Vile Gas "73020": [ "69240", "73019", "71218", "73020", "72272", "72273", # "69244", "73173", "71288", "73174" ], # Mutated Infection "73023": [ "69674", "73022", "71224", "73023", ], # Volatile Ooze Adhesive - Green Target "72838": [ "70447", "72837", "72836", "72838", ], # Gaseous Bloat - Red Target "72833": [ "70672", "72832", "72455", "72833", ], # Choking Gas "72620": [ "71278", "72619", "72460", "72620", "71279", "72621", "72459", "72622", ], # Unbound Plague "72856": [ "70911", "72855", "72854", "72856", ], # Legion Flame "68125": [ "66197", "68124", "68123", "68125", ], # Mistress' Kiss "67907": [ "66334", "67906", "67905", "67907", ], # Hellscream's Warsong "73822": [ "73816", "73818", "73819", "73820", "73821", "73822", ], # Strength of Wrynn "73828": [ "73762", "73824", "73825", "73826", "73827", "73828", ], } MULTISPELLS_D = {y: x for x, spells in MULTISPELLS.items() for y in spells} ICC_BUFFS = set(MULTISPELLS["73822"]) | set(MULTISPELLS["73828"]) SPELLS = AURAS_SELF | AURAS_EXTERNAL | AURAS_BOSS_MECHANICS | AURAS_SPEC for spell_id, spell_id_main in MULTISPELLS_D.items(): SPELLS[spell_id] = SPELLS[spell_id_main] class Aura: __slots__ = "count", "uptime" def __init__(self, count: int=0, uptime: float=0.0) -> None: self.count = count self.uptime = uptime def __str__(self): return ' | '.join(( f"{self.count:>3}", f"{self.uptime:>6.1f}" )) def __add__(self, other: "Aura"): self.count += other.count self.uptime += other.uptime return self def __lt__(self, other: "Aura"): return self.uptime < other.uptime class AuraUptimePercentage(Aura): pass class AuraUptimeDuration(Aura): pass class AuraLine: __slots__ = "timestamp", "flag" def __init__(self, timestamp: str, flag: str) -> None: self.timestamp = timestamp self.flag = flag class AuraLines(list[AuraLine]): def calc_total_uptime(self, spell_id: str, delta_func): MAX_DURATION = SPELLS.get(spell_id, DEFAULT_DURATION) applied_timestamp = None aura = AuraUptimeDuration() for aura_line in self: if applied_timestamp is not None: _delta = delta_func(applied_timestamp, aura_line.timestamp) _delta = min(_delta, MAX_DURATION) if _delta > 0: aura.count += 1 aura.uptime += _delta if aura_line.flag == "SPELL_AURA_REMOVED": applied_timestamp = None else: applied_timestamp = aura_line.timestamp return aura class AuraLinesSources(dict[str, AuraLines]): def __missing__(self, key): v = self[key] = AuraLines() return v class AuraLinesByTarget(dict[str, AuraLinesSources]): def __missing__(self, key): v = self[key] = AuraLinesSources() return v @running_time def __init__(self, logs_slice: list[str]): for line in logs_slice: if "SPELL_A" not in line: continue _line = line.split(',', 7) if _line[6] not in SPELLS: continue # if _line[6] == "16886": # print(line) spell_id = MULTISPELLS_D.get(_line[6], _line[6]) self[_line[4]][spell_id].append(AuraLine(*_line[:2])) self._add_missing_events(logs_slice) def _add_missing_events(self, logs_slice: list[str]): first_timestamp = logs_slice[0].split(',', 1)[0] last_timestamp = logs_slice[-1].split(',', 1)[0] AURA_APPLIED = AuraLine(first_timestamp, "SPELL_AURA_APPLIED") AURA_REMOVED = AuraLine(last_timestamp, "SPELL_AURA_REMOVED") for spells in self.values(): for aura_timestamps in spells.values(): if aura_timestamps[0].flag != "SPELL_AURA_APPLIED": aura_timestamps.insert(0, AURA_APPLIED) if aura_timestamps[-1].flag != "SPELL_AURA_REMOVED": aura_timestamps.append(AURA_REMOVED) def check_icc_buff(self): icc = defaultdict(int) for target_guid, target_data in self.items(): for spell_id in target_data: if spell_id in ICC_BUFFS: icc[spell_id] += 1 if icc: return list(sort_dict_by_value(icc))[0] def room_grabs_timestamps(self): timestamps = set() for target_guid, target_data in self.items(): for spell_id, aura_lines in target_data.items(): if spell_id != ROOM_AURA_ID: continue g_timestamps = (aura_line.timestamp for aura_line in aura_lines) timestamps.update(g_timestamps) return timestamps class AuraUptimeDurationByTarget(dict[str, dict[str, AuraUptimeDuration]]): def __missing__(self, spell_id: str): v = self[spell_id] = {} return v # @running_time def __init__(self, data: AuraLinesByTarget, delta_func) -> None: for target_guid, target_data in data.items(): for spell_id, aura_timestamps in target_data.items(): if target_guid[:3] != "0x0": continue self[target_guid][spell_id] = aura_timestamps.calc_total_uptime(spell_id, delta_func) class AuraUptimePercentageByTarget(dict[str, dict[str, AuraUptimePercentage]]): def __missing__(self, spell_id: str): v = self[spell_id] = {} return v def __init__(self, data: AuraUptimeDurationByTarget, duration: float) -> None: for target_guid, target_data in data.items(): for spell_id, aura in target_data.items(): uptime_percentage = round(aura.uptime / duration * 100, 1) self[target_guid][spell_id] = AuraUptimePercentage(aura.count, uptime_percentage) class AurasUptimes(logs_base.THE_LOGS): @logs_base.cache_wrap @running_time def get_auras_uptime_duration(self, s, f): logs_slice = self.LOGS[s:f] auras_lines = AuraLinesByTarget(logs_slice) auras_uptime = AuraUptimeDurationByTarget(auras_lines, self.get_timedelta_seconds) custom_auras = {} _room_timestamps = auras_lines.room_grabs_timestamps() if _room_timestamps: custom_auras[ROOM_AURA_ID] = self._aura_lk_room(_room_timestamps, logs_slice[-1]) icc_buff = auras_lines.check_icc_buff() if icc_buff: duration = self.get_slice_duration(s, f) custom_auras[icc_buff] = AuraUptimeDuration(1, duration) for auras_on_target in auras_uptime.values(): auras_on_target.update(custom_auras) return auras_uptime def get_auras_uptime_percentage(self, s, f): SLICE_DURATION = self.get_slice_duration(s, f) auras_groupped_by_target = self.get_auras_uptime_duration(s, f) return AuraUptimePercentageByTarget(auras_groupped_by_target, SLICE_DURATION) def get_auras_uptime_wrap(self, boss, attempt=-1): s, f = self.get_enc_data()[boss][attempt] return self.get_auras_uptime_duration(s, f) def get_auras_uptime_percentage_wrap(self, boss, attempt=-1): s, f = self.get_enc_data()[boss][attempt] return self.get_auras_uptime_percentage(s, f) def _aura_lk_room(self, _room_timestamps: set[str], last_line: str): room_aura = AuraUptimeDuration(count=1) room_timestamps = sorted(_room_timestamps) for x, y in zip(room_timestamps, room_timestamps[1:]): if self.get_timedelta_seconds(x, y) > 60: room_aura.count += 1 room_aura.uptime += ROOM_DURATION last_grab = room_timestamps[-1] gap_after_last_room_grab = self.get_timedelta_seconds(last_grab, last_line) room_aura.uptime += min(gap_after_last_room_grab, ROOM_DURATION) if gap_after_last_room_grab < 10: room_aura.count -= 1 return room_aura def _pretty_print(self, d, guid_filter=None, spell_id_filter=None): print('='*100) for target_guid, spells in d.items(): if guid_filter and target_guid != guid_filter: continue spells = sort_dict_by_value(spells) for spell_id, q in spells.items(): if spell_id_filter and spell_id != spell_id_filter: continue try: spell_name = self.SPELLS[int(spell_id)].name except KeyError: spell_name = spell_id print(f"{self.guid_to_name(target_guid):12} | {spell_id:>5} | {q} | {spell_name}") def test1(): report = AurasUptimes("24-03-01--21-02--Meownya--Lordaeron") report.LOGS # q = report.get_auras_uptime_wrap("The Lich King", -2) # q = report.get_auras_uptime_wrap("Deathbringer Saurfang", -1) # for _ in range(10): q = report.get_auras_uptime_wrap("Blood-Queen Lana'thel", -1) # q = report.get_auras_uptime_percentage_wrap("Blood-Queen Lana'thel", -1) report._pretty_print(q, guid_filter="0x06000000004544FD") q = report.get_auras_uptime_wrap("The Lich King", -2) report._pretty_print(q, guid_filter="0x06000000004544FD") def test2(): report = AurasUptimes("24-06-16--20-36--Saforafire--Icecrown") report.LOGS q = report.get_auras_uptime_wrap("The Lich King", -2) report._pretty_print(q, guid_filter="0x07000000009766B7") def test3(): report = AurasUptimes("24-09-04--19-22--Фуфыкс--WoW-Circle") report.LOGS print(report.PLAYERS_NAMES) q = report.get_auras_uptime_wrap("The Lich King", -2) report._pretty_print(q, guid_filter="0x000000000193F1FA") def test4(): report = AurasUptimes("24-09-04--19-01--Какойтопарен--WoW-Circle") report.LOGS # print(report.PLAYERS_NAMES) # pc = perf_counter() # print("="*100) q = report.get_auras_uptime_wrap("The Lich King", -2) # report._pretty_print(q, guid_filter="0x000000000191C4A9") # q = report.get_auras_uptime_percentage_wrap("The Lich King", -2) # report._pretty_print(q, guid_filter="0x000000000191C4A9") # print(f'{(perf_counter() - pc)*1000:>10,.3f}ms | Done') # input() # report.get_uptime_wrap("Rotface", -1) # q = report.get_uptime_wrap("Festergut", -1) # report.preprint(q, spell_id="75473") # report._pretty_print(q, guid_filter="0x06000000004544FD") # report.preprint(q, target_guid="0x0700000000830A43") # report.preprint(q, "53908") if __name__ == "__main__": test4()