Esempio n. 1
0
    def test_sample_sch_rotation_correct_casts_237gcd(self):
        """ Ensure the time variable sim allows weaving after instant casts on a cursed gcd """
        my_stat_spread = CharacterStatSpread(wd=180,
                                             mainstat=5577,
                                             det=2272,
                                             crit=3802,
                                             dh=1100,
                                             speed=1674,
                                             ten=380,
                                             pie=340)
        test_char = Character(Jobs.SCH, my_stat_spread)
        mypps = SchPps()
        # in 2 minutes on a 4th gcd chain opener, youd have: 2 AF, 9 ED, 1 CS, 1 SC, 1 Diss, 4 bio, 3 R2, 42 B3
        sim_result = mypps.get_total_potency_variable_time(
            120, test_char, SampleSchRotation(), 0.1)

        # assert this is the expected gcd
        self.assertAlmostEqual(2.37, test_char.get_gcd(), places=2)

        self.assertEqual(len(sim_result.timeline[SchAction.AETHERFLOW]), 2)
        self.assertEqual(len(sim_result.timeline[SchAction.ENERGYDRAIN]), 9)
        self.assertEqual(len(sim_result.timeline[SchAction.CHAINSTRATAGEM]), 1)
        self.assertEqual(len(sim_result.timeline[SchAction.SWIFTCAST]), 2)
        self.assertEqual(len(sim_result.timeline[SchAction.DISSIPATION]), 1)
        self.assertEqual(len(sim_result.timeline[SchAction.BIOLYSIS]), 4)
        self.assertEqual(len(sim_result.timeline[SchAction.RUIN2]), 3)
        self.assertEqual(len(sim_result.timeline[SchAction.BROIL3]), 41)

        # assert expected bio timings
        self.assertListEqual(
            [round(x, 2) for x in sim_result.timeline[SchAction.BIOLYSIS]],
            [0, 29.14, 61.15, 90.69])
 def test_scholar(self):
     """ E2E scholar damage calc test """
     my_stat_spread = CharacterStatSpread(
         wd=180, mainstat=5577, det=2272, crit=3802, dh=1100, speed=2139, ten=380, pie=340)
     my_character = Character(Jobs.SCH, my_stat_spread)
     the_shitters_i_raid_with = \
         Comp({Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.WHM, Jobs.SAM, Jobs.SAM, Jobs.MCH, Jobs.BLM})
     # have to manually provide pps for testing for now
     self.assertEqual(14238.04118, my_character.calc_damage(139.71, the_shitters_i_raid_with))
Esempio n. 3
0
 def test_gcd(self):
     """ Test gcd is applying speed correctly """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     self.assertAlmostEqual(2.32, test_char.get_gcd(), places=2)
Esempio n. 4
0
 def test_base_piety(self):
     """ Test applying piety correctly """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     self.assertEqual(200, test_char.calc_piety())
Esempio n. 5
0
 def test_dot_scalar(self):
     """ Test speed is applying dot bonus correctly """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     self.assertAlmostEqual(1.069, test_char.get_dot_scalar(), places=3)
Esempio n. 6
0
def update_stats():
    """Calculates damage, mp consumption, and gcd based on input.
    Accepts and returns JSON. JSON format is as follows:
    input: {'player': Object
                {'weaponDamage': int
                'mainStat': int
                'det': int
                'crit': int
                'dh': int
                'speed': int
                'ten': int
                'pie': int}
            'job': string
            'comp': Array}
    output: {'dps': float,
             'gcd': float,
             'mp': float}
    """
    data = request.get_json()

    if not data:
        return "abort", abort(400)

    player_data = data['player']

    try:
        my_job = Jobs.create_job(data['job'])[0]
    except KeyError:
        return f"Currently {data['job']} is not supported."

    player = Character(
        my_job,
        CharacterStatSpread(wd=player_data['weaponDamage'],
                            mainstat=player_data['mainStat'],
                            det=player_data['det'],
                            crit=player_data['crit'],
                            dh=player_data['dh'],
                            speed=player_data['speed'],
                            ten=player_data['ten'],
                            pie=player_data['pie']))
    my_sch_pps = SchPps()
    potency = my_sch_pps.get_pps(player)
    try:
        comp_jobs = [Jobs.create_job(comp_job)[0] for comp_job in data['comp']]
    except KeyError:
        return "A job was not supported in that team comp."

    my_comp = Comp(comp_jobs)

    dps = round(player.calc_damage(potency, my_comp), 2)
    gcd = player.get_gcd()
    mp = round(my_sch_pps.get_mp_per_min(player), 2)
    return jsonify({"dps": dps, "gcd": gcd, "mp": mp})
