def process_spell(player_name, spell_id, spell_lines, spell_power=None, show=True, path=None): spell_name = sd.spell_name(spell_id) relative_underheal = [] is_crit = [] for h, oh, crit in spell_lines: relative_underheal.append(1.0 - oh / h) is_crit.append(1 if crit else 0) cast_fraction = np.linspace(0, 1, len(relative_underheal)) relative_underheal = np.array(relative_underheal) relative_underheal = np.sort(relative_underheal) # is_crit = np.array(is_crit) plt.figure(constrained_layout=True) plt.fill_between(cast_fraction, relative_underheal, label="Underheal") plt.fill_between(cast_fraction, 1, relative_underheal, label="Overheal") # add base heal line if spell-power is included base_heal = sd.spell_heal(spell_id) coeff = sd.spell_coefficient(spell_id) if spell_power and base_heal > 0 and coeff > 0: extra_heal = coeff * spell_power base_heal_fraction = base_heal / (base_heal + extra_heal) plt.axhline(base_heal_fraction, linestyle="--", color="k", label="Base heal") plt.title(f"{spell_name}, {len(spell_lines)} casts") plt.xlabel("Fraction of casts") plt.ylabel("Fraction of heal (orange overheal, blue underheal)") plt.xlim((0, 1)) plt.ylim((0, 1)) plt.legend() if path is None: path = "figs/cdf" plt.savefig(f"{path}/{player_name}_cdf_{spell_id}.png") # plt.figure(constrained_layout=True) # plt.fill_between(relative_underheal, 1 - cast_fraction, label="Underheal") # plt.fill_between(relative_underheal, 1, 1 - cast_fraction, label="Overheal") # plt.title(spell_name) # plt.ylabel("Fraction of casts") # plt.xlabel("Fraction of overheals (orange overheal, blue underheal)") # plt.xlim((0, 1)) # plt.ylim((0, 1)) # plt.legend() if show: plt.show()
def display_data(res_data, buff_data, dispel_data): all_data = (res_data, buff_data, dispel_data) data_names = ("Resurrections", "Buffs", "Dispels") for data, desc in zip(all_data, data_names): print(f"{desc} data:") for spell_id, d in data.items(): spell_name = sd.spell_name(spell_id) print(f" {spell_name}") for source, casts in d.items(): print(f" {source+':':<17s} {casts:3d}")
def pick_spell(self, deficit, mana, h, **_): # pick spell by deficit choices = { "10917": "Flash Heal (Rank 7)", # "10916": "Flash Heal (Rank 6)", # "10915": "Flash Heal (Rank 5)", "9474": "Flash Heal (Rank 4)", # "9473": "Flash Heal (Rank 3)", # "9472": "Flash Heal (Rank 2)", # "2061": "Flash Heal (Rank 1)", # "2053": "Lesser Heal (Rank 3)", # "10965": "Greater Heal (Rank 4)", # "10964": "Greater Heal (Rank 3)", # "10963": "Greater Heal (Rank 2)", # "2060": "Greater Heal (Rank 1)", # "6064": "Heal (Rank 4)", # "6063": "Heal (Rank 3)", # "2055": "Heal (Rank 2)", # "2054": "Heal (Rank 1)", }.keys() # filter choices by mana available choices = filter( lambda sid: sd.spell_mana(sid, talents=self.talents) < mana, choices) # convert to healing heals = map( lambda sid: (sid, sd.spell_heal(sid) + sd.spell_coefficient(sid) * h), choices) # pick max heal with small amount of overhealing heals = filter(lambda x: 0.80 * x[1] < -deficit, heals) try: spell_id, heal = max(heals, key=lambda x: x[1]) except ValueError: return 0, 0, 0 mana = sd.spell_mana(spell_id) cast_time = 1.5 if "Flash" in sd.spell_name(spell_id) else 2.5 return heal, mana, cast_time
def process_spell(spell_id, spell_lines, heal_increase=0.0): spell_name = sd.spell_name(spell_id) n_heals = 0 n_crits = 0 raw_heals = [] for h, _, crit in spell_lines: n_heals += 1 if crit: n_crits += 1 h /= 1.5 raw_heals.append(h) raw_heals = np.array(raw_heals) raw_heals = filter_out_reduced_healing(raw_heals) sample_size = len(raw_heals) median_heal = np.median(raw_heals) crit_rate = n_crits / n_heals # Include heal increase from Improved Renew or Spiritual Healing spell_heal = sd.spell_heal(spell_id) spell_heal *= 1.0 + heal_increase if spell_heal == 0: # Unknown or 0 base heal, cannot estimate +heal extra_heal = 0.0 else: extra_heal = median_heal - spell_heal coefficient = sd.spell_coefficient(spell_id) if coefficient == 0: # Unknown or 0 coefficient, cannot estimate +heal est_plus_heal = 0 else: est_plus_heal = extra_heal / coefficient print( f" {spell_id:>5s} {spell_name:>26s} {sample_size:3d} {spell_heal:+7.1f} {median_heal:+7.1f}" f" {extra_heal:+6.1f} {est_plus_heal:+7.1f} {crit_rate:5.1%}") return est_plus_heal, n_heals, n_crits
def pick_spell(self, deficit, mana, h, **_): # pick spell by deficit spell_id = self.spell_id base_heal = sd.spell_heal(spell_id) coef = sd.spell_coefficient(spell_id) spell_mana = sd.spell_mana(spell_id, talents=self.talents) if spell_mana > mana: # print(f"{spell_mana} > {mana}") # could not cast return 0, 0, 0 heal = base_heal + h * coef # include random variation and crit maybe? cast_time = 1.5 if "Flash" in sd.spell_name(spell_id) else 2.5 return heal, spell_mana, cast_time
def display_lines(total_data, data_list, group): """Print data lines for cli display""" if len(data_list) == 0: return print( f"{'id':>5s} {group + ' name':28s} {'#H':>3s} {'No OH':>7s} {'Any OH':>7s} {'Half OH':>7s}" f" {'Full OH':>7s} {'% OHd':>7s}") for spell_id, data in data_list: print_spell_aggregate(spell_id, sd.spell_name(spell_id), data) print("-" * (5 + 2 + 28 + 2 + 3 + 2 + 7 + 2 + 7 + 2 + 7 + 2 + 7 + 2 + 7)) group_name = "Total" if group: group_name += " " + group print_spell_aggregate("", group_name, total_data)
def plot_casts(casts_dict, encounter, mark=None, anonymize=True, deaths=None): casts = list(casts_dict.values()) labels = list(casts_dict.keys()) most_casts = max((len(c) for c in casts)) if deaths is None: deaths = () if anonymize: labels = [anonymize_name(s) for s in labels] w = 16 duration = encounter.duration w = duration / 4 h = 0.6 * len(labels) fig = plt.figure(figsize=(w, h), constrained_layout=True) ax = fig.subplots(1, 1) for i in range(most_casts): even = i % 2 == 0 widths = [] lefts = [] colors = [] tags = [] for j, cast_list in enumerate(casts): color = "#99ff99" if even else "#ccff99" if i >= len(cast_list): widths.append(0) lefts.append(0) colors.append(color) else: c = cast_list[i] spell_name = sd.spell_name(c[3], warn_on_not_found=False) spell_tag = shorten_spell_name(spell_name) if "[" in spell_name: color = "#b3b3b3" if even else "#cccccc" target = c[4] if target in TANKS: color = "#99ccff" if even else "#b3d9ff" # color = "#ff9999" if even else "#ffb3b3" elif target == "[Interrupted]": color = "#ff99ff" if even else "#ffccff" target = "[I]" elif target == "[Cancelled]": color = "#ff99ff" if even else "#ffccff" target = "[C]" elif target == "[Your target is dead]": color = "#800000" if even else "#b30000" target = "[TD]" elif target == "[Source died]": color = "#800000" if even else "#b30000" target = "[SD]" elif target == "nil": target = "" if anonymize and target: target = anonymize_name(target) spell_tag += "\n" + target w = (c[2] - c[1]).total_seconds() x = (c[1] - encounter.start_t).total_seconds() if w == 0: # if no width, make it GCD length w = 1.5 widths.append(w) lefts.append(x) colors.append(color) tags.append((spell_tag, x + w / 2, j)) ax.barh(labels, widths, left=lefts, height=0.8, color=colors) for tag, x, y in tags: ax.text(x, y, tag, ha="center", va="center") plt.axvline(duration, color="k") title = "Casts" if encounter: title += f" during {encounter.boss}" plt.title(title) plt.xlabel("Fight duration [s]") plt.grid(axis="x") if mark: parts = mark.split(":") m = int(parts[0]) s = int(parts[1]) plt.axvline(60 * m + s, color="k") last_ts = 0 y = -1 for timestamp, unit_id, player in deaths: x = timestamp.total_seconds() plt.axvline(x, color="k", alpha=0.5) if timestamp == last_ts: y -= 0.2 else: y = -1 plt.text(x, y, player, ha="right", va="top") last_ts = timestamp if encounter: e_name = encounter.short_name else: e_name = "all" fig_path = f"figs/casts_{e_name}.pdf" plt.savefig(fig_path) plt.close() print(f"Saved casts figure to `{fig_path}`")
def plot_overheal(player, spell_powers, spell_id, data, sp_shift=0, sp_extrap=200, path=None, encounter=None): if path is None: path = "figs/overheal" os.makedirs(path, exist_ok=True) sp = spell_powers + sp_shift spell_name = sd.spell_name(spell_id) total_heals, total_overheals, total_underheals, count_heals, nn_underheals, nn_overheals, nn_full_overheals = data plt.figure() plt.plot(sp, total_heals, label="Total heal") plt.plot(sp, total_overheals, label="Overheal") plt.plot(sp, total_underheals, label="Actual heal") title = f"Average healing per cast as +heal varies\n{spell_name}" if encounter: title = encounter.boss + "\n" + title plt.title(title) plt.xlabel("Heal power") plt.ylabel("Healing output") plt.ylim([0, None]) plt.grid() plt.legend() plt.tight_layout() fig_name = f"{player}_heal" if spell_id: fig_name += "_" + spell_id plt.savefig(f"{path}/{fig_name}.png") # number of heals figure fig = plt.figure(figsize=(10, 4), constrained_layout=True) ax1, ax2 = fig.subplots(1, 2) ax1.plot(sp, nn_underheals, label="Underheal") ax1.plot(sp, nn_overheals, label="Any overheal") ax1.plot(sp, nn_full_overheals, label="Full overheal") title = "Number of overheals as a +heal varies" if spell_id: title = sd.spell_name(spell_id) + "\n" + title if encounter: title = encounter.boss + "\n" + title ax1.set_title(title) ax1.set_xlabel("Heal power") ax1.set_ylabel("Overheal fraction of total casts") ax1.set_ylim([0.0, 1.0]) ax1.grid() ax1.legend() # gradient figure d_sp = np.diff(spell_powers) sp = 0.5 * (sp[1:] + sp[:-1]) dth = np.diff(total_heals) / d_sp doh = np.diff(total_overheals) / d_sp dh = np.diff(total_underheals) / d_sp ax2.plot(sp, dth, label="Total heal") ax2.plot(sp, doh, label="Overheal") ax2.plot(sp, dh, label="Net heal") # linear fit of coefficient # coef = np.polyfit(sp, dh, 1) # ax2.plot(sp, np.poly1d(coef)(sp), "k--", label=f"Linear fit, Sc={coef[0] * 100:.2g} * 100h + {coef[1]:.2g}") title = "Effective spell coefficient as total +heal varies" if spell_id: title = sd.spell_name(spell_id) + "\n" + title if encounter: title = encounter.boss + "\n" + title ax2.set_title(title) ax2.set_xlabel("Heal power") ax2.set_ylabel("d(Healing)/d(Spell power)") ax2.set_ylim([0.0, 1.0]) ax2.grid() ax2.legend() fig_name = f"{player}_overheal" if spell_id: fig_name += "_" + spell_id plt.savefig(f"{path}/{fig_name}.png") plt.close() print(f"Saving fig for {player}, {spell_id}, {encounter}")
def main(player_name, source, spell_power): # log_lines = raw.get_lines(log_file) heal_lines, periodic_lines, _ = read_heals(player_name, source) # Group lines heal_lines = group_processed_lines(heal_lines, False) periodic_lines = group_processed_lines(periodic_lines, False) labels = [] nn_underheal = [] nn_overheal = [] nn_downrank = [] nn_drop_h = [] for spell_id, lines in heal_lines.items(): labels.append(sd.spell_name(spell_id)) n_heal, n_underheal, n_overheal, n_downrank, n_drop_h = process_spell( spell_id, lines, spell_power) nn_underheal.append(n_underheal / n_heal) nn_overheal.append(n_overheal / n_heal) nn_downrank.append(n_downrank / n_heal) nn_drop_h.append(n_drop_h / n_heal) for spell_id, lines in periodic_lines.items(): labels.append(sd.spell_name(spell_id)) n_heal, n_underheal, n_overheal, n_downrank, n_drop_h = process_spell( spell_id, lines, spell_power) nn_underheal.append(n_underheal / n_heal) nn_overheal.append(n_overheal / n_heal) nn_downrank.append(n_downrank / n_heal) nn_drop_h.append(n_drop_h / n_heal) ii = np.argsort(nn_underheal) labels = np.array(labels)[ii] nn_overheal = np.array(nn_overheal)[ii] nn_underheal = np.array(nn_underheal)[ii] nn_downrank = np.array(nn_downrank)[ii] nn_drop_h = np.array(nn_drop_h)[ii] b0 = nn_underheal b1 = b0 + nn_drop_h b2 = b1 + nn_downrank plt.figure(figsize=(8, 6), constrained_layout=True) plt.bar(labels, nn_underheal, color="green", label="Underheal") plt.bar( labels, nn_drop_h, color="yellow", bottom=b0, label="Partial OH, less than +heal", ) plt.bar( labels, nn_downrank, color="orange", bottom=b1, label="Partial OH, more than +heal", ) plt.bar(labels, nn_overheal, color="red", bottom=b2, label="Full overheal") plt.ylabel("Fraction of casts") plt.xticks(rotation=90) plt.legend(loc="center left", bbox_to_anchor=(1, 0.5)) plt.savefig(f"figs/{player_name}_summary.png") plt.show()
def overheal_summary(source, character_name, spell_power, path=None, show=False, encounter=None): # log_lines = raw.get_lines(log_file) # heal_lines, periodic_lines, _ = read_heals(source, character_name=character_name) processor = readers.get_processor(source, character_name=character_name) encounter = processor.select_encounter(encounter=encounter) processor.process(encounter=encounter) heal_lines = processor.heals if path is None: path = "figs" os.makedirs(path, exist_ok=True) # Group lines heal_lines = group_processed_lines(heal_lines, False) # periodic_lines = group_processed_lines(periodic_lines, False) labels = [] nn_underheal = [] nn_overheal = [] nn_downrank = [] nn_drop_h = [] for spell_id, lines in heal_lines.items(): labels.append(sd.spell_name(spell_id)) data = process_spell(spell_id, lines, spell_power) if not data: continue n_heal, n_underheal, n_overheal, n_downrank, n_drop_h = data nn_underheal.append(n_underheal / n_heal) nn_overheal.append(n_overheal / n_heal) nn_downrank.append(n_downrank / n_heal) nn_drop_h.append(n_drop_h / n_heal) # for spell_id, lines in periodic_lines.items(): # labels.append(sd.spell_name(spell_id)) # data = process_spell(spell_id, lines, spell_power) # # if not data: # continue # # n_heal, n_underheal, n_overheal, n_downrank, n_drop_h = data # # nn_underheal.append(n_underheal / n_heal) # nn_overheal.append(n_overheal / n_heal) # nn_downrank.append(n_downrank / n_heal) # nn_drop_h.append(n_drop_h / n_heal) ii = np.argsort(nn_underheal) labels = np.array(labels)[ii] nn_overheal = np.array(nn_overheal)[ii] nn_underheal = np.array(nn_underheal)[ii] nn_downrank = np.array(nn_downrank)[ii] nn_drop_h = np.array(nn_drop_h)[ii] b0 = nn_underheal b1 = b0 + nn_drop_h b2 = b1 + nn_downrank plt.figure(figsize=(8, 6), constrained_layout=True) plt.bar(labels, nn_underheal, color="green", label="Underheal") plt.bar(labels, nn_drop_h, color="yellow", bottom=b0, label="Partial OH, less than +heal") plt.bar(labels, nn_downrank, color="orange", bottom=b1, label="Partial OH, more than +heal") plt.bar(labels, nn_overheal, color="red", bottom=b2, label="Full overheal") if encounter: title = f"{character_name}: {encounter.boss}" else: title = character_name plt.title(title) plt.ylabel("Fraction of casts") plt.xticks(rotation=90) plt.legend(loc="center left", bbox_to_anchor=(1, 0.5)) plt.savefig(f"{path}/{character_name}_summary.png") if show: plt.show() plt.close()
def print_results(data): if len(data) == 0: print("No data found.") return data = sorted(data, key=lambda d: sd.spell_name(d[0])) print(f"Crits:") nn_crits = 0 nn_spells = 0 s_crit_fh = 0 s_crit_uh = 0 s_coef = 0 t_hh = 0 t_oh = 0 for spell_id, n_crits, n_spells, crit_fh, crit_uh, hh, ohh in data: spell_name = sd.spell_name(spell_id) coef = sd.spell_coefficient(spell_id) nn_crits += n_crits nn_spells += n_spells s_crit_fh += crit_fh * n_crits s_crit_uh += crit_uh * n_crits s_coef += coef * n_crits t_hh += hh t_oh += ohh crit_pc = n_crits / n_spells message = ( f" {spell_name:<30s}: {n_crits:3d} / {n_spells:3d} crits ({crit_pc:5.1%}); ({ohh / hh:5.1%} OH)" ) if n_crits == 0: print(message) continue crit_oh = crit_fh - crit_uh oh_pc = crit_oh / crit_fh crit_heal = 0.01 * crit_uh eq_h_0c = crit_heal / coef eq_h = eq_h_0c / (1.0 + 0.5 * crit_pc) message += f", Crit H: {crit_fh:4.0f} ({crit_uh:4.0f} + {crit_oh:4.0f} oh) ({oh_pc:5.1%} oh)" message += f", 1% crit gives {0.01 * crit_uh:+4.1f} healing eq to {eq_h:+5.1f} h ({eq_h_0c:+5.1f} at 0% crit)." print(message) print("") crit_pc = nn_crits / nn_spells spell_name = "Overall / Average" coef = s_coef / nn_crits message = ( f" {spell_name:<30s}: {nn_crits:3d} / {nn_spells:3d} crits ({crit_pc:5.1%}); ({t_oh / t_hh:5.1%} OH)" ) if nn_crits == 0: print(message) return crit_fh = s_crit_fh / nn_crits crit_uh = s_crit_uh / nn_crits crit_oh = crit_fh - crit_uh oh_pc = crit_oh / crit_fh crit_heal = 0.01 * crit_uh eq_h_0c = crit_heal / coef eq_h = eq_h_0c / (1.0 + 0.5 * crit_pc) message += f", Crit H: {crit_fh:4.0f} ({crit_uh:4.0f} + {crit_oh:4.0f} oh) ({oh_pc:5.1%} oh)" message += f", 1% crit gives {0.01 * crit_uh:+4.1f} healing eq to {eq_h:+5.1f} h ({eq_h_0c:+5.1f} at 0% crit)." print(message)
def evaluate_casting_strategy( character_name, times, deficits_time, name_dict, character_data, encounter_time, strategy=None, talents=None, spell_id=None, verbose=False, show=True, path=None, plot=True, ): """Evaluate a casting strategy.""" if show is True: plot = True if path is None: path = "figs/optimise" os.makedirs(path, exist_ok=True) if strategy is None: strategy = CastingStrategy(talents) print() print( f" Evaluating casting strategy, ignoring heals from {character_name}") print() print(f" mana: {character_data.mana}") print(f" +heal: {character_data.h}") print(f" mp5: {character_data.mp5}") print(f" mp5 (ooc): {character_data.mp5ooc}") if spell_id: print() print(f" Using {sd.spell_name(spell_id)}") times = iter(times) deficits_time = iter(deficits_time) available_mana = character_data.mana pending_heal = None # dictionary of applied heals for characters applied_heals = dict() sum_net_healing = 0.0 sum_gross_healing = 0.0 regen_mana = 0.0 casts = 0 deficits = next(deficits_time) next_deficit_time = next(times) last_finish_time = -5.0 finish_time = 0.0 next_time = 0.0 ticks = [] heals = [] manas = [] hots = [] time_step = 0.1 time = 0.0 while time < encounter_time: if time >= finish_time: # process heal and/or start new heal if pending_heal: # apply heal target_id = pending_heal.target heal = pending_heal.heal mana = pending_heal.mana # do crit if random() < character_data.a: heal *= 1.5 applied_heal = applied_heals.get(target_id, 0) # heals even if target died deficit = min(0, deficits.get(target_id, 0) + applied_heal) net = min(-deficit, heal) applied_heals[target_id] = applied_heal + net pending_heal = None # count cast and add healing and deduct mana casts += 1 sum_net_healing += net sum_gross_healing += heal available_mana -= mana last_finish_time = finish_time if verbose: print( f" {next_time:4.1f} heal {name_dict[target_id]} ({deficit: 5.0f}) for {net:4.0f}; {sum_net_healing:5.0f}" ) # min wait time is time_step finish_time = time + time_step # able to cast again target_id, deficit = pick_heal_target(deficits, applied_heals) # only heal if there is someone with a deficit if deficit < 0: # pick spell by deficit heal, mana, cast_time = strategy.pick_spell( deficit, available_mana, character_data.h) # check we have enough mana if mana > available_mana: continue # check we expect to heal anything if heal <= 0: continue if verbose: print( f" {next_time:4.1f} tar {name_dict[target_id]} ({deficit: 5.0f}) for {heal:4.0f}" ) finish_time = time + cast_time pending_heal = PendingHeal(target_id, heal, mana) ticks.append(time) manas.append(available_mana) heals.append(sum_net_healing) # get next time next_time = min(time + time_step, finish_time, next_deficit_time, encounter_time) dtime = next_time - time time = next_time # add mana for elapsed time if next_time - last_finish_time > 5.0: # ooc regen regen = dtime / 5 * character_data.mp5ooc else: regen = dtime / 5 * character_data.mp5 missing_mana = character_data.mana - available_mana regen = min(regen, missing_mana) # simplify mana regen to be constant, and not in 2s batches available_mana += regen regen_mana += regen # update deficits if needed if next_time >= next_deficit_time: try: deficits = next(deficits_time) next_deficit_time = next(times) except StopIteration: next_deficit_time = encounter_time + time_step ticks.append(encounter_time) heals.append(sum_net_healing) manas.append(available_mana) print() print(f" Total healing: {sum_net_healing:.0f}") print(f" Casts: {casts}") print(f" CPM: {casts / encounter_time * 60:.1f}") print(f" Total hps: {sum_net_healing / encounter_time:.1f}") print( f" Regen mana: {regen_mana:.0f} ({regen_mana / encounter_time * 5:.1f} mp5)" ) print(f" End mana: {available_mana:.0f}") if plot: fig, ax = plt.subplots(figsize=(12, 8), constrained_layout=True) ax.step(ticks, manas, where="post", color="blue") # ax.plot(ticks, manas, "+-", color="blue") ax.set_ylabel("Available mana") ax = ax.twinx() ax.step(ticks, heals, where="post", color="green") # ax.plot(heal_times, heals, "+", color="orange") ax.set_ylabel("Net healing") ax.set_axisbelow(True) ax.grid(axis="x") ax.set_xlabel("Encounter time [s]") title = f"{sum_net_healing / 1000:.1f}k healing" if spell_id: title = sd.spell_name(spell_id) + ", " + title ax.set_title(title) plt.savefig(f"{path}/{spell_id}.png") if show: plt.show() plt.close(fig) return sum_net_healing, sum_gross_healing
def analyse_spell(source, character_name, encounter=None, reduce_crits=False, spell_power=None, zandalar_buff=False, **_): if spell_power is None: spell_power = 0 log = raw.get_lines(source) # todo: fix encounter, encounter_lines, encounter_start, encounter_end = encounter_picker( log, encounter) print() print(f"Analysis for {encounter}:") print() try: with open("me.json") as fp: me = json.load(fp) talents = me["talents"] gear = me["gear"] except FileNotFoundError: talents = {"Meditation": 0, "Spiritual Guidance": 0} gear = {"3T2": False} spell_casts, spell_heals, spell_periodics, spell_absorbs = get_casts( character_name, encounter_lines) all_spells = group_spells(spell_casts, spell_heals, spell_periodics, spell_absorbs, reduce_crits=reduce_crits, neg_sp=spell_power) if "10901" in all_spells: del all_spells["10901"] total_data, spell_data = sum_spells(all_spells, talents) spells = sorted(spell_data.items(), key=lambda x: sd.spell_name(x[0], warn_on_not_found=False)) for spell_id, spell in spells: name = sd.spell_name(spell_id) casts = spell["casts"] mana = spell["mana"] net_heal = spell["net_heal"] gross_heal = spell["gross_heal"] overheal = spell["overheal"] if mana > 0: hpm = net_heal / mana else: hpm = float("inf") coef = spell["coef"] crits = spell["crits"] crit_rate = crits / casts if casts > 0 else 0 print( f"{name+':':29s} {casts:4d} {mana:4.0f} mpc {net_heal:4.0f} hpc {hpm:5.2f} hpm {coef:.3f}" f" {crit_rate:6.2%} {overheal / gross_heal:6.2%} OH") casts = total_data["casts"] crits = total_data["crits"] can_crit = total_data["can_crit"] mana = total_data["mana"] gross_heal = total_data["gross_heal"] gross_crit = total_data["gross_crit"] net_heal = total_data["net_heal"] net_crit = total_data["net_crit"] overheal = total_data["overheal"] crit_rate = crits / can_crit g_coef = total_data["coef"] c_coef = g_coef + crit_rate * total_data["crit_coef"] # reduced sp data dh = 20.0 all_spells = group_spells(spell_casts, spell_heals, spell_periodics, spell_absorbs, reduce_crits=reduce_crits, neg_sp=dh) if "10901" in all_spells: del all_spells["10901"] total_data, _ = sum_spells(all_spells, talents) dheal = net_heal - total_data["net_heal"] n_coef = dheal / dh print() print( f"{'Average:':29s} {casts:4d} {mana/casts:4.0f} mpc {net_heal/casts:4.0f} hpc " f"{net_heal/mana:5.2f} hpm {n_coef/casts:.3f} {crit_rate:.2%} {overheal/gross_heal:.2%} OH" ) print( f"Coefficient {n_coef/casts:.3f} ({g_coef/casts:.3f}) {1.0 - n_coef / g_coef:.0%} reduction" ) print() encounter_length = (encounter_end - encounter_start).total_seconds() gross_hps = gross_heal / encounter_length net_hps = net_heal / encounter_length gross_hpm = gross_heal / mana net_hpm = net_heal / mana print(f" Fight duration: {encounter_length:.1f}s") print(f" Avg cast time: {encounter_length / casts:.3f}s") print() print(f" Gross heal: {gross_heal:,.0f}") print(f" Gross crit: {gross_crit:,.0f}") print(f" Net heal: {net_heal:,.0f}") print(f" Net crit: {net_crit:,.0f}") print(f" Used mana: {mana:,.0f}") print() print(f" Heal coef: {n_coef:.2f} ({g_coef:.3f}, {c_coef:.3f})") print() print( f" HPS: {net_hps:.1f} ({gross_hps:.1f}) {1.0 - net_hps / gross_hps:.2%} reduction" ) print( f" HPM: {net_hpm:.3f} ({gross_hpm:.3f}) {1.0 - net_hpm / gross_hpm:.2%} reduction" ) print(f" VPM: {net_hpm / n_coef:.3f} ({gross_hpm / c_coef:.3f})") print() combat_regen = talents.get("Meditation", 0) * 0.05 if gear.get("3T2", False): combat_regen += 0.15 mana_per_spirit = combat_regen * (1 / 4) * (encounter_length / 2) hp_per_spirit = talents.get("Spiritual Guidance", 0) * 0.05 crit_per_int = 1 / 60 mana_per_int = 15.0 * (1.0 + talents.get("Mental Strength") * 0.02) if zandalar_buff: crit_per_int *= 1.1 mana_per_int *= 1.1 mana_per_spirit *= 1.1 hp_per_spirit *= 1.1 heal_hp = n_coef heal_mp5 = encounter_length / 5 * net_hpm heal_spirit = mana_per_spirit * net_hpm + hp_per_spirit * heal_hp heal_crit = 0.01 * net_crit heal_int = mana_per_int * net_hpm + crit_per_int * heal_crit g_heal_hp = c_coef g_heal_mp5 = (encounter_length / 5) * gross_hpm g_heal_spirit = mana_per_spirit * gross_hpm + hp_per_spirit * g_heal_hp g_heal_crit = 0.01 * gross_crit g_heal_int = mana_per_int * gross_hpm + crit_per_int * g_heal_crit print(f" Int, Spi, +hp, mp5, crit") print( f" {heal_int:5.1f}, {heal_spirit:5.1f}, {heal_hp:5.1f}, {heal_mp5:5.1f}, {heal_crit:5.1f}" ) print(f" {heal_int/heal_hp:5.4g}," f" {heal_spirit/heal_hp:5.3f}," f" 1.000," f" {heal_mp5/heal_hp:5.4g}," f" {heal_crit/heal_hp:5.4g}") print() print(f" ({g_heal_int:5.4g}," f" {g_heal_spirit:5.4g}," f" {g_heal_hp:5.4g}," f" {g_heal_mp5:5.4g}," f" {g_heal_crit / (1.0 + crit_rate):5.4g})") print(f" ({g_heal_int/g_heal_hp:5.4g}," f" {g_heal_spirit/g_heal_hp:5.4g}," f" 1.000," f" {g_heal_mp5/g_heal_hp:5.4g}," f" {g_heal_crit/g_heal_hp:5.4g})") print()
def sum_spells(spells, talents=None): total_data = dict( casts=0, gross_heal=0, overheal=0, net_heal=0, mana=0, coef=0, crit_coef=0, gross_crit=0, net_crit=0, crits=0, can_crit=0, ) spell_data = dict() for spell_id, spell in spells.items(): casts = spell["casts"] net_heal = spell["net_heal"] net_crit = spell["net_crit"] gross_heal = spell["gross_heal"] gross_crit = spell["gross_crit"] overheal = spell["overheal"] crits = spell["crits"] can_crit = spell["can_crit"] if gross_heal == 0: mana = sd.spell_mana(spell_id, talents, warn_on_not_found=False) if mana == 0: continue else: mana = sd.spell_mana(spell_id, talents) coef = sd.spell_coefficient(spell_id, warn_on_not_found=True) if "Renew" in sd.spell_name(spell_id): coef *= 5 average_net_heal = net_heal / casts if casts > 0 else net_heal crit_heal = 0.5 * can_crit * gross_heal / casts if casts > 0 else 0.5 * can_crit * gross_heal spell_data[spell_id] = dict(spell, mana=mana, net_heal=average_net_heal, coef=coef, crit_heal=crit_heal) gross_coef = casts * coef if crits == 0: net_crit = 0 else: # extrapolate crit value for non-crit casts net_crit *= casts / crits total_data["casts"] += casts total_data["net_heal"] += net_heal total_data["net_crit"] += net_crit total_data["gross_heal"] += gross_heal total_data["gross_crit"] += gross_crit total_data["overheal"] += overheal total_data["mana"] += casts * mana total_data["coef"] += gross_coef total_data["crit_coef"] += gross_coef * 0.5 * can_crit total_data["crits"] += crits total_data["can_crit"] += casts * can_crit return total_data, spell_data
def plot_casts(casts_dict, encounter=None, start=None, end=None, mark=None, anonymize=True): casts = list(casts_dict.values()) labels = list(casts_dict.keys()) most_casts = max((len(c) for c in casts)) if anonymize: labels = [anonymize_name(s) for s in labels] w = 16 if end and start: duration = (end - start).total_seconds() w = duration / 4 h = 0.6 * len(labels) fig = plt.figure(figsize=(w, h), constrained_layout=True) ax = fig.subplots(1, 1) for i in range(most_casts): even = i % 2 == 0 widths = [] lefts = [] colors = [] tags = [] for j, cast_list in enumerate(casts): color = "#99ff99" if even else "#ccff99" if i >= len(cast_list): widths.append(0) lefts.append(0) colors.append(color) else: c = cast_list[i] spell_name = sd.spell_name(c[3], warn_on_not_found=False) spell_name_parts = spell_name.split() if "[" in spell_name: spell_tag = spell_name_parts[1][:-1] color = "#b3b3b3" if even else "#cccccc" elif "(" in spell_name: spell_tag = ("".join([k[0] for k in spell_name_parts[:-2]]) + spell_name_parts[-1][:-1]) else: spell_tag = "".join([k[0] for k in spell_name_parts]) if start is None: start = c[1] target = c[4] if target in TANKS: color = "#99ccff" if even else "#b3d9ff" # color = "#ff9999" if even else "#ffb3b3" elif target == CANCEL_LABEL: color = "#ff99ff" if even else "#ffccff" target = "" elif target == "nil": target = "" if anonymize and target: target = anonymize_name(target) spell_tag += "\n" + target w = (c[2] - c[1]).total_seconds() x = (c[1] - start).total_seconds() if w == 0: # if no width, make it GCD length w = 1.5 widths.append(w) lefts.append(x) colors.append(color) tags.append((spell_tag, x + w / 2, j)) ax.barh(labels, widths, left=lefts, height=0.8, color=colors) for tag, x, y in tags: ax.text(x, y, tag, ha="center", va="center") if end: x = (end - start).total_seconds() plt.axvline(x, color="k") title = "Casts" if encounter: title += " during " + encounter plt.title(title) plt.xlabel("Fight duration [s]") plt.grid(axis="x") if mark: parts = mark.split(":") m = int(parts[0]) s = int(parts[1]) plt.axvline(60 * m + s, color="k") fig_path = "figs/casts.png" plt.savefig(fig_path) print(f"Saved casts figure to `{fig_path}`")
def main(argv=None): from src.parser import OverhealParser parser = OverhealParser(need_character=True, accept_encounter=True, accept_spell_id=True, accept_spell_power=True) parser.add_argument("-v", "--verbose") parser.add_argument("--mana", type=int) args = parser.parse_args(argv) source = args.source encounter = args.encounter spell_power = args.spell_power mana = args.mana if spell_power is None or spell_power == 0: spell_power = 800.0 if mana is None: mana = 8000.0 lines = raw.get_lines(source) # todo: fix # encounter, encounter_lines, encounter_start, encounter_end = encounter_picker(lines, encounter) data = raw.get_processed_lines(lines) events = data.all_events # encounter_time = (encounter_end - encounter_start).total_seconds() mp5 = 40.0 mp5ooc = mp5 + 160.0 character_data_nc = CharacterData(spell_power, 0.0, mp5, mp5ooc, mana) character_data_ac = CharacterData(spell_power, 1.0, mp5, mp5ooc, mana) times, _, deficits, name_dict, _ = raid_damage_taken( events, character_name=args.character_name) # encounter_time = (encounter_end - encounter_start).total_seconds() encounter = 120.0 # optimise_casts(args.character_name, times["all"], deficits, name_dict, character_data, encounter_time, spell_id=args.spell_id, verbose=args.verbose) talents = None sids = { "10917": "Flash Heal (Rank 7)", "10916": "Flash Heal (Rank 6)", "10915": "Flash Heal (Rank 5)", "9474": "Flash Heal (Rank 4)", "9473": "Flash Heal (Rank 3)", "9472": "Flash Heal (Rank 2)", "2061": "Flash Heal (Rank 1)", "2053": "Lesser Heal (Rank 3)", "10965": "Greater Heal (Rank 4)", "10964": "Greater Heal (Rank 3)", "10963": "Greater Heal (Rank 2)", "2060": "Greater Heal (Rank 1)", "6064": "Heal (Rank 4)", "6063": "Heal (Rank 3)", "2055": "Heal (Rank 2)", "2054": "Heal (Rank 1)", } sids = {} names = [] lows = [] highs = [] path = "figs/optimise" for sid in sids: strategy = SingleSpellStrategy(talents, sid) nh_nc, gh_nc = evaluate_casting_strategy( args.character_name, times["all"], deficits, name_dict, character_data_nc, encounter_time, strategy=strategy, verbose=False, show=False, plot=False, path=path, ) # nh_ac, gh_ac = evaluate_casting_strategy( # args.character_name, # times["all"], # deficits, # name_dict, # character_data_ac, # encounter_time, # spell_id=sid, # verbose=False, # show=False, # plot=False, # path=path, # ) lows.append(nh_nc) highs.append(gh_nc) names.append(strategy.name) # Add basic strategy as well strategy = CastingStrategy(talents) nh_nc, gh_nc = evaluate_casting_strategy( args.character_name, times["all"], deficits, name_dict, character_data_nc, encounter_time, strategy=strategy, verbose=False, show=False, plot=False, path=path, ) lows.append(nh_nc) highs.append(gh_nc) names.append(strategy.name) sids = list(map(lambda s: shorten_spell_name(sd.spell_name(s)), sids)) fig, ax = plt.subplots(figsize=(12, 8), constrained_layout=True) ax.bar(names, lows, color="#33cc33", label="Net heal") ax.bar(names, np.subtract(highs, lows), bottom=lows, color="#85e085", label="Overheal") ax.grid(axis="y") ax.set_axisbelow(True) ax.legend() # add labels for rect, low, high in zip(ax.patches, lows, highs): x = rect.get_x() + rect.get_width() / 2 y = low - 300 ax.text(x, y, f"{low/1000:.1f}k", ha="center", va="top") y = high + 200 ax.text(x, y, f"{high/1000:.1f}k", ha="center", va="bottom") character_data_str = str(character_data_nc) ax.set_title(f"Spam cast healing for {encounter}\n{character_data_str}") ax.set_ylabel("Net healing") ax.set_xlabel("Healing strategy") ax.tick_params(axis="x", rotation=70) plt.savefig(f"{path}/overview.png") # plt.show() plt.close(fig) print() h_strat, h_low = max(zip(names, lows), key=lambda x: x[1]) print( f" Highest healing: {h_strat} with {h_low / 1000:.1f}k healing ({h_low / encounter_time:.1f} hps)" ) print()