Exemplo 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])
Exemplo n.º 2
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)
Exemplo n.º 3
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})
Exemplo n.º 4
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())
Exemplo n.º 5
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})
Exemplo 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
            '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})
Exemplo n.º 7
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)