Esempio n. 7
0
 def test_applies_comp_penalty(self):
     """ 3 roles, no raid buffs, should be lower than the number in test_baseline """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     # 3 roles, who even needs raid buffs
     the_shitters_i_raid_with = Comp({Jobs.PLD, Jobs.WHM, Jobs.SAM})
     # have to manually provide pps for testing for now
     self.assertEqual(19855.659200000002,
                      test_char.calc_damage(200, the_shitters_i_raid_with))
Esempio n. 8
0
 def test_total_potency(self):
     """ Test the spreadsheet port for total potency """
     my_stat_spread = CharacterStatSpread(
         wd=180, mainstat=5577, det=2272, crit=3802, dh=1100, speed=2139, ten=380, pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     mypps = SchPps()
     self.assertEqual(24326.1264, mypps.total_potency_spreadsheet_port(test_char, 0.12, 4, 0))
Esempio n. 9
0
 def test_cycle(self):
     """ Test the spreadsheet port for cycle length """
     my_stat_spread = CharacterStatSpread(
         wd=180, mainstat=5577, det=2272, crit=3802, dh=1100, speed=2139, ten=380, pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     mypps = SchPps()
     self.assertEqual(174.12, mypps.get_cycle(test_char, 0.12))
Esempio n. 10
0
 def test_mp(self):
     """ Test the spreadsheet port for mp generation """
     my_stat_spread = CharacterStatSpread(
         wd=180, mainstat=5577, det=2272, crit=3802, dh=1100, speed=2139, ten=380, pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     mypps = SchPps()
     self.assertEqual(508.0952931652587,
                      mypps.get_mp_per_min(test_char, caster_tax=0.1, succ=0, adlo=0, energy_drain=4, rez=0), )
Esempio n. 11
0
 def test_applies_crit_dh_raid_bonuses(self):
     """ 5 roles, sch and brd for raid buffs, should be higher than test_baseline """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     # DNC not included to isolate confounding variable (tech step)
     the_shitters_i_raid_with = Comp(
         {Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.SAM, Jobs.BRD, Jobs.BLM})
     # have to manually provide pps for testing for now
     self.assertEqual(20685.98776726701,
                      test_char.calc_damage(200, the_shitters_i_raid_with))
Esempio n. 12
0
 def test_multiple_raidbuff(self):
     """ Test multiple raid buffs are applying damage correctly """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     the_shitters_i_raid_with = Comp({
         Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.AST, Jobs.NIN, Jobs.SAM,
         Jobs.MCH, Jobs.BLM
     })
     # have to manually provide pps for testing for now
     self.assertEqual(20790.90666410063,
                      test_char.calc_damage(200, the_shitters_i_raid_with))
Esempio n. 13
0
 def test_embolden_has_no_effect(self):
     """ Test embolden isn't mistakenly applying damage """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     the_shitters_i_raid_with = Comp({
         Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.WHM, Jobs.SAM, Jobs.SAM,
         Jobs.MCH, Jobs.RDM
     })
     # have to manually provide pps for testing for now
     self.assertEqual(20381.368540000003,
                      test_char.calc_damage(200, the_shitters_i_raid_with))
Esempio n. 14
0
 def test_baseline(self):
     """ Baseline test: 5 roles, no raid buffs """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     the_shitters_i_raid_with = Comp({
         Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.WHM, Jobs.SAM, Jobs.SAM,
         Jobs.MCH, Jobs.BLM
     })
     # have to manually provide pps for testing for now
     self.assertEqual(20381.368540000003,
                      test_char.calc_damage(200, the_shitters_i_raid_with))
Esempio n. 15
0
 def test_single_raidbuff(self):
     """ Test raid buff is applying damage correctly """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     test_comp = Comp({
         Jobs.PLD, Jobs.WAR, Jobs.SCH, Jobs.AST, Jobs.SAM, Jobs.SAM,
         Jobs.MCH, Jobs.BLM
     })
     # have to manually provide pps for testing for now
     self.assertAlmostEqual(20534.2288,
                            test_char.calc_damage(200, test_comp),
                            places=3)
Esempio n. 16
0
 def test_variable_time_sim_allows_weaves(self):
     """ Ensure the time variable sim allows weaving after instant casts """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     mypps = SchPps()
     # If the sim properly supports weaving, this should be 4 full gcds
     fixed_rotation = [
         SchAction.BIOLYSIS, SchAction.AETHERFLOW, SchAction.ENERGYDRAIN,
         SchAction.BROIL3, SchAction.RUIN2, SchAction.ENERGYDRAIN,
         SchAction.SWIFTCAST, SchAction.BROIL3, SchAction.ENERGYDRAIN
     ]
     sim_result = mypps.get_total_potency_variable_time(
         test_char.get_gcd() * 4, test_char,
         FixedSchRotation(fixed_rotation, SchAction.BROIL3), 0.1)
     # 3 ED, 2 B3, 1 R2
     self.assertEqual(1080, sim_result.get_non_dot_potency())
Esempio n. 17
0
 def test_variable_time_sim_notes_af_overspending(self):
     """ Ensure the time variable sim allows weaving after instant casts """
     my_stat_spread = CharacterStatSpread(wd=180,
                                          mainstat=5577,
                                          det=2272,
                                          crit=3802,
                                          dh=1100,
                                          speed=2139,
                                          ten=380,
                                          pie=340)
     test_char = Character(Jobs.SCH, my_stat_spread)
     mypps = SchPps()
     # Burn an aetherflow we don't have
     fixed_rotation = [SchAction.ENERGYDRAIN]
     sim_result = mypps.get_total_potency_variable_time(
         1, test_char, FixedSchRotation(fixed_rotation, SchAction.BROIL3),
         0.1)
     # 3 ED, 2 B3, 1 R2
     self.assertIn(SchSimNotice.SCH_SIM_AETHERFLOW_OVERSPENDING,
                   sim_result.notices)
Esempio n. 18
0
def etro_main():
    """Calculates damage, given a gearsetID from Etro. Should check database to see if gearset exist first.
    JSON format:
    'job': String,
    'comp': Array,
    'etro_id': String,
    """

    data = request.get_json()

    # Check comp first before making request
    if not data:
        return "abort", abort(400)
    try:
        comp_jobs = [Jobs.create_job(comp_job)[0] for comp_job in data['comp']]
    except KeyError:
        return "An unsupported job was found in the team composition."

    my_comp = Comp(comp_jobs)

    player_job_data = Jobs.create_job(data['job'])

    # TODO: Check internal database to see if cached before request

    try:
        etro_data = requests.get("/".join(
            ["https://etro.gg/api/gearsets", data["etro_id"]])).json()
    except requests.exceptions.RequestException:
        return "There was an error making the etro request"

    etro_stats = etro_data["totalParams"]
    eq_stats = {stat["name"]: stat["value"] for stat in etro_stats}

    # For Etro sets that are non-tanks
    if "TEN" not in eq_stats:
        eq_stats["TEN"] = 0

    # Making speed job-agnostic
    if "SPS" in eq_stats:
        eq_stats["SPEED"] = eq_stats["SPS"]
    else:
        eq_stats["SPEED"] = eq_stats["SKS"]

    player = Character(
        player_job_data[0],
        CharacterStatSpread(wd=eq_stats["Weapon Damage"],
                            mainstat=eq_stats[player_job_data[1]],
                            det=eq_stats["DET"],
                            crit=eq_stats["CRT"],
                            dh=eq_stats["DH"],
                            speed=eq_stats["SPEED"],
                            ten=eq_stats["TEN"],
                            pie=eq_stats["PIE"]))

    my_sch_pps = SchPps()
    potency = my_sch_pps.get_pps(player)

    dps = round(player.calc_damage(potency, my_comp), 2)
    gcd = player.get_gcd()
    mp = round(my_sch_pps.get_mp_per_min(player), 2)
    return jsonify({"dps": dps, "gcd": gcd, "mp": mp})
Esempio n. 19
0
def update_stats():
    """Calculates damage, mp consumption, and gcd based on input.
    Accepts and returns JSON. JSON format is as follows:
    input: {'player': Object
                {'weaponDamage': int
                'mainStat': int
                'det': int
                'crit': int
                'dh': int
                'speed': int
                'ten': int
                'pie': int}
            'job': string
            'comp': Array
            'rotation': Object
                {'adloquium': int
                 'energyDrain': int
                 'raise': int
                 'succor': int}}
    output: {'dps': float,
             'gcd': float,
             'mp': float}
    """
    data = request.get_json()

    if not data:
        return "abort", abort(400)

    player_data = data['player']

    try:
        my_job = Jobs.create_job(data['job'])[0]
    except KeyError:
        return f"Currently {data['job']} is not supported."

    player = Character(
        my_job,
        CharacterStatSpread(wd=player_data['weaponDamage'],
                            mainstat=player_data['mainStat'],
                            det=player_data['det'],
                            crit=player_data['crit'],
                            dh=player_data['dh'],
                            speed=player_data['speed'],
                            ten=player_data['ten'],
                            pie=player_data['pie']))

    try:
        rotation_data = data['rotation']
        energy_drain_per_min = rotation_data.get('energyDrain', 4)
        adloquium_per_min = rotation_data.get('adloquium', 0)
        raise_per_min = rotation_data.get('raise', 0)
        succor_per_min = rotation_data.get('succor', 0)
    except KeyError:
        # Rotation data was not present in request, use defaults
        energy_drain_per_min, adloquium_per_min, raise_per_min, succor_per_min = 4, 0, 0, 0

    my_sch_pps = SchPps()
    potency = my_sch_pps.get_pps(player,
                                 num_ed_per_min=energy_drain_per_min,
                                 num_filler_casts=adloquium_per_min +
                                 raise_per_min + succor_per_min)
    try:
        comp_jobs = [Jobs.create_job(comp_job)[0] for comp_job in data['comp']]
    except KeyError:
        return "A job was not supported in that team comp."

    dps = round(player.calc_damage(potency, Comp(comp_jobs)), 2)
    gcd = player.get_gcd()
    mp = round(
        my_sch_pps.get_mp_per_min(player,
                                  succ=succor_per_min,
                                  adlo=adloquium_per_min,
                                  energy_drain=energy_drain_per_min,
                                  rez=raise_per_min), 2)
    return jsonify({"dps": dps, "gcd": gcd, "mp": mp})
Esempio n. 20
0
    def get_total_potency_variable_time(
        self,
        sim_length: ElapsedTime,
        character_stats: Character,
        rotation: SchRotation,
        caster_tax: float = 0.1,
        ping: ElapsedTime = 0
    ) -> SchSimResults:  # pylint: disable=too-many-arguments, too-many-locals
        """
        Get the total potency in sim_length seconds
        rotation is a SchRotation object
        todo: support dot damage calculation (will be implemented in a separate method
        todo: how long is animation lock actually?  Hard coded here for 0.75, have no idea if that's true
        There are some nuances to how this works:
            At present, this sim does not calculate dot damage
            If an action with an active cooldown is selected, the time jumps forward to the cooldown's expiry
            This simulator does not enforce resource requirements - ED can be used even with no aetherflow
        """
        # approximate animation lock is on average 600 ms + average 40 ms server latency + ping
        animation_lock = 0.64 + ping

        timeline: SchSimTimeline = defaultdict(lambda: [])
        notices: set[SchSimNotice] = set()
        current_time: ElapsedTime = 0

        short_gcd = character_stats.get_gcd()

        next_active: defaultdict[SchAction,
                                 ElapsedTime] = defaultdict(lambda: 0)
        active_effects: defaultdict[SchEffect,
                                    ElapsedTime] = defaultdict(lambda: -1)
        resources: defaultdict[SchResource, int] = defaultdict(lambda: 0)

        while current_time <= sim_length:
            # get the selected action
            selected_action = rotation.get_action(
                current_time,
                {key: next_active[key] - current_time
                 for key in SchAction}, {
                     key: active_effects[key] - current_time
                     for key in active_effects
                 }, resources, short_gcd, ping)

            # find the actual cast time of the selected action (not including caster tax)
            cast_time = character_stats.get_cast_time(
                selected_action.cast_time)

            # if swiftcast is up and this is a casted spell, eat swiftcast and reduce the cast time to 0
            if active_effects[
                    SchEffect.SWIFTCAST] > current_time and cast_time > 0:
                active_effects[SchEffect.SWIFTCAST] = -1
                cast_time = 0

            # advance the time to when the action is actually available
            current_time = max(current_time, next_active[selected_action])

            # clip the cast if the sim ends before then
            if current_time + cast_time <= sim_length:
                timeline[selected_action].append(current_time)

            # apply cooldowns
            if selected_action.is_gcd:
                for action in SchAction:
                    if action.is_gcd:
                        next_active[action] = current_time + short_gcd
                # For Sch specifically, this is invariably equal to short_gcd, but sonic break exists
                next_active[
                    selected_action] = current_time + character_stats.get_cast_time(
                        selected_action.cooldown)
            else:
                next_active[
                    selected_action] = current_time + selected_action.cooldown

            # apply resource and effect timers as needed
            if selected_action in [
                    SchAction.AETHERFLOW, SchAction.DISSIPATION
            ]:
                resources[SchResource.AETHERFLOW] = 3
            elif selected_action == SchAction.ENERGYDRAIN:
                resources[SchResource.AETHERFLOW] -= 1
                if resources[SchResource.AETHERFLOW] < 0:
                    notices.add(SchSimNotice.SCH_SIM_AETHERFLOW_OVERSPENDING)
            elif selected_action == SchAction.SWIFTCAST:
                active_effects[SchEffect.SWIFTCAST] = current_time + 10
            elif selected_action == SchAction.BIOLYSIS:
                active_effects[SchEffect.BIOLYSIS] = current_time + 30

            # advance time based on cast time or animation lock as needed
            current_time += max(cast_time + caster_tax, animation_lock)

        return SchSimResults(timeline=timeline, notices=notices)