예제 #1
0
def group_lines_for_spell(spell_id, lines, spell_powers):
    total_heals = []
    total_overheals = []
    total_underheals = []

    count_heals = []

    nn_underheals = []
    nn_overheals = []
    nn_full_overheals = []

    coefficient = sd.spell_coefficient(spell_id)
    base_heal = sd.spell_heal(spell_id)

    for i, sp in enumerate(spell_powers):
        data = process_lines_for_spell(lines, -sp * coefficient, base_heal)
        n_h, n_oh, n_f_oh, n_oh_nc, total_h, total_oh, total_h_nc, total_oh_nc = data

        total_heals.append(total_h / n_h)
        total_overheals.append(total_oh / n_h)
        total_underheals.append((total_h - total_oh) / n_h)

        count_heals.append(n_h)

        nn_underheals.append((n_h - n_oh) / n_h)
        nn_overheals.append(n_oh / n_h)
        nn_full_overheals.append(n_f_oh / n_h)

    return (total_heals, total_overheals, total_underheals, count_heals,
            nn_underheals, nn_overheals, nn_full_overheals)
예제 #2
0
def process_spell(spell_id, spell_lines, spell_power):
    base_heal = sd.spell_heal(spell_id)
    coeff = sd.spell_coefficient(spell_id)

    if base_heal == 0 or coeff == 0:
        return

    extra_heal = coeff * spell_power
    base_heal_fraction = base_heal / (base_heal + extra_heal)

    n_heal = 0
    n_underheal = 0
    n_overheal = 0
    n_downrank = 0
    n_drop_h = 0

    for h, oh, crit in spell_lines:
        h_fraction = 1.0 - oh / h

        n_heal += 1

        if h_fraction >= 1:
            n_underheal += 1
        elif h_fraction <= 0:
            n_overheal += 1
        elif h_fraction < base_heal_fraction:
            n_downrank += 1
        else:
            n_drop_h += 1

    return n_heal, n_underheal, n_overheal, n_downrank, n_drop_h
예제 #3
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()
예제 #4
0
def aggregate_lines(grouped_lines, spell_power=0.0):
    """Aggregates and evaluates grouped lines"""
    # heals, any OH, half OH, full OH, aH, aOH
    total_data = np.zeros(6)
    data_list = []

    for spell_id, spell_data in grouped_lines.items():
        # heals, any OH, half OH, full OH, aH, aOH
        data = np.zeros(6)
        data[0] = len(spell_data)

        # Fail more gracefully if we are missing a coefficient
        coefficient = sd.spell_coefficient(spell_id)

        for h, oh, crit in spell_data:
            dh = coefficient * spell_power

            if crit:
                # scale spell power differential by 1.5 if spell was a crit
                dh *= 1.5

            h = h - dh
            oh = oh - dh

            if h < 0.0:
                # could happen for heals on healing reduced players, we just ignore these for now
                continue

            if oh < 0.0:
                oh = 0.0

            # if crit:
            #     print(f"* h: {h:.1f}  oh: {oh:.1f} *")
            # else:
            #     print(f"  h: {h:.1f}  oh: {oh:.1f}")

            if oh == h:
                data[3] += 1
                data[2] += 1
                data[1] += 1
            elif oh >= 0.5 * h:
                data[2] += 1
                data[1] += 1
            elif oh > 0.0:
                data[1] += 1

            data[4] += h
            data[5] += oh

        data_list.append((spell_id, data))
        total_data += data

    return total_data, data_list
예제 #5
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
예제 #6
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
예제 #7
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
예제 #8
0
def spell_overheal_probability(player_name, spell_id, lines, spell_power=None):
    """Plots overheal probability of each spell"""

    if spell_power is None:
        sp_neg = 400.0
        sp_shift = 0.0
        sp_extrap = 200.0
    else:
        sp_neg = spell_power
        sp_shift = spell_power
        sp_extrap = 1000.0 - spell_power

        if sp_extrap < 0:
            sp_extrap = 1500.0 - spell_power

    spell_powers = np.linspace(0, -sp_neg, int(sp_neg / 1) + 1)
    n_heals = []
    n_overheals = []
    n_overheals_nc = []

    # Fail more gracefully if we are missing a coefficient
    coefficient = sd.spell_coefficient(spell_id)
    if coefficient == 0:
        return

    for sp in spell_powers:
        n_h = 0
        n_oh = 0
        n_oh_nc = 0

        for h, oh, crit in lines:
            dh = coefficient * -sp
            dh_c = dh

            oh_nc = oh

            if crit:
                # scale spell power differential by 1.5 if spell was a crit
                dh_c *= 1.5

                # Scale oh down
                oh_nc = oh - (h - h / 1.5)

            # remove spell power contribution
            h -= dh_c
            oh -= dh_c
            oh_nc -= dh

            if h < 0.0:
                # could happen for heals on healing reduced players, we just ignore these for now
                continue

            n_h += 1
            # n_h_nc += not_crit

            if oh > 0.0:
                n_oh += 1

            if oh_nc > 0.0:
                n_oh_nc += 1

        n_heals.append(n_h)
        n_overheals.append(n_oh)

        n_overheals_nc.append(n_oh_nc)

    # plot probabilities
    plot_oh_prob(
        player_name,
        spell_id,
        spell_powers,
        sp_extrap,
        sp_shift,
        n_heals,
        n_overheals,
        n_overheals_nc,
    )
예제 #9
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)
예제 #10
0
def group_spells(spell_casts,
                 spell_heals,
                 spell_periodics,
                 spell_absorbs,
                 reduce_crits=False,
                 neg_sp=0.0):
    spell_dict = {}

    for _, spell_id in spell_casts:
        if spell_id not in spell_dict:
            spell_dict[spell_id] = dict(casts=0,
                                        heals=0,
                                        net_heal=0,
                                        gross_heal=0,
                                        overheal=0,
                                        crits=0,
                                        gross_crit=0,
                                        net_crit=0,
                                        can_crit=0)

        spell_dict[spell_id]["casts"] += 1

    for spell_id, th, oh, is_crit, can_crit in spell_heals + spell_periodics + spell_absorbs:
        if spell_id == "27805":
            # holy nova pairs with different cast id
            spell_id = "27801"

        if spell_id not in spell_dict:
            # heal but no cast, maybe pre-casted?
            spell_dict[spell_id] = dict(casts=0,
                                        heals=0,
                                        net_heal=0,
                                        gross_heal=0,
                                        overheal=0,
                                        crits=0,
                                        gross_crit=0,
                                        net_crit=0,
                                        can_crit=0)

        spell = spell_dict[spell_id]
        spell["heals"] += 1

        dhp = neg_sp * sd.spell_coefficient(spell_id)

        if can_crit:
            spell["can_crit"] = 1

        if is_crit:
            spell["crits"] += 1

            if reduce_crits:
                crit_heal = th / 3
                th -= crit_heal
                oh = max(0.0, oh - crit_heal)
            else:
                dhp *= 1.5

        th = max(0.0, th - dhp)
        oh = max(0.0, oh - dhp)

        if is_crit:
            g_crit = th / 3
            n_crit = max(0.0, g_crit - oh)
        else:
            g_crit = th * 0.5
            n_crit = 0

        spell["gross_heal"] += th
        spell["overheal"] += oh
        spell["net_heal"] += th - oh
        spell["gross_crit"] += g_crit
        spell["net_crit"] += n_crit

    return spell_dict
예제 #11
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