예제 #1
0
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()
예제 #2
0
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}")
예제 #3
0
    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
예제 #4
0
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
예제 #5
0
    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
예제 #6
0
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)
예제 #7
0
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}`")
예제 #8
0
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}")
예제 #9
0
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()
예제 #10
0
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()
예제 #11
0
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)
예제 #12
0
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
예제 #13
0
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()
예제 #14
0
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
예제 #15
0
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}`")
예제 #16
0
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()