From 9a4d78134b6c5663c1e45c63daf4e6ad8f504490 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 22 Aug 2014 09:46:23 -0400 Subject: [PATCH 001/265] Added DfA for combat and assassination DfA shows as dps loss for combat and minor increase for assassination, not totally implausible but should be checked --- scripts/assassination.py | 10 +- scripts/combat.py | 16 +-- shadowcraft/calcs/rogue/Aldriana/__init__.py | 112 +++++++++++++++++-- shadowcraft/calcs/rogue/Aldriana/settings.py | 5 +- shadowcraft/calcs/rogue/__init__.py | 37 +++++- 5 files changed, 152 insertions(+), 28 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 0c805ce..3a7c993 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -66,7 +66,7 @@ multistrike=121,) # Initialize talents.. -test_talents = talents.Talents('122213', test_class, test_level) +test_talents = talents.Talents('3322133', test_class, test_level) # Set up glyphs. glyph_list = ['recuperate', 'sprint', 'vendetta'] #just to have something @@ -90,7 +90,7 @@ execute_total = sum(entry[1] for entry in execute_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep() +#ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) @@ -106,7 +106,7 @@ ] # trinkets_ep_value = calculator.get_upgrades_ep_fast(trinkets_list) -talent_ranks = calculator.get_talents_ranking() +#talent_ranks = calculator.get_talents_ranking() def max_length(dict_list): max_len = 0 @@ -132,13 +132,13 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, + #ep_values, #tier_ep_values, #mh_enchants_and_dps_ep_values, #oh_enchants_and_dps_ep_values, #trinkets_ep_value, #glyph_values, - talent_ranks, + #talent_ranks, ] pretty_print(dicts_for_pretty_print) diff --git a/scripts/combat.py b/scripts/combat.py index 007876a..2550083 100644 --- a/scripts/combat.py +++ b/scripts/combat.py @@ -66,14 +66,14 @@ multistrike=121,) # Initialize talents.. -test_talents = talents.Talents('332213', test_class, test_level) +test_talents = talents.Talents('3322133', test_class, test_level) # Set up glyphs. glyph_list = ['energy', 'disappearance'] test_glyphs = glyphs.Glyphs(test_class, *glyph_list) # Set up settings. -test_cycle = settings.CombatCycle(revealing_strike_pooling=True, blade_flurry=False) +test_cycle = settings.CombatCycle(revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=True) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=False, latency=.03, merge_damage=True, use_opener='always', opener_name='ambush', num_boss_adds=0.0, adv_params="", potion=True, prepot=True) # 0.2 = 20% of the fight is an add present @@ -86,8 +86,8 @@ total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) -tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) +#ep_values = calculator.get_ep(baseline_dps=total_dps) +#tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) trinkets_list = { @@ -104,7 +104,7 @@ # Compute weapon type modifier. #weapon_type_mod = calculator.get_oh_weapon_modifier() -talent_ranks = calculator.get_talents_ranking() +#talent_ranks = calculator.get_talents_ranking() def max_length(dict_list): max_len = 0 @@ -130,13 +130,13 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, - tier_ep_values, + #ep_values, + #tier_ep_values, #mh_enchants_and_dps_ep_values, #oh_enchants_and_dps_ep_values, #trinkets_ep_value, #glyph_values, - talent_ranks, + #talent_ranks, ] pretty_print(dicts_for_pretty_print) pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c155bb9..041870c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2,6 +2,7 @@ import gettext import __builtin__ import math +from operator import add __builtin__._ = gettext.gettext @@ -204,9 +205,13 @@ def get_crit_rates(self, stats): elif self.settings.dmg_poison == 'sp': poisons = ('swift_poison', ) + talent_attacks = () + if self.talents.death_from_above: + talent_attacks = ('death_from_above', 'death_from_above_strike', 'death_from_above_pulse') + openers = tuple([self.settings.opener_name]) - for attack in spec_attacks + poisons + openers: + for attack in spec_attacks + poisons + openers + talent_attacks: if attack is None: pass crit_rates[attack] = base_melee_crit_rate @@ -391,6 +396,12 @@ def update_with_autoattack_passives(self, attacks_per_second, *args, **kwargs): if self.swing_reset_spacing is not None: attacks_per_second['mh_autoattacks'] *= (1 - max((1 - .5 * self.stats.mh.speed / kwargs['attack_speed_multiplier']), 0) / self.swing_reset_spacing) attacks_per_second['oh_autoattacks'] *= (1 - max((1 - .5 * self.stats.oh.speed / kwargs['attack_speed_multiplier']), 0) / self.swing_reset_spacing) + + #Account for dfa auto attack loss--2 seconds is an approximation + if self.talents.death_from_above: + attacks_per_second['mh_autoattacks'] = (20*attacks_per_second['mh_autoattacks']-math.floor(2*attacks_per_second['mh_autoattacks']))/20 + attacks_per_second['oh_autoattacks'] = (20*attacks_per_second['oh_autoattacks']-math.floor(2*attacks_per_second['mh_autoattacks']))/20 + if not args or 'autoattack_hits' in args: attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance @@ -563,7 +574,10 @@ def get_poison_counts(self, attacks_per_second): if self.settings.is_assassination_rogue(): poison_base_proc_rate += .2 poison_envenom_proc_rate = poison_base_proc_rate + .3 - envenom_uptime = min(sum([(1 + cps + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) * attacks_per_second['envenom'][cps] for cps in xrange(1, 6)]), 1) + aps_envenom = attacks_per_second['envenom'] + if self.talents.death_from_above: + aps_envenom = map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom']) + envenom_uptime = min(sum([(1 + cps + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) * aps_envenom[cps] for cps in xrange(1, 6)]), 1) avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime else: avg_poison_proc_rate = poison_base_proc_rate @@ -1005,14 +1019,35 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra energy_for_rupture = cpg_per_rupture * cpg_energy_cost + self.get_spell_stats('rupture', cost_mod=ability_cost_modifier)[0] energy_for_rupture -= avg_rupture_size * self.relentless_strikes_energy_return_per_cp - energy_for_envenoms = energy_per_cycle - energy_for_rupture + + energy_for_dfa = 0 + if self.talents.death_from_above: + #dfa_gap probably should be handled more accurately especially in the non-anticipation case + dfa_gap = 0 + .5 * (.5 * self.settings.response_time) + dfa_interval = 1./(self.get_spell_cd('death_from_above') + dfa_gap) + energy_for_dfa = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('death_from_above', cost_mod=ability_cost_modifier)[0] + energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + + attacks_per_second['death_from_above'] = dfa_interval + attacks_per_second['death_from_above_strike'] = [finisher_chance * dfa_interval for finisher_chance in envenom_size_breakdown] + attacks_per_second['death_from_above_pulse'] = [finisher_chance * dfa_interval for finisher_chance in envenom_size_breakdown] + + #Normalize DfA energy intervals to rupture intervals + energy_for_dfa *= (avg_cycle_length)/(1./dfa_interval) + + + energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa envenom_energy_cost = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('envenom', cost_mod=ability_cost_modifier)[0] envenom_energy_cost -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost envenoms_per_second = envenoms_per_cycle / avg_cycle_length - cpgs_per_second = envenoms_per_second * cpg_per_finisher + attacks_per_second['rupture'] * cpg_per_rupture + finishers_per_second = envenoms_per_second + attacks_per_second['rupture'] + if self.talents.death_from_above: + finishers_per_second += attacks_per_second['death_from_above'] + cpgs_per_second = cpg_per_finisher * finishers_per_second + if cpg in attacks_per_second: attacks_per_second[cpg] += cpgs_per_second else: @@ -1051,6 +1086,9 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra self.update_with_autoattack_passives(attacks_per_second, attack_speed_multiplier=attack_speed_multiplier) + for i in attacks_per_second: + print i, attacks_per_second[i] + print "--------------------" return attacks_per_second, crit_rates def assassination_attack_counts_anticipation(self, current_stats, cpg, crit_rates=None): @@ -1123,15 +1161,33 @@ def assassination_attack_counts_anticipation(self, current_stats, cpg, crit_rate cpg_per_finisher = cp_per_finisher / avg_cp_per_cpg energy_for_rupture = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('rupture', cost_mod=ability_cost_modifier)[0] - energy_for_rupture -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp - energy_for_envenoms = energy_per_cycle - energy_for_rupture + energy_for_rupture -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + + energy_for_dfa = 0 + if self.talents.death_from_above: + dfa_gap = 0 + .5 * (.5 * self.settings.response_time) + dfa_interval = 1./(self.get_spell_cd('death_from_above') + dfa_gap) + energy_for_dfa = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('death_from_above', cost_mod=ability_cost_modifier)[0] + energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + + attacks_per_second['death_from_above'] = dfa_interval + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, dfa_interval] + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval] + + #Normalize DfA energy intervals to rupture intervals + energy_for_dfa *= (avg_cycle_length)/(1./dfa_interval) + + energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa envenom_energy_cost = cpg_per_finisher * cpg_energy_cost + self.get_spell_stats('envenom', cost_mod=ability_cost_modifier)[0] envenom_energy_cost -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost envenoms_per_second = envenoms_per_cycle / avg_cycle_length - cpgs_per_second = envenoms_per_second * cpg_per_finisher + attacks_per_second['rupture'] * cpg_per_finisher + finishers_per_second = envenoms_per_second + attacks_per_second['rupture'] + if self.talents.death_from_above: + finishers_per_second += attacks_per_second['death_from_above'] + cpgs_per_second = cpg_per_finisher * finishers_per_second if cpg in attacks_per_second: attacks_per_second[cpg] += cpgs_per_second else: @@ -1165,6 +1221,7 @@ def assassination_attack_counts_anticipation(self, current_stats, cpg, crit_rate self.update_with_autoattack_passives(attacks_per_second, attack_speed_multiplier=attack_speed_multiplier) + #print attacks_per_second return attacks_per_second, crit_rates @@ -1336,6 +1393,12 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): if self.swing_reset_spacing is not None: attacks_per_second['mh_autoattacks'] *= (1 - max((1 - .5 * self.stats.mh.speed / self.attack_speed_increase), 0) / self.swing_reset_spacing) attacks_per_second['oh_autoattacks'] *= (1 - max((1 - .5 * self.stats.oh.speed / self.attack_speed_increase), 0) / self.swing_reset_spacing) + + #Account for dfa auto attack loss--2 seconds is an approximation + if self.talents.death_from_above and (self.settings.cycle.dfa_during_ar or not ar): + attacks_per_second['mh_autoattacks'] = (20*attacks_per_second['mh_autoattacks']-math.floor(2*attacks_per_second['mh_autoattacks']))/20 + attacks_per_second['oh_autoattacks'] = (20*attacks_per_second['oh_autoattacks']-math.floor(2*attacks_per_second['mh_autoattacks']))/20 + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance attacks_per_second['main_gauche'] = attacks_per_second['mh_autoattack_hits'] * main_gauche_proc_rate @@ -1383,9 +1446,25 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): if self.talents.marked_for_death: energy_regen -= 10. / marked_for_death_cd energy_regen -= revealing_strike_energy_cost / rvs_interval - + + energy_for_dfa = 0 + if self.talents.death_from_above and (self.settings.cycle.dfa_during_ar or not ar): + #dfa_gap probably should be handled more accurately especially in the non-anticipation case + dfa_gap = 0 + .5 * (.5 * self.settings.response_time) + dfa_interval = 1./(self.get_spell_cd('death_from_above') + dfa_gap) + energy_for_dfa = ss_per_finisher * sinister_strike_energy_cost + self.get_spell_stats('death_from_above', cost_mod=cost_modifier)[0] + energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + energy_for_dfa *= dfa_interval + + attacks_per_second['death_from_above'] = dfa_interval + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, dfa_interval] + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval] + #Base CPGs attacks_per_second['sinister_strike_base'] = ss_per_snd / snd_duration + if self.talents.death_from_above and (self.settings.cycle.dfa_during_ar or not ar): + attacks_per_second['sinister_strike_base'] += ss_per_finisher / (1/dfa_interval) + attacks_per_second['revealing_strike'] = 1. / rvs_interval extra_finishers_per_second = attacks_per_second['revealing_strike'] / (5/cp_per_cpg) #Scaling CPGs @@ -1393,12 +1472,16 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): free_gcd -= 1./snd_duration + (attacks_per_second['sinister_strike_base'] + attacks_per_second['revealing_strike'] + extra_finishers_per_second) if self.talents.marked_for_death: free_gcd -= (1. / marked_for_death_cd) - energy_available_for_evis = energy_regen - energy_spent_on_snd + #2 seconds is an approximation of GCD loss while in air + if self.talents.death_from_above and (self.settings.cycle.dfa_during_ar or not ar): + free_gcd -= dfa_interval * (1 + (2 / gcd_size)) + energy_available_for_evis = energy_regen - energy_spent_on_snd - energy_for_dfa total_evis_per_second = energy_available_for_evis / total_eviscerate_cost evisc_actions_per_second = (total_evis_per_second * ss_per_finisher + total_evis_per_second) attacks_per_second['sinister_strike'] = total_evis_per_second * ss_per_finisher # If GCD capped if evisc_actions_per_second > free_gcd: + print "GCD LOCK" gcd_cap_mod = evisc_actions_per_second / free_gcd wasted_energy = (attacks_per_second['sinister_strike'] - attacks_per_second['sinister_strike'] / gcd_cap_mod) / sinister_strike_energy_cost attacks_per_second['sinister_strike'] = attacks_per_second['sinister_strike'] / gcd_cap_mod @@ -1439,13 +1522,16 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): self.get_poison_counts(attacks_per_second) - #print attacks_per_second + #print attacks_per_second return attacks_per_second, crit_rates def rb_actual_cds(self, attacks_per_second, base_cds, avg_rb_effect=10): final_cds = {} # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster offensive_finisher_rate = attacks_per_second['eviscerate'][5] + if self.talents.death_from_above: + offensive_finisher_rate += attacks_per_second['death_from_above'] + #should never happen, catch error just in case if offensive_finisher_rate != 0: for cd_name in base_cds: @@ -1457,6 +1543,9 @@ def rb_actual_cd(self, attacks_per_second, base_cd, avg_rb_effect=10): final_cd = base_cd # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster offensive_finisher_rate = attacks_per_second['eviscerate'][5] + if self.talents.death_from_above and self.settings.cycle.dfa_during_ar: + offensive_finisher_rate += attacks_per_second['death_from_above'] + #should never happen, catch error just in case if offensive_finisher_rate != 0: return base_cd * (1 - avg_rb_effect / (1. / offensive_finisher_rate + avg_rb_effect)) @@ -1464,6 +1553,9 @@ def rb_actual_cd(self, attacks_per_second, base_cd, avg_rb_effect=10): def rb_cd_modifier(self, attacks_per_second, avg_rb_effect=10): # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster offensive_finisher_rate = attacks_per_second['eviscerate'][5] + if self.talents.death_from_above and self.settings.cycle.dfa_during_ar: + offensive_finisher_rate += attacks_per_second['death_from_above'] + if offensive_finisher_rate != 0: #should never happen, catch divide-by-zero error just in case return (1 - avg_rb_effect / (1. / offensive_finisher_rate + avg_rb_effect)) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 45acaa3..76a30dd 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -115,10 +115,11 @@ def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5, p class CombatCycle(Cycle): _cycle_type = 'combat' - def __init__(self, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=False, ): + def __init__(self, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=False): self.blade_flurry = bool(blade_flurry) self.ksp_immediately = bool(ksp_immediately) # Determines whether to KSp the instant it comes off cool or wait until Bandit's Guile stacks up. self.revealing_strike_pooling = bool(revealing_strike_pooling) + self.dfa_during_ar = bool(dfa_during_ar) class SubtletyCycle(Cycle): _cycle_type = 'subtlety' @@ -128,4 +129,4 @@ def __init__(self, raid_crits_per_second, use_hemorrhage='24'): self.use_hemorrhage = use_hemorrhage # Allowed values are 'always' (main CP generator), #'never' (default to backstab), # or a number denoting the interval in seconds between applications - \ No newline at end of file + diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 36aa4ee..7e300b9 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -20,9 +20,9 @@ class RogueDamageCalculator(DamageCalculator): 'eviscerate', 'envenom', 'ambush', 'garrote', 'sinister_strike', 'revealing_strike', 'main_gauche', 'mh_killing_spree', 'oh_killing_spree', 'backstab', 'hemorrhage', - 'mutilate', 'mh_mutilate', 'oh_mutilate', 'dispatch'] + 'mutilate', 'mh_mutilate', 'oh_mutilate', 'dispatch', "death_from_above_strike"] other_attacks = ['deadly_instant_poison', 'swift_poison'] - aoe_attacks = ['fan_of_knives', 'crimson_tempest'] + aoe_attacks = ['fan_of_knives', 'crimson_tempest', "death_from_above_pulse"] dot_ticks = ['rupture_ticks', 'garrote_ticks', 'deadly_poison', 'hemorrhage_dot'] ranged_attacks = ['shuriken_toss', 'throw'] non_dot_attacks = melee_attacks + ranged_attacks + aoe_attacks @@ -57,6 +57,7 @@ class RogueDamageCalculator(DamageCalculator): 'shuriken_toss': (40, 'strike'), 'shiv': (20, 'strike'), 'feint': (20, 'buff'), + 'death_from_above': (50, 'strike'), } ability_cds = { 'tricks_of_the_trade': 30, @@ -70,6 +71,7 @@ class RogueDamageCalculator(DamageCalculator): 'shadowmeld': 120, 'marked_for_death': 60, 'preparation': 300, + 'death_from_above': 20, } cd_reduction_table = {'assassination': ['vanish', 'vendetta'], 'combat': ['adrenaline_rush', 'killing_spree'], @@ -244,6 +246,30 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['eviscerate'], attacks_per_second['eviscerate'][i], crit_damage_modifier) average_dps += dps_tuple damage_breakdown['eviscerate'] = average_dps + + if 'death_from_above_strike' in attacks_per_second: + if self.settings.get_spec() == 'assassination': + average_dps = 0 + for i in xrange(1, 6): + dps_tuple = self.envenom_damage(average_ap, i) * potent_poisons_mod * spell_modifier * 1.5 + dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_strike'], attacks_per_second['death_from_above_strike'][i], crit_damage_modifier) + average_dps += dps_tuple + damage_breakdown['death_from_above_strike'] = average_dps + else: + average_dps = 0 + for i in xrange(1, 6): + dps_tuple = self.eviscerate_damage(average_ap, i) * physical_modifier * executioner_mod * 1.5 + dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_strike'], attacks_per_second['death_from_above_strike'][i], crit_damage_modifier) + average_dps += dps_tuple + damage_breakdown['death_from_above_strike'] = average_dps + + if 'death_from_above_pulse' in attacks_per_second: + average_dps = 0 + for i in xrange(1, 6): + dps_tuple = self.death_from_above_pulse_damage(average_ap, i) * physical_modifier * executioner_mod + dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_pulse'], attacks_per_second['death_from_above_pulse'][i], crit_damage_modifier) + average_dps += dps_tuple + damage_breakdown['death_from_above_pulse'] = average_dps for proc in damage_procs: if proc.proc_name not in damage_breakdown: @@ -372,6 +398,10 @@ def throw_damage(self, ap): def shuriken_toss_damage(self, ap): return 1.2 * ap + + #Check this formula with SimC + def death_from_above_pulse_damage(self, ap, cp): + return 0.132 * ap * cp def get_formula(self, name): formulas = { @@ -390,7 +420,8 @@ def get_formula(self, name): 'wound_poison': self.wound_poison_damage, 'deadly_instant_poison': self.deadly_instant_poison_damage, 'swift_poison': self.swift_poison_damage, - 'shuriken_toss': self.shuriken_toss_damage + 'shuriken_toss': self.shuriken_toss_damage, + 'death_from_above_pulse':self.death_from_above_pulse_damage } return formulas[name] From 4ce2a7000ae1f7b6bb81c39dfa5c85a91b9cb746 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 30 May 2015 02:40:18 -0400 Subject: [PATCH 002/265] Assn 2pc and 4pc, combat 2pc (no 2pc 4pc synergy) added --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 21 ++++++++++++- shadowcraft/calcs/rogue/__init__.py | 32 ++++++++++---------- shadowcraft/objects/stats.py | 14 ++++++++- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 5083964..52af064 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -890,6 +890,9 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren damage_breakdown[key] *= 1 + self.vendetta_multiplier if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): damage_breakdown[key] *= self.emp_envenom_percentage + if self.stats.gear_buffs.rogue_t18_2pc: + if key == 'dispatch' + damage_breakdown*=1.45 def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) @@ -922,8 +925,12 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi n_chance = 1 - crit_rates['dispatch'] n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+1, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) + if self.stats.gear_buffs.rogue_t18_4pc: + n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) c_chance = crit_rates['dispatch'] c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) + if self.stats.gear_buffs.rogue_t18_4pc: + c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+4, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) avg_cp = n_chance*n_value + c_chance*c_value avg_bs_afterwards = n_chance*n_proc + c_chance*c_proc @@ -993,6 +1000,9 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra mutilate_cps = 3 - (1 - crit_rates['mutilate']) ** 2 # 1 - (1 - crit_rates['mutilate']) ** 2 is the Seal Fate CP dispatch_cps = 1 + crit_rates['dispatch'] + if self.stats.gear_buffs.rogue_t18_4pc: + dispatch_cps += 2 + if self.talents.anticipation: avg_finisher_size = 5 avg_size_breakdown = [0,0,0,0,0,1.] #this is for determining the % likelyhood of sizes, not frequency of the sizes @@ -1357,7 +1367,16 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): gcd_size -= .2 cp_per_cpg = 1. dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + + #https://www.wolframalpha.com/input/?i=0.5*%28%28p-p%5E2%29*4%2B%28p%5E2%29*2%29+for+p%3D0.08 + #8% proc on SnD internal tick + #0.5 proc chances per second, p^2 is chance of back to back procs => 15.36% uptime + if self.stats.gear_buffs.rogue_t18_2pc: + self.attack_speed_increase *= 1 + (0.1536 *0.2) + self.base_energy_regen *= 1 + (0.1536 * 2) + gcd_size -= (0.1536 * 0.2) + # Combine energy cost scalers to reduce function calls (ie, 40% reduced energy cost). Assume multiplicative. cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_modifier() # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG @@ -1647,7 +1666,7 @@ def subtlety_dps_breakdown(self): #damage_breakdown[key] *= find_weakness_multiplier damage_breakdown[key] *= 1 + additional_info['backstab_fw_rate'] * (find_weakness_damage_boost - 1) if key in ('rupture', 'sr_rupture', 'rupture_sc'): - damage_breakdown[key] *= 1.1 + damage_breakdown[key] *= 1.3 if key is not 'rupture_sc': damage_breakdown[key] *= (1 + multistrike_multiplier) damage_breakdown[key] *= mos_multiplier diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 25c0a02..4e7813c 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -29,8 +29,8 @@ class RogueDamageCalculator(DamageCalculator): all_attacks = melee_attacks + ranged_attacks + dot_ticks + aoe_attacks + other_attacks assassination_mastery_conversion = .035 - combat_mastery_conversion = .02 - subtlety_mastery_conversion = .03 + combat_mastery_conversion = .022 + subtlety_mastery_conversion = .0276 assassination_readiness_conversion = 1.0 combat_readiness_conversion = 1.0 subtlety_readiness_conversion = 1.0 @@ -329,10 +329,10 @@ def oh_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) def mh_shuriken(self, ap): - return .75 * mh_damage(ap) #update? + return mh_damage(ap) def oh_shuriken(self, ap): - return .75 * oh_damage(ap) #update? + return oh_damage(ap) #abilities def ambush_damage(self, ap): @@ -341,9 +341,9 @@ def ambush_sr_damage(self, ap): return 3.10 * 1.4 * 1.8 * 0.924 * ap / 3.5 def backstab_damage(self, ap): - return 2.10 * self.get_weapon_damage('mh', ap) + return 2.52 * self.get_weapon_damage('mh', ap) def backstab_sr_damage(self, ap): - return 2.10 * 1.8 * 0.924 * ap / 3.5 + return 2.52 * 1.8 * 0.924 * ap / 3.5 def death_from_above_pulse_damage(self, ap, cp): return 0.266 * cp * ap @@ -356,14 +356,14 @@ def dispatch_sr_damage(self, ap): return 3.30 * 1.8 * 0.924 * ap / 3.5 def envenom_damage(self, ap, cp): - return .321 * cp * ap + return .417 * cp * ap def envenom_sr_damage(self, ap, cp): - return .321 * cp * 0.924 * ap + return .417 * cp * 0.924 * ap def eviscerate_damage(self, ap, cp): - return .508 * cp * ap + return .491 * cp * ap #check this datamining contradicts patch notes def eviscerate_sr_damage(self, ap, cp): - return .508 * cp * 0.924 * ap + return .491 * cp * 0.924 * ap def garrote_tick_damage(self, ap): return .2241 * ap @@ -397,14 +397,14 @@ def main_gauche_sr_damage(self, ap): return 1.4 * 1.8 * 0.924 * ap / 3.5 def mh_mutilate_damage(self, ap): - return 2.1 * self.get_weapon_damage('mh', ap) + return 2.73 * self.get_weapon_damage('mh', ap) def mh_mutilate_sr_damage(self, ap): - return 2.1 * 1.8 * 0.924 * ap / 3.5 + return 2.73 * 1.8 * 0.924 * ap / 3.5 def oh_mutilate_damage(self, ap): - return 2.1 * self.oh_penalty() * self.get_weapon_damage('oh', ap) + return 2.73 * self.oh_penalty() * self.get_weapon_damage('oh', ap) def oh_mutilate_sr_damage(self, ap): - return 2.1 * 1.8 * 0.924 * ap / 3.5 * 0.5 + return 2.73 * 1.8 * 0.924 * ap / 3.5 * 0.5 def revealing_strike_damage(self, ap): return 1.2 * self.get_weapon_damage('mh', ap) @@ -417,9 +417,9 @@ def rupture_tick_sr_damage(self, ap, cp): return .0685 * 0.924 * ap def sinister_strike_damage(self, ap): - return 1.6 * self.get_weapon_damage('mh', ap) + return 1.76 * self.get_weapon_damage('mh', ap) def sinister_strike_sr_damage(self, ap): - return 1.6 * 1.8 * 0.924 * ap / 3.5 + return 1.76 * 1.8 * 0.924 * ap / 3.5 def venomous_wounds_damage(self, ap): return 1.2 * .320 * ap diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 6ed96b6..3c1fef0 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -186,6 +186,8 @@ class GearBuffs(object): 'rogue_t17_2pc', # Mut and Dispatch crits generate 7 energy, RvS has 20% higher chance to generate a CP, generate 60e when casting ShD 'rogue_t17_4pc', # Envenom generates 1 CP, finishers have a 20% chance to generate 5CP and next Evisc costs 0, 5 CP at the end of ShD 'rogue_t17_4pc_lfr', # 1.1 RPPM, 30% energy generation for 6s + 'rogue_t18_2pc', # Dispatch deals 45% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 10% for 10 sec + 'rogue_t18_4pc', #Dispatch generates +2cps, AR increased damage by 25%, Evis and Rupture reduce the CD of vanish by 2 seconds per CP ] allowed_buffs = frozenset(other_gear_buffs) @@ -270,9 +272,19 @@ def rogue_t17_4pc_bonus(self): if self.rogue_t17_4pc: return True return False + + def rogue_t18_2pc_bonus(self): + if self.rogue_t18_2pc: + return True + return False + + def rogue_t18_4pc_bonus(self): + if self.rogue_T18_4pc: + return True + return False def gear_specialization_multiplier(self): if self.gear_specialization: return 1.05 else: - return 1 \ No newline at end of file + return 1 From b6e3fd6af562b19bb3461dd6c7e4776ea0bce950 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Sun, 21 Jun 2015 18:20:43 -0700 Subject: [PATCH 003/265] Updated Combat and assassination bonuses based on new build Check combat 2pc AR uptime computation --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 35 +++++++++++++------- shadowcraft/objects/stats.py | 4 +-- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 52af064..a08fe8b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -891,8 +891,8 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): damage_breakdown[key] *= self.emp_envenom_percentage if self.stats.gear_buffs.rogue_t18_2pc: - if key == 'dispatch' - damage_breakdown*=1.45 + if key == 'dispatch': + damage_breakdown*= (1.25 * 1+self.stats.get_mastery_from_rating(rating=current_stats['mastery'])) def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) @@ -1226,7 +1226,7 @@ def combat_dps_breakdown(self): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.136 if getattr(self.stats.procs, 'fury_of_xuen'): getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.15 - + #combat specific constants self.max_bandits_guile_buff = 1.3 self.combat_cd_delay = 0 #this is for DFA convergence, mostly @@ -1244,6 +1244,10 @@ def combat_dps_breakdown(self): if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) self.ar_duration = 15 + # recurance relation of 0.16*x until convergence + # not 100% in confident in this approach + if self.stats.gear_buffs.rogue_t18_2pc: + self.ar_duration = 17.857 self.revealing_strike_multiplier = 1.35 self.extra_cp_chance = .25 # Assume all casts during RvS if self.stats.gear_buffs.rogue_t17_2pc: @@ -1278,7 +1282,15 @@ def combat_dps_breakdown(self): none_tuple = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) phases['none'] = (self.rb_actual_cds(aps, cds)['ar'] + self.settings.response_time + self.major_cd_delay, self.update_with_bandits_guile(none_tuple[0], none_tuple[1]) ) - + + if self.stats.gear_buffs.rogue_t18_4pc: + for key in phases['ar'][1]: + phases['ar'][1][key] *=1.15 + for key in phases['none'][1]: + #15% damage boost with 16% uptime + phases['none'][1][key] *= 1.024 + + total_duration = phases['ar'][0] + phases['none'][0] #average it together damage_breakdown = self.average_damage_breakdowns(phases, denom = total_duration) @@ -1292,7 +1304,7 @@ def combat_dps_breakdown(self): for key in damage_breakdown: if key in self.melee_attacks: damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) - + #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! @@ -1368,13 +1380,12 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): cp_per_cpg = 1. dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - #https://www.wolframalpha.com/input/?i=0.5*%28%28p-p%5E2%29*4%2B%28p%5E2%29*2%29+for+p%3D0.08 - #8% proc on SnD internal tick - #0.5 proc chances per second, p^2 is chance of back to back procs => 15.36% uptime - if self.stats.gear_buffs.rogue_t18_2pc: - self.attack_speed_increase *= 1 + (0.1536 *0.2) - self.base_energy_regen *= 1 + (0.1536 * 2) - gcd_size -= (0.1536 * 0.2) + # 8% proc on SnD internal tick + # assuming no waste uptime outside AR is simply (0.08*4)/2=0.16 uptime + if self.stats.gear_buffs.rogue_t18_2pc and not ar: + self.attack_speed_increase *= 1 + (0.16 *0.2) + self.base_energy_regen *= 1 + (0.16 * 2) + gcd_size -= (0.16 * 0.2) # Combine energy cost scalers to reduce function calls (ie, 40% reduced energy cost). Assume multiplicative. diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 3c1fef0..2c06446 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -186,8 +186,8 @@ class GearBuffs(object): 'rogue_t17_2pc', # Mut and Dispatch crits generate 7 energy, RvS has 20% higher chance to generate a CP, generate 60e when casting ShD 'rogue_t17_4pc', # Envenom generates 1 CP, finishers have a 20% chance to generate 5CP and next Evisc costs 0, 5 CP at the end of ShD 'rogue_t17_4pc_lfr', # 1.1 RPPM, 30% energy generation for 6s - 'rogue_t18_2pc', # Dispatch deals 45% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 10% for 10 sec - 'rogue_t18_4pc', #Dispatch generates +2cps, AR increased damage by 25%, Evis and Rupture reduce the CD of vanish by 2 seconds per CP + 'rogue_t18_2pc', # Dispatch deals 25% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 30% for 10 sec + 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP ] allowed_buffs = frozenset(other_gear_buffs) From b8319412d721b27c8ec5f5772287586434b8e94b Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Mon, 22 Jun 2015 01:25:03 -0700 Subject: [PATCH 004/265] Added 6.2 Procs except Mirror of Blademaster Further testing needed on trinket implementations --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 58 +++++++++++++++++-- shadowcraft/objects/proc_data.py | 61 ++++++++++++++++++++ shadowcraft/objects/procs.py | 2 +- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a08fe8b..95a454d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -841,6 +841,10 @@ def init_assassination(self): self.set_constants() self.stat_multipliers['mastery'] *= 1.05 + if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): + spec_needs_converge = True + self.envenom_crit_modifier = 0.0 + self.vendetta_duration = 20 + 10 * self.glyphs.vendetta self.vendetta_uptime = self.vendetta_duration / (self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay) self.vendetta_multiplier = .3 - .05 * self.glyphs.vendetta @@ -882,10 +886,19 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) + soul_cap_mod = 1.0 + if getattr(self.stats.procs, 'soul_capacitor'): + soul_cap= getattr(self.stats.procs, 'soul_capacitor') + self.set_rppm_uptime(soul_cap) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) + + + for key in damage_breakdown: damage_breakdown[key] *= 1 + multistrike_multiplier if ('sr_' not in key): damage_breakdown[key] *= self.vendetta_mult + damage_breakdown[key] *= soul_cap_mod elif 'sr_' in key: damage_breakdown[key] *= 1 + self.vendetta_multiplier if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): @@ -966,6 +979,9 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra additional_info = {} #can't rely on a cache, due to the Cold Blood perk crit_rates = self.get_crit_rates(current_stats) + for key in crit_rates: + if key in ('mutilate', 'dispatch'): + crit_rates[key]+=self.envenom_crit_modifier haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod ability_cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() @@ -1150,6 +1166,10 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if self.talents.death_from_above: finisher_per_second += sum(attacks_per_second['death_from_above_strike']) self.emp_envenom_percentage = 1 + .3 * (1 - attacks_per_second['rupture']/finisher_per_second) + crit_mod = 1 + if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): + crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value)/10000 + envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) if self.talents.shadow_reflection: sr_uptime = 8. / self.get_spell_cd('shadow_reflection') @@ -1226,7 +1246,7 @@ def combat_dps_breakdown(self): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.136 if getattr(self.stats.procs, 'fury_of_xuen'): getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.15 - + #combat specific constants self.max_bandits_guile_buff = 1.3 self.combat_cd_delay = 0 #this is for DFA convergence, mostly @@ -1245,9 +1265,9 @@ def combat_dps_breakdown(self): self.max_energy = round(self.max_energy * 1.05, 0) self.ar_duration = 15 # recurance relation of 0.16*x until convergence - # not 100% in confident in this approach + # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex if self.stats.gear_buffs.rogue_t18_2pc: - self.ar_duration = 17.857 + self.ar_duration = 17.8571 self.revealing_strike_multiplier = 1.35 self.extra_cp_chance = .25 # Assume all casts during RvS if self.stats.gear_buffs.rogue_t17_2pc: @@ -1305,14 +1325,29 @@ def combat_dps_breakdown(self): if key in self.melee_attacks: damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) + evis_multiplier = 1 + if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): + evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value*1.31132259)/10000 + + + soul_cap_mod = 1.0 + if getattr(self.stats.procs, 'soul_capacitor'): + soul_cap= getattr(self.stats.procs, 'soul_capacitor') + self.set_rppm_uptime(soul_cap) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) + #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) for ability in damage_breakdown: + if 'sr_' not in ability: + damage_breakdown[ability] *= soul_cap_mod damage_breakdown[ability] *= (1 + multistrike_multiplier) - + if ability == 'eviscerate': + damage_breakdown[ability] *= evis_multiplier + return damage_breakdown def update_with_bandits_guile(self, damage_breakdown, additional_info): @@ -1662,11 +1697,22 @@ def subtlety_dps_breakdown(self): find_weakness_damage_boost = 1. / self.max_level_armor_multiplier() find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * additional_info['fw_uptime'] + + trinket_multiplier = 1 + if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): + trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value*1.38590017)/10000 + #calculate multistrike here for Sub and Assassination, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) + soul_cap_mod = 1.0 + if getattr(self.stats.procs, 'soul_capacitor'): + soul_cap= getattr(self.stats.procs, 'soul_capacitor') + self.set_rppm_uptime(soul_cap) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) + for key in damage_breakdown: if key in ('eviscerate', 'hemorrhage', 'shuriken_toss', 'hemorrhage_dot', 'autoattack'): #'burning_wounds' damage_breakdown[key] *= find_weakness_multiplier @@ -1680,6 +1726,10 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= 1.3 if key is not 'rupture_sc': damage_breakdown[key] *= (1 + multistrike_multiplier) + if key in ('ambush', 'garrote'): + damage_breakdown[key] *=trinket_multiplier + if "sr_" not in key: + damage_breakdown[key] *= soul_cap_mod damage_breakdown[key] *= mos_multiplier #discard the loose rupture component to clean up the breakdown diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index e9dfad0..76ad724 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -158,6 +158,67 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, + #6.2 procs + 'malicious_censer': { + 'stat': 'stats', + 'value': {'agi':1093}, + 'duration': 20, + 'proc_name': 'Malicious Censer', + 'upgradable': True, + 'scaling': 1.79180327869, + 'item_level': 700, + 'type': 'rppm', + 'source': 'trinket', + 'icd': 0, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, + + 'soul_capacitor': { + 'stat': 'damage_modifier', + 'value': 2677, + 'duration': 10, + 'proc_name': 'Soul Capacitor', + 'upgradable': True, + 'scaling': 4.59965635, + 'item_level': 695, + 'type': 'rppm', + 'source': 'trinket', + 'icd': 0, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, + + 'mirror_of_the_blademaster': { + 'stat': 'physical_damage', + 'value': 1, + 'duration': 0, + 'proc_name': 'Mirror of the Blademaster', + 'upgradable': True, + 'scaling': 0, + 'item_level': 0, + 'type': 'icd', + 'source': 'trinket', + 'icd': 60, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, + + 'bleeding_hollow_toxin_vessel': { + 'stat': 'ability_modifer', + 'value': 5149, + 'duration': 0, + 'proc_name': 'Bleeding Hollow Toxin Vessel', + 'upgradable': True, + 'scaling': 8.05790297, + 'item_level': 705, + 'type': 'perk', + 'source': 'trinket', + 'icd': 0, + 'proc_rate': 0.0, + 'trigger': 'all_attacks' + }, + #6.1 procs (alch only) 'stone_of_wind': { 'stat': 'stats', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 078b6cf..0b93c47 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -46,7 +46,7 @@ def update_proc_value(self): #see above for stat value initialization if self.source in ('trinket',): for e in self.value: - self.value[e] = round(self.scaling * tools.get_random_prop_point(self.item_level)) + self.value[e] = round(self.scaling* tools.get_random_prop_point(self.item_level)) def procs_off_auto_attacks(self): if self.trigger in ('all_attacks', 'auto_attacks', 'all_spells_and_attacks', 'all_melee_attacks'): From d8bf1ca229ebeacf58b5ac224f03a9e315767c3d Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Tue, 23 Jun 2015 02:14:38 -0700 Subject: [PATCH 005/265] 6.2 launch update Updated assn/combat set bonus and trinket modeling Added support for mirror trinket Added sub set bonuses Checks needed on mirror trinket damage modifers and shadow reflection spec trinket interaction --- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 67 ++++++++++++++------ shadowcraft/objects/proc_data.py | 4 +- shadowcraft/objects/procs.py | 9 +-- 4 files changed, 54 insertions(+), 28 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index c8ba8ad..4f90a1d 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -30,7 +30,7 @@ class DamageCalculator(object): normalize_ep_stat = None def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '6.1.0' # should reflect the game patch being targetted + self.WOW_BUILD_TARGET = '6.2.0' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = '1.0' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 95a454d..6970dd8 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -333,6 +333,16 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ crit_rate = self.crit_rate(crit=current_stats['crit']) proc_value = (average_ap * .40 + 1) * 10 * (1 + min(4., self.settings.num_boss_adds)) + if proc is getattr(self.stats.procs, 'mirror_of_the_blademaster'): + crit_rate = self.crit_rate(crit=current_stats['crit']) + # Each mirror produces 10 swings scaling with haste + # There are 4 mirrors, 2 spawn in front of the get and are parryable + # Each mirror swings a weapon with weapon damage based on 100% of AP + haste_mult = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) + swings_per_mirror = 20.0/(2.0/haste_mult) + total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror + proc_value = total_swings*(average_ap/3.5) + average_hit = proc_value * multiplier average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count #print proc.proc_name, average_hit, multiplier @@ -890,9 +900,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) - - + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) for key in damage_breakdown: damage_breakdown[key] *= 1 + multistrike_multiplier @@ -905,7 +913,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren damage_breakdown[key] *= self.emp_envenom_percentage if self.stats.gear_buffs.rogue_t18_2pc: if key == 'dispatch': - damage_breakdown*= (1.25 * 1+self.stats.get_mastery_from_rating(rating=current_stats['mastery'])) + damage_breakdown[key]*= 1+(0.25 * (1+(self.stats.get_mastery_from_rating(rating=current_stats['mastery'])*self.assassination_mastery_conversion))) def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) @@ -982,7 +990,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra for key in crit_rates: if key in ('mutilate', 'dispatch'): crit_rates[key]+=self.envenom_crit_modifier - + crit_rates[key] = min(crit_rates[key], 1.0) + haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod ability_cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() @@ -1166,10 +1175,11 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if self.talents.death_from_above: finisher_per_second += sum(attacks_per_second['death_from_above_strike']) self.emp_envenom_percentage = 1 + .3 * (1 - attacks_per_second['rupture']/finisher_per_second) - crit_mod = 1 + crit_mod = 0 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value)/10000 - envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) + #may need a better model for envenom uptime at high cp gen + crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 + self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) if self.talents.shadow_reflection: sr_uptime = 8. / self.get_spell_cd('shadow_reflection') @@ -1327,14 +1337,14 @@ def combat_dps_breakdown(self): evis_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value*1.31132259)/10000 + evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.31132259)/10000 soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate @@ -1415,14 +1425,11 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): cp_per_cpg = 1. dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - # 8% proc on SnD internal tick - # assuming no waste uptime outside AR is simply (0.08*4)/2=0.16 uptime if self.stats.gear_buffs.rogue_t18_2pc and not ar: self.attack_speed_increase *= 1 + (0.16 *0.2) - self.base_energy_regen *= 1 + (0.16 * 2) + self.base_energy_regen *= 1.16 gcd_size -= (0.16 * 0.2) - - + # Combine energy cost scalers to reduce function calls (ie, 40% reduced energy cost). Assume multiplicative. cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_modifier() # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG @@ -1669,11 +1676,14 @@ def subtlety_dps_breakdown(self): self.sc_trigger_rate = 0 mos_value = .1 + + self.vanish_cd_modifier = 1.0 + # leveling perks if self.level == 100: mos_value += .05 self.ability_cds['vanish'] = 90 - + #update spec specific proc rates if getattr(self.stats.procs, 'legendary_capacitive_meta'): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.114 @@ -1683,7 +1693,7 @@ def subtlety_dps_breakdown(self): self.stat_multipliers['agi'] *= 1.15 #sinister calling requires convergence to calculate (for now?) self.spec_needs_converge = True - + self.settings.cycle.raid_crits_per_second = self.get_adv_param('hat_triggers_per_second', self.settings.cycle.raid_crits_per_second, min_bound=0, max_bound=600) self.settings.cycle.clip_fw = self.get_adv_param('clip_fw', self.settings.cycle.clip_fw, ignore_bounds=True) @@ -1700,7 +1710,7 @@ def subtlety_dps_breakdown(self): trinket_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value*1.38590017)/10000 + trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.38590017)/10000 #calculate multistrike here for Sub and Assassination, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! @@ -1711,9 +1721,15 @@ def subtlety_dps_breakdown(self): if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value/10000) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + + vanish_damage_mod = 1.0 + if self.stats.gear_buffs.rogue_t18_2pc: + vanish_damage_buff_uptime = 10/self.get_spell_cd('vanish') + vanish_damage_mod += vanish_damage_buff_uptime * 0.3 for key in damage_breakdown: + damage_breakdown[key] *= vanish_damage_mod if key in ('eviscerate', 'hemorrhage', 'shuriken_toss', 'hemorrhage_dot', 'autoattack'): #'burning_wounds' damage_breakdown[key] *= find_weakness_multiplier if key == 'ambush': @@ -1744,7 +1760,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): additional_info = {} if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - + + self.ability_cds['vanish'] = 90 * self.vanish_cd_modifier + base_energy_regen = 10. max_energy = 100. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): @@ -1801,6 +1819,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): hemo_cd = 24. snd_cd = 36. base_cp_per_second = hat_cp_per_second * (shd_cd-8.)/shd_cd + self.total_openers_per_second * 2 + if self.stats.gear_buffs.rogue_t18_2pc: + base_cp_per_second += 5 / self.get_spell_cd('vanish') if self.stats.gear_buffs.rogue_t15_2pc: rupture_ticks_per_cast += 2 rupture_cd += 4 @@ -1978,5 +1998,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['sr_ambush'] = shd_ambushes / sr_cd self.get_poison_counts(attacks_per_second, current_stats) - + + if self.stats.gear_buffs.rogue_t18_4pc: + finishers_per_second = sum(attacks_per_second['eviscerate']) + attacks_per_second['rupture'] + avg_cdr = 5 #assume all 5cp finishers + self.vanish_cd_modifier = (1./avg_cdr) / (finishers_per_second + (1./avg_cdr)) + return attacks_per_second, crit_rates, additional_info diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 76ad724..1b06679 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -176,7 +176,7 @@ 'soul_capacitor': { 'stat': 'damage_modifier', - 'value': 2677, + 'value': {'damage_mod': 2677}, 'duration': 10, 'proc_name': 'Soul Capacitor', 'upgradable': True, @@ -206,7 +206,7 @@ 'bleeding_hollow_toxin_vessel': { 'stat': 'ability_modifer', - 'value': 5149, + 'value': {'ability_mod':5149}, 'duration': 0, 'proc_name': 'Bleeding Hollow Toxin Vessel', 'upgradable': True, diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 0b93c47..f3e3ae3 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -44,9 +44,10 @@ def update_proc_value(self): tools = class_data.Util() #http://forums.elitistjerks.com/topic/130561-shadowcraft-for-mists-of-pandaria/page-3 #see above for stat value initialization - if self.source in ('trinket',): - for e in self.value: - self.value[e] = round(self.scaling* tools.get_random_prop_point(self.item_level)) + if self.scaling: + if self.source in ('trinket',): + for e in self.value: + self.value[e] = round(self.scaling * tools.get_random_prop_point(self.item_level)) def procs_off_auto_attacks(self): if self.trigger in ('all_attacks', 'auto_attacks', 'all_spells_and_attacks', 'all_melee_attacks'): @@ -188,4 +189,4 @@ def get_all_damage_procs(self): if proc.stat in ('spell_damage', 'physical_damage'): procs.append(proc) - return procs + return procs \ No newline at end of file From b854e76c545e0c1ab604e00a6968b3891c3db177 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Thu, 25 Jun 2015 07:52:57 -0700 Subject: [PATCH 006/265] Performance and tuning Modified sub and assn 4pc implementations for efficiency per Pathal's suggestion Ambush is 315% weapon damage Combat and Sub SR benefit from archimonde trinket --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 15 +++++++-------- shadowcraft/calcs/rogue/__init__.py | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 6970dd8..1a99436 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -945,13 +945,13 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi new_count['dispatch'] += 1 n_chance = 1 - crit_rates['dispatch'] - n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+1, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - if self.stats.gear_buffs.rogue_t18_4pc: - n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) c_chance = crit_rates['dispatch'] - c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) if self.stats.gear_buffs.rogue_t18_4pc: + n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+4, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) + else: + n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+1, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) + c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) avg_cp = n_chance*n_value + c_chance*c_value avg_bs_afterwards = n_chance*n_proc + c_chance*c_proc @@ -1355,7 +1355,7 @@ def combat_dps_breakdown(self): if 'sr_' not in ability: damage_breakdown[ability] *= soul_cap_mod damage_breakdown[ability] *= (1 + multistrike_multiplier) - if ability == 'eviscerate': + if ability in ('eviscerate', 'sr_eviscerate'): damage_breakdown[ability] *= evis_multiplier return damage_breakdown @@ -1742,7 +1742,7 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= 1.3 if key is not 'rupture_sc': damage_breakdown[key] *= (1 + multistrike_multiplier) - if key in ('ambush', 'garrote'): + if key in ('ambush', 'garrote', 'sr_ambush'): damage_breakdown[key] *=trinket_multiplier if "sr_" not in key: damage_breakdown[key] *= soul_cap_mod @@ -2002,6 +2002,5 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.stats.gear_buffs.rogue_t18_4pc: finishers_per_second = sum(attacks_per_second['eviscerate']) + attacks_per_second['rupture'] avg_cdr = 5 #assume all 5cp finishers - self.vanish_cd_modifier = (1./avg_cdr) / (finishers_per_second + (1./avg_cdr)) - + self.vanish_cd_modifier = 1./((finishers_per_second * avg_cdr) + 1) return attacks_per_second, crit_rates, additional_info diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 4e7813c..cde4adf 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -336,9 +336,9 @@ def oh_shuriken(self, ap): #abilities def ambush_damage(self, ap): - return 3.10 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) + return 3.15 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) def ambush_sr_damage(self, ap): - return 3.10 * 1.4 * 1.8 * 0.924 * ap / 3.5 + return 3.15 * 1.4 * 1.8 * 0.924 * ap / 3.5 def backstab_damage(self, ap): return 2.52 * self.get_weapon_damage('mh', ap) From aa57d83007c49fd4c5e1d327f9157f636ae1d5ec Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Thu, 25 Jun 2015 22:48:15 -0700 Subject: [PATCH 007/265] Added LFR 4pc and alchemy trinket upgrades Alchemy trinket collapsed into a single 'alchemy_stone' proc since its just a scaling proc --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 13 ++++++ shadowcraft/objects/proc_data.py | 49 ++------------------ shadowcraft/objects/stats.py | 1 + 3 files changed, 18 insertions(+), 45 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1a99436..d4c6f01 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -845,8 +845,11 @@ def init_assassination(self): self.max_energy += 15 if self.glyphs.energy: self.max_energy += 20 + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + self.max_energy += 20 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) + self.set_constants() self.stat_multipliers['mastery'] *= 1.05 @@ -1000,6 +1003,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 #https://twitter.com/Celestalon/status/525350819856535552 energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + energy_regen *= 1.05 energy_regen += self.bonus_energy_regen if cpg == 'dispatch': #this is for the effects of pooling going into execute phase @@ -1271,6 +1276,8 @@ def combat_dps_breakdown(self): self.max_energy += 15 if self.glyphs.energy: self.max_energy += 20 + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + self.max_energy += 20 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) self.ar_duration = 15 @@ -1485,6 +1492,8 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 #https://twitter.com/Celestalon/status/525350819856535552 energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + energy_regen *= 1.05 energy_regen += self.bonus_energy_regen + combat_potency_regen + bonus_energy_from_openers #Rough idea to factor in a full energy bar if not ar: @@ -1772,6 +1781,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): max_energy += 15 if self.glyphs.energy: max_energy += 20 + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + self.max_energy += 20 if self.race.expansive_mind: max_energy = round(max_energy * 1.05, 0) shd_duration = 8 @@ -1837,6 +1848,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 #https://twitter.com/Celestalon/status/525350819856535552 energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + energy_regen *= 1.05 energy_regen += self.bonus_energy_regen + max_energy / self.settings.duration + er_energy energy_regen += self.get_bonus_energy_from_openers() if self.stats.gear_buffs.rogue_t16_2pc_bonus(): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 1b06679..9199557 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -219,54 +219,13 @@ 'trigger': 'all_attacks' }, - #6.1 procs (alch only) - 'stone_of_wind': { - 'stat': 'stats', - 'value': {'agi':931}, - 'duration': 15, - 'proc_name': 'Stone of Wind', - 'upgradable': True, - 'scaling': 2.6670000553, - 'item_level': 640, - 'type': 'icd', - 'source': 'trinket', - 'icd': 55, - 'proc_rate': 0.35, - 'trigger': 'all_attacks' - }, - 'stone_of_the_earth': { - 'stat': 'stats', - 'value': {'agi':1069}, - 'duration': 15, - 'proc_name': 'Stone of the Earth', - 'upgradable': True, - 'scaling': 2.6670000553, - 'item_level': 655, - 'type': 'icd', - 'source': 'trinket', - 'icd': 55, - 'proc_rate': 0.35, - 'trigger': 'all_attacks' - }, - 'stone_of_the_waters': { - 'stat': 'stats', - 'value': {'agi':1229}, - 'duration': 15, - 'proc_name': 'Stone of the Waters', - 'upgradable': True, - 'scaling': 2.6670000553, - 'item_level': 670, - 'type': 'icd', - 'source': 'trinket', - 'icd': 55, - 'proc_rate': 0.35, - 'trigger': 'all_attacks' - }, - 'stone_of_fire': { + #all alchemy trinket upgrades are just scales + #with different names, collapsed into single proc + 'alchemy_stone': { 'stat': 'stats', 'value': {'agi':1350}, 'duration': 15, - 'proc_name': 'Stone of Fire', + 'proc_name': 'Alchemy Trinket Proc', 'upgradable': True, 'scaling': 2.6670000553, 'item_level': 680, diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 2c06446..d9a3b35 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -188,6 +188,7 @@ class GearBuffs(object): 'rogue_t17_4pc_lfr', # 1.1 RPPM, 30% energy generation for 6s 'rogue_t18_2pc', # Dispatch deals 25% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 30% for 10 sec 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP + 'rogue_t18_4pc_lfr' # Energy increased by 20, 5% increase in energy regen ] allowed_buffs = frozenset(other_gear_buffs) From eb8cca310a631ae1acc0b9339231ab4595e5b241 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Fri, 26 Jun 2015 00:02:18 -0700 Subject: [PATCH 008/265] Mirror of Blademaster doesn't inherit damage bonuses --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d4c6f01..843b8b5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -907,6 +907,9 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren for key in damage_breakdown: damage_breakdown[key] *= 1 + multistrike_multiplier + #mirror of the blademaster doesn't get any player buffs + if key == 'Mirror of the Blademaster': + continue if ('sr_' not in key): damage_breakdown[key] *= self.vendetta_mult damage_breakdown[key] *= soul_cap_mod @@ -1369,6 +1372,8 @@ def combat_dps_breakdown(self): def update_with_bandits_guile(self, damage_breakdown, additional_info): for key in damage_breakdown: + if key == 'Mirror of the Blademaster': + continue if key in ('killing_spree', 'mh_killing_spree', 'oh_killing_spree'): if self.settings.cycle.ksp_immediately: damage_breakdown[key] *= self.bandits_guile_multiplier @@ -1755,7 +1760,8 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *=trinket_multiplier if "sr_" not in key: damage_breakdown[key] *= soul_cap_mod - damage_breakdown[key] *= mos_multiplier + if "Mirror" not in key: + damage_breakdown[key] *= mos_multiplier #discard the loose rupture component to clean up the breakdown if 'rupture_sc' in damage_breakdown and self.settings.merge_damage: From 11906a69e34940b8a3e0a5af54f9c8fec57a4976 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Fri, 26 Jun 2015 01:01:44 -0700 Subject: [PATCH 009/265] Assassination T17 2pc nerf --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 843b8b5..ee1fb87 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1085,7 +1085,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra cpg_energy_cost = self.get_spell_stats(cpg, cost_mod=ability_cost_modifier)[0] cpg_cost_reduction = 0 if self.stats.gear_buffs.rogue_t17_2pc: - cpg_cost_reduction = 14 * crit_rates['mutilate'] #7 per hand, double crit is 2 procs + cpg_cost_reduction = 8 * crit_rates['mutilate'] #4 per hand, double crit is 2 procs if self.stats.gear_buffs.rogue_t16_2pc_bonus(): cpg_cost_reduction = 6 * seal_fate_proc_rate cpg_energy_cost -= cpg_cost_reduction From 5084f49ac0117ede649469a874ec408c105c239f Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Fri, 26 Jun 2015 15:50:59 -0700 Subject: [PATCH 010/265] Fix sub lfr 4pc error max_energy isn't a member function of sub for some reason --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ee1fb87..84101a2 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1788,7 +1788,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.glyphs.energy: max_energy += 20 if self.stats.gear_buffs.rogue_t18_4pc_lfr: - self.max_energy += 20 + max_energy += 20 if self.race.expansive_mind: max_energy = round(max_energy * 1.05, 0) shd_duration = 8 From 4986c1e4b0ce404752ef6983a33244433c22b39c Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Mon, 29 Jun 2015 20:52:04 -0700 Subject: [PATCH 011/265] June 29 Hotfix updates and Felmouth Frenzy support Pretty sure felmouth frenzy isn't actually good but folks asked for it so here it is --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 24 ++++++++++++++++---- shadowcraft/objects/buffs.py | 6 +++++ shadowcraft/objects/proc_data.py | 15 ++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 84101a2..428d18b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -342,6 +342,11 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ swings_per_mirror = 20.0/(2.0/haste_mult) total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror proc_value = total_swings*(average_ap/3.5) + + #.424*max(AP, SP) + if proc is getattr(self.stats.procs, 'felmouth_frenzy'): + proc_value = average_ap * 0.424 + average_hit = proc_value * multiplier average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count @@ -607,7 +612,10 @@ def determine_stats(self, attack_counts_function): active_procs_no_icd = [] damage_procs = [] weapon_damage_procs = [] - + + if self.buffs.felmouth_food(): + self.stats.procs.set_proc('felmouth_frenzy') + shatt_hand = 0 for hand in ('mh', 'oh'): if getattr(getattr(self.stats, hand), 'mark_of_the_shattered_hand'): @@ -906,6 +914,9 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) for key in damage_breakdown: + #Fel Lash doesn't MS + if key == 'Fel Lash': + continue damage_breakdown[key] *= 1 + multistrike_multiplier #mirror of the blademaster doesn't get any player buffs if key == 'Mirror of the Blademaster': @@ -1347,7 +1358,7 @@ def combat_dps_breakdown(self): evis_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.31132259)/10000 + evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 soul_cap_mod = 1.0 @@ -1364,6 +1375,9 @@ def combat_dps_breakdown(self): for ability in damage_breakdown: if 'sr_' not in ability: damage_breakdown[ability] *= soul_cap_mod + #Fel Lash doesn't MS + if ability == 'Fel Lash': + continue damage_breakdown[ability] *= (1 + multistrike_multiplier) if ability in ('eviscerate', 'sr_eviscerate'): damage_breakdown[ability] *= evis_multiplier @@ -1372,7 +1386,7 @@ def combat_dps_breakdown(self): def update_with_bandits_guile(self, damage_breakdown, additional_info): for key in damage_breakdown: - if key == 'Mirror of the Blademaster': + if key in ('Mirror of the Blademaster', 'Fel Lash'): continue if key in ('killing_spree', 'mh_killing_spree', 'oh_killing_spree'): if self.settings.cycle.ksp_immediately: @@ -1724,7 +1738,7 @@ def subtlety_dps_breakdown(self): trinket_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.38590017)/10000 + trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.940260238)/10000 #calculate multistrike here for Sub and Assassination, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! @@ -1754,7 +1768,7 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= 1 + additional_info['backstab_fw_rate'] * (find_weakness_damage_boost - 1) if key in ('rupture', 'sr_rupture', 'rupture_sc'): damage_breakdown[key] *= 1.3 - if key is not 'rupture_sc': + if key not in ('rupture_sc', 'Fel Lash'): damage_breakdown[key] *= (1 + multistrike_multiplier) if key in ('ambush', 'garrote', 'sr_ambush'): damage_breakdown[key] *=trinket_multiplier diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index cfe5ed5..1bbc441 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -42,6 +42,7 @@ class Buffs(object): 'food_wod_multistrike', # 'food_wod_multistrike_75', # 'food_wod_multistrike_125', # + 'food_felmouth_frenzy', # Felmouth frenzy, 2 haste scaling RPPM dealing 0.424 AP in damage ]) buffs_debuffs = frozenset([ @@ -166,3 +167,8 @@ def buff_multistrike(self, race=False): def buff_readiness(self, race=False): return 0 + + def felmouth_food(self): + if self.food_felmouth_frenzy : + return True + return False diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 9199557..e6c86b1 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -159,6 +159,21 @@ 'trigger': 'all_attacks' }, #6.2 procs + 'felmouth_frenzy': { + 'stat':'spell_damage', + 'value': 1, + 'duration': 0, + 'proc_name': 'Fel Lash', + 'scaling': 0.0, + 'type': 'rppm', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 2, + 'haste_scales': True, + 'can_crit': False, + 'trigger': 'all_attacks' + }, + 'malicious_censer': { 'stat': 'stats', 'value': {'agi':1093}, From 83cf087c5c23854ec469541a595bcab550daf1ae Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Mon, 29 Jun 2015 21:54:14 -0700 Subject: [PATCH 012/265] Version bump Felmouth Frenzy hits 5 times each proc --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- shadowcraft/calcs/rogue/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 428d18b..7f30913 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -345,7 +345,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ #.424*max(AP, SP) if proc is getattr(self.stats.procs, 'felmouth_frenzy'): - proc_value = average_ap * 0.424 + proc_value = average_ap * 0.424 * 5 average_hit = proc_value * multiplier diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index cde4adf..ee0d72c 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -513,4 +513,4 @@ def crit_rate(self, crit=None): # should be coded better? base_crit = .15 base_crit += self.stats.get_crit_from_rating(crit) - return base_crit + self.buffs.buff_all_crit() + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction + return base_crit + self.buffs.buff_all_crit() + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction \ No newline at end of file From 71189d4f3333caa61d603cb5e8dd43341fcfb051 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Mon, 29 Jun 2015 21:55:46 -0700 Subject: [PATCH 013/265] Version bump for real this time --- shadowcraft/calcs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 4f90a1d..64d1e9f 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -31,7 +31,7 @@ class DamageCalculator(object): def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '6.2.0' # should reflect the game patch being targetted - self.SHADOWCRAFT_BUILD = '1.0' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch + self.SHADOWCRAFT_BUILD = '1.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats self.talents = talents From 312b6960e6f86a49271020541af741ab90858991 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Thu, 30 Jul 2015 10:58:45 -0700 Subject: [PATCH 014/265] Legendary Ring Support and Trinket Updates Added preliminary support for maluus, no stacking considered Fixed bug with bleeding hollow toxin vessel applying to blade flurry Added AoE scaling for mirror of the blademaster --- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 49 +++++++++++++++++--- shadowcraft/objects/proc_data.py | 17 ++++++- 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 64d1e9f..08307ec 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -31,7 +31,7 @@ class DamageCalculator(object): def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '6.2.0' # should reflect the game patch being targetted - self.SHADOWCRAFT_BUILD = '1.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch + self.SHADOWCRAFT_BUILD = '1.02' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats self.talents = talents diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 7f30913..418b4b4 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -341,7 +341,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ haste_mult = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) swings_per_mirror = 20.0/(2.0/haste_mult) total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror - proc_value = total_swings*(average_ap/3.5) + proc_value = total_swings*(average_ap/3.5) * (1+ self.settings.num_boss_adds) #.424*max(AP, SP) if proc is getattr(self.stats.procs, 'felmouth_frenzy'): @@ -913,7 +913,15 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + maluus_mod = 1.0 + if getattr(self.stats.procs,'maluus'): + maluus = getattr(self.stats.procs, 'maluus') + maluus_val = maluus.value['damage_mod']/10000 + maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + + for key in damage_breakdown: + damage_breakdown[key] *= maluus_mod #Fel Lash doesn't MS if key == 'Fel Lash': continue @@ -932,6 +940,10 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if key == 'dispatch': damage_breakdown[key]*= 1+(0.25 * (1+(self.stats.get_mastery_from_rating(rating=current_stats['mastery'])*self.assassination_mastery_conversion))) + #add maluus burst + if maluus_mod > 1.0: + damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) + def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_non_execute) @@ -1345,6 +1357,11 @@ def combat_dps_breakdown(self): total_duration = phases['ar'][0] + phases['none'][0] #average it together damage_breakdown = self.average_damage_breakdowns(phases, denom = total_duration) + + evis_multiplier = 1 + if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): + evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 + bf_mod = .35 bf_max_targets = 4 @@ -1354,12 +1371,10 @@ def combat_dps_breakdown(self): damage_breakdown['blade_flurry'] = 0 for key in damage_breakdown: if key in self.melee_attacks: - damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) - - evis_multiplier = 1 - if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 - + if key == "eviscerate": + damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) * evis_multiplier + else: + damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): @@ -1367,6 +1382,12 @@ def combat_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + maluus_mod = 1.0 + if getattr(self.stats.procs,'maluus'): + maluus = getattr(self.stats.procs, 'maluus') + maluus_val = maluus.value['damage_mod']/10000 + maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! @@ -1382,6 +1403,10 @@ def combat_dps_breakdown(self): if ability in ('eviscerate', 'sr_eviscerate'): damage_breakdown[ability] *= evis_multiplier + #add maluus burst + if maluus_mod > 1.0: + damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) + return damage_breakdown def update_with_bandits_guile(self, damage_breakdown, additional_info): @@ -1751,6 +1776,12 @@ def subtlety_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + maluus_mod = 1.0 + if getattr(self.stats.procs,'maluus'): + maluus = getattr(self.stats.procs, 'maluus') + maluus_val = maluus.value['damage_mod']/10000 + maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + vanish_damage_mod = 1.0 if self.stats.gear_buffs.rogue_t18_2pc: vanish_damage_buff_uptime = 10/self.get_spell_cd('vanish') @@ -1781,6 +1812,10 @@ def subtlety_dps_breakdown(self): if 'rupture_sc' in damage_breakdown and self.settings.merge_damage: damage_breakdown['rupture'] += damage_breakdown['rupture_sc'] del damage_breakdown['rupture_sc'] + + #add maluus burst + if maluus_mod > 1.0: + damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) return damage_breakdown diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index e6c86b1..6c9bea8 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -159,13 +159,28 @@ 'trigger': 'all_attacks' }, #6.2 procs + 'maluus': { + 'stat': 'damage_modifier', + 'value': {'damage_mod': 2500}, + 'duration': 15, + 'proc_name': 'Maluus', + 'upgradable': True, + 'scaling': 2.95857988166, + 'item_level': 735, + 'type': 'rppm', + 'source': 'trinket', + 'icd': 120, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, + 'felmouth_frenzy': { 'stat':'spell_damage', 'value': 1, 'duration': 0, 'proc_name': 'Fel Lash', 'scaling': 0.0, - 'type': 'rppm', + 'type': 'icd', 'source': 'unique', 'icd': 0, 'proc_rate': 2, From 9b9686c61e9aa37632fa4e0c4377b95f0e90d677 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Thu, 30 Jul 2015 11:07:47 -0700 Subject: [PATCH 015/265] Fixed spelling of Maalus --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 50 ++++++++++---------- shadowcraft/objects/proc_data.py | 4 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 418b4b4..b5cfc4d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -913,15 +913,15 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) - maluus_mod = 1.0 - if getattr(self.stats.procs,'maluus'): - maluus = getattr(self.stats.procs, 'maluus') - maluus_val = maluus.value['damage_mod']/10000 - maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + maalus_mod = 1.0 + if getattr(self.stats.procs,'maalus'): + maalus = getattr(self.stats.procs, 'maalus') + maalus_val = maalus.value['damage_mod']/10000 + maalus_mod = 1 + (15.0/120* maalus_val) #super hackish for key in damage_breakdown: - damage_breakdown[key] *= maluus_mod + damage_breakdown[key] *= maalus_mod #Fel Lash doesn't MS if key == 'Fel Lash': continue @@ -940,9 +940,9 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if key == 'dispatch': damage_breakdown[key]*= 1+(0.25 * (1+(self.stats.get_mastery_from_rating(rating=current_stats['mastery'])*self.assassination_mastery_conversion))) - #add maluus burst - if maluus_mod > 1.0: - damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) + #add maalus burst + if maalus_mod > 1.0: + damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) @@ -1382,11 +1382,11 @@ def combat_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) - maluus_mod = 1.0 - if getattr(self.stats.procs,'maluus'): - maluus = getattr(self.stats.procs, 'maluus') - maluus_val = maluus.value['damage_mod']/10000 - maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + maalus_mod = 1.0 + if getattr(self.stats.procs,'maalus'): + maalus = getattr(self.stats.procs, 'maalus') + maalus_val = maalus.value['damage_mod']/10000 + maalus_mod = 1 + (15.0/120* maalus_val) #super hackish #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate @@ -1403,9 +1403,9 @@ def combat_dps_breakdown(self): if ability in ('eviscerate', 'sr_eviscerate'): damage_breakdown[ability] *= evis_multiplier - #add maluus burst - if maluus_mod > 1.0: - damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) + #add maalus burst + if maalus_mod > 1.0: + damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) return damage_breakdown @@ -1776,11 +1776,11 @@ def subtlety_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) - maluus_mod = 1.0 - if getattr(self.stats.procs,'maluus'): - maluus = getattr(self.stats.procs, 'maluus') - maluus_val = maluus.value['damage_mod']/10000 - maluus_mod = 1 + (15.0/120* maluus_val) #super hackish + maalus_mod = 1.0 + if getattr(self.stats.procs,'maalus'): + maalus = getattr(self.stats.procs, 'maalus') + maalus_val = maalus.value['damage_mod']/10000 + maalus_mod = 1 + (15.0/120* maalus_val) #super hackish vanish_damage_mod = 1.0 if self.stats.gear_buffs.rogue_t18_2pc: @@ -1813,9 +1813,9 @@ def subtlety_dps_breakdown(self): damage_breakdown['rupture'] += damage_breakdown['rupture_sc'] del damage_breakdown['rupture_sc'] - #add maluus burst - if maluus_mod > 1.0: - damage_breakdown['maluus'] = sum(damage_breakdown.values())*(maluus_mod-1.0) * (self.settings.num_boss_adds+1) + #add maalus burst + if maalus_mod > 1.0: + damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) return damage_breakdown diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 6c9bea8..af1b8d0 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -159,11 +159,11 @@ 'trigger': 'all_attacks' }, #6.2 procs - 'maluus': { + 'maalus': { 'stat': 'damage_modifier', 'value': {'damage_mod': 2500}, 'duration': 15, - 'proc_name': 'Maluus', + 'proc_name': 'Maalus', 'upgradable': True, 'scaling': 2.95857988166, 'item_level': 735, From e252f2dc365a9162b10b07dfdb17c14f95301959 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Thu, 30 Jul 2015 11:31:46 -0700 Subject: [PATCH 016/265] Felmouth is rppm not icd --- shadowcraft/objects/proc_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index af1b8d0..25e5385 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -167,8 +167,8 @@ 'upgradable': True, 'scaling': 2.95857988166, 'item_level': 735, - 'type': 'rppm', - 'source': 'trinket', + 'type': 'icd', + 'source': 'unique', 'icd': 120, 'proc_rate': 1.0, 'trigger': 'all_attacks' @@ -180,7 +180,7 @@ 'duration': 0, 'proc_name': 'Fel Lash', 'scaling': 0.0, - 'type': 'icd', + 'type': 'rppm', 'source': 'unique', 'icd': 0, 'proc_rate': 2, From 5b92a7175a58e07a2c11cb7824da794eb48da0cb Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Fri, 31 Jul 2015 10:27:45 -0700 Subject: [PATCH 017/265] Fixed proc scaling for legendary ring Assorted trinket bugfixes --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 15 ++++++++------- shadowcraft/objects/proc_data.py | 8 ++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b5cfc4d..98126f5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -347,7 +347,6 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc is getattr(self.stats.procs, 'felmouth_frenzy'): proc_value = average_ap * 0.424 * 5 - average_hit = proc_value * multiplier average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count #print proc.proc_name, average_hit, multiplier @@ -911,12 +910,12 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000 + maalus_val = maalus.value['damage_mod']/10000. maalus_mod = 1 + (15.0/120* maalus_val) #super hackish @@ -1380,12 +1379,12 @@ def combat_dps_breakdown(self): if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000 + maalus_val = maalus.value['damage_mod']/10000. maalus_mod = 1 + (15.0/120* maalus_val) #super hackish #combat gets it's own MS calculation due to BF mechanics @@ -1394,6 +1393,7 @@ def combat_dps_breakdown(self): multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) for ability in damage_breakdown: + damage_breakdown[ability] *=maalus_mod if 'sr_' not in ability: damage_breakdown[ability] *= soul_cap_mod #Fel Lash doesn't MS @@ -1774,12 +1774,12 @@ def subtlety_dps_breakdown(self): if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000) + soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000 + maalus_val = maalus.value['damage_mod']/10000. maalus_mod = 1 + (15.0/120* maalus_val) #super hackish vanish_damage_mod = 1.0 @@ -1789,6 +1789,7 @@ def subtlety_dps_breakdown(self): for key in damage_breakdown: damage_breakdown[key] *= vanish_damage_mod + damage_breakdown[key] *= maalus_mod if key in ('eviscerate', 'hemorrhage', 'shuriken_toss', 'hemorrhage_dot', 'autoattack'): #'burning_wounds' damage_breakdown[key] *= find_weakness_multiplier if key == 'ambush': diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 25e5385..3e2d3ef 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -168,7 +168,7 @@ 'scaling': 2.95857988166, 'item_level': 735, 'type': 'icd', - 'source': 'unique', + 'source': 'trinket', 'icd': 120, 'proc_rate': 1.0, 'trigger': 'all_attacks' @@ -222,11 +222,11 @@ 'mirror_of_the_blademaster': { 'stat': 'physical_damage', 'value': 1, - 'duration': 0, + 'duration': 20, 'proc_name': 'Mirror of the Blademaster', 'upgradable': True, - 'scaling': 0, - 'item_level': 0, + 'scaling': 1.0, + 'item_level': 695, 'type': 'icd', 'source': 'trinket', 'icd': 60, From 65e0fd337230ad2ddb7019d2717f73e037f90e31 Mon Sep 17 00:00:00 2001 From: Ben Feinberg Date: Fri, 31 Jul 2015 11:20:59 -0700 Subject: [PATCH 018/265] Mirror should actually work now --- shadowcraft/objects/proc_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 3e2d3ef..a5a30d5 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -221,7 +221,7 @@ 'mirror_of_the_blademaster': { 'stat': 'physical_damage', - 'value': 1, + 'value': {'damage': 1}, 'duration': 20, 'proc_name': 'Mirror of the Blademaster', 'upgradable': True, From 565f0b92f689562cf3ce432efb1498fd968ecab2 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 30 Nov 2015 15:46:56 -0500 Subject: [PATCH 019/265] Infallible Tracking Charm Added --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 25 ++++++++++++++++ shadowcraft/calcs/rogue/Aldriana/settings.py | 3 +- shadowcraft/objects/proc_data.py | 31 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 98126f5..6047a23 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -912,6 +912,14 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) + infallible_trinket_mod = 1.0 + if self.settings.is_demon: + if getattr(self.stats.procs, 'infallible_tracking_charm_mod'): + ift = getattr(self.stats.procs, 'infallible_tracking_charm_mod') + self.set_rppm_uptime(ift) + infallible_trinket_mod = 1+(ift.uptime *0.10) + + maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') @@ -931,6 +939,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren if ('sr_' not in key): damage_breakdown[key] *= self.vendetta_mult damage_breakdown[key] *= soul_cap_mod + damage_breakdown[key] *= infallible_trinket_mod elif 'sr_' in key: damage_breakdown[key] *= 1 + self.vendetta_multiplier if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): @@ -1381,6 +1390,13 @@ def combat_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) + infallible_trinket_mod = 1.0 + if self.settings.is_demon: + if getattr(self.stats.procs, 'infallible_tracking_charm_mod'): + ift = getattr(self.stats.procs, 'infallible_tracking_charm_mod') + self.set_rppm_uptime(ift) + infallible_trinket_mod = 1+(ift.uptime *0.10) + maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') @@ -1396,6 +1412,7 @@ def combat_dps_breakdown(self): damage_breakdown[ability] *=maalus_mod if 'sr_' not in ability: damage_breakdown[ability] *= soul_cap_mod + damage_breakdown[ability] *= infallible_trinket_mod #Fel Lash doesn't MS if ability == 'Fel Lash': continue @@ -1776,6 +1793,13 @@ def subtlety_dps_breakdown(self): self.set_rppm_uptime(soul_cap) soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) + infallible_trinket_mod = 1.0 + if self.settings.is_demon: + if getattr(self.stats.procs, 'infallible_tracking_charm_mod'): + ift = getattr(self.stats.procs, 'infallible_tracking_charm_mod') + self.set_rppm_uptime(ift) + infallible_trinket_mod = 1+(ift.uptime *0.10) + maalus_mod = 1.0 if getattr(self.stats.procs,'maalus'): maalus = getattr(self.stats.procs, 'maalus') @@ -1806,6 +1830,7 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *=trinket_multiplier if "sr_" not in key: damage_breakdown[key] *= soul_cap_mod + damage_breakdown[key] *= infallible_trinket_mod if "Mirror" not in key: damage_breakdown[key] *= mos_multiplier diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 0b08a6b..929e923 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -5,7 +5,7 @@ class Settings(object): def __init__(self, cycle, time_in_execute_range=.35, response_time=.5, latency=.03, dmg_poison='dp', utl_poison=None, duration=300, use_opener='always', opener_name='default', is_pvp=False, shiv_interval=0, adv_params=None, - merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False): + merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False): self.cycle = cycle self.time_in_execute_range = time_in_execute_range self.response_time = response_time @@ -19,6 +19,7 @@ def __init__(self, cycle, time_in_execute_range=.35, response_time=.5, latency=. self.feint_interval = feint_interval self.merge_damage = merge_damage self.is_day = is_day + self.is_demon = is_demon self.num_boss_adds = max(num_boss_adds, 0) self.shiv_interval = float(shiv_interval) self.adv_params = self.interpret_adv_params(adv_params) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index a5a30d5..50608df 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -158,6 +158,37 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, + #6.2.3 procs + 'infallible_tracking_charm': { + 'stat':'spell_damage', + 'value': 42872, + 'duration': 0, + 'proc_name': "Cleansing Flame", + #'scaling': 61.1583452211, + 'item_level': 715, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 3.5, + 'haste_scales': True, + 'can_crit': False, + 'trigger': 'all_attacks' + }, + + 'infallible_tracking_charm_mod': { + 'stat':'damage_modifier', + 'value': {'damage_mod': 10}, + 'proc_name': "Cleansing Flame", + 'scaling': 0.0, + 'item_level': 715, + 'type': 'rppm', + 'source': 'trinket', + 'duration': 5, + 'proc_rate': 3.5, + 'haste_scales': True, + 'trigger': 'all_attacks' + }, + + #6.2 procs 'maalus': { 'stat': 'damage_modifier', From 4156338e0e5d7c56d8fdac757ed4a340e7916eef Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 4 Dec 2015 18:40:41 -0500 Subject: [PATCH 020/265] Updated trinket ranking get_upgrade_ep_fast() now takes an optional exclude_list argument. If an exlcude_list is provided only procs on the exclude_list will be removed during trinket ranking rather than all procs. If no exclude_list is provided the function behaves as before. Exclude lists should improve trinket ranking accuracy if called once by not removing non-trinket procs and if called twice can provide trinket rankings with the other trinket equipped providing potentially unique trinket valuation lists. --- shadowcraft/calcs/__init__.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 08307ec..79cfc51 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -459,25 +459,28 @@ def get_upgrades_ep(self, _list, normalize_ep_stat=None): # this function is in comparison to get_upgrades_ep a lot faster but not 100% accurate # the error is around 1% which is accurate enough for the ranking in Shadowcraft-UI - def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None): + def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None): if not normalize_ep_stat: normalize_ep_stat = self.normalize_ep_stat # This method computes ep for every other buff/proc not covered by # get_ep or get_weapon_ep. Weapon enchants, being tied to the # weapons they are on, are computed by get_weapon_ep. - active_procs_cache = [] - procs_list = [] + active_procs_cache = [] #procs removed by ranker, all procs if no exclude_list provided + procs_list = [] #holds all procs to consider ep_values = {} for i in _list: + if i in self.stats.procs.allowed_procs: procs_list.append( (i, _list[i]) ) - if getattr(self.stats.procs, i): + #if an excludelist is provided only add values on exclude_list to active_proc_cache + #if no exclude_list add all procs to active_proc_cache + if (exclude_list and i in exclude_list) or (not exclude_list and getattr(self.stats.procs, i)): active_procs_cache.append((i, getattr(self.stats.procs, i).item_level)) delattr(self.stats.procs, i) else: ep_values[i] = _('not allowed') - + baseline_dps = self.get_dps() normalize_dps = self.ep_helper(normalize_ep_stat) @@ -503,19 +506,13 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None): proc.item_level = group[0] proc.update_proc_value() # after setting item_level re-set the proc value item_level = proc.item_level - if proc.proc_name == 'Rune of Re-Origination': - scale_factor = 1/(1.15**((528-item_level)/15.0)) * proc.base_ppm - else: - scale_factor = self.tools.get_random_prop_point(item_level) + scale_factor = self.tools.get_random_prop_point(item_level) new_dps = self.get_dps() if new_dps != base_dps: for l in group: ep = abs(new_dps - base_dps) / (base_normalize_dps - base_dps) if l > proc.item_level: - if proc.proc_name == 'Rune of Re-Origination': - upgraded_scale_factor = 1/(1.15**((528-(l))/15.0)) * proc.base_ppm - else: - upgraded_scale_factor = self.tools.get_random_prop_point(l) + upgraded_scale_factor = self.tools.get_random_prop_point(l) ep *= float(upgraded_scale_factor) / float(scale_factor) ep_values[proc_name][l] = ep if old_proc: From f7dcc52fc8041cf4cfa72546615ef35f8c054e9e Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 18 Feb 2016 21:45:46 -0500 Subject: [PATCH 021/265] Updated IFT to correct values --- shadowcraft/objects/proc_data.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 50608df..41eb694 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -168,8 +168,8 @@ 'item_level': 715, 'type': 'rppm', 'source': 'trinket', - 'proc_rate': 3.5, - 'haste_scales': True, + 'proc_rate': 3, + 'haste_scales': False, 'can_crit': False, 'trigger': 'all_attacks' }, @@ -183,8 +183,8 @@ 'type': 'rppm', 'source': 'trinket', 'duration': 5, - 'proc_rate': 3.5, - 'haste_scales': True, + 'proc_rate': 3, + 'haste_scales': False, 'trigger': 'all_attacks' }, From 6d4dc5ec2c85aa8b60965519f6b9de5c77191e41 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 18 Apr 2016 17:46:08 -0400 Subject: [PATCH 022/265] Initial Legion Commit--Totally non-functional -Legion talents -Spec now required explicitly --- shadowcraft/calcs/rogue/Aldriana/settings.py | 1 - shadowcraft/objects/proc_data.py | 15 -- shadowcraft/objects/talents.py | 5 +- shadowcraft/objects/talents_data.py | 184 +++++++++++-------- 4 files changed, 113 insertions(+), 92 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 929e923..23edf82 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -15,7 +15,6 @@ def __init__(self, cycle, time_in_execute_range=.35, response_time=.5, latency=. self.duration = duration self.use_opener = use_opener # Allowed values are 'always' (vanish/shadowmeld on cooldown), 'opener' (once per fight) and 'never' self.opener_name = opener_name - self.is_pvp = is_pvp self.feint_interval = feint_interval self.merge_damage = merge_damage self.is_day = is_day diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 50608df..a3b4984 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -605,21 +605,6 @@ 'proc_rate': 1.0, 'trigger': 'all_attacks' }, - #5.4 procs - 'assurance_of_consequence': { #DBC - 197491 - 'stat': 'stats', - 'value': {'agi':268}, - 'duration': 20, - 'proc_name': 'Assurance of Consequence', - 'upgradable': True, - 'scaling': 4.000, - 'item_level': 572, - 'type': 'icd', - 'source': 'trinket', - 'icd': 115, - 'proc_rate': 0.15, - 'trigger': 'all_attacks' - }, } allowed_melee_enchants = { diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index 8535d7f..94201ce 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -7,9 +7,10 @@ class InvalidTalentException(exceptions.InvalidInputException): class Talents(object): - def __init__(self, talent_string, game_class='rogue', level='100'): + def __init__(self, talent_string, class_spec, game_class, level='110'): self.game_class = game_class - self.class_talents = talents_data.talents[game_class] + self.class_spec = class_spec + self.class_talents = talents_data.talents[(game_class,class_spec)] self.level = level self.max_rows = 7 self.allowed_talents = [talent for tier in self.class_talents for talent in tier] diff --git a/shadowcraft/objects/talents_data.py b/shadowcraft/objects/talents_data.py index 87e55bb..cefea00 100644 --- a/shadowcraft/objects/talents_data.py +++ b/shadowcraft/objects/talents_data.py @@ -1,91 +1,127 @@ talents = { - 'death_knight': ( - ('roiling_blood', 'plague_leech', 'unholy_blight'), - ('lichborne', 'anti-magic_zone', 'purgatory'), - ('deaths_advance', 'chilblains', 'asphyxiate'), - ('death_pact', 'death_siphon', 'conversion'), - ('blood_tap', 'runic_empowerment', 'runic_corruption'), - ('gorefiends_grasp', 'remorseless_winter', 'desecrated_ground') + ('death_knight', 'blood'): ( + ('bloodworms', 'heart_strike', 'consume_vitality'), + ('bloody_reprisal', 'bloodbolt', 'ossuary'), + ('rapid_decomposition', 'red_thirst', 'anti-magic_barrier'), + ('rune_tap', 'purgatory', 'mark_of_blood'), + ('tightening_grasp', 'tremble_before_me', 'march_of_the_damned'), + ('will_of_the_necropolis', 'exhume', 'foul_bulwark'), + ('bonestorm', 'blood_mirror', 'blood_beasts') ), - 'druid': ( - ('feline_swiftness', 'displacer_beast', 'wild_charge'), - ('natures_swiftness', 'renewal', 'cenarion_ward'), - ('faerie_swarm', 'mass_entanglement', 'typhoon'), - ('soul_of_the_forest', 'incarnation', 'force_of_nature'), - ('disorienting_roar', 'ursols_vortex', 'mighty_bash'), - ('heart_of_the_wild', 'dream_of_cenarius', 'natures_vigil') + ('demon_hunter', 'havoc'): ( + ('fel_mastery', 'first_blood', 'blind_fury'), + ('prepared', 'demon_blades', 'master_of_the_glaive'), + ('demon_reborn', 'bloodlet', 'feed_the_demon'), + ('desperate_instincts', 'netherwalk', 'soul_rending'), + ('nemesis', 'chaos_cleave', 'momentum'), + ('improved_chaos_nova', "ill_swallow_your_soul", 'cull_the_weak'), + ('place_holder1', 'place_holder2', 'place_holder3') ), - 'hunter': ( - ('posthaste', 'narrow_escape', 'crouching_tiger_hidden_chimera'), - ('silencing_shot', 'wyvern_sting', 'binding_shot'), - ('exhilaration', 'aspect_of_the_iron_hawk', 'spirit_bond'), - ('fervor', 'readiness', 'thrill_of_the_hunt'), - ('a_murder_of_crows', 'dire_beast', 'lynx_rush'), - ('glaive_toss', 'powershot', 'barrage') + ('druid', 'balance'): ( + ('force_of_nature', 'warrior_of_elune', 'starlord'), + ('renewal', 'displacer_beast', 'wild_charge'), + ('feral_affinity', 'guardian_affinity', 'restoration_affinity'), + ('mighty_bash', 'mass_entanglement', 'typhoon'), + ('soul_of_the_forest', 'incarnation_chosen_of_elune', 'stellar_flare'), + ('shooting_starts', 'astral_communion', 'blessing_of_the_ancients'), + ('collapsing_stars', 'stellar_drift', 'natures_balance') ), - 'mage': ( - ('presence_of_mind', 'scorch', 'ice_floes'), - ('temporal_shield', 'blazing_speed', 'ice_barrier'), - ('ring_of_frost', 'ice_ward', 'frostjaw'), - ('greater_invisibility', 'cauterize', 'cold_snap'), - ('nether_tempest', 'living_bomb', 'frost_bomb'), - ('invocation', 'rune_of_power', 'incanters_ward') + ('hunter', 'beast_mastery'): ( + ('one_with_the_pack', 'way_of_the_cobra', 'dire_stable'), + ('posthaste', 'farstrider', 'dash'), + ('stomp', 'exptic_munitions', 'chimaera_shot'), + ('binding_shot', 'wyvern_sting', 'intimidation'), + ('big_game_hunter', 'bestial_fury', 'blink_strikes'), + ('a_murder_of_crows', 'barrage', 'volley'), + ('stampede', 'killer_cobra', 'aspect_of_the_beast') ), - 'monk': ( - ('celerity', 'tigers_lust', 'momentum'), - ('chi_wave', 'zen_sphere', 'chi_burst'), - ('power_strikes', 'ascension', 'chi_brew'), - ('deadly_reach', 'charging_ox_wave', 'leg_sweep'), - ('healing_elixirs', 'dampen_harm', 'diffuse_magic'), - ('rushing_jade_wind', 'invoke_xuen,_the_white_tiger', 'chi_torpedo') + ('mage', 'arcane'): ( + ('arcane_familiar', 'presence_of_mind', 'torrent'), + ('shimmer', 'cauterize', 'ice_block'), + ('mirror_image', 'rune_of_power', 'incanters_flow'), + ('supernova', 'charged_up', 'words_of_power'), + ('ice_floes', 'ring_of_frost', 'ice_ward'), + ('nether_tempest', 'unstable_magic', 'erosion'), + ('overpowered', 'quickening', 'arcane_orb') ), - 'paladin': ( - ('speed_of_light', 'long_arm_of_the_law', 'pursuit_of_justice'), - ('fist_of_justice', 'repentance', 'burden_of_guilt'), - ('selfless_healer', 'eternal_flame', 'sacred_shield'), - ('hand_of_purity', 'unbreakable_spirit', 'clemency'), - ('holy_avenger', 'sanctified_wrath', 'divine_purpose'), - ('holy_prism', 'lights_hammer', 'execution_sentence') + ('monk', 'windwalker'): ( + ('chi_burst', 'eye_of_the_tiger', 'chi_wave'), + ('chi_torpedo', 'tiger_lust', 'celerity'), + ('energizing_elixer', 'ascension', 'power_strikes'), + ('ring_of_peace', 'dizzying_kicks', 'leg_sweep'), + ('healing_elixirs', 'diffuse_magic', 'dampen_harm'), + ('rushing_jade_wind', 'invoke_xuen_the_white_tiger', 'hit_combo'), + ('chi_orbit', 'spinning_dragon_strike', 'serenity') ), - 'priest': ( - ('void_tendrils', 'psyfiend', 'dominate_mind'), - ('body_and_soul', 'angelic_feather', 'phantasm'), - ('from_darkness_comes_light', 'mindbender', 'power_word:_solace'), + ('paladin', 'retribution'): ( + ('execution_sentence', 'turalyons_might', 'consecration'), + ('the_fires_of_justice', 'crusaders_flurry', 'zeal'), + ('fist_of_justice', 'repentance', 'blinding_light'), + ('virtues_blade', 'blade_of_wrath', 'divine_hammer'), + ('judgements_of_the_bold', 'might_of_the_virtue', 'mass_judgement') + ('blaze_of_light', 'divine_speed', 'eye_for_an_eye'), + ('final_verdict', 'seal_of_light', 'holy_wrath') + ), + ('priest', 'shadow'): ( + ('twist_of_fate', 'fortress_of_the_mind', 'shadow_word_void'), + ('mania', 'body_and_soul', 'masochism'), + ('mind_bomb', 'psychic_voice', 'dominate_mind'), ('desperate_prayer', 'spectral_guise', 'angelic_bulwark'), ('twist_of_fate', 'power_infusion', 'divine_insight'), ('cascade', 'divine_star', 'halo') ), - 'rogue': ( + ('rogue', 'assassination'): ( + ('master_poisoner', 'elaborate_planning', 'hemorrhage'), + ('nightstalker', 'subterfuge', 'shadow_focus'), + ('deeper_strategem', 'anticipation', 'vigor'), + ('leeching_poison', 'elusiveness', 'cheat_death'), + ('thuggee', 'prey_on_the_weak', 'internal_bleeding'), + ('agonizing_poison', 'alacrity', 'blood_sweat'), + ('lemon_zest', 'marked_for_death', 'death_from_above') + ), + ('rogue', 'outlaw'): ( + ('ghostly_strike', 'swordmaster', 'quick_draw'), + ('grappling_hook', 'acrobatic_strikes', 'hit_and_run'), + ('deeper_strategem', 'anticipation', 'vigor'), + ('iron_stomach', 'elusiveness', 'cheat_death'), + ('parley', 'prey_on_the_weak', 'dirty_tricks'), + ('cannonball_barrage', 'alacrity', 'killing_spree'), + ('slice_and_dice', 'marked_for_death', 'death_from_above') + ), + ('rogue', 'subtlety'): ( + ('master_of_subtlety', 'weaponmaster', 'gloomblade'), ('nightstalker', 'subterfuge', 'shadow_focus'), - ('deadly_throw', 'nerve_strike', 'combat_readiness'), - ('cheat_death', 'leeching_poison', 'elusiveness'), - ('cloak_and_dagger', 'shadowstep', 'burst_of_speed'), - ('prey_on_the_weak', 'internal_bleeding', 'dirty_tricks'), - ('shuriken_toss', 'marked_for_death', 'anticipation'), - ('lemon_zest', 'shadow_reflection', 'death_from_above') + ('deeper_strategem', 'anticipation', 'vigor'), + ('soothing_darkness', 'elusiveness', 'cheat_death'), + ('strike_from_the_shadows', 'prey_on_the_weak', 'tangled_shadow'), + ('premeditation', 'alacrity', 'enveloping_shadows'), + ('master_of_shadows', 'marked_for_death', 'death_from_above') ), - 'shaman': ( - ('natures_guardian', 'stone_bulwark_totem', 'astral_shift'), - ('frozen_power', 'earthgrab_totem', 'windwalk_totem'), - ('call_of_the_elements', 'totemic_restoration', 'totemic_projection'), - ('elemental_mastery', 'ancestral_swiftness', 'echo_of_the_elements'), - ('healing_tide_totem', 'ancestral_guidance', 'conductivity'), - ('unleashed_fury', 'primal_elementalist', 'elemental_blast') + ('shaman', 'elemental'): ( + ('path_of_flame', 'path_of_elements', 'maelstrom_totem'), + ('gust_of_wind', 'fleet_of_foot', 'wind_rush_totem'), + ('lightening_surge_totem', 'earthgrab_totem', 'voodoo_totem'), + ('elemental_blast', 'ancestral_swiftness', 'echo_of_the_elements'), + ('elemental_fusion', 'sons_of_flame', 'magnitude'), + ('lightning_rod', 'storm_elemental', 'liquid_magma_totem'), + ('ascendance', 'primal_elementalist', 'totemic_fury') ), - 'warlock': ( - ('dark_regeneration', 'soul_leech', 'harvest_life'), - ('howl_of_terror', 'mortal_coil', 'shadowfury'), - ('soul_link', 'sacrificial_pact', 'dark_bargain'), - ('blood_fear', 'burning_rush', 'unbound_will'), - ('grimoire_of_supremacy', 'grimoire_of_service', 'grimoire_of_sacrifice'), - ('archimondes_vengeance', 'kiljaedens_cunning', 'mannoroths_fury') + ('affliction', 'warlock'): ( + ('haunt', 'writhe_in_agony', 'drain_soul'), + ('contagion', 'absolute_corruption', 'mana_tap'), + ('soul_leech', 'mortal_coil', 'howl_of_terror'), + ('siphon_life', 'sow_the_seeds', 'soul_harvest'), + ('demonic_circle', 'burning_rush', 'dark_pact'), + ('grimore_of_supremacy', 'grimore_of_service', 'grimore_of_sacrifce'), + ('soul_effigy', 'phantom_singularity', 'demonic_servitude') ), - 'warrior': ( - ('juggernaut', 'double_time', 'warbringer'), - ('enraged_regeneration', 'second_wind', 'impending_victory'), - ('staggering_shout', 'piercing_howl', 'disrupting_shout'), - ('bladestorm', 'shockwave', 'dragon_roar'), - ('mass_spell_reflection', 'safeguard', 'vigilance'), - ('avatar', 'bloodbath', 'storm_bolt') + ('warrior', 'arms'): ( + ('shockwave', 'storm_bolt', 'sweeping_strikes'), + ('impending_victory', 'bounding_stride', 'die_by_the_sword'), + ('dauntless', 'overpower', 'avatar'), + ('second_wind', 'double_time', 'imposing_roar'), + ('fervor_of_battle', 'rend', 'bladestorm'), + ('heroic_strike', 'mortal_combo', 'titanic_might'), + ('anger_management', 'opportunity_strikes', 'ravager') ), } From bebc2de11eb1b8c89e1e3b3ea4e2c28f41acab9b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 31 May 2016 19:02:24 -0400 Subject: [PATCH 023/265] Initial artifact implementation:non-functional -Rogue artifact data added --- shadowcraft/objects/artifact.py | 38 +++++++++++++++++ shadowcraft/objects/artifact_data.py | 62 ++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 shadowcraft/objects/artifact.py create mode 100644 shadowcraft/objects/artifact_data.py diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py new file mode 100644 index 0000000..6464362 --- /dev/null +++ b/shadowcraft/objects/artifact.py @@ -0,0 +1,38 @@ +from shadowcraft.core import exceptions +from shadowcraft.objects import artifact_data + +class InvalidTraitException(exceptions.InvalidInputException): + pass + +class Artifact(object): + def __init__(self, class_spec, game_class, trait_string='', trait_dict= {}): + self.allowed_traits = artifact_data.traits[(game_class, class_spec)] + + if trait_string: + self.initialize_traits(trait_string) + + else: + self.traits = {} + for trait in self.allowed_traits: + if trait in trait_dict: + self.traits[trait] = trait_dict[trait] + else: + self.traits[trait] = 0 + + + def __getattr__(self, attr): + if attr in self.traits: + return self.traits[attr] + return False + + def __setattr__(self, attr, value): + if attr not in self.traits: + raise InvalidTraitException(_('Invalid trait name {trait}').format(trait=attr)) + self.traits[attr] = value + + def initialize_traits(self, trait_string): + if len(trait_string) != 18: + raise InvalidTraitException(_('Trait strings must be 18 characters long')) + self.traits = {} + for trait in xrange(18): + setattr(self, self.allowed_traits[trait], int(trait_string[trait])) \ No newline at end of file diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py new file mode 100644 index 0000000..410845b --- /dev/null +++ b/shadowcraft/objects/artifact_data.py @@ -0,0 +1,62 @@ +traits = { + ('rogue', 'assassination'): ( + 'kingsbane', + 'assassins_blades', + 'toxic_blades', + 'poison_knives', + 'urge_to_kill', + 'balanced_blades', + 'surge_of_toxins', + 'shadow_walker', + 'master_assassin', + 'shadow_swiftness', + 'serrated_edge', + 'bag_of_tricks', + 'master_alchemist', + 'gushing_wounds', + 'fade_into_shadows', + 'from_the_shadows', + 'blood_of_the_assassinated', + 'slayers_precision', + ), + ('rogue', 'outlaw'): ( + 'curse_of_the_dreadblades', + 'cursed_edges', + 'fates_thirst', + 'blade_dancer', + 'fatebringer', + 'gunslinger', + 'hidden_blade', + 'fortune_strikes', + 'ghostly_shell', + 'deception', + 'black_powder', + 'greed', + 'blurred_time', + 'fortunes_boon', + 'fortunes_strike', + 'blademaster', + 'blunderbuss', + 'cursed_steel', + ), + ('rogue', 'subtlety'): ( + 'goremaws_bite', + 'shadow_fangs', + 'gutripper', + 'fortunes_bite', + 'catlike_reflexes', + 'embrace_of_darkness', + 'ghost_armor', + 'precision_strike', + 'energetic_stabbing', + 'flickering_shadows', + 'second_shuriken', + 'demons_kiss', + 'finality', + 'the_quiet_knife', + 'akarris_soul', + 'soul_shadows', + 'shadow_nova', + 'legionblade' + ), +} \ No newline at end of file From b9525a5631b755f2a23e483fe38fff05f5439588 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 1 Jun 2016 14:25:58 -0400 Subject: [PATCH 024/265] Artifact ranking function--non-functional -Modeled after talent ranker, may need to modify semantics for artifact traits but good enough for a first pass. --- shadowcraft/calcs/__init__.py | 73 ++++++++++++++++++++++----------- shadowcraft/objects/artifact.py | 5 ++- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 79cfc51..ccc1ac6 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -8,6 +8,7 @@ from shadowcraft.calcs import armor_mitigation from shadowcraft.objects import class_data from shadowcraft.objects import talents +from shadowcraft.objects import artifact from shadowcraft.objects import procs from shadowcraft.objects.procs import InvalidProcException @@ -22,16 +23,16 @@ class DamageCalculator(object): TARGET_BASE_ARMOR_VALUES = {88:11977., 93:24835., 103:100000.} AOE_TARGET_CAP = 20 - + # Override this in your class specfic subclass to list appropriate stats # possible values are agi, str, spi, int, haste, crit, mastery default_ep_stats = [] # normalize_ep_stat is the stat with value 1 EP, override in your subclass normalize_ep_stat = None - def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '6.2.0' # should reflect the game patch being targetted - self.SHADOWCRAFT_BUILD = '1.02' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch + def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=110, target_level=None, char_class='rogue'): + self.WOW_BUILD_TARGET = '7.0.0' # should reflect the game patch being targetted + self.SHADOWCRAFT_BUILD = '0.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats self.talents = talents @@ -47,16 +48,16 @@ def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100 self.stats.procs.set_proc('touch_of_the_grave') if self.race.race_name == 'goblin': self.stats.procs.set_proc('rocket_barrage') - + self.level_difference = max(self.target_level - level, 0) self.base_one_hand_miss_rate = 0 self.base_parry_chance = .01 * self.level_difference self.base_dodge_chance = 0 - + self.dw_miss_penalty = .17 self._set_constants_for_class() self.level = level - + self.recalculate_hit_constants() self.base_block_chance = .03 + .015 * self.level_difference @@ -92,10 +93,10 @@ def _set_constants_for_class(self): if self.talents.game_class != self.glyphs.game_class: raise exceptions.InvalidInputException(_('You must specify the same class for your talents and glyphs')) self.game_class = self.talents.game_class - + def recalculate_hit_constants(self): self.base_dw_miss_rate = self.base_one_hand_miss_rate + self.dw_miss_penalty - + def get_adv_param(self, type, default_val, min_bound=-10000, max_bound=10000, ignore_bounds=False): if type in self.settings.adv_params and not ignore_bounds: return max( min(float(self.settings.adv_params[type]), max_bound), min_bound ) @@ -104,12 +105,12 @@ def get_adv_param(self, type, default_val, min_bound=-10000, max_bound=10000, ig else: return default_val raise exceptions.InvalidInputException(_('Improperly defined parameter type: '+type)) - + def add_exported_data(self, damage_breakdown): #used explicitly to highjack data outputs to export additional data. if self.get_version_number: damage_breakdown['version_' + self.WOW_BUILD_TARGET + '_' + self.SHADOWCRAFT_BUILD] = [.0, 0] - + def set_rppm_uptime(self, proc): #http://iam.yellingontheinternet.com/2013/04/12/theorycraft-201-advanced-rppm/ haste = 1. @@ -129,7 +130,7 @@ def set_rppm_uptime(self, proc): else: mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate()) + proc.icd - min(proc.icd, 10) proc.uptime = 1.1307 * proc.duration / mean_proc_time - + def set_uptime(self, proc, attacks_per_second, crit_rates): if proc.is_real_ppm(): self.set_rppm_uptime(proc) @@ -151,7 +152,7 @@ def set_uptime(self, proc, attacks_per_second, crit_rates): else: P = 1 - Q proc.uptime = P * (1 - P ** proc.max_stacks) / Q - + def average_damage_breakdowns(self, aps_dict, denom=180): final_breakdown = {} #key: phase name @@ -165,7 +166,7 @@ def average_damage_breakdowns(self, aps_dict, denom=180): else: final_breakdown[entry] = aps_dict[key][1][entry] * (aps_dict[key][0]/denom) return final_breakdown - + def ep_helper(self, stat): setattr(self.stats, stat, getattr(self.stats, stat) + 1.) dps = self.get_dps() @@ -177,10 +178,10 @@ def get_ep(self, ep_stats=None, normalize_ep_stat=None, baseline_dps=None): normalize_ep_stat = self.get_adv_param('normalize_stat', self.settings.default_ep_stat, ignore_bounds=True) if not ep_stats: ep_stats = self.default_ep_stats - + if baseline_dps == None: baseline_dps = self.get_dps() - + if normalize_ep_stat == 'dps': normalize_dps_difference = 1. else: @@ -188,7 +189,7 @@ def get_ep(self, ep_stats=None, normalize_ep_stat=None, baseline_dps=None): normalize_dps_difference = normalize_dps - baseline_dps if normalize_dps_difference == 0: normalize_dps_difference = 1 - + ep_values = {} for stat in ep_stats: ep_values[stat] = 1.0 @@ -395,14 +396,14 @@ def get_other_ep(self, list, normalize_ep_stat=None): delattr(self.stats.procs, i) return ep_values - + def get_upgrades_ep(self, _list, normalize_ep_stat=None): if not normalize_ep_stat: normalize_ep_stat = self.normalize_ep_stat # This method computes ep for every other buff/proc not covered by # get_ep or get_weapon_ep. Weapon enchants, being tied to the # weapons they are on, are computed by get_weapon_ep. - + active_procs_cache = [] procs_list = [] ep_values = {} @@ -465,7 +466,7 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) # This method computes ep for every other buff/proc not covered by # get_ep or get_weapon_ep. Weapon enchants, being tied to the # weapons they are on, are computed by get_weapon_ep. - + active_procs_cache = [] #procs removed by ranker, all procs if no exclude_list provided procs_list = [] #holds all procs to consider ep_values = {} @@ -474,13 +475,13 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) if i in self.stats.procs.allowed_procs: procs_list.append( (i, _list[i]) ) #if an excludelist is provided only add values on exclude_list to active_proc_cache - #if no exclude_list add all procs to active_proc_cache + #if no exclude_list add all procs to active_proc_cache if (exclude_list and i in exclude_list) or (not exclude_list and getattr(self.stats.procs, i)): active_procs_cache.append((i, getattr(self.stats.procs, i).item_level)) delattr(self.stats.procs, i) else: ep_values[i] = _('not allowed') - + baseline_dps = self.get_dps() normalize_dps = self.ep_helper(normalize_ep_stat) @@ -571,9 +572,31 @@ def get_talents_ranking(self, list=None): except: talents_ranking[talent] = _('not implemented') setattr(self.talents, talent, not getattr(self.talents, talent)) - + return talents_ranking + def get_artifact_ranking(self, list=None): + trait_ranking = {} + baseline_dps = self.get_dps() + trait_list = [] + + if not list: + trait_list = self.artifact.get_trait_list() + else: + trait_list = list + + for trait in trait_list: + base_trait_rank = getattr(self.artifact, trait) + setattr(self.artifact, trait, base_trait_rank+1) + try: + new_dps = self.get_dps() + if new_dps != baseline_dps: + trait_ranking[trait] = abs(new_dps-baseline_dps) + except: + trait_ranking[trait] = _('not_implemented') + setattr(self.artifact, trait, base_trait_rank) + + def get_engine_info(self): data = { 'wow_build_target': self.WOW_BUILD_TARGET, @@ -593,10 +616,10 @@ def get_dps(self): def armor_mitigation_multiplier(self, armor): return armor_mitigation.multiplier(armor, cached_parameter=self.armor_mitigation_parameter) - + def max_level_armor_multiplier(self): return 3610.0 / (3610.0 + 1938.0) - + def get_trinket_cd_reducer(self): trinket_cd_reducer_value = .0 proc = getattr(self.stats.procs, 'assurance_of_consequence') diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 6464362..c56179a 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -35,4 +35,7 @@ def initialize_traits(self, trait_string): raise InvalidTraitException(_('Trait strings must be 18 characters long')) self.traits = {} for trait in xrange(18): - setattr(self, self.allowed_traits[trait], int(trait_string[trait])) \ No newline at end of file + setattr(self, self.allowed_traits[trait], int(trait_string[trait])) + + def get_trait_list(self): + return list(self.allowed_traits) \ No newline at end of file From 014725f986447abc8fc4ec6c7c143238d00a29aa Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 14 Jun 2016 15:04:40 -0400 Subject: [PATCH 025/265] Update DamageCalculator and armor mitigation -armor_mitigation file removed, parameters moved to class_data -general clean-up in DamageCalculator --- shadowcraft/calcs/armor_mitigation.py | 38 --------------------------- 1 file changed, 38 deletions(-) delete mode 100644 shadowcraft/calcs/armor_mitigation.py diff --git a/shadowcraft/calcs/armor_mitigation.py b/shadowcraft/calcs/armor_mitigation.py deleted file mode 100644 index 81ff521..0000000 --- a/shadowcraft/calcs/armor_mitigation.py +++ /dev/null @@ -1,38 +0,0 @@ -from shadowcraft.core.exceptions import InvalidLevelException - -# tiered parameters for use in armor mitigation calculations. first tuple -# element is the minimum level of the tier. the tuples must be in descending -# order of minimum level for the lookup to work. parameters taken from -# http://code.google.com/p/simulationcraft/source/browse/branches/mop/engine/sc_player.cpp#1365 -PARAMETERS = [ (91, 1938, 3610.0), #lvl 100 371830 3610.0 - (86, 1044, 1945.0), #lvl 90 - (81, 8000, 1047.0), #lvl 85 - (60, 4000, 747.0), - ( 1, 85.0, 157.0) ] - -def _get_appropriate_level_for_armor_table(level): - for i in xrange(0, len(PARAMETERS)): - if level >= PARAMETERS[i][0]: - return i - -def lookup_parameters(level): - for parameters in PARAMETERS: - if level >= parameters[0]: - return parameters - raise InvalidLevelException(_('No armor mitigation parameters available for level {level}').format(level=level)) - -def parameter(level=100): - parameters = lookup_parameters(level) - return level * parameters[1] - parameters[2] - -# this is the fraction of damage reduced by the armor -def mitigation(armor, level=100, cached_parameter=None): - if cached_parameter == None: - cached_parameter = parameter(level) - #cached_parameter = lookup_parameters(level) - return armor / (armor + cached_parameter) - -# this is the fraction of damage retained despite the armor, 1 - mitigation. -def multiplier(armor, level=100, cached_parameter=None): - table_level = _get_appropriate_level_for_armor_table(level) - return PARAMETERS[table_level][2] / (PARAMETERS[table_level][1] + PARAMETERS[table_level][2]) \ No newline at end of file From a446027ee84bc187f0798b3203c3f19ef7a494c3 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 14 Jun 2016 15:12:53 -0400 Subject: [PATCH 026/265] Remove Glyph Support --- shadowcraft/objects/glyphs.py | 16 --------- shadowcraft/objects/glyphs_data.py | 56 ------------------------------ 2 files changed, 72 deletions(-) delete mode 100644 shadowcraft/objects/glyphs.py delete mode 100644 shadowcraft/objects/glyphs_data.py diff --git a/shadowcraft/objects/glyphs.py b/shadowcraft/objects/glyphs.py deleted file mode 100644 index fa5e172..0000000 --- a/shadowcraft/objects/glyphs.py +++ /dev/null @@ -1,16 +0,0 @@ -from shadowcraft.objects import glyphs_data - -class Glyphs(object): - - def __init__(self, game_class='rogue', *args): - self.game_class = game_class - self.allowed_glyphs = glyphs_data.glyphs[game_class] - for arg in args: - if arg in self.allowed_glyphs: - setattr(self, arg, True) - - def __getattr__(self, name): - # Any glyph we haven't assigned a value to, we don't have. - if name in self.allowed_glyphs: - return False - object.__getattribute__(self, name) diff --git a/shadowcraft/objects/glyphs_data.py b/shadowcraft/objects/glyphs_data.py deleted file mode 100644 index d3ff283..0000000 --- a/shadowcraft/objects/glyphs_data.py +++ /dev/null @@ -1,56 +0,0 @@ -glyphs = { - 'death_knight': frozenset([]), - 'druid': frozenset([]), - 'hunter': frozenset([]), - 'mage': frozenset([]), - 'monk': frozenset([]), - 'paladin': frozenset([]), - 'priest': frozenset([]), - 'rogue': frozenset([ - # Major - 'adrenaline_rush', - 'ambush', - 'blade_flurry', - 'blind', - 'cheap_shot', - 'cloak_of_shadows', - 'crippling_poison', - 'deadly_momentum', - 'debilitation', - 'evasion', - 'expose_armor', - 'feint', - 'garrote', - 'gouge', - 'kick', - 'recuperate', - 'sap', - 'shadow_walk', - 'shiv', - 'smoke_bomb', - 'sprint', - 'stealth', - 'vanish', - 'vendetta', - 'disappearance', - 'energy', - 'elusiveness', - 'energy_flows', - # Minor - 'blurred_speed', - 'decoy', - 'detection', - 'disguise', - 'distract', - 'hemorrhage', - 'killing_spree', - 'pick_lock', - 'pick_pocket', - 'poisons', - 'safe_fall', - 'tricks_of_the_trade', - ]), - 'shaman': frozenset([]), - 'warlock': frozenset([]), - 'warrior': frozenset([]), -} From 7c4945c3c7396221291453909a1e86068af1f533 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 14 Jun 2016 15:14:52 -0400 Subject: [PATCH 027/265] Update DamageCalculator and Code Cleanup --- shadowcraft/calcs/__init__.py | 69 ++++++---------------------- shadowcraft/objects/artifact.py | 4 +- shadowcraft/objects/artifact_data.py | 12 ++--- shadowcraft/objects/class_data.py | 53 +++++++++++++++++++-- shadowcraft/objects/talents_data.py | 2 +- 5 files changed, 73 insertions(+), 67 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index ccc1ac6..69cd6ad 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -21,27 +21,25 @@ class DamageCalculator(object): # calcs..DamageCalculator instead - for an example, see # calcs.rogue.RogueDamageCalculator - TARGET_BASE_ARMOR_VALUES = {88:11977., 93:24835., 103:100000.} - AOE_TARGET_CAP = 20 - # Override this in your class specfic subclass to list appropriate stats # possible values are agi, str, spi, int, haste, crit, mastery default_ep_stats = [] # normalize_ep_stat is the stat with value 1 EP, override in your subclass normalize_ep_stat = None - def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=110, target_level=None, char_class='rogue'): + def __init__(self, stats, talents, traits, buffs, race, class_spec, settings=None, level=110, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '7.0.0' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = '0.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats self.talents = talents - self.glyphs = glyphs + self.traits = traits self.buffs = buffs self.race = race self.char_class = char_class + self.class_spec = class_spec self.settings = settings - self.target_level = [target_level, level + 3][target_level is None] #assumes 3 levels higher if not explicit + self.target_level = target_level if target_level else level+3 #assumes 3 levels higher if not explicit #racials if self.race.race_name == 'undead': @@ -78,20 +76,18 @@ def _set_constants_for_level(self): self.race.level = self.level self.stats.gear_buffs.level = self.level # calculate and cache the level-dependent armor mitigation parameter - self.armor_mitigation_parameter = armor_mitigation.parameter(self.level) + self.attacker_k_value = self.tools.get_k_value(self.level) # target level dependent constants - try: - self.target_base_armor = self.TARGET_BASE_ARMOR_VALUES[self.target_level] - except KeyError as e: - raise exceptions.InvalidInputException(_('There\'s no armor value for a target level {level}').format(level=str(e))) - self.crit_reduction = .01 * self.level_difference + self.target_base_armor = self.tools.get_base_armor(self.target_level) + + #Crit suppression removed in Legion + #Source: http://blue.mmo-champion.com/topic/409203-theorycrafting-questions/#post274 + self.crit_reduction = 0 def _set_constants_for_class(self): # These factors are class-specific. Generaly those go in the class module, # unless it's basic stuff like combat ratings or base stats that we can # datamine for all classes/specs at once. - if self.talents.game_class != self.glyphs.game_class: - raise exceptions.InvalidInputException(_('You must specify the same class for your talents and glyphs')) self.game_class = self.talents.game_class def recalculate_hit_constants(self): @@ -531,28 +527,6 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) return ep_values - def get_glyphs_ranking(self, list=None): - glyphs = [] - glyphs_ranking = {} - baseline_dps = self.get_dps() - - if list == None: - glyphs = self.glyphs.allowed_glyphs - else: - glyphs = list - - for i in glyphs: - setattr(self.glyphs, i, not getattr(self.glyphs, i)) - try: - new_dps = self.get_dps() - if new_dps != baseline_dps: - glyphs_ranking[i] = abs(new_dps - baseline_dps) - except: - glyphs_ranking[i] = _('not implemented') - setattr(self.glyphs, i, not getattr(self.glyphs, i)) - - return glyphs_ranking - def get_talents_ranking(self, list=None): talents_ranking = {} baseline_dps = self.get_dps() @@ -596,7 +570,6 @@ def get_artifact_ranking(self, list=None): trait_ranking[trait] = _('not_implemented') setattr(self.artifact, trait, base_trait_rank) - def get_engine_info(self): data = { 'wow_build_target': self.WOW_BUILD_TARGET, @@ -614,21 +587,10 @@ def get_dps(self): # gear_boosts = self.stats.gear_buffs.get_all_activated_boosts() # return racial_boosts + gear_boosts - def armor_mitigation_multiplier(self, armor): - return armor_mitigation.multiplier(armor, cached_parameter=self.armor_mitigation_parameter) - - def max_level_armor_multiplier(self): - return 3610.0 / (3610.0 + 1938.0) - - def get_trinket_cd_reducer(self): - trinket_cd_reducer_value = .0 - proc = getattr(self.stats.procs, 'assurance_of_consequence') - if proc and proc.scaling: - trinket_cd_reducer_value = 0.2532840073 / 100 * self.tools.get_random_prop_point(proc.item_level) - if self.level == 100: - trinket_cd_reducer_value *= 23./110. - return 1 / (1 + trinket_cd_reducer_value) - return 1 + def armor_mitigation_multiplier(self, armor=None): + if not armor: + armor = self.target_base_armor + return self.attacker_k_value / (self.attacker_k_value + armor) def armor_mitigate(self, damage, armor): # Pass in raw physical damage and armor value, get armor-mitigated @@ -728,5 +690,4 @@ def raid_settings_modifiers(self, attack_kind, armor=None, affect_resil=True): elif attack_kind == 'bleed': return self.buffs.bleed_damage_multiplier() elif attack_kind == 'physical': - armor_override = self.target_armor(armor) - return self.buffs.physical_damage_multiplier() * self.armor_mitigation_multiplier(armor_override) + return self.buffs.physical_damage_multiplier() * self.armor_mitigation_multiplier(armor) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index c56179a..67dac28 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -31,10 +31,10 @@ def __setattr__(self, attr, value): self.traits[attr] = value def initialize_traits(self, trait_string): - if len(trait_string) != 18: + if len(trait_string) != len(self.allowed_traits): raise InvalidTraitException(_('Trait strings must be 18 characters long')) self.traits = {} - for trait in xrange(18): + for trait in xrange(len(self.allowed_traits)): setattr(self, self.allowed_traits[trait], int(trait_string[trait])) def get_trait_list(self): diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 410845b..694eab6 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -1,7 +1,7 @@ traits = { ('rogue', 'assassination'): ( - 'kingsbane', - 'assassins_blades', + #'kingsbane', + #'assassins_blades', 'toxic_blades', 'poison_knives', 'urge_to_kill', @@ -20,8 +20,8 @@ 'slayers_precision', ), ('rogue', 'outlaw'): ( - 'curse_of_the_dreadblades', - 'cursed_edges', + #'curse_of_the_dreadblades', + #'cursed_edges', 'fates_thirst', 'blade_dancer', 'fatebringer', @@ -40,8 +40,8 @@ 'cursed_steel', ), ('rogue', 'subtlety'): ( - 'goremaws_bite', - 'shadow_fangs', + #'goremaws_bite', + #'shadow_fangs', 'gutripper', 'fortunes_bite', 'catlike_reflexes', diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index c78f7e0..690c349 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -8,7 +8,7 @@ class Util(object): 5:"priest", 6:"death_knight", 7:"shaman", 8:"mage", 9:"warlock", 10:"monk", 11:"druid" } - + #constant scaling (for level based calculations and the like) CONSTANT_SCALING = [ 3.000000000000000, 3.000000000000000, 4.000000000000000, 4.000000000000000, 5.000000000000000, @@ -33,6 +33,42 @@ class Util(object): 225.000000000000000, 234.000000000000000, 242.000000000000000, 252.000000000000000, 261.000000000000000, ] + #Base armor values for enemy level + #Source: http://blue.mmo-champion.com/topic/409203-theorycrafting-questions/#post109 + BASE_ARMOR = {100: 1536., + 101: 2313., + 102: 2388., + 103: 2467., + 104: 2550., + 105: 2638., + 106: 2729., + 107: 2826., + 108: 2927., + 109: 3035., + 110: 3144., + 111: 3254., + 112: 3364., + 113: 3474., + } + + #K value for armor mitigation computation + #Source: http://blue.mmo-champion.com/topic/409203-theorycrafting-questions/#post46 + ATTACKER_K_VALUE = {100: 3610., + 101: 3973., + 102: 4373., + 103: 4812., + 104: 5296., + 105: 5829., + 106: 6415., + 107: 6642., + 108: 6880., + 109: 7132., + 110: 7390., + 111: 7648., + 112: 7906., + 113: 8164., + } + #SimC keeps their enchant scale data here: https://code.google.com/p/simulationcraft/source/browse/engine/dbc/sc_scale_data.inc#342 #these, however, are the trinket scale data RANDOM_PROP_POINTS = [ @@ -1038,7 +1074,7 @@ class Util(object): [999,9886], [1000,9978], ] - + def get_class_number(self, game_class): for i in self.GAME_CLASS_NUMBER.keys(): if self.GAME_CLASS_NUMBER[i] == game_class: @@ -1055,7 +1091,16 @@ def get_random_prop_point(self, item_level): if item_level < 1: raise exceptions.InvalidInputException(_('item_level={item_level} need to be >= 1').format(item_level=item_level)) return self.RANDOM_PROP_POINTS[item_level][1] - + def get_constant_scaling_point(self, level): return self.CONSTANT_SCALING[level-1] - + + def get_base_armor(self, level): + if level < 100: + raise exceptions.InvalidInputException(_('There\'s no armor value for a target level {level}').format(level=str(e))) + return self.BASE_ARMOR[level] + + def get_k_value(self, level): + if level < 100: + raise exceptions.InvalidInputException(_('There\'s no k value for a level {level} player').format(level=str(e))) + return self.ATTACKER_K_VALUE[level] \ No newline at end of file diff --git a/shadowcraft/objects/talents_data.py b/shadowcraft/objects/talents_data.py index cefea00..c56b064 100644 --- a/shadowcraft/objects/talents_data.py +++ b/shadowcraft/objects/talents_data.py @@ -58,7 +58,7 @@ ('the_fires_of_justice', 'crusaders_flurry', 'zeal'), ('fist_of_justice', 'repentance', 'blinding_light'), ('virtues_blade', 'blade_of_wrath', 'divine_hammer'), - ('judgements_of_the_bold', 'might_of_the_virtue', 'mass_judgement') + ('judgements_of_the_bold', 'might_of_the_virtue', 'mass_judgement'), ('blaze_of_light', 'divine_speed', 'eye_for_an_eye'), ('final_verdict', 'seal_of_light', 'holy_wrath') ), From b7f5b6582a107af31e8d6e8b5163cf12ee8b0d68 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 4 Jul 2016 11:31:18 -0400 Subject: [PATCH 028/265] Fix Artifact System New system is tested and actually works --- shadowcraft/objects/artifact.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 67dac28..9a9126e 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -25,17 +25,17 @@ def __getattr__(self, attr): return self.traits[attr] return False - def __setattr__(self, attr, value): - if attr not in self.traits: - raise InvalidTraitException(_('Invalid trait name {trait}').format(trait=attr)) - self.traits[attr] = value + def set_trait(self, trait, value): + if trait not in self.allowed_traits: + raise InvalidTraitException(_('Invalid trait name {trait}').format(trait=trait)) + self.traits[trait] = value def initialize_traits(self, trait_string): if len(trait_string) != len(self.allowed_traits): - raise InvalidTraitException(_('Trait strings must be 18 characters long')) + raise InvalidTraitException(_('Trait strings must be 16 characters long')) self.traits = {} for trait in xrange(len(self.allowed_traits)): - setattr(self, self.allowed_traits[trait], int(trait_string[trait])) + self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) def get_trait_list(self): return list(self.allowed_traits) \ No newline at end of file From 3bc5815dc20c576623ff293cf322b6910aa626ff Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 4 Jul 2016 13:02:00 -0400 Subject: [PATCH 029/265] Update most object for Legion May need further changes on proc, buffs and racials but this should cover most major changes. --- shadowcraft/objects/buffs.py | 140 +++++++++++++----------------- shadowcraft/objects/class_data.py | 4 +- shadowcraft/objects/race.py | 9 +- shadowcraft/objects/stats.py | 44 ++-------- 4 files changed, 73 insertions(+), 124 deletions(-) diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index 1bbc441..5d92e94 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -8,23 +8,21 @@ class Buffs(object): allowed_buffs = frozenset([ 'short_term_haste_buff', # Heroism/Blood Lust, Time Warp - 'stat_multiplier_buff', # Mark of the Wild, Blessing of Kings, Legacy of the Emperor - 'crit_chance_buff', # Leader of the Pack, Legacy of the White Tiger, Arcane Brillance - 'haste_buff', # Swiftblade's Cunning, Unholy Aura - 'multistrike_buff', # Swiftblade's Cunning, ... - 'attack_power_buff', # Horn of Winter, Trueshot Aura, Battle Shout - 'mastery_buff', # Blessing of Might, Grace of Air - 'stamina_buff', # PW: Fortitude, Blood Pact, Commanding Shout - 'versatility_buff', # - 'spell_power_buff', # Dark Intent, Arcane Brillance + #'stat_multiplier_buff', # Mark of the Wild, Blessing of Kings, Legacy of the Emperor + #'crit_chance_buff', # Leader of the Pack, Legacy of the White Tiger, Arcane Brillance + #'haste_buff', # Swiftblade's Cunning, Unholy Aura + #'multistrike_buff', # Swiftblade's Cunning, ... + #'attack_power_buff', # Horn of Winter, Trueshot Aura, Battle Shout + #'mastery_buff', # Blessing of Might, Grace of Air + #'stamina_buff', # PW: Fortitude, Blood Pact, Commanding Shout + #'versatility_buff', # + #'spell_power_buff', # Dark Intent, Arcane Brillance #'armor_debuff', # Sunder, Expose Armor, Faerie Fire - 'physical_vulnerability_debuff', # Brittle Bones, Ebon Plaguebringer, Judgments of the Bold, Colossus Smash - 'spell_damage_debuff', # Master Poisoner, Curse of Elements + #'physical_vulnerability_debuff', # Brittle Bones, Ebon Plaguebringer, Judgments of the Bold, Colossus Smash + #'spell_damage_debuff', # Master Poisoner, Curse of Elements #'slow_casting_debuff', 'mortal_wounds_debuff', # consumables - 'agi_flask_mop', # Flask of Spring Blossoms - 'food_mop_agi', # Sea Mist Rice Noodles 'flask_wod_agi_200', # 'flask_wod_agi', # 250 'food_wod_mastery', # 100 @@ -39,30 +37,42 @@ class Buffs(object): 'food_wod_versatility', # 'food_wod_versatility_75', # 'food_wod_versatility_125', # - 'food_wod_multistrike', # - 'food_wod_multistrike_75', # - 'food_wod_multistrike_125', # 'food_felmouth_frenzy', # Felmouth frenzy, 2 haste scaling RPPM dealing 0.424 AP in damage + ###LEGION### + 'flask_legion_agi', #Flask of the Seventh Demon + 'food_legion_masstery_450', # + 'food_legion_crit_450', # + 'food_legion_haste_450', # + 'food_legion_haste_450', # + 'food_legion_masstery_600', # + 'food_legion_crit_600', # + 'food_legion_haste_600', # + 'food_legion_haste_600', # + 'food_legion_masstery_700', # + 'food_legion_crit_700', # + 'food_legion_haste_700', # + 'food_legion_haste_700', # + 'food_legion_damage_1', # + 'food_legion_damage_2', # + 'food_legion_damage_3', # ]) buffs_debuffs = frozenset([ 'short_term_haste_buff', # Heroism/Blood Lust, Time Warp - 'stat_multiplier_buff', # Mark of the Wild, Blessing of Kings, Legacy of the Emperor - 'crit_chance_buff', # Leader of the Pack, Legacy of the White Tiger, Arcane Brillance - 'haste_buff', # Swiftblade's Cunning, Unholy Aura - 'multistrike_buff', # Swiftblade's Cunning, ... - 'attack_power_buff', # Horn of Winter, Trueshot Aura, Battle Shout - 'mastery_buff', # Blessing of Might, Grace of Air - 'spell_power_buff', # Dark Intent, Arcane Brillance - 'versatility_buff', - 'stamina_buff', # PW: Fortitude, Blood Pact, Commanding Shout - 'physical_vulnerability_debuff', # Brittle Bones, Ebon Plaguebringer, Judgments of the Bold, Colossus Smash - 'spell_damage_debuff', # Master Poisoner, Curse of Elements + #'stat_multiplier_buff', # Mark of the Wild, Blessing of Kings, Legacy of the Emperor + #'crit_chance_buff', # Leader of the Pack, Legacy of the White Tiger, Arcane Brillance + #'haste_buff', # Swiftblade's Cunning, Unholy Aura + #'multistrike_buff', # Swiftblade's Cunning, ... + #'attack_power_buff', # Horn of Winter, Trueshot Aura, Battle Shout + #'mastery_buff', # Blessing of Might, Grace of Air + #'spell_power_buff', # Dark Intent, Arcane Brillance + #'versatility_buff', + #'stamina_buff', # PW: Fortitude, Blood Pact, Commanding Shout + #'physical_vulnerability_debuff', # Brittle Bones, Ebon Plaguebringer, Judgments of the Bold, Colossus Smash + #'spell_damage_debuff', # Master Poisoner, Curse of Elements 'mortal_wounds_debuff', ]) - buff_scaling = {80: 91, 85: 122, 90: 141, 100: 550} - def __init__(self, *args, **kwargs): for buff in args: if buff not in self.allowed_buffs: @@ -81,52 +91,13 @@ def __setattr__(self, name, value): if name == 'level': self._set_constants_for_level() - def _set_constants_for_level(self): - try: - self.mast_buff_bonus = self.buff_scaling[self.level] - except KeyError as e: - raise exceptions.InvalidLevelException(_('No conversion factor available for level {level}').format(level=self.level)) - - def get_max_buffs(self): - return frozenset(buffs_debuffs + ['food_wod_multistrike_125', 'flask_wod_agi']) - - def stat_multiplier(self): - return [1, 1.05][self.stat_multiplier_buff] - - def spell_damage_multiplier(self): - return [1, 1.08][self.spell_damage_debuff] - - def physical_damage_multiplier(self): - return [1, 1.05][self.physical_vulnerability_debuff] - - def bleed_damage_multiplier(self): - return self.physical_damage_multiplier() - - def attack_power_multiplier(self): - return [1, 1.1][self.attack_power_buff] - - def haste_multiplier(self): - return [1, 1.05][self.haste_buff] - - def versatility_bonus(self): - return [0, 0.03][self.versatility_buff] - - def buff_all_crit(self): - return [0, .05][self.crit_chance_buff] - - def multistrike_bonus(self): - return [0, 0.05][self.multistrike_buff] - - #stat buffs - def buff_str(self, race=False): - return 0 - def buff_agi(self, race=False): bonus_agi = 0 bonus_agi += 114 * self.agi_flask_mop bonus_agi += 200 * self.flask_wod_agi_200 bonus_agi += 250 * self.flask_wod_agi bonus_agi += 34 * self.food_mop_agi * [1, 2][race] + bonus_agi += 1300 * self.flask_legion_agi return bonus_agi def buff_haste(self, race=False): @@ -134,6 +105,9 @@ def buff_haste(self, race=False): bonus_haste += 125 * self.food_wod_haste_125 * [1, 2][race] bonus_haste += 100 * self.food_wod_haste * [1, 2][race] bonus_haste += 75 * self.food_wod_haste_75 * [1, 2][race] + bonus_haste += 450 * self.food_wod_haste_450 * [1, 2][race] + bonus_haste += 600 * self.food_wod_haste_600 * [1, 2][race] + bonus_haste += 700 * self.food_wod_haste_700 * [1, 2][race] return bonus_haste def buff_crit(self, race=False): @@ -141,6 +115,9 @@ def buff_crit(self, race=False): bonus_crit += 125 * self.food_wod_crit_125 * [1, 2][race] bonus_crit += 100 * self.food_wod_crit * [1, 2][race] bonus_crit += 75 * self.food_wod_crit_75 * [1, 2][race] + bonus_crit += 450 * self.food_wod_crit_450 * [1, 2][race] + bonus_crit += 600 * self.food_wod_crit_600 * [1, 2][race] + bonus_crit += 700 * self.food_wod_crit_700 * [1, 2][race] return bonus_crit def buff_mast(self, race=False): @@ -149,6 +126,9 @@ def buff_mast(self, race=False): bonus_mastery += 125 * self.food_wod_mastery_125 * [1, 2][race] bonus_mastery += 100 * self.food_wod_mastery * [1, 2][race] bonus_mastery += 75 * self.food_wod_mastery_75 * [1, 2][race] + bonus_mastery += 450 * self.food_wod_mastery_450 * [1, 2][race] + bonus_mastery += 600 * self.food_wod_mastery_600 * [1, 2][race] + bonus_mastery += 700 * self.food_wod_mastery_700 * [1, 2][race] return bonus_mastery def buff_versatility(self, race=False): @@ -156,19 +136,21 @@ def buff_versatility(self, race=False): bonus_versatility += 125 * self.food_wod_versatility_125 * [1, 2][race] bonus_versatility += 100 * self.food_wod_versatility * [1, 2][race] bonus_versatility += 75 * self.food_wod_versatility_75 * [1, 2][race] + bonus_versatility += 450 * self.food_wod_versatility_450 * [1, 2][race] + bonus_versatility += 600 * self.food_wod_versatility_600 * [1, 2][race] + bonus_versatility += 700 * self.food_wod_versatility_700 * [1, 2][race] return bonus_versatility - - def buff_multistrike(self, race=False): - bonus_multistrike = 0 - bonus_multistrike += 125 * self.food_wod_multistrike_125 * [1, 2][race] - bonus_multistrike += 100 * self.food_wod_multistrike * [1, 2][race] - bonus_multistrike += 75 * self.food_wod_multistrike_75 * [1, 2][race] - return bonus_multistrike - - def buff_readiness(self, race=False): - return 0 def felmouth_food(self): if self.food_felmouth_frenzy : return True return False + + def damage_food(self): + if self.food_legion_damage_1: + return 1 + if self.food_legion_damage_2: + return 2 + if self.food_legion_damage_3: + return 3 + return 0 diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index 690c349..70ee42d 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -6,7 +6,7 @@ class Util(object): GAME_CLASS_NUMBER = { 1:"warrior", 2:"paladin", 3:"hunter", 4:"rogue", 5:"priest", 6:"death_knight", 7:"shaman", 8:"mage", 9:"warlock", - 10:"monk", 11:"druid" + 10:"monk", 11:"druid", 12:"demon_hunter" } #constant scaling (for level based calculations and the like) @@ -31,6 +31,8 @@ class Util(object): 60.000000000000000, 63.000000000000000, 65.000000000000000, 66.000000000000000, 67.000000000000000, 101.000000000000000, 118.000000000000000, 139.000000000000000, 162.000000000000000, 190.000000000000000, 225.000000000000000, 234.000000000000000, 242.000000000000000, 252.000000000000000, 261.000000000000000, + 261.000000000000000, 261.000000000000000, 261.000000000000000, 261.000000000000000, 261.000000000000000, + 261.000000000000000, 261.000000000000000, 261.000000000000000, 261.000000000000000, 261.000000000000000, ] #Base armor values for enemy level diff --git a/shadowcraft/objects/race.py b/shadowcraft/objects/race.py index 8613ead..9ddda20 100755 --- a/shadowcraft/objects/race.py +++ b/shadowcraft/objects/race.py @@ -10,6 +10,7 @@ class Race(object): 85: ( 288, 306, 212, 169, 127), 90: ( 339, 361, 250, 200, 150), 100:(1206, 1284, 890, 711, 533), + 110:(8481, 9030, 6259, 5000, 0), } #(ap,sp) @@ -18,16 +19,14 @@ class Race(object): 85: {'ap': 30, 'sp': 30}, 90: {'ap': 50, 'sp': 50}, 100:{'ap': 120, 'sp': 120}, + 110:{'ap':2243, 'sp': 2243}, } touch_of_the_grave_bonuses = { 80: {'spell_damage': 200}, 90: {'spell_damage': 400}, 100:{'spell_damage': 1000}, - } - versatility_bonuses = { - 0: 1, - 90: 26, - 100: 100, + #TODO: CHECK + 110:{'spell_damage': 1000}, } #Arguments are ap, spellpower:fire, and int diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index d9a3b35..33edf08 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -9,17 +9,13 @@ class Stats(object): # rows 1-9 from my WotLK spreadsheets to see how these are typically # defined, though the numbers will need to updated for level 85. - crit_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0} - haste_rating_conversion_values = {60:9.00, 70:10.0, 80:12.0, 85:14.0, 90:20.0, 100:90.0} - mastery_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0} - multistrike_rating_conversion_values = {60:3.00, 70:4.00, 80:5.00, 85:6.00, 90:14.0, 100:66.0} - readiness_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0} - versatility_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:27.0, 100:130.0} - pvp_power_rating_conversion_values = {60:5.00, 70:7.00, 80:8.00, 85:9.00, 90:10.0, 100:49.0} - pvp_resil_rating_conversion_values = {60:9.29, 70:14.65, 80:30.46, 85:92.31, 90:310.0, 100:600.0} + crit_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:350.0} + haste_rating_conversion_values = {60:9.00, 70:10.0, 80:12.0, 85:14.0, 90:20.0, 100:100.0, 110:325.0} + mastery_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:350.0} + versatility_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:27.0, 100:130.0, 110:400.0} def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, spirit=0, stam=0, ap=0, crit=0, haste=0, mastery=0, - readiness=0, multistrike=0, versatility=0, level=None, pvp_power=0, pvp_resil=0, pvp_target_armor=None): + versatility=0, level=None): # This will need to be adjusted if at any point we want to support # other classes, but this is probably the easiest way to do it for # the moment. @@ -32,29 +28,19 @@ def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, spirit=0, sta self.crit = crit self.haste = haste self.mastery = mastery - self.readiness = readiness - self.multistrike = multistrike self.versatility = versatility self.mh = mh self.oh = oh self.procs = procs self.gear_buffs = gear_buffs self.level = level - self.pvp_power = pvp_power - self.pvp_resil = pvp_resil - self.pvp_target_armor = pvp_target_armor - def _set_constants_for_level(self): self.procs.level = self.level try: self.crit_rating_conversion = self.crit_rating_conversion_values[self.level] self.haste_rating_conversion = self.haste_rating_conversion_values[self.level] self.mastery_rating_conversion = self.mastery_rating_conversion_values[self.level] - self.multistrike_rating_conversion = self.multistrike_rating_conversion_values[self.level] - self.readiness_rating_conversion = self.readiness_rating_conversion_values[self.level] self.versatility_rating_conversion = self.versatility_rating_conversion_values[self.level] - self.pvp_power_rating_conversion = self.pvp_power_rating_conversion_values[self.level] - self.pvp_resil_rating_conversion = self.pvp_resil_rating_conversion_values[self.level] except KeyError: raise exceptions.InvalidLevelException(_('No conversion factor available for level {level}').format(level=self.level)) @@ -78,30 +64,10 @@ def get_haste_multiplier_from_rating(self, rating=None): rating = self.haste return 1 + rating / (100. * self.haste_rating_conversion) - def get_readiness_multiplier_from_rating(self, rating=None, readiness_conversion=1): - if rating is None: - rating = self.readiness - return 1. / (1 + (readiness_conversion * rating) / (self.readiness_rating_conversion * 100.)) - - def get_multistrike_chance_from_rating(self, rating=None): - if rating is None: - rating = self.multistrike - return rating / (100. * self.multistrike_rating_conversion) - def get_versatility_multiplier_from_rating(self, rating=None): if rating is None: rating = self.versatility return 1. + rating / (100. * self.versatility_rating_conversion) - - def get_pvp_power_multiplier_from_rating(self, rating=None): - if rating is None: - rating = self.pvp_power - return 1. + rating / (100. * self.pvp_power_rating_conversion) - - def get_pvp_resil_multiplier_from_rating(self, rating=None): - if rating is None: - rating = self.pvp_resil - return 0.6*(rating/(rating+11727)) # 0% base resil now class Weapon(object): allowed_melee_enchants = proc_data.allowed_melee_enchants From 916689328eaea20bfe46082ad521cc76b69d9e2b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 31 Jul 2016 11:26:11 -0400 Subject: [PATCH 030/265] Final code cleanup for subtlety -Updated food buffs -Removed MS and Readiness --- scripts/subtlety.py | 42 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 385 +++++++++---------- shadowcraft/calcs/rogue/__init__.py | 96 ++--- shadowcraft/objects/buffs.py | 61 ++- 4 files changed, 276 insertions(+), 308 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 6c06cb2..2e660ce 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -1,7 +1,7 @@ # Simple test program to debug + play with subtlety models. from os import path import sys -#sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) +sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.calcs.rogue.Aldriana import settings @@ -11,7 +11,7 @@ from shadowcraft.objects import stats from shadowcraft.objects import procs from shadowcraft.objects import talents -from shadowcraft.objects import glyphs +from shadowcraft.objects import artifact from shadowcraft.core import i18n @@ -20,24 +20,16 @@ i18n.set_language(test_language) # Set up level/class/race -test_level = 100 -test_race = race.Race('night_elf') +test_level = 110 +test_race = race.Race('troll') test_class = 'rogue' +test_spec = 'subtlety' # Set up buffs. test_buffs = buffs.Buffs( 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'haste_buff', - 'multistrike_buff', - 'versatility_buff', - 'attack_power_buff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', 'flask_wod_agi', - 'food_mop_agi' + 'food_wod_versatility' ) # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand @@ -45,8 +37,9 @@ test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_frostwolf') # Set up procs. - trinkets, other things (legendary procs) -test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), - 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') +#test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), +# 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') +test_procs = procs.ProcsList() # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here @@ -58,31 +51,28 @@ crit=1039, haste=0, mastery=1315, - readiness=0, - versatility=122, - multistrike=1834,) + versatility=122,) # Initialize talents.. -test_talents = talents.Talents('2000002', test_class, test_level) +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) -# Set up glyphs. -glyph_list = [] -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) +#initialize artifact traits.. +test_traits = artifact.Artifact(test_spec, test_class, '0000000000000000') # Set up settings. test_cycle = settings.SubtletyCycle(5, use_hemorrhage='never', clip_fw=False) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=False, - adv_params="") + adv_params="", is_demon=True) # Build a DPS object. -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) +calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) +#ep_values = calculator.get_ep(baseline_dps=total_dps) #ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 6047a23..9f65c74 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -65,21 +65,12 @@ def are_close_enough(self, old_dist, new_dist, precision=PRECISION_REQUIRED): if abs(new_dist[item][index] - old_dist[item][index]) > precision: return False return True - + ########################################################################### # Overrides: these make the ep methods default to glyphs/talents or weapon # setups that we are really modeling. ########################################################################### - def get_glyphs_ranking(self, list=None): - if list is None: - list = [ - 'vendetta', - 'energy', - 'disappearance', - ] - return super(AldrianasRogueDamageCalculator, self).get_glyphs_ranking(list) - def get_talents_ranking(self, list=None): if list is None: list = [ @@ -126,7 +117,7 @@ def get_heroism_haste_multiplier(self): def get_cp_distribution_for_cycle(self, cp_distribution_per_move, target_cp_quantity): avg_cp_per_cpg = sum([key * cp_distribution_per_move[key] for key in cp_distribution_per_move]) - + time_spent_at_cp = [0, 0, 0, 0, 0, 0] cur_min_cp = 0 cur_dist = {(0, 0): 1} @@ -233,10 +224,10 @@ def set_constants(self): self.bonus_energy_regen -= self.get_spell_stats('shiv')[0] / self.settings.shiv_interval if self.settings.feint_interval != 0: self.bonus_energy_regen -= self.get_spell_stats('feint')[0] / self.settings.feint_interval - + self.set_openers() - - #only include if general multiplier applies to spec calculations + + #only include if general multiplier applies to spec calculations self.true_haste_mod *= self.get_heroism_haste_multiplier() self.base_stats = { 'agi': (self.stats.agi + self.buffs.buff_agi(race=self.race.epicurean) + self.race.racial_agi), @@ -244,29 +235,25 @@ def set_constants(self): 'crit': (self.stats.crit + self.buffs.buff_crit(race=self.race.epicurean)), 'haste': (self.stats.haste + self.buffs.buff_haste(race=self.race.epicurean)), 'mastery': (self.stats.mastery + self.buffs.buff_mast(race=self.race.epicurean)), - 'readiness': (self.stats.readiness + self.buffs.buff_readiness(race=self.race.epicurean)), - 'multistrike': (self.stats.multistrike + self.buffs.buff_multistrike(race=self.race.epicurean)), 'versatility': (self.stats.versatility + self.buffs.buff_versatility(race=self.race.epicurean)), } self.stat_multipliers = { 'str': 1., - 'agi': self.buffs.stat_multiplier() * self.stats.gear_buffs.gear_specialization_multiplier(), - 'ap': self.buffs.attack_power_multiplier(), + 'agi': self.stats.gear_buffs.gear_specialization_multiplier(), + 'ap': 1, 'crit': 1., 'haste': 1., 'mastery': 1., - 'readiness': 1., - 'multistrike': 1., 'versatility': 1., } - + if self.race.human_spirit: self.base_stats['versatility'] += self.race.versatility_bonuses[self.level] - + for boost in self.race.get_racial_stat_boosts(): if boost['stat'] in self.base_stats: self.base_stats[boost['stat']] += boost['value'] * boost['duration'] * 1.0 / (boost['cooldown'] + self.settings.response_time) - + if self.stats.procs.virmens_bite: getattr(self.stats.procs, 'virmens_bite').icd = self.settings.duration if self.stats.procs.virmens_bite_prepot: @@ -276,39 +263,36 @@ def set_constants(self): if self.stats.procs.draenic_agi_prepot: getattr(self.stats.procs, 'draenic_agi_prepot').icd = self.settings.duration - self.base_strength = self.stats.str + self.buffs.buff_str() + self.race.racial_str - self.base_strength *= self.buffs.stat_multiplier() + self.base_strength = self.stats.str + self.race.racial_str self.base_intellect = self.stats.int + self.race.racial_int - self.base_intellect *= self.buffs.stat_multiplier() self.relentless_strikes_energy_return_per_cp = 5 #.20 * 25 - + #should only include bloodlust if the spec can average it in, deal with this later self.base_speed_multiplier = 1.4 if self.race.berserking: self.true_haste_mod *= (1 + .15 * 10. / (180 + self.settings.response_time)) self.true_haste_mod *= 1 + self.race.get_racial_haste() #doesn't include Berserking - self.true_haste_mod *= self.buffs.haste_multiplier() if self.stats.gear_buffs.rogue_t14_4pc: self.true_haste_mod *= 1.05 - + #hit chances self.dw_mh_hit_chance = self.dual_wield_mh_hit_chance() self.dw_oh_hit_chance = self.dual_wield_oh_hit_chance() - + def load_from_advanced_parameters(self): self.true_haste_mod = self.get_adv_param('haste_buff', 1., min_bound=.1, max_bound=3.) - + self.major_cd_delay = self.get_adv_param('major_cd_delay', 0, min_bound=0, max_bound=600) self.settings.feint_interval = self.get_adv_param('feint_interval', self.settings.feint_interval, min_bound=0, max_bound=600) - + self.settings.is_day = self.get_adv_param('is_day', self.settings.is_day, ignore_bounds=True) self.get_version_number = self.get_adv_param('print_version', False, ignore_bounds=True) - + def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ap, damage_breakdown): crit_multiplier = self.crit_damage_modifiers() crit_rate = self.crit_rate(crit=current_stats['crit']) - + if proc.stat == 'spell_damage': multiplier = self.get_modifiers(current_stats, damage_type='spell') elif proc.stat == 'physical_damage': @@ -328,7 +312,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc is getattr(self.stats.procs, 'legendary_capacitive_meta'): crit_rate = self.crit_rate(crit=current_stats['crit']) proc_value = average_ap * 1.5 + 50 - + if proc is getattr(self.stats.procs, 'fury_of_xuen'): crit_rate = self.crit_rate(crit=current_stats['crit']) proc_value = (average_ap * .40 + 1) * 10 * (1 + min(4., self.settings.num_boss_adds)) @@ -342,7 +326,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ swings_per_mirror = 20.0/(2.0/haste_mult) total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror proc_value = total_swings*(average_ap/3.5) * (1+ self.settings.num_boss_adds) - + #.424*max(AP, SP) if proc is getattr(self.stats.procs, 'felmouth_frenzy'): proc_value = average_ap * 0.424 * 5 @@ -350,10 +334,10 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ average_hit = proc_value * multiplier average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count #print proc.proc_name, average_hit, multiplier - + if proc.stat == 'physical_dot': average_damage *= proc.uptime / proc_count - + return average_damage def set_openers(self): @@ -370,7 +354,7 @@ def set_openers(self): else: total_openers_per_second = 0 opener_spacing = None - + self.total_openers_per_second = total_openers_per_second self.swing_reset_spacing = opener_spacing @@ -385,7 +369,7 @@ def get_bonus_energy_from_openers(self, *cycle_abilities): # else, it's a rotational ability and we have SF, so we should add energy # this lets us save computational time in the aps methods return self.get_net_energy_cost(self.settings.opener_name) * (1 - self.get_shadow_focus_multiplier()) * self.total_openers_per_second - + def get_net_energy_cost(self, ability): return self.get_spell_stats(ability)[0] @@ -485,25 +469,25 @@ def get_procs_per_second(self, proc, attacks_per_second, crit_rates): procs_per_second += self.get_oh_procs_per_second(proc, attacks_per_second, crit_rates) procs_per_second += self.get_other_procs_per_second(proc, attacks_per_second, crit_rates) return procs_per_second - + def lost_swings_from_swing_delay(self, delay, swing_timer): # delay = swing delay = s (see: graphs) # swing timer = x (see: graphs) delay_remainder = delay % .5 #m num_sum = min(swing_timer, delay) #n - + #TODO: Wiki Documentation explaining swing delay calculations #OLD SWING DELAY METHODS: delay//swing_timer + (delay%swing_timer)/swing_timer # : delay/swing_timer # : OH is the same value but 1 lower - + t0 = max(min( delay_remainder/swing_timer*1.5, 1.5 ), 0) t1 = max(min( num_sum - delay_remainder, .5 )/swing_timer, 0) t2 = max(min( num_sum - delay_remainder - .5, .5 )/swing_timer * .5, 0) - + #print "total delay: ", t0, t1, t2, (t0+t1+t2) return (t0+t1+t2)/swing_timer - + def set_uptime_for_ramping_proc(self, proc, procs_per_second): time_for_one_stack = 1 / procs_per_second if time_for_one_stack * proc.max_stacks > self.settings.duration: @@ -535,7 +519,7 @@ def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): frequency = 1. / (proc.icd + 0.5 / self.get_procs_per_second(proc, attacks_per_second, crit_rates)) else: frequency = self.get_procs_per_second(proc, attacks_per_second, crit_rates) - + if proc.proc_name in attacks_per_second: attacks_per_second[proc.proc_name] += frequency else: @@ -573,13 +557,13 @@ def get_poison_counts(self, attacks_per_second, current_stats): poison_base_proc_rate += .2 poison_envenom_proc_rate = poison_base_proc_rate + .3 aps_envenom = attacks_per_second['envenom'] - if self.talents.death_from_above: + if self.talents.death_from_above: aps_envenom = map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom']) envenom_uptime = min(sum([(1 + cps + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) * aps_envenom[cps] for cps in xrange(1, 6)]), 1) avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime else: avg_poison_proc_rate = poison_base_proc_rate - + if self.settings.dmg_poison == 'sp': poison_procs = avg_poison_proc_rate * total_hits_per_second * proc_multiplier - 1 / self.settings.duration attacks_per_second['swift_poison'] = poison_procs @@ -603,7 +587,7 @@ def determine_stats(self, attack_counts_function): 'versatility': self.base_stats['versatility'] * self.stat_multipliers['versatility'], } self.current_variables = {} - + #arrys to store different types of procs active_procs_rppm_stat_mods = [] active_procs_rppm = [] @@ -628,7 +612,7 @@ def determine_stats(self, attack_counts_function): self.set_rppm_uptime(getattr(self.stats.procs, 'mark_of_the_shattered_hand_dot')) if not shatt_hand: self.stats.procs.del_proc('mark_of_the_shattered_hand_dot') - + #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): if (proc.stat == 'stats'): @@ -645,7 +629,7 @@ def determine_stats(self, attack_counts_function): damage_procs.append(proc) elif proc.stat == 'extra_weapon_damage': weapon_damage_procs.append(proc) - + #calculate weapon procs weapon_enchants = set([]) for hand, enchant in [(x, y) for x in ('mh', 'oh') for y in ('dancing_steel', 'mark_of_the_frostwolf', @@ -664,7 +648,7 @@ def determine_stats(self, attack_counts_function): active_procs_no_icd.append(proc) elif enchant in ('mark_of_the_shattered_hand', ): damage_procs.append(proc) - + static_proc_stats = { 'str': 0, 'agi': 0, @@ -676,7 +660,7 @@ def determine_stats(self, attack_counts_function): 'multistrike': 0, 'versatility': 0, } - + for proc in active_procs_rppm_stat_mods: self.set_rppm_uptime(proc) for e in proc.value: @@ -688,13 +672,13 @@ def determine_stats(self, attack_counts_function): self.set_rppm_uptime(proc) for e in proc.value: static_proc_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] - + for k in static_proc_stats: current_stats[k] += static_proc_stats[ k ] - + attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats) recalculate_crit = False - + #check need to converge need_converge = False convergence_stats = False @@ -714,7 +698,7 @@ def determine_stats(self, attack_counts_function): } for k in static_proc_stats: current_stats[k] += static_proc_stats[k] - + for proc in active_procs_no_icd: self.set_uptime(proc, attacks_per_second, crit_rates) for e in proc.value: @@ -723,28 +707,28 @@ def determine_stats(self, attack_counts_function): if e == 'crit': recalculate_crit = True current_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] - + #only have to converge with specific procs #check if... assassination:crit/haste, combat:mastery/haste, sub:haste/mastery if not convergence_stats and not self.spec_needs_converge: break - + old_attacks_per_second = attacks_per_second if recalculate_crit: crit_rates = None recalculate_crit = False attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats, crit_rates=crit_rates) - + if self.are_close_enough(old_attacks_per_second, attacks_per_second): break - + for proc in active_procs_icd: self.set_uptime(proc, attacks_per_second, crit_rates) for e in proc.value: if e == 'crit': recalculate_crit = True current_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] - + #if no new stats are added, skip this step if len(active_procs_icd) > 0 or self.spec_needs_converge: if recalculate_crit: @@ -785,29 +769,29 @@ def determine_stats(self, attack_counts_function): uptime = buff_duration / attack_spacing res = self.vendetta_duration * uptime * mas_per_stack * avg_stacks_per_attack / self.get_spell_cd('vendetta') current_stats['mastery'] += res - + #some procs need specific prep, think RoRO/VoS self.setup_unique_procs(current_stats, current_stats['agi']+current_stats['ap']) - + for proc in damage_procs: self.update_with_damaging_proc(proc, attacks_per_second, crit_rates) - + for proc in weapon_damage_procs: self.set_uptime(proc, attacks_per_second, crit_rates) return current_stats, attacks_per_second, crit_rates, damage_procs, additional_info - + def compute_damage_from_aps(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): # this method exists solely to let us use cached values you would get from determine stats # really only useful for combat calculations (restless blades calculations) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) return damage_breakdown, additional_info - + def compute_damage(self, attack_counts_function): current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(attack_counts_function) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) #damage_breakdown, additional_info = self.get_damage_breakdown(self.determine_stats(attack_counts_function)) return damage_breakdown, additional_info - + ########################################################################### # Assassination DPS functions ########################################################################### @@ -823,26 +807,26 @@ def init_assassination(self): raise InputNotModeledException(_('You must specify an assassination cycle to match your assassination spec.')) if self.stats.mh.type != 'dagger' or self.stats.oh.type != 'dagger': raise InputNotModeledException(_('Assassination modeling requires daggers in both hands')) - + #set readiness coefficient self.readiness_spec_conversion = self.assassination_readiness_conversion self.spec_convergence_stats = ['haste', 'crit', 'readiness'] - + # Assassasins's Resolve self.damage_modifier_cache = 1.17 - + #update spec specific proc rates if getattr(self.stats.procs, 'legendary_capacitive_meta'): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.789 if getattr(self.stats.procs, 'fury_of_xuen'): getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.55 - + #spec specific glyph behaviour if self.glyphs.disappearance: self.ability_cds['vanish'] = 60 else: self.ability_cds['vanish'] = 120 - + self.base_energy_regen = 10 self.max_energy = 120. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): @@ -857,7 +841,7 @@ def init_assassination(self): if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) - + self.set_constants() self.stat_multipliers['mastery'] *= 1.05 @@ -897,7 +881,7 @@ def assassination_dps_breakdown(self): dps_breakdown[source] += quantity * execute_weight else: dps_breakdown[source] = quantity * execute_weight - + return dps_breakdown def update_assassination_breakdown_with_modifiers(self, damage_breakdown, current_stats): @@ -905,7 +889,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) - + soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') @@ -944,7 +928,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren damage_breakdown[key] *= 1 + self.vendetta_multiplier if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): damage_breakdown[key] *= self.emp_envenom_percentage - if self.stats.gear_buffs.rogue_t18_2pc: + if self.stats.gear_buffs.rogue_t18_2pc: if key == 'dispatch': damage_breakdown[key]*= 1+(0.25 * (1+(self.stats.get_mastery_from_rating(rating=current_stats['mastery'])*self.assassination_mastery_conversion))) @@ -956,7 +940,7 @@ def assassination_dps_breakdown_non_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_non_execute) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) - + self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) return damage_breakdown @@ -964,10 +948,10 @@ def assassination_dps_breakdown_execute(self): #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_execute) current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_execute) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) - + self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) return damage_breakdown - + def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, ability_count, size_breakdown, cp_limit=4, blindside_proc=0, execute=False): current_sizes = copy(size_breakdown) if (current_cp >= cp_limit and not blindside_proc and not execute) or current_cp >= 5: @@ -977,10 +961,10 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi avg_count = {'mutilate':0, 'dispatch':0} avg_breakdown = [0,0,0,0,0,0] new_count = copy(ability_count) - + if blindside_proc or execute: new_count['dispatch'] += 1 - + n_chance = 1 - crit_rates['dispatch'] c_chance = crit_rates['dispatch'] if self.stats.gear_buffs.rogue_t18_4pc: @@ -989,7 +973,7 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi else: n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+1, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - + avg_cp = n_chance*n_value + c_chance*c_value avg_bs_afterwards = n_chance*n_proc + c_chance*c_proc for key in new_count: @@ -1000,17 +984,17 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi else: bs_proc_rate = .3 new_count['mutilate'] += 1 - + n_chance = ((1 - crit_rates['mutilate']) ** 2) * (1-bs_proc_rate) n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit) n_bs_chance = ((1 - crit_rates['mutilate']) ** 2) * bs_proc_rate n_bs_value, n_bs_proc, n_bs_count, n_bs_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, blindside_proc=1.) - + c_chance = (1 - (1 - crit_rates['mutilate']) ** 2) * (1-bs_proc_rate) c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit) c_bs_chance = (1 - (1 - crit_rates['mutilate']) ** 2) * bs_proc_rate c_bs_value, c_bs_proc, c_bs_count, c_bs_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, blindside_proc=1.) - + avg_cp = n_chance*n_value + n_bs_chance*n_bs_value + c_chance*c_value + c_bs_chance*c_bs_value avg_bs_afterwards = n_chance*n_proc + n_bs_chance*n_bs_proc + c_chance*c_proc + c_bs_chance*c_bs_proc for key in new_count: @@ -1018,7 +1002,7 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi for i in xrange(1, 6): avg_breakdown[i] = n_chance*n_breakdown[i] + n_bs_chance*n_bs_breakdown[i] + c_chance*c_breakdown[i] + c_bs_chance*c_bs_breakdown[i] return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown - + def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_rates=None): attacks_per_second = {} additional_info = {} @@ -1031,7 +1015,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod ability_cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() - + energy_regen = self.base_energy_regen * haste_multiplier if self.stats.gear_buffs.rogue_t17_4pc_lfr: #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 @@ -1046,44 +1030,44 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra vw_energy_return = 10 vw_energy_per_bleed_tick = vw_energy_return - + blindside_proc_rate = [0, .3][cpg == 'mutilate'] attacks_per_second['envenom'] = [0,0,0,0,0,0] attacks_per_second['dispatch'] = 0 - + if self.talents.marked_for_death: energy_regen -= 10. / self.get_spell_cd('marked_for_death') # 35-25 - + attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier self.attack_speed_increase = attack_speed_multiplier - + seal_fate_proc_rate = crit_rates['dispatch'] if cpg == 'mutilate': seal_fate_proc_rate *= blindside_proc_rate seal_fate_proc_rate += 1 - (1 - crit_rates['mutilate']) ** 2 - + mutilate_cps = 3 - (1 - crit_rates['mutilate']) ** 2 # 1 - (1 - crit_rates['mutilate']) ** 2 is the Seal Fate CP dispatch_cps = 1 + crit_rates['dispatch'] if self.stats.gear_buffs.rogue_t18_4pc: dispatch_cps += 2 - + if self.talents.anticipation: avg_finisher_size = 5 avg_size_breakdown = [0,0,0,0,0,1.] #this is for determining the % likelyhood of sizes, not frequency of the sizes cp_needed_per_finisher = 5 if self.stats.gear_buffs.rogue_t17_4pc: cp_needed_per_finisher -= 1 - + if cpg == 'mutilate': avg_cp_per_cpg = mutilate_cps + dispatch_cps * blindside_proc_rate else: avg_cp_per_cpg = dispatch_cps - + avg_cpgs_per_finisher = cp_needed_per_finisher / avg_cp_per_cpg else: ability_count = {'mutilate':0, 'dispatch':0} finisher_size_breakdown = [0,0,0,0,0,0] - + #This is incredibly verbose, but functional. It exhaustively calculates the potential finisher size outcomes using recursion. #avg_finisher_size - measures average finisher size #avg_bs_afterwards - likelyhood of finishing with a blindside proc active @@ -1103,16 +1087,16 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra mut_start_chance = 1/(1+avg_bs) bs_start_chance = 1 - mut_start_chance extra_tuple = self.assassination_cp_distribution_for_finisher(base_cp, crit_rates, ability_count, finisher_size_breakdown, cp_limit=4, blindside_proc=1) - + avg_finisher_size = avg_finisher_size*mut_start_chance + extra_tuple[0]*bs_start_chance for key in ability_count: avg_count[key] = avg_count[key]*mut_start_chance + extra_tuple[2][key]*bs_start_chance for i in xrange(1,6): avg_size_breakdown[i] = avg_size_breakdown[i]*mut_start_chance + extra_tuple[3][i]*bs_start_chance - + avg_cpgs_per_finisher = avg_count[cpg] avg_cp_per_cpg = avg_finisher_size / avg_cpgs_per_finisher - + cpg_energy_cost = self.get_spell_stats(cpg, cost_mod=ability_cost_modifier)[0] cpg_cost_reduction = 0 if self.stats.gear_buffs.rogue_t17_2pc: @@ -1120,11 +1104,11 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if self.stats.gear_buffs.rogue_t16_2pc_bonus(): cpg_cost_reduction = 6 * seal_fate_proc_rate cpg_energy_cost -= cpg_cost_reduction - + current_opener_name = self.settings.opener_name if self.settings.opener_name == 'cpg': current_opener_name = cpg - + cp_generated = 0 if current_opener_name == 'envenom': opener_net_cost = self.get_spell_stats('envenom', cost_mod=ability_cost_modifier*(1-self.get_shadow_focus_multiplier()))[0] @@ -1146,19 +1130,19 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra energy_regen -= opener_net_cost * self.total_openers_per_second for i in xrange(1,6): attacks_per_second['envenom'][i] = self.total_openers_per_second * cp_generated / i * avg_size_breakdown[i] - + attacks_per_second['venomous_wounds'] = .5 energy_regen_with_rupture = energy_regen + .5 * vw_energy_return - + avg_cycle_length = 4. * (1 + avg_finisher_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - + energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_stats('rupture', cost_mod=ability_cost_modifier)[0] energy_for_rupture -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp - + attacks_per_second['rupture'] = 1. / avg_cycle_length energy_per_cycle = avg_cycle_length * energy_regen_with_rupture - - energy_for_dfa = 0 + + energy_for_dfa = 0 if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time dfa_cd += energy_for_rupture / (4 * energy_regen_with_rupture) @@ -1172,7 +1156,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra #Normalize DfA energy intervals to rupture intervals energy_for_dfa *= (avg_cycle_length)/(1./dfa_interval) - + energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_stats('envenom', cost_mod=ability_cost_modifier)[0] @@ -1190,7 +1174,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attacks_per_second[cpg] = cpgs_per_second if cpg == 'mutilate': attacks_per_second['dispatch'] += cpgs_per_second * blindside_proc_rate - + attacks_per_second['rupture_ticks'] = [0,0,0,0,0,.5] if self.talents.anticipation: attacks_per_second['envenom'][5] += envenoms_per_second @@ -1201,14 +1185,14 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra for i in xrange(1, 6): ticks_per_rupture = 2 * (1 + i + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) attacks_per_second['rupture_ticks'][i] = ticks_per_rupture * attacks_per_second['rupture'] * avg_size_breakdown[i] - + if self.talents.marked_for_death: attacks_per_second['envenom'][5] += 1. / self.get_spell_cd('marked_for_death') - + if 'garrote' in attacks_per_second: attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] attacks_per_second['envenom'][5] += 1. / 180 - + if self.level == 100: finisher_per_second = sum(attacks_per_second['envenom']) + attacks_per_second['rupture'] if self.talents.death_from_above: @@ -1219,7 +1203,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra #may need a better model for envenom uptime at high cp gen crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) - + if self.talents.shadow_reflection: sr_uptime = 8. / self.get_spell_cd('shadow_reflection') for ability in ('rupture_ticks', 'dispatch'): @@ -1235,25 +1219,25 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if 'mutilate' in attacks_per_second: attacks_per_second['sr_mh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] attacks_per_second['sr_oh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] - + white_swing_downtime = 0 if self.swing_reset_spacing is not None: white_swing_downtime += .5 / self.swing_reset_spacing attacks_per_second['mh_autoattacks'] = self.attack_speed_increase / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = self.attack_speed_increase / self.stats.oh.speed * (1 - white_swing_downtime) - + if self.talents.death_from_above: lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / self.attack_speed_increase) lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / self.attack_speed_increase) - + attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd - + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - + self.get_poison_counts(attacks_per_second, current_stats) - + if self.level == 100: #this is to update the crit rate for envenom due to the 'crit on Vendetta cast' perk, unlikely to ever be another ability crit_uptime = (1./(self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay)) / sum(attacks_per_second['envenom']) @@ -1262,7 +1246,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra crit_rates['envenom'] += crit_uptime * (1 - crit_rates['envenom']) return attacks_per_second, crit_rates, additional_info - + def assassination_attack_counts_non_execute(self, current_stats, crit_rates=None): return self.assassination_attack_counts(current_stats, 'mutilate', self.settings.cycle.min_envenom_size_non_execute, crit_rates=crit_rates) @@ -1279,17 +1263,17 @@ def combat_dps_estimate(self): def combat_dps_breakdown(self): if not self.settings.is_combat_rogue(): raise InputNotModeledException(_('You must specify a combat cycle to match your combat spec.')) - + #set readiness coefficient self.readiness_spec_conversion = self.combat_readiness_conversion self.spec_convergence_stats = ['haste', 'mastery', 'readiness'] - + #spec specific glyph behaviour if self.glyphs.disappearance: self.ability_cds['vanish'] = 60 else: self.ability_cds['vanish'] = 120 - + #update spec specific proc rates if getattr(self.stats.procs, 'legendary_capacitive_meta'): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.136 @@ -1326,17 +1310,17 @@ def combat_dps_breakdown(self): self.rvs_duration = 24 if self.settings.dmg_poison == 'dp' and self.level == 100: self.settings.dmg_poison = 'sp' - + self.set_constants() self.stat_multipliers['haste'] *= 1.05 self.stat_multipliers['ap'] *= 1.50 - + if self.talents.death_from_above: self.spec_needs_converge = True - + cds = {'ar':self.get_spell_cd('adrenaline_rush'), 'ks':self.get_spell_cd('killing_spree')} - + # actual damage calculations here phases = {} #AR phase @@ -1345,7 +1329,7 @@ def combat_dps_breakdown(self): phases['ar'] = (self.ar_duration, self.update_with_bandits_guile(ar_tuple[0], ar_tuple[1])) for e in cds: cds[e] -= self.ar_duration / self.rb_cd_modifier(aps) - + #none self.tmp_ks_cd = cds['ks'] self.tmp_phase_length = cds['ar'] #This is to approximate the value of a full energy bar to be used when not during AR or SB @@ -1370,7 +1354,7 @@ def combat_dps_breakdown(self): if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 - + bf_mod = .35 bf_max_targets = 4 if self.level == 100: @@ -1425,7 +1409,7 @@ def combat_dps_breakdown(self): damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) return damage_breakdown - + def update_with_bandits_guile(self, damage_breakdown, additional_info): for key in damage_breakdown: if key in ('Mirror of the Blademaster', 'Fel Lash'): @@ -1446,20 +1430,20 @@ def update_with_bandits_guile(self, damage_breakdown, additional_info): damage_breakdown[key] *= self.bandits_guile_multiplier * self.revealing_strike_multiplier else: damage_breakdown[key] *= self.bandits_guile_multiplier #* self.ksp_multiplier - + return damage_breakdown - + def combat_cpg_per_finisher(self, current_cp, ability_count): if current_cp >= 5: return ability_count new_count = copy(ability_count) new_count += 1 - + normal = self.combat_cpg_per_finisher(current_cp+1, new_count) rvs_proc = self.combat_cpg_per_finisher(current_cp+2, new_count) - + return (1 - self.extra_cp_chance)*normal + self.extra_cp_chance*rvs_proc - + def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second = {} additional_info = {} @@ -1481,7 +1465,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): combat_potency_from_mg = 15 * .2 FINISHER_SIZE = 5 ruthlessness_value = 1 # 1CP gained at 20% chance per CP spent (5CP spent means 1 is always added) - + if ar: self.attack_speed_increase *= 1.2 self.base_energy_regen *= 2.0 @@ -1492,17 +1476,17 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): gcd_size -= .2 cp_per_cpg = 1. dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - + if self.stats.gear_buffs.rogue_t18_2pc and not ar: self.attack_speed_increase *= 1 + (0.16 *0.2) self.base_energy_regen *= 1.16 gcd_size -= (0.16 * 0.2) - + # Combine energy cost scalers to reduce function calls (ie, 40% reduced energy cost). Assume multiplicative. cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_modifier() # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG cost_reducer = main_gauche_proc_rate * combat_potency_from_mg - + eviscerate_energy_cost = self.get_spell_stats('eviscerate', cost_mod=cost_modifier)[0] eviscerate_energy_cost -= cost_reducer eviscerate_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp @@ -1516,7 +1500,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): #but also the MG procs from the AOE which hits the main target plus each additional add (strike + aoe) if self.stats.gear_buffs.rogue_t16_2pc_bonus(): sinister_strike_energy_cost -= 15 * self.extra_cp_chance - + ## Base CPs and Attacks #Autoattacks white_swing_downtime = 0 @@ -1524,22 +1508,22 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): white_swing_downtime += self.settings.response_time / self.swing_reset_spacing #from vanish swing_timer_mh = self.stats.mh.speed / self.attack_speed_increase swing_timer_mh = self.stats.oh.speed / self.attack_speed_increase - + attacks_per_second['mh_autoattacks'] = self.attack_speed_increase / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = self.attack_speed_increase / self.stats.oh.speed * (1 - white_swing_downtime) #swing delays should be handled here if self.talents.death_from_above and not ar: lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / self.attack_speed_increase) lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / self.attack_speed_increase) - + attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd - + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance attacks_per_second['main_gauche'] = attacks_per_second['mh_autoattack_hits'] * main_gauche_proc_rate combat_potency_regen = attacks_per_second['oh_autoattack_hits'] * combat_potency_regen_per_oh - + #Base energy bonus_energy_from_openers = self.get_bonus_energy_from_openers('sinister_strike', 'revealing_strike') if self.settings.opener_name in ('ambush', 'garrote'): @@ -1559,14 +1543,14 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): #Rough idea to factor in a full energy bar if not ar: energy_regen += self.max_energy / self.settings.duration - + #Base actions rvs_interval = self.rvs_duration if self.settings.cycle.revealing_strike_pooling: min_energy_while_pooling = energy_regen * gcd_size max_energy_while_pooling = self.max_energy - 20 rvs_interval += (max_energy_while_pooling - min_energy_while_pooling) / energy_regen - + #Minicycle sizes and cpg_per_finisher stats if self.talents.anticipation: ss_per_finisher = (FINISHER_SIZE - ruthlessness_value) / (cp_per_cpg + self.extra_cp_chance) @@ -1582,7 +1566,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): snd_cost = ss_per_snd * sinister_strike_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp snd_duration = 6 + 6 * (snd_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) energy_spent_on_snd = snd_cost / snd_duration - + #Base Actions #marked for death CD self.combat_cd_delay = (.5 * total_eviscerate_cost) / (2 * energy_regen) @@ -1590,13 +1574,13 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): if self.talents.marked_for_death: energy_regen -= 10. / marked_for_death_cd energy_regen -= revealing_strike_energy_cost / rvs_interval - - energy_for_dfa = 0 + + energy_for_dfa = 0 if self.talents.death_from_above and not ar: #dfa_gap probably should be handled more accurately especially in the non-anticipation case dfa_interval = 1./(dfa_cd) energy_for_dfa = energy_cost_for_cpgs + death_from_above_energy_cost - energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp energy_for_dfa *= dfa_interval attacks_per_second['death_from_above'] = dfa_interval @@ -1615,7 +1599,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): free_gcd -= 1./snd_duration + (attacks_per_second['sinister_strike_base'] + attacks_per_second['revealing_strike'] + extra_finishers_per_second) if self.talents.marked_for_death: free_gcd -= (1. / marked_for_death_cd) - #2 seconds is an approximation of GCD loss while in air + #2 seconds is an approximation of GCD loss while in air if self.talents.death_from_above and not ar: free_gcd -= dfa_interval * (2. / gcd_size) #wowhead claims a 2s GCD energy_available_for_evis = energy_regen - energy_spent_on_snd - energy_for_dfa @@ -1637,7 +1621,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): total_evis_per_second) * main_gauche_proc_rate if self.talents.death_from_above and not ar: attacks_per_second['main_gauche'] += attacks_per_second['death_from_above_strike'][5] * main_gauche_proc_rate - + #attacks_per_second['eviscerate'] = [finisher_chance * total_evis_per_second for finisher_chance in finisher_size_breakdown] attacks_per_second['eviscerate'] = [0,0,0,0,0,total_evis_per_second] for opener, cps in [('ambush', 2), ('garrote', 1)]: @@ -1648,11 +1632,11 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['eviscerate'][5] += 1. / marked_for_death_cd if self.stats.gear_buffs.rogue_t17_4pc: attacks_per_second['eviscerate'][5] *= 1.25 - + #self.current_variables['cp_spent_on_damage_finishers_per_second'] = (total_evis_per_second) * cp_per_finisher if 'garrote' in attacks_per_second: attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] - + time_at_level = 4 / attacks_per_second['sinister_strike'] cycle_duration = 3 * time_at_level + 15 if self.level == 100: @@ -1660,7 +1644,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): else: avg_stacks = (3 * time_at_level + 45) / cycle_duration #45 is the duration (15s) multiplied by the stack power (30% BG) self.bandits_guile_multiplier = 1 + .1 * avg_stacks - + if not ar: ks_duration = 3 if self.stats.gear_buffs.rogue_pvp_wod_4pc: @@ -1671,7 +1655,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['mh_killing_spree'] = (1 + 2*ks_duration) / (final_ks_cd + self.settings.response_time) attacks_per_second['oh_killing_spree'] = (1 + 2*ks_duration) / (final_ks_cd + self.settings.response_time) attacks_per_second['main_gauche'] += attacks_per_second['mh_killing_spree'] * main_gauche_proc_rate - + if self.talents.shadow_reflection: sr_uptime = 8. / self.get_spell_cd('shadow_reflection') lst = ('sinister_strike', 'eviscerate', 'revealing_strike') @@ -1684,30 +1668,30 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] else: attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] - + self.get_poison_counts(attacks_per_second, current_stats) - - #print attacks_per_second + + #print attacks_per_second return attacks_per_second, crit_rates, additional_info - + def rb_actual_cds(self, attacks_per_second, base_cds, avg_rb_effect=10): final_cds = {} # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster for cd_name in base_cds: final_cds[cd_name] = base_cds[cd_name] * self.rb_cd_modifier(attacks_per_second) return final_cds - + def rb_actual_cd(self, attacks_per_second, base_cd, avg_rb_effect=10): # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster return base_cd * self.rb_cd_modifier(attacks_per_second) - + def rb_cd_modifier(self, attacks_per_second, avg_rb_effect=10): # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster offensive_finisher_rate = attacks_per_second['eviscerate'][5] if 'death_from_above' in attacks_per_second: offensive_finisher_rate += attacks_per_second['death_from_above'] return (1./avg_rb_effect) / (offensive_finisher_rate + (1./avg_rb_effect)) - + def combat_attack_counts_ar(self, current_stats, crit_rates=None): return self.combat_attack_counts(current_stats, ar=True, crit_rates=crit_rates) @@ -1733,17 +1717,17 @@ def subtlety_dps_breakdown(self): raise InputNotModeledException(_('Hemorrhage usage must be set to always, never or a positive number')) if float(self.settings.cycle.use_hemorrhage) > self.settings.duration: raise InputNotModeledException(_('Interval between Hemorrhages cannot be higher than the fight duration')) - + #set readiness coefficient self.readiness_spec_conversion = self.subtlety_readiness_conversion self.spec_convergence_stats = ['haste', 'multistrike'] - + #overrides setting, using Ambush + Vanish on CD is critical self.settings.use_opener = 'always' self.settings.opener_name = 'ambush' # Sanguinary Vein self.damage_modifier_cache = 1.25 - + self.sc_trigger_rate = 0 mos_value = .1 @@ -1757,26 +1741,25 @@ def subtlety_dps_breakdown(self): #update spec specific proc rates if getattr(self.stats.procs, 'legendary_capacitive_meta'): getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.114 - + self.set_constants() - self.stat_multipliers['multistrike'] *= 1.05 self.stat_multipliers['agi'] *= 1.15 #sinister calling requires convergence to calculate (for now?) self.spec_needs_converge = True self.settings.cycle.raid_crits_per_second = self.get_adv_param('hat_triggers_per_second', self.settings.cycle.raid_crits_per_second, min_bound=0, max_bound=600) self.settings.cycle.clip_fw = self.get_adv_param('clip_fw', self.settings.cycle.clip_fw, ignore_bounds=True) - + self.vanish_rate = 1. / (self.get_spell_cd('vanish') + self.settings.response_time) + 1. / (self.get_spell_cd('preparation') + self.settings.response_time * 3) #vanish CD + Prep CD mos_multiplier = 1. + mos_value * (6 + 3 * self.talents.subterfuge * [1, 2][self.glyphs.vanish]) * self.vanish_rate - + stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) armor_value = self.target_armor() find_weakness_damage_boost = 1. / self.max_level_armor_multiplier() find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * additional_info['fw_uptime'] - + trinket_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): @@ -1786,7 +1769,7 @@ def subtlety_dps_breakdown(self): #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) multistrike_multiplier = min(.6, multistrike_multiplier) - + soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') @@ -1833,7 +1816,7 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= infallible_trinket_mod if "Mirror" not in key: damage_breakdown[key] *= mos_multiplier - + #discard the loose rupture component to clean up the breakdown if 'rupture_sc' in damage_breakdown and self.settings.merge_damage: damage_breakdown['rupture'] += damage_breakdown['rupture_sc'] @@ -1842,7 +1825,7 @@ def subtlety_dps_breakdown(self): #add maalus burst if maalus_mod > 1.0: damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) - + return damage_breakdown def subtlety_attack_counts(self, current_stats, crit_rates=None): @@ -1850,7 +1833,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): additional_info = {} if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - + self.ability_cds['vanish'] = 90 * self.vanish_cd_modifier base_energy_regen = 10. @@ -1870,7 +1853,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.level == 100: shd_duration += 2 shd_cd = self.get_spell_cd('shadow_dance') + self.settings.response_time + self.major_cd_delay - + cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() shd_ambush_cost_modifier = 1. backstab_cost_mod = cost_modifier @@ -1882,16 +1865,16 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): normal_ambush_cost = self.get_spell_stats('ambush')[0] if self.talents.death_from_above: self.dfa_cost = self.get_spell_stats('death_from_above', cost_mod=cost_modifier)[0] - + #haste and attack speed haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod mastery_snd_speed = 1 + .4 * (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier * mastery_snd_speed / 1.4 - + cpg_name = 'backstab' if self.settings.cycle.use_hemorrhage == 'always': cpg_name = 'hemorrhage' - + #constant and base values hat_triggers_per_second = self.settings.cycle.raid_crits_per_second hat_cp_per_second = 1. / (2 + 1. / hat_triggers_per_second) @@ -1917,12 +1900,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): rupture_ticks_per_cast += 2 rupture_cd += 4 snd_cd += 6 - + #sinister calling mechanic sc_scaler = .5 / (.5 + self.sc_trigger_rate) rupture_cd *= sc_scaler hemo_cd *= sc_scaler - + #passive energy regen energy_regen = base_energy_regen * haste_multiplier if self.stats.gear_buffs.rogue_t17_4pc_lfr: @@ -1939,19 +1922,19 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): energy_regen += 60. / shd_cd if self.stats.gear_buffs.rogue_t17_4pc: energy_regen -= (base_eviscerate_cost - 25) / shd_cd - + #deal with extra subterfuge ambushes if self.talents.subterfuge: attacks_per_second['ambush'] += (1. / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] energy_regen -= (normal_ambush_cost / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] base_cp_per_second += (2. / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] - + ##calculations dependent on energy regen cpg_costs_for_cycle = base_backstab_energy_cost * 5 if self.settings.cycle.use_hemorrhage == 'always': cpg_costs_for_cycle = base_hemo_cost * 5 typical_cycle_size = cpg_costs_for_cycle + (base_eviscerate_cost - 25) - + #swing timer white_swing_downtime = 0 if self.swing_reset_spacing is not None: @@ -1960,16 +1943,16 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = attack_speed_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - + lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / attack_speed_multiplier) lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / attack_speed_multiplier) - + attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd - + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - + ##start consuming energy #base energy reductions marked_for_death_cd = self.get_spell_cd('marked_for_death') + (.5 * typical_cycle_size / energy_regen) + self.settings.response_time @@ -1983,8 +1966,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['ambush'] += shadowmeld_ambushes energy_regen -= normal_ambush_cost * shadowmeld_ambushes base_cp_per_second += shadowmeld_ambushes * 2 - - #base CPs, CPGs, and finishers + + #base CPs, CPGs, and finishers if self.settings.cycle.use_hemorrhage != 'always' and self.settings.cycle.use_hemorrhage != 'never': if self.settings.cycle.use_hemorrhage == 'uptime': hemo_per_second = 1. / hemo_cd @@ -2005,8 +1988,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): energy_regen -= (base_rupture_cost - 25) / rupture_cd #no need to add slice and dice to attacks per second base_cp_per_second -= 5. / snd_cd - - energy_for_dfa = 0 + + energy_for_dfa = 0 if self.talents.death_from_above: #dfa_gap probably should be handled more accurately especially in the non-anticipation case dfa_interval = 1./(dfa_cd) @@ -2017,7 +2000,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval * (self.settings.num_boss_adds+1)] attacks_per_second[cpg_name] += dfa_interval * 5 energy_regen -= energy_for_dfa / dfa_cd - + base_cp_per_second += self.vanish_rate * 2 #if we've consumed more CP's than we have for base functionality, lets generate some more CPs if base_cp_per_second < 0: @@ -2033,7 +2016,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['eviscerate'][5] += extra_evisc if energy_regen < 0: raise InputNotModeledException(_('Catastrophic failure: cycle not sustainable.')) - + #calculate shd ambush cycles shd_energy = (max_energy - self.get_adv_param('max_pool_reduct', 10, min_bound=0, max_bound=50)) + energy_regen * shd_duration #lasts 8s, assume we pool to ~10 energy below max shd_cycle_cost = 2 * sd_ambush_cost + (base_eviscerate_cost - 25) @@ -2042,7 +2025,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['ambush'] += (shd_ambushes / shd_cd) * ((self.settings.duration - fw_duration) / self.settings.duration) attacks_per_second['eviscerate'][5] += (shd_eviscerates / shd_cd) * ((self.settings.duration - fw_duration) / self.settings.duration) energy_regen -= (shd_cycle_cost * shd_eviscerates) / shd_cd * ((self.settings.duration - fw_duration) / self.settings.duration) - + #calculate percentage of ambushes with FW ambush_no_fw = shadowmeld_ambushes + 1. / shd_cd - 1. / self.settings.duration if not self.settings.cycle.clip_fw: @@ -2068,7 +2051,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['eviscerate'][5] += filler_cycles_per_second if self.stats.gear_buffs.rogue_t17_4pc: attacks_per_second['eviscerate'][5] += 1. / shd_cd - + #Hemo ticks if 'hemorrhage' in attacks_per_second and self.settings.cycle.use_hemorrhage != 'never': if self.settings.cycle.use_hemorrhage == 'always': @@ -2077,22 +2060,22 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): hemo_gap = hemo_cd ticks_per_second = min(1. / (3 * sc_scaler), 8. / hemo_gap) attacks_per_second['hemorrhage_ticks'] = ticks_per_second - + sc_ms_chance = min(2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()), 2) #this is a cache for convergence self.sc_trigger_rate = attacks_per_second['ambush'] * sc_ms_chance if 'backstab' in attacks_per_second: self.sc_trigger_rate += attacks_per_second['backstab'] * sc_ms_chance self.sc_trigger_rate = min(self.sc_trigger_rate, 2) - + if self.talents.shadow_reflection: sr_cd = self.get_spell_cd('shadow_reflection') attacks_per_second['sr_eviscerate'] = [0,0,0,0,0, shd_eviscerates / sr_cd] attacks_per_second['sr_rupture_ticks'] = [0,0,0,0,0, 12. / sr_cd] attacks_per_second['sr_ambush'] = shd_ambushes / sr_cd - + self.get_poison_counts(attacks_per_second, current_stats) - + if self.stats.gear_buffs.rogue_t18_4pc: finishers_per_second = sum(attacks_per_second['eviscerate']) + attacks_per_second['rupture'] avg_cdr = 5 #assume all 5cp finishers diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index ee0d72c..2b1a64a 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -14,12 +14,12 @@ class RogueDamageCalculator(DamageCalculator): # put the calculations in your object. But there are things - like # backstab damage as a function of AP - that (almost) any rogue damage # calculator will need to know, so things like that go here. - + default_ep_stats = ['agi', 'haste', 'crit', 'mastery', 'ap', 'multistrike', 'versatility'] #'readiness' melee_attacks = ['mh_autoattack_hits', 'oh_autoattack_hits', 'autoattack', 'eviscerate', 'envenom', 'ambush', 'garrote', 'sinister_strike', 'revealing_strike', 'main_gauche', 'mh_killing_spree', 'oh_killing_spree', - 'backstab', 'hemorrhage', + 'backstab', 'hemorrhage', 'mutilate', 'mh_mutilate', 'oh_mutilate', 'dispatch', "death_from_above_strike"] other_attacks = ['deadly_instant_poison', 'swift_poison'] aoe_attacks = ['fan_of_knives', 'crimson_tempest', "death_from_above_pulse"] @@ -27,18 +27,18 @@ class RogueDamageCalculator(DamageCalculator): ranged_attacks = ['shuriken_toss', 'throw'] non_dot_attacks = melee_attacks + ranged_attacks + aoe_attacks all_attacks = melee_attacks + ranged_attacks + dot_ticks + aoe_attacks + other_attacks - + assassination_mastery_conversion = .035 combat_mastery_conversion = .022 subtlety_mastery_conversion = .0276 assassination_readiness_conversion = 1.0 combat_readiness_conversion = 1.0 subtlety_readiness_conversion = 1.0 - + raid_modifiers_cache = {'physical':None, 'bleed':None, 'spell':None} - + ability_info = { 'ambush': (60., 'strike'), 'backstab': (35., 'strike'), @@ -78,7 +78,7 @@ class RogueDamageCalculator(DamageCalculator): 'combat': ['adrenaline_rush', 'killing_spree'], 'subtlety': ['shadow_dance'] } - + def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': @@ -106,32 +106,32 @@ def get_modifiers(self, current_stats, damage_type='physical', armor=None, execu # self.damage_modifier_cache stores common modifiers like Assassin's Resolve that won't change between calculations # this cuts down on repetitive if statements base_modifier = self.damage_modifier_cache - + # Raid modifiers if not self.raid_modifiers_cache[damage_type]: self.raid_modifiers_cache[damage_type] = self.raid_settings_modifiers(attack_kind=damage_type, armor=armor) base_modifier *= self.raid_modifiers_cache[damage_type] - + # potent poisons and executioner should be calculated outside, and passed in, no need to recalculate the % each time base_modifier *= executioner_modifier base_modifier *= potent_poisons_modifier - + #versatility is a generic damage modifier base_modifier *= (self.stats.get_versatility_multiplier_from_rating(rating=current_stats['versatility']) + self.buffs.versatility_bonus()) return base_modifier - + def get_dps_contribution(self, base_damage, crit_rate, frequency, crit_modifier): average_hit = base_damage * (1 - crit_rate) + base_damage * crit_rate * crit_modifier return average_hit * frequency - + def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): average_ap = current_stats['ap'] + current_stats['agi'] * self.stat_multipliers['ap'] - + self.setup_unique_procs(current_stats, average_ap) damage_breakdown = {} - + # we calculate mastery here to reduce redundant calls # can't rely on spec init thread because stats change afterwards executioner_mod = 1. @@ -140,13 +140,13 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da executioner_mod = 1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) if self.settings.is_assassination_rogue(): potent_poisons_mod = 1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) - + # these return the tuple (damage_modifier, crit_multiplier) crit_damage_modifier = self.crit_damage_modifiers() physical_modifier = self.get_modifiers(current_stats, damage_type='physical') spell_modifier = self.get_modifiers(current_stats, damage_type='spell') bleed_modifier = self.get_modifiers(current_stats, damage_type='bleed') - + if 'mh_autoattacks' in attacks_per_second: # Assumes mh and oh attacks are both active at the same time. As they should always be. # Friends don't let friends raid without gear. @@ -154,7 +154,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da mh_hit_rate = self.dw_mh_hit_chance - crit_rates['mh_autoattacks'] average_mh_hit = mh_hit_rate * mh_base_damage + crit_rates['mh_autoattacks'] * mh_base_damage * crit_damage_modifier mh_dps_tuple = average_mh_hit * attacks_per_second['mh_autoattacks'] - + oh_base_damage = self.oh_damage(average_ap) * physical_modifier oh_hit_rate = self.dw_oh_hit_chance - crit_rates['oh_autoattacks'] average_oh_hit = oh_hit_rate * oh_base_damage + crit_rates['oh_autoattacks'] * oh_base_damage * crit_damage_modifier @@ -164,12 +164,12 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da else: damage_breakdown['mh_autoattack'] = mh_dps_tuple damage_breakdown['oh_autoattack'] = oh_dps_tuple - + # this removes keys with empty values, prevents errors from: attacks_per_second['sinister_strike'] = None for key in attacks_per_second.keys(): if not attacks_per_second[key]: del attacks_per_second[key] - + if 'mutilate' in attacks_per_second: mh_dmg = self.mh_mutilate_damage(average_ap) * physical_modifier oh_dmg = self.oh_mutilate_damage(average_ap) * physical_modifier @@ -180,7 +180,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da else: damage_breakdown['mh_mutilate'] = mh_mutilate_dps damage_breakdown['oh_mutilate'] = oh_mutilate_dps - + for strike in ('hemorrhage', 'backstab', 'sinister_strike', 'revealing_strike', 'main_gauche', 'ambush', 'dispatch', 'shuriken_toss'): if strike in attacks_per_second: dps = self.get_formula(strike)(average_ap) * physical_modifier @@ -207,16 +207,16 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da else: damage_breakdown['mh_killing_spree'] = mh_killing_spree_dps damage_breakdown['oh_killing_spree'] = oh_killing_spree_dps - + if 'garrote_ticks' in attacks_per_second: dps_tuple = self.garrote_tick_damage(average_ap) * bleed_modifier - damage_breakdown['garrote'] = self.get_dps_contribution(dps_tuple, crit_rates['garrote'], attacks_per_second['garrote_ticks'], crit_damage_modifier) - + damage_breakdown['garrote'] = self.get_dps_contribution(dps_tuple, crit_rates['garrote'], attacks_per_second['garrote_ticks'], crit_damage_modifier) + if 'hemorrhage_ticks' in attacks_per_second: hemo_hit = self.hemorrhage_tick_damage(average_ap) * bleed_modifier dps_from_hit_hemo = self.get_dps_contribution(hemo_hit, crit_rates['hemorrhage'], attacks_per_second['hemorrhage_ticks'], crit_damage_modifier) damage_breakdown['hemorrhage_dot'] = dps_from_hit_hemo - + if 'rupture_ticks' in attacks_per_second: average_dps = 0 for i in xrange(1, 6): @@ -231,7 +231,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da dps_tuple = self.get_dps_contribution(dps_tuple, 0, attacks_per_second['rupture_ticks_sc'][i], crit_damage_modifier) average_dps += dps_tuple damage_breakdown['rupture_sc'] = average_dps - + if 'envenom' in attacks_per_second: average_dps = 0 for i in xrange(1, 6): @@ -247,7 +247,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['eviscerate'], attacks_per_second['eviscerate'][i], crit_damage_modifier) average_dps += dps_tuple damage_breakdown['eviscerate'] = average_dps - + if 'death_from_above_strike' in attacks_per_second: if self.settings.get_spec() == 'assassination': average_dps = 0 @@ -263,7 +263,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_strike'], attacks_per_second['death_from_above_strike'][i], crit_damage_modifier) average_dps += dps_tuple damage_breakdown['death_from_above_strike'] = average_dps - + if 'death_from_above_pulse' in attacks_per_second: average_dps = 0 for i in xrange(1, 6): @@ -271,7 +271,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_pulse'], attacks_per_second['death_from_above_pulse'][i], crit_damage_modifier) average_dps += dps_tuple damage_breakdown['death_from_above_pulse'] = average_dps - + #shadow reflection code block if self.talents.shadow_reflection: for ability in attacks_per_second: @@ -301,7 +301,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da average_dps = self.get_formula(ability)(average_ap) * modifier average_dps = self.get_dps_contribution(average_dps, crit_rates[crit_name], attacks_per_second[ability], crit_damage_modifier) damage_breakdown[ability] = average_dps - + for proc in damage_procs: if proc.proc_name not in damage_breakdown: # Toss multiple damage procs with the same name (Avalanche): @@ -317,29 +317,29 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da nightstalker_percent = self.total_openers_per_second / (ability) modifier = 1 + nightstalker_mod * nightstalker_percent damage_breakdown[self.settings.opener_name] *= modifier - - + + return damage_breakdown, additional_info - + #autoattacks def mh_damage(self, ap): return self.get_weapon_damage('mh', ap, is_normalized=False) def oh_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) - + def mh_shuriken(self, ap): return mh_damage(ap) - + def oh_shuriken(self, ap): return oh_damage(ap) - + #abilities def ambush_damage(self, ap): return 3.15 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) def ambush_sr_damage(self, ap): return 3.15 * 1.4 * 1.8 * 0.924 * ap / 3.5 - + def backstab_damage(self, ap): return 2.52 * self.get_weapon_damage('mh', ap) def backstab_sr_damage(self, ap): @@ -349,27 +349,27 @@ def death_from_above_pulse_damage(self, ap, cp): return 0.266 * cp * ap def death_from_above_pulse_sr_damage(self, ap, cp): return 0.266 * cp * 0.924 * ap - + def dispatch_damage(self, ap): return 3.30 * self.get_weapon_damage('mh', ap) def dispatch_sr_damage(self, ap): return 3.30 * 1.8 * 0.924 * ap / 3.5 - + def envenom_damage(self, ap, cp): return .417 * cp * ap def envenom_sr_damage(self, ap, cp): return .417 * cp * 0.924 * ap - + def eviscerate_damage(self, ap, cp): return .491 * cp * ap #check this datamining contradicts patch notes def eviscerate_sr_damage(self, ap, cp): return .491 * cp * 0.924 * ap - + def garrote_tick_damage(self, ap): return .2241 * ap def garrote_tick_sr_damage(self, ap): return .2241 * 0.924 * ap - + #20% damage more hotfix def hemorrhage_damage(self, ap): return 1.3 * 1.2 * .40 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) @@ -390,12 +390,12 @@ def oh_killing_spree_damage(self, ap): return 1.0 * self.oh_penalty() * self.get_weapon_damage('oh', ap) def oh_killing_spree_sr_damage(self, ap): return 1.0 * 1.8 * 0.924 * ap / 3.5 * 0.5 - + def main_gauche_damage(self, ap): return 1.4 * self.oh_penalty() * self.get_weapon_damage('oh', ap) def main_gauche_sr_damage(self, ap): return 1.4 * 1.8 * 0.924 * ap / 3.5 - + def mh_mutilate_damage(self, ap): return 2.73 * self.get_weapon_damage('mh', ap) def mh_mutilate_sr_damage(self, ap): @@ -410,17 +410,17 @@ def revealing_strike_damage(self, ap): return 1.2 * self.get_weapon_damage('mh', ap) def revealing_strike_sr_damage(self, ap): return 1.2 * 1.8 * 0.924 * ap / 3.5 - + def rupture_tick_damage(self, ap, cp): return .0685 * cp * ap def rupture_tick_sr_damage(self, ap, cp): return .0685 * 0.924 * ap - + def sinister_strike_damage(self, ap): return 1.76 * self.get_weapon_damage('mh', ap) def sinister_strike_sr_damage(self, ap): return 1.76 * 1.8 * 0.924 * ap / 3.5 - + def venomous_wounds_damage(self, ap): return 1.2 * .320 * ap def venomous_wounds_sr_damage(self, ap): @@ -438,7 +438,7 @@ def swift_poison_damage(self, ap): def wound_poison_damage(self, ap): return .6 * .2179999948 * ap #40% reduction hotfix - + #unused def fan_of_knives_damage(self, ap): return .231 * ap @@ -457,7 +457,7 @@ def throw_damage(self, ap): def shuriken_toss_damage(self, ap): return 1.2 * ap - + def get_formula(self, name): formulas = { 'backstab': self.backstab_damage, @@ -499,7 +499,7 @@ def get_formula(self, name): def get_spell_stats(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod return (cost, self.ability_info[ability][1]) - + def get_spell_cd(self, ability): #need to update list of affected abilities if ability in self.cd_reduction_table[self.settings.get_spec()]: diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index 5d92e94..78a47cb 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -39,22 +39,22 @@ class Buffs(object): 'food_wod_versatility_125', # 'food_felmouth_frenzy', # Felmouth frenzy, 2 haste scaling RPPM dealing 0.424 AP in damage ###LEGION### - 'flask_legion_agi', #Flask of the Seventh Demon - 'food_legion_masstery_450', # - 'food_legion_crit_450', # - 'food_legion_haste_450', # - 'food_legion_haste_450', # - 'food_legion_masstery_600', # - 'food_legion_crit_600', # - 'food_legion_haste_600', # - 'food_legion_haste_600', # - 'food_legion_masstery_700', # - 'food_legion_crit_700', # - 'food_legion_haste_700', # - 'food_legion_haste_700', # - 'food_legion_damage_1', # - 'food_legion_damage_2', # - 'food_legion_damage_3', # + 'flask_legion_agi', # Flask of the Seventh Demon + 'food_legion_mastery_225', # Pickeled Stormray + 'food_legion_crit_225', # Salt & Pepper Shank + 'food_legion_haste_225', # Deep-Fried Mossgill + 'food_legion_versatility_225', # Faronaar Fizz + 'food_legion_mastery_300', # Barracude Mrglgagh + 'food_legion_crit_300', # Leybeque Ribs + 'food_legion_haste_300', # Suramar Surf and Turf + 'food_legion_versatility_300', # Koi-Scented Stormray + 'food_legion_mastery_375', # Nightborne Delicacy Platter + 'food_legion_crit_375', # The Hungry Magister + 'food_legion_haste_375', # Azshari Salad + 'food_legion_versatility_375', # Seed-Battered Fish Plate + 'food_legion_damage_1', # Spiced Rib Roast + 'food_legion_damage_2', # Drogbar-Style Salmon + 'food_legion_damage_3', # Fishbrul Special ]) buffs_debuffs = frozenset([ @@ -88,15 +88,11 @@ def __getattr__(self, name): def __setattr__(self, name, value): object.__setattr__(self, name, value) - if name == 'level': - self._set_constants_for_level() def buff_agi(self, race=False): bonus_agi = 0 - bonus_agi += 114 * self.agi_flask_mop bonus_agi += 200 * self.flask_wod_agi_200 bonus_agi += 250 * self.flask_wod_agi - bonus_agi += 34 * self.food_mop_agi * [1, 2][race] bonus_agi += 1300 * self.flask_legion_agi return bonus_agi @@ -105,9 +101,9 @@ def buff_haste(self, race=False): bonus_haste += 125 * self.food_wod_haste_125 * [1, 2][race] bonus_haste += 100 * self.food_wod_haste * [1, 2][race] bonus_haste += 75 * self.food_wod_haste_75 * [1, 2][race] - bonus_haste += 450 * self.food_wod_haste_450 * [1, 2][race] - bonus_haste += 600 * self.food_wod_haste_600 * [1, 2][race] - bonus_haste += 700 * self.food_wod_haste_700 * [1, 2][race] + bonus_haste += 225 * self.food_legion_haste_225 * [1, 2][race] + bonus_haste += 300 * self.food_legion_haste_300 * [1, 2][race] + bonus_haste += 375 * self.food_legion_haste_375 * [1, 2][race] return bonus_haste def buff_crit(self, race=False): @@ -115,20 +111,19 @@ def buff_crit(self, race=False): bonus_crit += 125 * self.food_wod_crit_125 * [1, 2][race] bonus_crit += 100 * self.food_wod_crit * [1, 2][race] bonus_crit += 75 * self.food_wod_crit_75 * [1, 2][race] - bonus_crit += 450 * self.food_wod_crit_450 * [1, 2][race] - bonus_crit += 600 * self.food_wod_crit_600 * [1, 2][race] - bonus_crit += 700 * self.food_wod_crit_700 * [1, 2][race] + bonus_crit += 225 * self.food_legion_crit_225 * [1, 2][race] + bonus_crit += 300 * self.food_legion_crit_300 * [1, 2][race] + bonus_crit += 375 * self.food_legion_crit_375 * [1, 2][race] return bonus_crit def buff_mast(self, race=False): bonus_mastery = 0 - bonus_mastery += [0, self.mast_buff_bonus][self.mastery_buff] bonus_mastery += 125 * self.food_wod_mastery_125 * [1, 2][race] bonus_mastery += 100 * self.food_wod_mastery * [1, 2][race] bonus_mastery += 75 * self.food_wod_mastery_75 * [1, 2][race] - bonus_mastery += 450 * self.food_wod_mastery_450 * [1, 2][race] - bonus_mastery += 600 * self.food_wod_mastery_600 * [1, 2][race] - bonus_mastery += 700 * self.food_wod_mastery_700 * [1, 2][race] + bonus_mastery += 225 * self.food_legion_mastery_225 * [1, 2][race] + bonus_mastery += 300 * self.food_legion_mastery_300 * [1, 2][race] + bonus_mastery += 375 * self.food_legion_mastery_375 * [1, 2][race] return bonus_mastery def buff_versatility(self, race=False): @@ -136,9 +131,9 @@ def buff_versatility(self, race=False): bonus_versatility += 125 * self.food_wod_versatility_125 * [1, 2][race] bonus_versatility += 100 * self.food_wod_versatility * [1, 2][race] bonus_versatility += 75 * self.food_wod_versatility_75 * [1, 2][race] - bonus_versatility += 450 * self.food_wod_versatility_450 * [1, 2][race] - bonus_versatility += 600 * self.food_wod_versatility_600 * [1, 2][race] - bonus_versatility += 700 * self.food_wod_versatility_700 * [1, 2][race] + bonus_versatility += 225 * self.food_legion_versatility_225 * [1, 2][race] + bonus_versatility += 300 * self.food_legion_versatility_300 * [1, 2][race] + bonus_versatility += 375 * self.food_legion_versatility_375 * [1, 2][race] return bonus_versatility def felmouth_food(self): From b33398b2058cb7d7255e79bec23c10f27b593c94 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 7 Aug 2016 10:53:02 -0400 Subject: [PATCH 031/265] Update all damage forumulas -Add first two traits back into artifact data, can be skipped for ranking --- shadowcraft/calcs/rogue/__init__.py | 357 ++++++++++----------------- shadowcraft/objects/artifact_data.py | 12 +- 2 files changed, 143 insertions(+), 226 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 2b1a64a..4502d3c 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -15,69 +15,72 @@ class RogueDamageCalculator(DamageCalculator): # backstab damage as a function of AP - that (almost) any rogue damage # calculator will need to know, so things like that go here. - default_ep_stats = ['agi', 'haste', 'crit', 'mastery', 'ap', 'multistrike', 'versatility'] #'readiness' - melee_attacks = ['mh_autoattack_hits', 'oh_autoattack_hits', 'autoattack', - 'eviscerate', 'envenom', 'ambush', 'garrote', - 'sinister_strike', 'revealing_strike', 'main_gauche', 'mh_killing_spree', 'oh_killing_spree', - 'backstab', 'hemorrhage', - 'mutilate', 'mh_mutilate', 'oh_mutilate', 'dispatch', "death_from_above_strike"] - other_attacks = ['deadly_instant_poison', 'swift_poison'] - aoe_attacks = ['fan_of_knives', 'crimson_tempest', "death_from_above_pulse"] - dot_ticks = ['rupture_ticks', 'garrote_ticks', 'deadly_poison', 'hemorrhage_dot'] - ranged_attacks = ['shuriken_toss', 'throw'] - non_dot_attacks = melee_attacks + ranged_attacks + aoe_attacks - all_attacks = melee_attacks + ranged_attacks + dot_ticks + aoe_attacks + other_attacks + default_ep_stats = ['agi', 'haste', 'crit', 'mastery', 'ap', 'versatility'] assassination_mastery_conversion = .035 combat_mastery_conversion = .022 subtlety_mastery_conversion = .0276 - assassination_readiness_conversion = 1.0 - combat_readiness_conversion = 1.0 - subtlety_readiness_conversion = 1.0 raid_modifiers_cache = {'physical':None, 'bleed':None, 'spell':None} ability_info = { - 'ambush': (60., 'strike'), - 'backstab': (35., 'strike'), - 'dispatch': (30., 'strike'), + #general + 'crimson_vial': (30., 'buff'), + 'death_from_above': (25., 'strike'), + 'feint': (20., 'buff'), + 'kick': (15., 'strike'), + #assassination 'envenom': (35., 'strike'), - 'eviscerate': (35., 'strike'), + 'fan_of_knives': (35., 'strike'), 'garrote': (45., 'strike'), 'hemorrhage': (30., 'strike'), + 'kingsbane': (35., 'strike'), 'mutilate': (55., 'strike'), - 'recuperate': (30., 'buff'), - 'revealing_strike': (40., 'strike'), + 'poisoned_knife': (40., 'strike'), 'rupture': (25., 'strike'), - 'sinister_strike': (50., 'strike'), + #outlaw + 'ambush': (60., 'strike'), + 'between_the_eyes': (35., 'strike'), + 'blunderbuss': (40., 'strike'), + 'ghostly_stike': (30., 'strike'), + 'pistol_shot': (40., 'strike'), + 'roll_the_bones': (25., 'buff'), + 'run_through': (35., 'strike'), + 'saber_slash': (50., 'strike'), 'slice_and_dice': (25., 'buff'), - 'tricks_of_the_trade': (0, 'buff'), + #subtlety + 'backstab': (35., 'strike'), + 'eviscerate': (35., 'strike'), + 'gloomblade': (35., 'strike'), + 'nightblade': (25., 'strike'), + 'shuriken_storm': (35., 'strike'), 'shuriken_toss': (40., 'strike'), - 'shiv': (20., 'strike'), - 'feint': (20., 'buff'), - 'death_from_above': (50., 'strike'), + 'symbols_of_death': (20., 'buff'), } ability_cds = { - 'tricks_of_the_trade': 30, - 'kick': 15, - 'shiv': 8, - 'vanish': 120, - 'vendetta': 120, - 'adrenaline_rush': 180, - 'killing_spree': 120, - 'shadow_dance': 60, - 'shadowmeld': 120, - 'marked_for_death': 60, - 'preparation': 300, - 'death_from_above': 20, - 'shadow_reflection': 120, + #general + 'crimson_vial': 30, + 'death_from_above': 20, + 'kick': 15, + 'marked_for_death': 60, + 'sprint': 60, + 'tricks_of_the_trade': 30, + 'vanish': 120, + #assassination + 'exsanguinate': 45, + 'kingsbane': 45, + 'vendetta': 120, + #outlaw + 'adrenaline_rush': 180, + 'cannonball_barrage': 60, + 'curse_of_the_dreadblades': 90, + 'killing_spree': 120, + #subtlety + 'goremaws_bite': 60, + 'shadow_dance': 60, } - cd_reduction_table = {'assassination': ['vanish', 'vendetta'], - 'combat': ['adrenaline_rush', 'killing_spree'], - 'subtlety': ['shadow_dance'] - } def __setattr__(self, name, value): object.__setattr__(self, name, value) @@ -272,53 +275,12 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da average_dps += dps_tuple damage_breakdown['death_from_above_pulse'] = average_dps - #shadow reflection code block - if self.talents.shadow_reflection: - for ability in attacks_per_second: - if 'sr_' in ability: - modifier = 1. - if ability[3:] in ('envenom'): - modifier *= spell_modifier * potent_poisons_mod - elif ability == 'sr_rupture_ticks': - modifier *= bleed_modifier - else: - if ability == 'sr_eviscerate': - modifier *= executioner_mod - modifier *= physical_modifier - crit_name = ability - if ability not in crit_rates: - crit_name = ability[3:] - if 'mh_' in crit_name or 'oh_' in crit_name: - crit_name = crit_name[3:] - if type(attacks_per_second[ability]) in (tuple, list): - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.get_formula(ability)(average_ap, i) * modifier - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates[crit_name], attacks_per_second[ability][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown[ability] = average_dps - else: - average_dps = self.get_formula(ability)(average_ap) * modifier - average_dps = self.get_dps_contribution(average_dps, crit_rates[crit_name], attacks_per_second[ability], crit_damage_modifier) - damage_breakdown[ability] = average_dps - for proc in damage_procs: if proc.proc_name not in damage_breakdown: # Toss multiple damage procs with the same name (Avalanche): # attacks_per_second is already being updated with that key. damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, damage_breakdown) - if self.talents.nightstalker: - nightstalker_mod = .50 - if self.settings.opener_name in ('eviscerate', 'envenom'): - ability = attacks_per_second[self.settings.opener_name][5] - elif self.settings.opener_name in attacks_per_second: - ability = attacks_per_second[self.settings.opener_name] - nightstalker_percent = self.total_openers_per_second / (ability) - modifier = 1 + nightstalker_mod * nightstalker_percent - damage_breakdown[self.settings.opener_name] *= modifier - - return damage_breakdown, additional_info #autoattacks @@ -328,177 +290,132 @@ def mh_damage(self, ap): def oh_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) - def mh_shuriken(self, ap): - return mh_damage(ap) - - def oh_shuriken(self, ap): - return oh_damage(ap) - - #abilities - def ambush_damage(self, ap): - return 3.15 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) - def ambush_sr_damage(self, ap): - return 3.15 * 1.4 * 1.8 * 0.924 * ap / 3.5 - - def backstab_damage(self, ap): - return 2.52 * self.get_weapon_damage('mh', ap) - def backstab_sr_damage(self, ap): - return 2.52 * 1.8 * 0.924 * ap / 3.5 - + #general abilities def death_from_above_pulse_damage(self, ap, cp): - return 0.266 * cp * ap - def death_from_above_pulse_sr_damage(self, ap, cp): - return 0.266 * cp * 0.924 * ap + return 0.3666 * cp * ap - def dispatch_damage(self, ap): - return 3.30 * self.get_weapon_damage('mh', ap) - def dispatch_sr_damage(self, ap): - return 3.30 * 1.8 * 0.924 * ap / 3.5 + #assassination + def deadly_poison_tick_damage(self, ap): + return .275 * ap + (1 + (0.05 * self.traits.master_alchemist)) + def deadly_instant_poison_damage(self, ap): + return .142 * ap + (1 + (0.05 * self.traits.master_alchemist)) + + #Maybe add better handling for "rule of three" for artifact traits def envenom_damage(self, ap, cp): - return .417 * cp * ap - def envenom_sr_damage(self, ap, cp): - return .417 * cp * 0.924 * ap + return .5 * cp * ap * (1 + (0.0333 * self.traits.toxic_blades)) - def eviscerate_damage(self, ap, cp): - return .491 * cp * ap #check this datamining contradicts patch notes - def eviscerate_sr_damage(self, ap, cp): - return .491 * cp * 0.924 * ap + def fan_of_knives_damage(self, ap): + return .8316 * ap def garrote_tick_damage(self, ap): - return .2241 * ap - def garrote_tick_sr_damage(self, ap): - return .2241 * 0.924 * ap + return .9 * ap - #20% damage more hotfix def hemorrhage_damage(self, ap): - return 1.3 * 1.2 * .40 * [1., 1.4][self.stats.mh.type == 'dagger'] * self.get_weapon_damage('mh', ap) - def hemorrhage_sr_damage(self, ap): - return 1.3 * 1.2 * .4 * 1.4 * 1.8 * 0.924 * ap / 3.5 - - def hemorrhage_tick_damage(self, ap): - return 1.3 * 1.2 * .035 * ap - def hemorrhage_tick_sr_damage(self, ap): - return 1.3 * 1.2 * .035 * 0.924 * ap + return 1 * self.get_weapon_damage('mh', ap) - def mh_killing_spree_damage(self, ap): - return 1.0 * self.get_weapon_damage('mh', ap) - def mh_killing_spree_sr_damage(self, ap): - return 1.0 * 1.8 * 0.924 * ap / 3.5 + def mh_kingsbane_damage(self, ap): + return 3 * self.get_weapon_damage('mh', ap) - def oh_killing_spree_damage(self, ap): - return 1.0 * self.oh_penalty() * self.get_weapon_damage('oh', ap) - def oh_killing_spree_sr_damage(self, ap): - return 1.0 * 1.8 * 0.924 * ap / 3.5 * 0.5 + def oh_kingsbane_damage(self, ap): + return 3 * self.oh_penalty * self.get_weapon_damage('oh', ap) - def main_gauche_damage(self, ap): - return 1.4 * self.oh_penalty() * self.get_weapon_damage('oh', ap) - def main_gauche_sr_damage(self, ap): - return 1.4 * 1.8 * 0.924 * ap / 3.5 + def kingsbane_tick_damage(self, ap): + return 0.45 * ap def mh_mutilate_damage(self, ap): - return 2.73 * self.get_weapon_damage('mh', ap) - def mh_mutilate_sr_damage(self, ap): - return 2.73 * 1.8 * 0.924 * ap / 3.5 + return 3.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.assassins_blades)) def oh_mutilate_damage(self, ap): - return 2.73 * self.oh_penalty() * self.get_weapon_damage('oh', ap) - def oh_mutilate_sr_damage(self, ap): - return 2.73 * 1.8 * 0.924 * ap / 3.5 * 0.5 + return 3.6 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.15 * self.traits.assassins_blades)) - def revealing_strike_damage(self, ap): - return 1.2 * self.get_weapon_damage('mh', ap) - def revealing_strike_sr_damage(self, ap): - return 1.2 * 1.8 * 0.924 * ap / 3.5 + def poisoned_knife_damage(self, ap): + return 0.6 * ap - def rupture_tick_damage(self, ap, cp): - return .0685 * cp * ap - def rupture_tick_sr_damage(self, ap, cp): - return .0685 * 0.924 * ap + def rupture_tick_damage(self, ap): + return .3 * ap * (1 + (0.0333 * self.traits.toxic_blades)) - def sinister_strike_damage(self, ap): + #outlaw + def ambush_damage(self, ap): + return 4.5 * self.get_weapon_damage('mh', ap) + + def between_the_eyes_damage(self, ap, cp): + return .75 * cp * ap * (1 + (0.08 / self.traits.black_powder)) + + #7*55% AP + def blunderbuss_damage(self, ap): + return 3.85 * ap + + #Ignoring that this behaves as a dot for simplicity + def cannonball_barrage_damage(self, ap): + return 7.2 * ap + + def ghostly_strike_damage(self, ap): return 1.76 * self.get_weapon_damage('mh', ap) - def sinister_strike_sr_damage(self, ap): - return 1.76 * 1.8 * 0.924 * ap / 3.5 - def venomous_wounds_damage(self, ap): - return 1.2 * .320 * ap - def venomous_wounds_sr_damage(self, ap): - return 1.2 * .320 * 0.924 * ap + def mh_greed_damage(self, ap): + return 3.5 * self.get_weapon_damage('mh', ap) - #poisons - def deadly_poison_tick_damage(self, ap): - return .25014 * ap + def oh_greed_damage(self, ap): + return 3.5 * self.oh_penalty * self.get_weapon_damage('oh', ap) - def deadly_instant_poison_damage(self, ap): - return .1287000030 * ap + #For KsP treat each hit individually + def mh_killing_spree_damage(self, ap): + return 2.108 * self.get_weapon_damage('mh', ap) - def swift_poison_damage(self, ap): - return .264 * ap + def oh_killing_spree_damage(self, ap): + return 2.018* self.oh_penalty() * self.get_weapon_damage('oh', ap) - def wound_poison_damage(self, ap): - return .6 * .2179999948 * ap #40% reduction hotfix + def main_gauche_damage(self, ap): + return 2.1 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.1 * self.traits.fortunes_strike)) - #unused - def fan_of_knives_damage(self, ap): - return .231 * ap + def pistol_shot_damage(self, ap): + return 1.5 * ap - def crimson_tempest_damage(self, ap, cp): - return .0903 * cp * ap + def run_through_damage(self, ap, cp): + return 1.2 * ap * cp * (1 * (0.08 * self.traits.fates_thirst)) - def crimson_tempest_tick_damage(self, ap, cp): - return self.crimson_tempest_damage(ap, cp) * (2.4 / 6) + def saber_slash_damage(self, ap): + return 2.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) + + #subtlety + #Ignore positional modifier for now + def backstab_damage(self, ap): + return 3.7 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) - def shiv_damage(self, ap): - return .10 * self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) + def eviscerate_damage(self, ap, cp): + return 1.28 * cp * ap - def throw_damage(self, ap): - return .05 * ap + def gloomblade_damage(self, ap): + return 4.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) + + def mh_goremaws_bite_damage(self, ap): + return 5 * self.get_weapon_damage('mh', ap) + + def oh_goremaws_bite_damage(self, ap): + return 5 * self.oh_penalty() * self.get_weapon_damage('oh', ap) + + def nightblade_tick_damage(self, ap): + return 1.2 * ap * (1 + (0.05 * self.traits.demon_kiss)) + + def shadowstrike_damage(self, ap): + return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) + + def mh_shadow_blades_damage(self, ap): + return self.get_weapon_damage('mh', ap, is_normalized=False) + + def oh_shadow_blades_damage(self, ap): + return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) + + def shuriken_storm_damage(self, ap): + return 0.5544 * ap def shuriken_toss_damage(self, ap): return 1.2 * ap - def get_formula(self, name): - formulas = { - 'backstab': self.backstab_damage, - 'hemorrhage': self.hemorrhage_damage, - 'sinister_strike': self.sinister_strike_damage, - 'revealing_strike': self.revealing_strike_damage, - 'main_gauche': self.main_gauche_damage, - 'ambush': self.ambush_damage, - 'eviscerate': self.eviscerate_damage, - 'dispatch': self.dispatch_damage, - 'mh_mutilate': self.mh_mutilate_damage, - 'oh_mutilate': self.oh_mutilate_damage, - 'venomous_wounds': self.venomous_wounds_damage, - 'deadly_poison': self.deadly_poison_tick_damage, - 'wound_poison': self.wound_poison_damage, - 'deadly_instant_poison': self.deadly_instant_poison_damage, - 'swift_poison': self.swift_poison_damage, - 'shuriken_toss': self.shuriken_toss_damage, - 'death_from_above_pulse':self.death_from_above_pulse_damage, - #shadow reflection abilities - 'sr_backstab': self.backstab_sr_damage, - 'sr_hemorrhage': self.hemorrhage_sr_damage, - 'sr_sinister_strike': self.sinister_strike_sr_damage, - 'sr_revealing_strike': self.revealing_strike_sr_damage, - 'sr_main_gauche': self.main_gauche_sr_damage, - 'sr_ambush': self.ambush_sr_damage, - 'sr_eviscerate': self.eviscerate_sr_damage, - 'sr_envenom': self.envenom_sr_damage, - 'sr_dispatch': self.dispatch_sr_damage, - 'sr_mh_mutilate': self.mh_mutilate_sr_damage, - 'sr_oh_mutilate': self.oh_mutilate_sr_damage, - 'sr_mh_killing_spree': self.mh_killing_spree_sr_damage, - 'sr_oh_killing_spree': self.oh_killing_spree_sr_damage, - 'sr_venomous_wounds': self.venomous_wounds_sr_damage, - 'sr_rupture_ticks': self.rupture_tick_sr_damage, - } - return formulas[name] - - def get_spell_stats(self, ability, cost_mod=1.0): + def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod - return (cost, self.ability_info[ability][1]) + return cost def get_spell_cd(self, ability): #need to update list of affected abilities diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 694eab6..410845b 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -1,7 +1,7 @@ traits = { ('rogue', 'assassination'): ( - #'kingsbane', - #'assassins_blades', + 'kingsbane', + 'assassins_blades', 'toxic_blades', 'poison_knives', 'urge_to_kill', @@ -20,8 +20,8 @@ 'slayers_precision', ), ('rogue', 'outlaw'): ( - #'curse_of_the_dreadblades', - #'cursed_edges', + 'curse_of_the_dreadblades', + 'cursed_edges', 'fates_thirst', 'blade_dancer', 'fatebringer', @@ -40,8 +40,8 @@ 'cursed_steel', ), ('rogue', 'subtlety'): ( - #'goremaws_bite', - #'shadow_fangs', + 'goremaws_bite', + 'shadow_fangs', 'gutripper', 'fortunes_bite', 'catlike_reflexes', From 38971f58e8d25f4884a72fa5532d4e346dd1fcf2 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 7 Aug 2016 17:19:51 -0400 Subject: [PATCH 032/265] Extensive redesign of RogueDamageCalculator -Note: Untested --- shadowcraft/calcs/__init__.py | 15 +- shadowcraft/calcs/rogue/__init__.py | 341 ++++++++++++++++------------ 2 files changed, 193 insertions(+), 163 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 69cd6ad..6ffca2f 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -677,17 +677,4 @@ def target_armor(self, armor=None): # Passes base armor reduced by armor debuffs or overridden armor if armor is None: armor = self.target_base_armor - return armor #* self.buffs.armor_reduction_multiplier() - - def raid_settings_modifiers(self, attack_kind, armor=None, affect_resil=True): - # This function wraps spell, bleed and physical debuffs from raid - # along with all-damage buff and armor reduction. It should be called - # from every damage dealing formula. Armor can be overridden if needed. - if attack_kind not in ('physical', 'spell', 'bleed'): - raise exceptions.InvalidInputException(_('Attacks must be categorized as physical, spell or bleed')) - elif attack_kind == 'spell': - return self.buffs.spell_damage_multiplier() - elif attack_kind == 'bleed': - return self.buffs.bleed_damage_multiplier() - elif attack_kind == 'physical': - return self.buffs.physical_damage_multiplier() * self.armor_mitigation_multiplier(armor) + return armor #* self.buffs.armor_reduction_multiplier() \ No newline at end of file diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 4502d3c..8b807b8 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -17,14 +17,40 @@ class RogueDamageCalculator(DamageCalculator): default_ep_stats = ['agi', 'haste', 'crit', 'mastery', 'ap', 'versatility'] + assassination_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'deadly_poison', 'deadly_instant_poison', 'envenom', + 'fan_of_knives', 'garrote_ticks', 'hemorrhage', + 'kingsbane', 'kingsbane_ticks', 'mutilate', + 'poisoned_knife', 'rupture_ticks'] + outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', + 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', + 'pistol_shot', 'run_through', 'saber_slash'] + subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', + 'shadow_blade', 'shuriken_storm', 'shuriken_toss'] + #All damage sources mitigated by armor + physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', + 'ambush, between_the_eyes', 'blunderbuss', 'cannonball_barrage', + 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', + 'pistol_shot', 'run_through', 'saber_slash', 'backstab', + 'eviscerate', 'shuriken_storm', 'shuriken_toss'] + #All damage sources the scale with mastery (assn or sub) + mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', + 'eviscerate', 'nightblade'] + #All damage sources that deal damage with both hands + dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', + 'goremaws_bite, shadow_blades'] + #All damage sources that scale with cps + cp_scaling_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'envenom', 'rupture_ticks', 'between_the_eyes', + 'run_through', 'eviscerate'] + assassination_mastery_conversion = .035 combat_mastery_conversion = .022 subtlety_mastery_conversion = .0276 - raid_modifiers_cache = {'physical':None, - 'bleed':None, - 'spell':None} - ability_info = { #general 'crimson_vial': (30., 'buff'), @@ -82,6 +108,8 @@ class RogueDamageCalculator(DamageCalculator): 'shadow_dance': 60, } + + def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': @@ -105,60 +133,61 @@ def get_weapon_damage(self, hand, ap, is_normalized=True): def oh_penalty(self): return .5 - def get_modifiers(self, current_stats, damage_type='physical', armor=None, executioner_modifier=1., potent_poisons_modifier=1.): - # self.damage_modifier_cache stores common modifiers like Assassin's Resolve that won't change between calculations - # this cuts down on repetitive if statements + def get_base_modifier(self, current_stats): base_modifier = self.damage_modifier_cache - - # Raid modifiers - if not self.raid_modifiers_cache[damage_type]: - self.raid_modifiers_cache[damage_type] = self.raid_settings_modifiers(attack_kind=damage_type, armor=armor) - base_modifier *= self.raid_modifiers_cache[damage_type] - - # potent poisons and executioner should be calculated outside, and passed in, no need to recalculate the % each time - base_modifier *= executioner_modifier - base_modifier *= potent_poisons_modifier - - #versatility is a generic damage modifier base_modifier *= (self.stats.get_versatility_multiplier_from_rating(rating=current_stats['versatility']) + self.buffs.versatility_bonus()) - return base_modifier def get_dps_contribution(self, base_damage, crit_rate, frequency, crit_modifier): average_hit = base_damage * (1 - crit_rate) + base_damage * crit_rate * crit_modifier return average_hit * frequency + + #Computes a merged dps contribution for an ability + def get_ability_dps(self, ap, ability, attacks_per_second, crit_rate, modifier, crit_modifier, both_hands=False, cps=0): + if both_hands: + ability_list = [hand + ability for hand in ['mh_', 'oh_']] + else: + ability_list = [ability] + + dps = 0 + if not cps: + for a in ability_list: + base_damage = self.get_formula(a)(ap) * modifier + dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second, crit_modifier) + else: + for i in xrange(1, cps+1): + for a in ability_list: + base_damage = self.get_formula(a)(ap, i) * modifier + dps += self.get_dps_contribution(base_damage, crit_rate[i], attacks_per_second[i]. crit_modifier) + return dps + def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): average_ap = current_stats['ap'] + current_stats['agi'] * self.stat_multipliers['ap'] + max_cps = 5 + int(self.talents.deeper_strategem) self.setup_unique_procs(current_stats, average_ap) damage_breakdown = {} - # we calculate mastery here to reduce redundant calls - # can't rely on spec init thread because stats change afterwards - executioner_mod = 1. - potent_poisons_mod = 1. - if self.settings.is_subtlety_rogue(): - executioner_mod = 1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) - if self.settings.is_assassination_rogue(): - potent_poisons_mod = 1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) - - # these return the tuple (damage_modifier, crit_multiplier) crit_damage_modifier = self.crit_damage_modifiers() - physical_modifier = self.get_modifiers(current_stats, damage_type='physical') - spell_modifier = self.get_modifiers(current_stats, damage_type='spell') - bleed_modifier = self.get_modifiers(current_stats, damage_type='bleed') + base_modifier = self.get_base_modifier(current_stats) + armor_modifier = self.armor_mitigation_multiplier() + + # this removes keys with empty values, prevents errors from: attacks_per_second['sinister_strike'] = None + for key in attacks_per_second.keys(): + if not attacks_per_second[key]: + del attacks_per_second[key] if 'mh_autoattacks' in attacks_per_second: # Assumes mh and oh attacks are both active at the same time. As they should always be. # Friends don't let friends raid without gear. - mh_base_damage = self.mh_damage(average_ap) * physical_modifier + mh_base_damage = self.mh_damage(average_ap) * armor_modifier * base_modifier mh_hit_rate = self.dw_mh_hit_chance - crit_rates['mh_autoattacks'] average_mh_hit = mh_hit_rate * mh_base_damage + crit_rates['mh_autoattacks'] * mh_base_damage * crit_damage_modifier mh_dps_tuple = average_mh_hit * attacks_per_second['mh_autoattacks'] - oh_base_damage = self.oh_damage(average_ap) * physical_modifier + oh_base_damage = self.oh_damage(average_ap) * armor_modifier * base_modifier oh_hit_rate = self.dw_oh_hit_chance - crit_rates['oh_autoattacks'] average_oh_hit = oh_hit_rate * oh_base_damage + crit_rates['oh_autoattacks'] * oh_base_damage * crit_damage_modifier oh_dps_tuple = average_oh_hit * attacks_per_second['oh_autoattacks'] @@ -168,112 +197,6 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da damage_breakdown['mh_autoattack'] = mh_dps_tuple damage_breakdown['oh_autoattack'] = oh_dps_tuple - # this removes keys with empty values, prevents errors from: attacks_per_second['sinister_strike'] = None - for key in attacks_per_second.keys(): - if not attacks_per_second[key]: - del attacks_per_second[key] - - if 'mutilate' in attacks_per_second: - mh_dmg = self.mh_mutilate_damage(average_ap) * physical_modifier - oh_dmg = self.oh_mutilate_damage(average_ap) * physical_modifier - mh_mutilate_dps = self.get_dps_contribution(mh_dmg, crit_rates['mutilate'], attacks_per_second['mutilate'], crit_damage_modifier) - oh_mutilate_dps = self.get_dps_contribution(oh_dmg, crit_rates['mutilate'], attacks_per_second['mutilate'], crit_damage_modifier) - if self.settings.merge_damage: - damage_breakdown['mutilate'] = mh_mutilate_dps + oh_mutilate_dps - else: - damage_breakdown['mh_mutilate'] = mh_mutilate_dps - damage_breakdown['oh_mutilate'] = oh_mutilate_dps - - for strike in ('hemorrhage', 'backstab', 'sinister_strike', 'revealing_strike', 'main_gauche', 'ambush', 'dispatch', 'shuriken_toss'): - if strike in attacks_per_second: - dps = self.get_formula(strike)(average_ap) * physical_modifier - dps = self.get_dps_contribution(dps, crit_rates[strike], attacks_per_second[strike], crit_damage_modifier) - if strike in ('sinister_strike', 'backstab'): - dps *= self.stats.gear_buffs.rogue_t14_2pc_damage_bonus(strike) - damage_breakdown[strike] = dps - - for poison in ('venomous_wounds', 'deadly_poison', 'wound_poison', 'deadly_instant_poison', 'swift_poison'): - if poison in attacks_per_second: - damage = self.get_formula(poison)(average_ap) * spell_modifier * potent_poisons_mod - damage = self.get_dps_contribution(damage, crit_rates[poison], attacks_per_second[poison], crit_damage_modifier) - if poison == 'venomous_wounds': - damage *= self.stats.gear_buffs.rogue_t14_2pc_damage_bonus('venomous_wounds') - damage_breakdown[poison] = damage - - if 'mh_killing_spree' in attacks_per_second: - mh_dmg = self.mh_killing_spree_damage(average_ap) * physical_modifier - oh_dmg = self.oh_killing_spree_damage(average_ap) * physical_modifier - mh_killing_spree_dps = self.get_dps_contribution(mh_dmg, crit_rates['killing_spree'], attacks_per_second['mh_killing_spree'], crit_damage_modifier) - oh_killing_spree_dps = self.get_dps_contribution(oh_dmg, crit_rates['killing_spree'], attacks_per_second['oh_killing_spree'], crit_damage_modifier) - if self.settings.merge_damage: - damage_breakdown['killing_spree'] = mh_killing_spree_dps + oh_killing_spree_dps - else: - damage_breakdown['mh_killing_spree'] = mh_killing_spree_dps - damage_breakdown['oh_killing_spree'] = oh_killing_spree_dps - - if 'garrote_ticks' in attacks_per_second: - dps_tuple = self.garrote_tick_damage(average_ap) * bleed_modifier - damage_breakdown['garrote'] = self.get_dps_contribution(dps_tuple, crit_rates['garrote'], attacks_per_second['garrote_ticks'], crit_damage_modifier) - - if 'hemorrhage_ticks' in attacks_per_second: - hemo_hit = self.hemorrhage_tick_damage(average_ap) * bleed_modifier - dps_from_hit_hemo = self.get_dps_contribution(hemo_hit, crit_rates['hemorrhage'], attacks_per_second['hemorrhage_ticks'], crit_damage_modifier) - damage_breakdown['hemorrhage_dot'] = dps_from_hit_hemo - - if 'rupture_ticks' in attacks_per_second: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.rupture_tick_damage(average_ap, i) * bleed_modifier * executioner_mod - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['rupture_ticks'], attacks_per_second['rupture_ticks'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['rupture'] = average_dps - if 'rupture_ticks_sc' in attacks_per_second: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.rupture_tick_damage(average_ap, i) * bleed_modifier * executioner_mod - dps_tuple = self.get_dps_contribution(dps_tuple, 0, attacks_per_second['rupture_ticks_sc'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['rupture_sc'] = average_dps - - if 'envenom' in attacks_per_second: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.envenom_damage(average_ap, i) * potent_poisons_mod * spell_modifier - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['envenom'], attacks_per_second['envenom'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['envenom'] = average_dps - - if 'eviscerate' in attacks_per_second: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.eviscerate_damage(average_ap, i) * physical_modifier * executioner_mod - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['eviscerate'], attacks_per_second['eviscerate'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['eviscerate'] = average_dps - - if 'death_from_above_strike' in attacks_per_second: - if self.settings.get_spec() == 'assassination': - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.envenom_damage(average_ap, i) * potent_poisons_mod * spell_modifier * 1.5 - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_strike'], attacks_per_second['death_from_above_strike'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['death_from_above_strike'] = average_dps - else: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.eviscerate_damage(average_ap, i) * physical_modifier * executioner_mod * 1.5 - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_strike'], attacks_per_second['death_from_above_strike'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['death_from_above_strike'] = average_dps - - if 'death_from_above_pulse' in attacks_per_second: - average_dps = 0 - for i in xrange(1, 6): - dps_tuple = self.death_from_above_pulse_damage(average_ap, i) * physical_modifier * executioner_mod - dps_tuple = self.get_dps_contribution(dps_tuple, crit_rates['death_from_above_pulse'], attacks_per_second['death_from_above_pulse'][i], crit_damage_modifier) - average_dps += dps_tuple - damage_breakdown['death_from_above_pulse'] = average_dps for proc in damage_procs: if proc.proc_name not in damage_breakdown: @@ -281,6 +204,83 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da # attacks_per_second is already being updated with that key. damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, damage_breakdown) + #compute damage breakdown for each spec + if self.spec == "assassination": + potent_poisons_mod = (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) * base_modifier + + for ability in self.assassination_damage_sources: + aps = attacks_per_second[ability] + crits = crit_rates[ability] + crit_mod = crit_damage_modifier + modifier = base_modifier + both_hands = ability in self.dual_wield_damage_sources + cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + + if ability in self.physical_damage_sources: + modifier *= armor_modifier + if ability in self.mastery_scaling_damage_sources: + modifier *= potent_poisons_mod + + #over ride for weird "weird" abilities + #death from above strike is actually an envenom with 1.5 modifier + if ability == "death_from_above_strike": + modifier *= 1.5 * potent_poisons_mod + ability = "envenom" + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + + if self.spec == "outlaw": + for ability in self.outlaw_damage_sources: + aps = attacks_per_second[ability] + crits = crit_rates[ability] + crit_mod = crit_damage_modifier + modifier = base_modifier + both_hands = ability in self.dual_wself.ield_damage_sources + cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + + if ability in self.physical_damage_sources: + modifier *= armor_modifier + + #over ride for weird "weird" abilities + #death from above strike is actually an envis with 1.5 modifier + if ability == "death_from_above_strike": + modifier *= 1.5 + ability = "eviscerate" + #between the eyes has additional crit damage + #Damage modifier 3 explained here: http://beta.askmrrobot.com/wow/simulator/docs/critdamage + if ability == "between_the_eyes": + crit_mod = self.crit_damage_modifiers(3) + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + + if self.spec == "subtlety": + executioner_mod = executioner_mod = 1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) + shadow_fangs_mod = (1 + (0.04 * self.traits.shadow_fangs)) + + for ability in self.subtlety_damage_sources: + aps = attacks_per_second[ability] + crits = crit_rates[ability] + crit_mod = crit_damage_modifier + modifier = base_modifier + both_hands = ability in self.dual_wield_damage_sources + cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + + if ability in self.physical_damage_sources: + modifier *= armor_modifier + #assume for now that all non-physical damage sources are shadow damage + else: + modifier *= shadow_fangs_mod + if ability in self.mastery_scaling_damage_sources: + modifier *= executioner_mod + + #over ride for weird "weird" abilities + #death from above strike is actually an envenom with 1.5 modifier + if ability == "death_from_above_strike": + modifier *= 1.5 * executioner_mod + ability = "eviscerate" + if ability == "death_from_above_pulse": + modifier *= executioner_mod + + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + return damage_breakdown, additional_info #autoattacks @@ -332,8 +332,8 @@ def oh_mutilate_damage(self, ap): def poisoned_knife_damage(self, ap): return 0.6 * ap - def rupture_tick_damage(self, ap): - return .3 * ap * (1 + (0.0333 * self.traits.toxic_blades)) + def rupture_tick_damage(self, ap, cp): + return .3 * cp * ap * (1 + (0.0333 * self.traits.gushing_wounds)) #outlaw def ambush_damage(self, ap): @@ -413,21 +413,64 @@ def shuriken_storm_damage(self, ap): def shuriken_toss_damage(self, ap): return 1.2 * ap + def get_formula(self, name): + formulas = { + #general + 'mh_autoattack': self.mh_damage, + 'oh_autoattack': self.oh_damage, + 'death_from_above_pulse':self.death_from_above_pulse_damage, + #assassination + 'deadly_poison': self.deadly_poison_tick_damage, + 'deadly_instant_poison': self.deadly_instant_poison_damage, + 'envenom': self.envenom_damage, + 'fan_of_knives_damage': self.fan_of_knives_damage, + 'garrote_ticks': self.garrote_tick_damage, + 'hemorrhage': self.hemorrhage_damage, + 'mh_kingsbane': self.mh_kingsbane_damage, + 'oh_kingsbane': self.oh_kingsbane_damage, + 'kingsbane_ticks': self.kingsbane_tick_damage, + 'mh_mutilate': self.mh_mutilate_damage, + 'oh_mutilate': self.oh_mutilate_damage, + 'poisoned_knife': self.poisoned_knife_damage, + 'rupture_ticks': self.rupture_tick_damage, + #outlaw + 'ambush': self.ambush_damage, + 'between_the_eyes': self.between_the_eyes_damage, + 'blunderbuss': self.blunderbuss_damage, + 'cannonball_barrage': self.cannonball_barrage_damage, + 'ghostly_strike': self.ghostly_strike_damage, + 'mh_greed': self.mh_greed_damage, + 'oh_greed': self.oh_greed_damage, + 'mh_killing_spree': self.mh_killing_spree_damage, + 'oh_killing_spree': self.oh_killing_spree_damage, + 'main_gauche': self.main_gauche_damage, + 'pistol_shot': self.pistol_shot_damage, + 'run_through': self.run_through_damage, + 'saber_slash': self.saber_slash_damage, + #subtlety + 'backstab': self.backstab_damage, + 'eviscerate': self.eviscerate_damage, + 'gloomblade': self.gloomblade_damage, + 'mh_goremaws_bite': self.mh_goremaws_bite_damage, + 'oh_goremaws_bite': self.oh_goremaws_bite_damage, + 'nightblade_ticks': self.nightblade_tick_damage, + 'mh_shadow_blades': self.mh_shadow_blades_damage, + 'oh_shadow_blades': self.oh_shadow_blades_damage, + 'shuriken_storm': self.shuriken_storm_damage, + 'shuriken_toss': self.shuriken_toss_damage, + } + return formulas[name] + def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod return cost def get_spell_cd(self, ability): - #need to update list of affected abilities - if ability in self.cd_reduction_table[self.settings.get_spec()]: - #self.stats.get_readiness_multiplier_from_rating(readiness_conversion=self.readiness_spec_conversion) - return self.ability_cds[ability] * self.get_trinket_cd_reducer() - else: - return self.ability_cds[ability] + return self.ability_cds[ability] def crit_rate(self, crit=None): # all rogues get 10% bonus crit, .05 of base crit for everyone # should be coded better? base_crit = .15 base_crit += self.stats.get_crit_from_rating(crit) - return base_crit + self.buffs.buff_all_crit() + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction \ No newline at end of file + return base_crit + self.buffs.buff_all_crit() + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction From e027e7b46dcb63ebd1df72fd88cae14937bc78cb Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 9 Aug 2016 13:56:42 -0400 Subject: [PATCH 033/265] New Sub Setting Object -Add shadowstrike to damage formulas -Fix artifact exception message -Initial cleanup of AldrianaCalc --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 78 ++------------------ shadowcraft/calcs/rogue/Aldriana/settings.py | 64 ++++++---------- shadowcraft/calcs/rogue/__init__.py | 74 +++++++++---------- shadowcraft/objects/artifact.py | 2 +- 4 files changed, 67 insertions(+), 151 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9f65c74..1975376 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -26,23 +26,23 @@ class AldrianasRogueDamageCalculator(RogueDamageCalculator): def get_dps(self): super(AldrianasRogueDamageCalculator, self).get_dps() - if self.settings.is_assassination_rogue(): + if self.spec == 'assassination': self.init_assassination() return self.assassination_dps_estimate() - elif self.settings.is_combat_rogue(): + elif self.spec == 'outlaw': return self.combat_dps_estimate() - elif self.settings.is_subtlety_rogue(): + elif self.spec == 'subtlety': return self.subtlety_dps_estimate() else: raise InputNotModeledException(_('You must specify a spec.')) def get_dps_breakdown(self): - if self.settings.is_assassination_rogue(): + if self.spec == 'assassination': self.init_assassination() return self.assassination_dps_breakdown() - elif self.settings.is_combat_rogue(): + elif self.spec == 'outlaw': return self.combat_dps_breakdown() - elif self.settings.is_subtlety_rogue(): + elif self.spec == 'subtlety': return self.subtlety_dps_breakdown() else: raise InputNotModeledException(_('You must specify a spec.')) @@ -115,60 +115,6 @@ def get_heroism_haste_multiplier(self): # Just average-casing for now. Should fix that at some point. return 1 + .3 * self.heroism_uptime_per_fight() - def get_cp_distribution_for_cycle(self, cp_distribution_per_move, target_cp_quantity): - avg_cp_per_cpg = sum([key * cp_distribution_per_move[key] for key in cp_distribution_per_move]) - - time_spent_at_cp = [0, 0, 0, 0, 0, 0] - cur_min_cp = 0 - cur_dist = {(0, 0): 1} - while cur_min_cp < target_cp_quantity: - cur_min_cp += 1 - - new_dist = {} - for (cps, moves), prob in cur_dist.items(): - if cps >= cur_min_cp: - if (cps, moves) in new_dist: - new_dist[(cps, moves)] += prob - else: - new_dist[(cps, moves)] = prob - else: - for (move_cp, move_prob) in cp_distribution_per_move.items(): - total_cps = cps + move_cp - if total_cps > 5: - total_cps = 5 - dist_entry = (total_cps, moves + 1) - time_spent_at_cp[total_cps] += move_prob * prob - if dist_entry in new_dist: - new_dist[dist_entry] += move_prob * prob - else: - new_dist[dist_entry] = move_prob * prob - cur_dist = new_dist - - for (cps, moves), prob in cur_dist.items(): - time_spent_at_cp[cps] += prob - - total_weight = sum(time_spent_at_cp) - for i in xrange(6): - time_spent_at_cp[i] /= total_weight - - return cur_dist, time_spent_at_cp, avg_cp_per_cpg - - def get_cp_per_cpg(self, base_cp_per_cpg=1, *probs): - # Computes the combined probabilites of getting an additional cp from - # each of the items in probs. - cp_per_cpg = {base_cp_per_cpg: 1} - for prob in probs: - if prob == 0: - continue - new_cp_per_cpg = {} - for cp in cp_per_cpg: - new_cp_per_cpg.setdefault(cp, 0) - new_cp_per_cpg.setdefault(cp + 1, 0) - new_cp_per_cpg[cp] += cp_per_cpg[cp] * (1 - prob) - new_cp_per_cpg[cp + 1] += cp_per_cpg[cp] * prob - cp_per_cpg = new_cp_per_cpg - return cp_per_cpg - def get_crit_rates(self, stats): base_melee_crit_rate = self.crit_rate(crit=stats['crit']) crit_rates = { @@ -1718,8 +1664,7 @@ def subtlety_dps_breakdown(self): if float(self.settings.cycle.use_hemorrhage) > self.settings.duration: raise InputNotModeledException(_('Interval between Hemorrhages cannot be higher than the fight duration')) - #set readiness coefficient - self.readiness_spec_conversion = self.subtlety_readiness_conversion + self.spec_convergence_stats = ['haste', 'multistrike'] #overrides setting, using Ambush + Vanish on CD is critical @@ -1733,15 +1678,6 @@ def subtlety_dps_breakdown(self): self.vanish_cd_modifier = 1.0 - # leveling perks - if self.level == 100: - mos_value += .05 - self.ability_cds['vanish'] = 90 - - #update spec specific proc rates - if getattr(self.stats.procs, 'legendary_capacitive_meta'): - getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.114 - self.set_constants() self.stat_multipliers['agi'] *= 1.15 #sinister calling requires convergence to calculate (for now?) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 23edf82..cd7c3a0 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -3,50 +3,19 @@ class Settings(object): # Settings object for AldrianasRogueDamageCalculator. - def __init__(self, cycle, time_in_execute_range=.35, response_time=.5, latency=.03, dmg_poison='dp', utl_poison=None, - duration=300, use_opener='always', opener_name='default', is_pvp=False, shiv_interval=0, adv_params=None, + def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_params=None, merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False): self.cycle = cycle - self.time_in_execute_range = time_in_execute_range self.response_time = response_time self.latency = latency - self.dmg_poison = dmg_poison - self.utl_poison = utl_poison self.duration = duration - self.use_opener = use_opener # Allowed values are 'always' (vanish/shadowmeld on cooldown), 'opener' (once per fight) and 'never' - self.opener_name = opener_name self.feint_interval = feint_interval - self.merge_damage = merge_damage self.is_day = is_day self.is_demon = is_demon self.num_boss_adds = max(num_boss_adds, 0) - self.shiv_interval = float(shiv_interval) self.adv_params = self.interpret_adv_params(adv_params) self.default_ep_stat = default_ep_stat - if self.shiv_interval < 10 and not self.shiv_interval == 0: - self.shiv_interval = 10 - allowed_openers_per_spec = { - 'assassination': ('mutilate', 'dispatch', 'envenom'), - 'combat': ('sinister_strike', 'revealing_strike', 'eviscerate'), - 'subtlety': () - } - allowed_openers = allowed_openers_per_spec[self.cycle._cycle_type] + ('ambush', 'garrote', 'default', 'cpg') - if opener_name not in allowed_openers: - raise exceptions.InvalidInputException(_('Opener {opener} is not allowed in {cycle} cycles.').format(opener=opener_name, cycle=self.cycle._cycle_type)) - if opener_name == 'default': - default_openers = { - 'assassination': 'mutilate', - 'combat': 'ambush', - 'subtlety': 'ambush'} - self.opener_name = default_openers[self.cycle._cycle_type] - if dmg_poison not in (None, 'dp', 'wp'): - raise exceptions.InvalidInputException(_('You can only choose Deadly(dp) or Wound(wp) as a damage poison')) - if utl_poison not in (None, 'cp', 'mnp', 'lp', 'pp'): - raise exceptions.InvalidInputException(_('You can only choose Crippling(cp), Mind-Numbing(mnp), Leeching(lp) or Paralytic(pp) as a non-lethal poison')) - - def get_spec(self): - return self.cycle._cycle_type - + def interpret_adv_params(self, s=""): data = {} max_effects = 8 @@ -63,7 +32,7 @@ def interpret_adv_params(self, s=""): except: raise exceptions.InvalidInputException(_('Advanced Parameter ' + e + ' found corrupt. Properly structure params and try again.')) return data - + def is_assassination_rogue(self): return self.cycle._cycle_type == 'assassination' @@ -108,10 +77,23 @@ def __init__(self, ksp_immediately=True, revealing_strike_pooling=True, blade_fl class SubtletyCycle(Cycle): _cycle_type = 'subtlety' - def __init__(self, raid_crits_per_second, use_hemorrhage='uptime', clip_fw=False): - self.clip_fw = clip_fw #reduces fw uptime, but increases ambush damage - self.raid_crits_per_second = raid_crits_per_second #used to calculate HAT procs per second. - self.use_hemorrhage = use_hemorrhage # Allowed values are 'always' (main CP generator), - #'never' (default to backstab), - #'uptime' (cast when hemo is down), - + def __init__(self, cp_builder='backstab', dance_cp_builder='shadowstrike', positional_uptime=1.0, symbols_policy='just', + eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, + dance_finisher_priority=[]): + self.cp_builder = cp_builder #Allowed values: fok, backstab, gloomblade + self.dance_cp_builder = dance_cp_builder #Allowed values: fok, shadowstrike + self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target + self.symbols_policy = symbols_policy #Allowed values: + #'always' - use SoD every dance (macro) + #'just' - Only use SoD when needed to refresh + #Finisher thresholds for each finisher, Allowed Values: 0, 1, 2, 3, 4, 5, 6 + self.eviscerate_cps = eviscerate_cps + self.finality_eviscerate_cps = finality_eviscerate_cps + self.nightblade_cps = nightblade_cps + self.finality_nightblade_cps = finality_nightblade_cps + + #List of following keys: 'eviscerate', 'nightblade', 'finality:eviscerate', 'finality:nightblade' + #Priority of finisher usage during dance + #Keys not included will not be used during dance + self.dance_finisher_priority = dance_finisher_priority + diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8b807b8..e430439 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -18,7 +18,7 @@ class RogueDamageCalculator(DamageCalculator): default_ep_stats = ['agi', 'haste', 'crit', 'mastery', 'ap', 'versatility'] assassination_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', - 'deadly_poison', 'deadly_instant_poison', 'envenom', + 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'] @@ -27,7 +27,7 @@ class RogueDamageCalculator(DamageCalculator): 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', - 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', + 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', 'shadowstrike', 'shadow_blade', 'shuriken_storm', 'shuriken_toss'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', @@ -35,7 +35,7 @@ class RogueDamageCalculator(DamageCalculator): 'ambush, between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash', 'backstab', - 'eviscerate', 'shuriken_storm', 'shuriken_toss'] + 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] #All damage sources the scale with mastery (assn or sub) mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', 'eviscerate', 'nightblade'] @@ -98,7 +98,7 @@ class RogueDamageCalculator(DamageCalculator): 'exsanguinate': 45, 'kingsbane': 45, 'vendetta': 120, - #outlaw + #outlaw 'adrenaline_rush': 180, 'cannonball_barrage': 60, 'curse_of_the_dreadblades': 90, @@ -106,10 +106,9 @@ class RogueDamageCalculator(DamageCalculator): #subtlety 'goremaws_bite': 60, 'shadow_dance': 60, + 'shadow_blades': 120, } - - def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': @@ -205,9 +204,9 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, damage_breakdown) #compute damage breakdown for each spec - if self.spec == "assassination": + if self.spec == 'assassination': potent_poisons_mod = (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) * base_modifier - + for ability in self.assassination_damage_sources: aps = attacks_per_second[ability] crits = crit_rates[ability] @@ -221,37 +220,38 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability in self.mastery_scaling_damage_sources: modifier *= potent_poisons_mod - #over ride for weird "weird" abilities + #override for "weird" abilities #death from above strike is actually an envenom with 1.5 modifier - if ability == "death_from_above_strike": - modifier *= 1.5 * potent_poisons_mod - ability = "envenom" + #manually add in base modifier because DfA strike is in physical sources + if ability == 'death_from_above_strike': + modifier = base_modifier * 1.5 * potent_poisons_mod + ability = 'envenom' damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) - if self.spec == "outlaw": + if self.spec == 'outlaw': for ability in self.outlaw_damage_sources: aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier modifier = base_modifier - both_hands = ability in self.dual_wself.ield_damage_sources + both_hands = ability in self.dual_wield_damage_sources cps = max_cps if ability in self.cp_scaling_damage_sources else 0 if ability in self.physical_damage_sources: modifier *= armor_modifier - #over ride for weird "weird" abilities - #death from above strike is actually an envis with 1.5 modifier - if ability == "death_from_above_strike": + #override for "weird" abilities + #death from above strike is actually an evis with 1.5 modifier + if ability == 'death_from_above_strike': modifier *= 1.5 - ability = "eviscerate" + ability = 'eviscerate' #between the eyes has additional crit damage #Damage modifier 3 explained here: http://beta.askmrrobot.com/wow/simulator/docs/critdamage - if ability == "between_the_eyes": + if ability == 'between_the_eyes': crit_mod = self.crit_damage_modifiers(3) damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) - if self.spec == "subtlety": + if self.spec == 'subtlety': executioner_mod = executioner_mod = 1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) shadow_fangs_mod = (1 + (0.04 * self.traits.shadow_fangs)) @@ -271,12 +271,12 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability in self.mastery_scaling_damage_sources: modifier *= executioner_mod - #over ride for weird "weird" abilities - #death from above strike is actually an envenom with 1.5 modifier - if ability == "death_from_above_strike": + #override for "weird" abilities + #death from above strike is actually an evis with 1.5 modifier and dfa pulse needs mastery + if ability == 'death_from_above_strike': modifier *= 1.5 * executioner_mod - ability = "eviscerate" - if ability == "death_from_above_pulse": + ability = 'eviscerate' + if ability == 'death_from_above_pulse': modifier *= executioner_mod damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) @@ -296,12 +296,12 @@ def death_from_above_pulse_damage(self, ap, cp): #assassination def deadly_poison_tick_damage(self, ap): - return .275 * ap + (1 + (0.05 * self.traits.master_alchemist)) + return .275 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) def deadly_instant_poison_damage(self, ap): - return .142 * ap + (1 + (0.05 * self.traits.master_alchemist)) + return .142 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) - #Maybe add better handling for "rule of three" for artifact traits + #Maybe add better handling for 'rule of three' for artifact traits def envenom_damage(self, ap, cp): return .5 * cp * ap * (1 + (0.0333 * self.traits.toxic_blades)) @@ -315,14 +315,11 @@ def hemorrhage_damage(self, ap): return 1 * self.get_weapon_damage('mh', ap) def mh_kingsbane_damage(self, ap): - return 3 * self.get_weapon_damage('mh', ap) - + return 3 * self.get_weapon_damage('mh', ap) * (1 + (0.4 * self.talents.master_poisoner)) def oh_kingsbane_damage(self, ap): - return 3 * self.oh_penalty * self.get_weapon_damage('oh', ap) - + return 3 * self.oh_penalty * self.get_weapon_damage('oh', ap) * (1 + (0.4 * self.talents.master_poisoner)) def kingsbane_tick_damage(self, ap): - return 0.45 * ap - + return 0.45 * ap * (1 + (0.4 * self.talents.master_poisoner)) def mh_mutilate_damage(self, ap): return 3.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.assassins_blades)) @@ -338,14 +335,14 @@ def rupture_tick_damage(self, ap, cp): #outlaw def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) - + def between_the_eyes_damage(self, ap, cp): return .75 * cp * ap * (1 + (0.08 / self.traits.black_powder)) #7*55% AP def blunderbuss_damage(self, ap): return 3.85 * ap - + #Ignoring that this behaves as a dot for simplicity def cannonball_barrage_damage(self, ap): return 7.2 * ap @@ -377,9 +374,9 @@ def run_through_damage(self, ap, cp): def saber_slash_damage(self, ap): return 2.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) - + #subtlety - #Ignore positional modifier for now + #Ignore positional modifier for now def backstab_damage(self, ap): return 3.7 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) @@ -454,6 +451,7 @@ def get_formula(self, name): 'mh_goremaws_bite': self.mh_goremaws_bite_damage, 'oh_goremaws_bite': self.oh_goremaws_bite_damage, 'nightblade_ticks': self.nightblade_tick_damage, + 'shadowstrike': self.shadowstrike_damage, 'mh_shadow_blades': self.mh_shadow_blades_damage, 'oh_shadow_blades': self.oh_shadow_blades_damage, 'shuriken_storm': self.shuriken_storm_damage, diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 9a9126e..c4a6fd9 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -32,7 +32,7 @@ def set_trait(self, trait, value): def initialize_traits(self, trait_string): if len(trait_string) != len(self.allowed_traits): - raise InvalidTraitException(_('Trait strings must be 16 characters long')) + raise InvalidTraitException(_('Trait strings must be {traits} characters long').format(traits=len(allowed_traits))) self.traits = {} for trait in xrange(len(self.allowed_traits)): self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) From 3bb96edd8019bf0e0602257f33f4b6a185cb1f7b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 13 Aug 2016 10:25:13 -0400 Subject: [PATCH 034/265] First pass sublety model -Incomplete, halts after SoD timelining --- scripts/subtlety.py | 36 +- shadowcraft/calcs/__init__.py | 15 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 526 +++++++++++++------ shadowcraft/calcs/rogue/Aldriana/settings.py | 6 +- shadowcraft/calcs/rogue/__init__.py | 163 +++--- 5 files changed, 497 insertions(+), 249 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 2e660ce..fed5286 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -21,7 +21,7 @@ # Set up level/class/race test_level = 110 -test_race = race.Race('troll') +test_race = race.Race('pandaren') test_class = 'rogue' test_spec = 'subtlety' @@ -34,7 +34,7 @@ # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand test_mh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_frostwolf') +test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') # Set up procs. - trinkets, other things (legendary procs) #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), @@ -46,23 +46,23 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=3650, - stam=2426, - crit=1039, - haste=0, - mastery=1315, - versatility=122,) + agi=7655, + stam=19566, + crit=2665, + haste=1594, + mastery=3350, + versatility=6522,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0200000', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '0000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. -test_cycle = settings.SubtletyCycle(5, use_hemorrhage='never', clip_fw=False) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=False, - adv_params="", is_demon=True) +test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', dance_finisher_priority=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate']) +test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, + adv_params="", is_demon=True, num_boss_adds=0) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) @@ -76,7 +76,8 @@ #ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) -talent_ranks = calculator.get_talents_ranking() +#talent_ranks = calculator.get_talents_ranking() +trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -101,11 +102,12 @@ def pretty_print(dict_list): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, + #ep_values, #tier_ep_values, - talent_ranks, + #talent_ranks, #trinkets_ep_value, - dps_breakdown + dps_breakdown, + trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 6ffca2f..79a81a5 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -27,7 +27,7 @@ class DamageCalculator(object): # normalize_ep_stat is the stat with value 1 EP, override in your subclass normalize_ep_stat = None - def __init__(self, stats, talents, traits, buffs, race, class_spec, settings=None, level=110, target_level=None, char_class='rogue'): + def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '7.0.0' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = '0.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() @@ -37,7 +37,7 @@ def __init__(self, stats, talents, traits, buffs, race, class_spec, settings=Non self.buffs = buffs self.race = race self.char_class = char_class - self.class_spec = class_spec + self.spec = spec self.settings = settings self.target_level = target_level if target_level else level+3 #assumes 3 levels higher if not explicit @@ -549,26 +549,27 @@ def get_talents_ranking(self, list=None): return talents_ranking - def get_artifact_ranking(self, list=None): + def get_trait_ranking(self, list=None): trait_ranking = {} baseline_dps = self.get_dps() trait_list = [] if not list: - trait_list = self.artifact.get_trait_list() + trait_list = self.traits.get_trait_list() else: trait_list = list for trait in trait_list: - base_trait_rank = getattr(self.artifact, trait) - setattr(self.artifact, trait, base_trait_rank+1) + base_trait_rank = getattr(self.traits, trait) + setattr(self.traits, trait, base_trait_rank+1) try: new_dps = self.get_dps() if new_dps != baseline_dps: trait_ranking[trait] = abs(new_dps-baseline_dps) except: trait_ranking[trait] = _('not_implemented') - setattr(self.artifact, trait, base_trait_rank) + setattr(self.traits, trait, base_trait_rank) + return trait_ranking def get_engine_info(self): data = { diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1975376..854171e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -28,9 +28,11 @@ def get_dps(self): super(AldrianasRogueDamageCalculator, self).get_dps() if self.spec == 'assassination': self.init_assassination() - return self.assassination_dps_estimate() + return 0 + #return self.assassination_dps_estimate() elif self.spec == 'outlaw': - return self.combat_dps_estimate() + return 0 + #return self.combat_dps_estimate() elif self.spec == 'subtlety': return self.subtlety_dps_estimate() else: @@ -39,9 +41,11 @@ def get_dps(self): def get_dps_breakdown(self): if self.spec == 'assassination': self.init_assassination() - return self.assassination_dps_breakdown() + return 0 + #return self.assassination_dps_breakdown() elif self.spec == 'outlaw': - return self.combat_dps_breakdown() + return 0 + #return self.combat_dps_breakdown() elif self.spec == 'subtlety': return self.subtlety_dps_breakdown() else: @@ -121,33 +125,27 @@ def get_crit_rates(self, stats): 'mh_autoattacks': min(base_melee_crit_rate, self.dw_mh_hit_chance), 'oh_autoattacks': min(base_melee_crit_rate, self.dw_oh_hit_chance), } + for attack in ('rupture_ticks', 'shuriken_toss'): crit_rates[attack] = base_melee_crit_rate - if self.settings.is_assassination_rogue(): - spec_attacks = ('mutilate', 'dispatch', 'envenom', 'venomous_wounds') - elif self.settings.is_combat_rogue(): - spec_attacks = ('main_gauche', 'sinister_strike', 'revealing_strike', 'eviscerate', 'killing_spree', 'oh_killing_spree', 'mh_killing_spree') - elif self.settings.is_subtlety_rogue(): - spec_attacks = ('eviscerate', 'backstab', 'ambush', 'hemorrhage') - - if self.settings.dmg_poison == 'dp': - poisons = ('deadly_instant_poison', 'deadly_poison') - elif self.settings.dmg_poison == 'wp': - poisons = ('wound_poison', ) - elif self.settings.dmg_poison == 'sp': - poisons = ('swift_poison', ) - - talent_attacks = () - if self.talents.death_from_above: - talent_attacks = ('death_from_above', 'death_from_above_strike', 'death_from_above_pulse') - - openers = tuple([self.settings.opener_name]) + if self.spec == 'assassination': + spec_attacks = self.assassination_damage_sources + elif self.spec == 'outlaw': + spec_attacks = self.outlaw_damage_sources + elif self.spec == 'subtlety': + spec_attacks = self.subtlety_damage_sources - for attack in spec_attacks + poisons + openers + talent_attacks: + for attack in spec_attacks: #for handling odd crit rates - if attack in ('eviscerate', 'envenom') and self.stats.gear_buffs.rogue_t15_4pc: - crit_rates[attack] = base_melee_crit_rate + .2 + if attack == 'mutilate' and self.traits.balanced_blades: + crit_rates[attack] = base_melee_crit_rate + (0.02 * self.traits.balanced_blades) + elif attack == 'rupture_ticks' and self.traits.serrated_edge: + crit_rates[attack] = base_melee_crit_rate + (0.03333 * self.traits.serrated_edge) + elif attack in ('pistol_shot', 'blunderbuss') and self.traits.gunslinger: + crit_rates[attack] = base_melee_crit_rate + (0.06 * self.traits.gunslinger) + elif attack == 'eviscerate' and self.traits.gutripper: + crit_rates[attack] = base_melee_crit_rate + (0.05 * self.traits.gutripper) else: crit_rates[attack] = base_melee_crit_rate @@ -166,12 +164,9 @@ def set_constants(self): if self.race.arcane_torrent: self.bonus_energy_regen += 15. / (120 + self.settings.response_time) #auxiliary rotational effects - if self.settings.shiv_interval != 0: - self.bonus_energy_regen -= self.get_spell_stats('shiv')[0] / self.settings.shiv_interval if self.settings.feint_interval != 0: self.bonus_energy_regen -= self.get_spell_stats('feint')[0] / self.settings.feint_interval - self.set_openers() #only include if general multiplier applies to spec calculations self.true_haste_mod *= self.get_heroism_haste_multiplier() @@ -239,7 +234,9 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ crit_multiplier = self.crit_damage_modifiers() crit_rate = self.crit_rate(crit=current_stats['crit']) - if proc.stat == 'spell_damage': + #TODO Re-add multipliers here + multiplier = 1 + '''if proc.stat == 'spell_damage': multiplier = self.get_modifiers(current_stats, damage_type='spell') elif proc.stat == 'physical_damage': multiplier = self.get_modifiers(current_stats, damage_type='physical') @@ -248,7 +245,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ elif proc.stat == 'bleed_damage': multiplier = self.get_modifiers(current_stats, damage_type='bleed') else: - return 0 + return 0''' if proc.can_crit == False: crit_rate = 0 @@ -520,6 +517,21 @@ def get_poison_counts(self, attacks_per_second, current_stats): elif self.settings.dmg_poison == 'wp': attacks_per_second['wound_poison'] = total_hits_per_second * avg_poison_proc_rate + def get_average_alacrity(self, attacks_per_second): + stacks_per_second = 0.0 + for finisher in self.finisher_damage_sources: + #Don't double count DfA + if finisher != 'death_from_above_pulse': + for cp in xrange(7): + stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] + stack_time = stacks_per_second/20 + if stack_time > self.settings.duration: + max_stacks = self.settings.duration * stacks_per_second + return max_stacks/2 + else: + max_time = self.settings.duration - stack_time + return (max_time/self.settings.duration) * 20 + (stack_time/self.settings.duration) * 10 + def determine_stats(self, attack_counts_function): current_stats = { 'str': self.base_strength, @@ -528,8 +540,6 @@ def determine_stats(self, attack_counts_function): 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], 'haste': self.base_stats['haste'] * self.stat_multipliers['haste'], 'mastery': self.base_stats['mastery'] * self.stat_multipliers['mastery'], - 'readiness': self.base_stats['readiness'] * self.stat_multipliers['readiness'], - 'multistrike': self.base_stats['multistrike'] * self.stat_multipliers['multistrike'], 'versatility': self.base_stats['versatility'] * self.stat_multipliers['versatility'], } self.current_variables = {} @@ -602,8 +612,6 @@ def determine_stats(self, attack_counts_function): 'crit': 0, 'haste': 0, 'mastery': 0, - 'readiness': 0, - 'multistrike': 0, 'versatility': 0, } @@ -1648,6 +1656,35 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): # Subtlety DPS functions ########################################################################### + #Legion TODO: + + #Talents: + #T1-MoS + #T1-Weaponmaster + #T1-Gloomblade + #T2:NS + #T3:DS + #T3:Ancitipcation + #T6:Alacrity + + #Artifact: + # 'flickering_shadows', + # 'second_shuriken', + # 'akarris_soul', + # 'shadow_nova', + # 'legionblade' + + #Items: + #Class hall set bonus + #Tier bonus + #Trinkets + #Legendaries + + #Rotation details: + #Openers + #Combo Point loss + #Non-dance stealths + def subtlety_dps_estimate(self): return sum(self.subtlety_dps_breakdown().values()) @@ -1655,57 +1692,35 @@ def subtlety_dps_breakdown(self): if not self.settings.is_subtlety_rogue(): raise InputNotModeledException(_('You must specify a subtlety cycle to match your subtlety spec.')) - if self.stats.mh.type != 'dagger' and self.settings.cycle.use_hemorrhage != 'always': - raise InputNotModeledException(_('Subtlety modeling requires a MH dagger if Hemorrhage is not the main combo point builder')) - - if self.settings.cycle.use_hemorrhage not in ('always', 'never', 'uptime'): - if float(self.settings.cycle.use_hemorrhage) <= 0: - raise InputNotModeledException(_('Hemorrhage usage must be set to always, never or a positive number')) - if float(self.settings.cycle.use_hemorrhage) > self.settings.duration: - raise InputNotModeledException(_('Interval between Hemorrhages cannot be higher than the fight duration')) - + #check to make sure cycle is sane: + if self.settings.cycle.cp_builder == 'gloomblade' and not self.talents.gloomblade: + raise InputNotModeledException(_('Gloomblade must be talented to be priamry cp builder')) + #Raise finality error here? - self.spec_convergence_stats = ['haste', 'multistrike'] - - #overrides setting, using Ambush + Vanish on CD is critical - self.settings.use_opener = 'always' - self.settings.opener_name = 'ambush' - # Sanguinary Vein - self.damage_modifier_cache = 1.25 - - self.sc_trigger_rate = 0 - mos_value = .1 + self.max_spend_cps = 5 + if self.talents.deeper_strategem: + self.max_spend_cps += 1 + self.max_store_cps = self.max_spend_cps + if self.talents.anticipation: + self.max_store_cps += 3 - self.vanish_cd_modifier = 1.0 + self.finisher_thresholds = {'eviscerate': min(self.settings.cycle.eviscerate_cps, self.max_spend_cps), + 'finality:eviscerate': min(self.settings.cycle.finality_eviscerate_cps, self.max_spend_cps), + 'nightblade': min(self.settings.cycle.nightblade_cps, self.max_spend_cps), + 'finality:nightblade': min(self.settings.cycle.finality_nightblade_cps, self.max_spend_cps), + } self.set_constants() - self.stat_multipliers['agi'] *= 1.15 #sinister calling requires convergence to calculate (for now?) - self.spec_needs_converge = True - - self.settings.cycle.raid_crits_per_second = self.get_adv_param('hat_triggers_per_second', self.settings.cycle.raid_crits_per_second, min_bound=0, max_bound=600) - self.settings.cycle.clip_fw = self.get_adv_param('clip_fw', self.settings.cycle.clip_fw, ignore_bounds=True) - - self.vanish_rate = 1. / (self.get_spell_cd('vanish') + self.settings.response_time) + 1. / (self.get_spell_cd('preparation') + self.settings.response_time * 3) #vanish CD + Prep CD - mos_multiplier = 1. + mos_value * (6 + 3 * self.talents.subterfuge * [1, 2][self.glyphs.vanish]) * self.vanish_rate + #self.spec_needs_converge = True stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - armor_value = self.target_armor() - find_weakness_damage_boost = 1. / self.max_level_armor_multiplier() - find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * additional_info['fw_uptime'] - - trinket_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.940260238)/10000 - #calculate multistrike here for Sub and Assassination, really cheap to calculate - #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! - multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) - multistrike_multiplier = min(.6, multistrike_multiplier) - soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') @@ -1733,30 +1748,13 @@ def subtlety_dps_breakdown(self): for key in damage_breakdown: damage_breakdown[key] *= vanish_damage_mod damage_breakdown[key] *= maalus_mod - if key in ('eviscerate', 'hemorrhage', 'shuriken_toss', 'hemorrhage_dot', 'autoattack'): #'burning_wounds' - damage_breakdown[key] *= find_weakness_multiplier - if key == 'ambush': - #damage_breakdown[key] *= find_weakness_multiplier - damage_breakdown[key] *= 1 + ((1 - additional_info['ambush_no_fw_rate']) * (find_weakness_damage_boost - 1)) - if key == 'backstab': - #damage_breakdown[key] *= find_weakness_multiplier - damage_breakdown[key] *= 1 + additional_info['backstab_fw_rate'] * (find_weakness_damage_boost - 1) - if key in ('rupture', 'sr_rupture', 'rupture_sc'): - damage_breakdown[key] *= 1.3 - if key not in ('rupture_sc', 'Fel Lash'): - damage_breakdown[key] *= (1 + multistrike_multiplier) if key in ('ambush', 'garrote', 'sr_ambush'): damage_breakdown[key] *=trinket_multiplier if "sr_" not in key: damage_breakdown[key] *= soul_cap_mod damage_breakdown[key] *= infallible_trinket_mod - if "Mirror" not in key: - damage_breakdown[key] *= mos_multiplier - - #discard the loose rupture component to clean up the breakdown - if 'rupture_sc' in damage_breakdown and self.settings.merge_damage: - damage_breakdown['rupture'] += damage_breakdown['rupture_sc'] - del damage_breakdown['rupture_sc'] + #if "Mirror" not in key: + # damage_breakdown[key] *= mos_multiplier #add maalus burst if maalus_mod > 1.0: @@ -1770,26 +1768,226 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - self.ability_cds['vanish'] = 90 * self.vanish_cd_modifier - + #Set up initial energy budget base_energy_regen = 10. - max_energy = 100. - if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): - max_energy += 30 - if self.talents.lemon_zest: - base_energy_regen *= 1 + .05 * (1 + min(self.settings.num_boss_adds, 2)) - max_energy += 15 - if self.glyphs.energy: - max_energy += 20 - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - max_energy += 20 - if self.race.expansive_mind: - max_energy = round(max_energy * 1.05, 0) - shd_duration = 8 - if self.level == 100: - shd_duration += 2 - shd_cd = self.get_spell_cd('shadow_dance') + self.settings.response_time + self.major_cd_delay + haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + self.energy_regen = base_energy_regen * haste_multiplier + if self.talents.vigor: + self.energy_regen *= 1.1 + + self.max_energy = 100. + if self.talents.vigor: + self.max_energy += 50 + self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy + + shadow_blades_duration = 15. + (3.3333 * self.traits.soul_shadows) + self.shadow_blades_uptime = shadow_blades_duration/self.get_spell_cd('shadow_blades') + + #swing timer + white_swing_downtime = 0 + + #TODO: Add swing resets back for vanishes + self.swing_reset_spacing = None + if self.swing_reset_spacing is not None: + white_swing_downtime += .5 / self.swing_reset_spacing + attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) + attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) + + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance + attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + + #Set up initial combo point budget + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5 + self.settings.marked_for_death_resets * 5) + self.cp_budget = mfd_cps + #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps + if self.talents.enveloping_shadows: + self.cp_budget += self.settings.duration/6. + + + #set initial dance budget + self.dance_budget = 3 + self.settings.duration/60. + + #print self.dance_budget + #print self.cp_budget + #print self.energy_budget + #print "-----" + + #finality evis tracking, should be 0 at end of rotation + finality_evis_count = 0 + #setup timelines + sod_duration = 35 + nightblade_duration = 6 + (2 * self.finisher_thresholds['nightblade']) + finality_nightblade_duration = 6 + (2 * self.finisher_thresholds['finality:nightblade']) + + #Add attacks that could occur during first pass to aps + attacks_per_second[self.settings.cycle.dance_cp_builder] = 0 + attacks_per_second['symbols_of_death'] = 0 + + #Leaving space for opener handling for the first cast + sod_timeline = range(sod_duration, self.settings.duration, sod_duration) + if self.traits.finality: + finality_nb_timeline = range(nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) + nightblade_timeline = range(finality_nightblade_duration + nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) + else: + finality_nb_timeline = [] + nightblade_timeline = range(nightblade_duration, self.settings.duration, nightblade_duration) + + #First timeline pass, since we're doing timeline matching use fixed priority + for finisher in ['finality:nightblade', 'nightblade', 'finality:eviscerate', 'eviscerate', None]: + attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] + dance_count = 0 + if finisher in self.settings.cycle.dance_finisher_priority: + if finisher == 'finality:nightblade': + #Allow SoDs to be used on pandemic for match purposes + joint, sod_timeline, finality_nb_timeline = self.timeline_overlap(sod_timeline, finality_nb_timeline, -0.3 * sod_duration) + #if there is overlap compute a dance rotation for this combo + dance_count = len(joint) + elif finisher == 'nightblade': + joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) + dance_count = len(joint) + #Assume finality evis will be available for half of these + if finisher == 'finality:eviscerate' and self.traits.finality: + dance_count = len(sod_timeline)/2 + sod_timeline = sod_timeline[dance_count:] + finality_evis_count += dance_count + if finisher == 'eviscerate': + dance_count = len(sod_timeline) + sod_timeline = sod_timeline[dance_count:] + finality_evis_count -= dance_count + #Whatever is left over is computed without finishers + if finisher is None: + dance_count = len(sod_timeline) + sod_timeline = sod_timeline[dance_count:] + + if dance_count: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=True, finisher=finisher) + self.energy_budget += dance_count * net_energy + self.cp_budget += dance_count * net_cps + self.dance_budget += ((3. * spent_cps* dance_count)/60) - dance_count + #merge attack counts into attacks_per_second + dances_per_second = float(dance_count)/self.settings.duration + for ability in attack_counts: + if ability in self.finisher_damage_sources: + for cp in xrange(7): + attacks_per_second[ability][cp] += dances_per_second * attack_counts[ability][cp] + else: + attacks_per_second[ability] += dances_per_second * attack_counts[ability] + + #Add in white swings + white_swing_downtime = 0 + + #TODO: Add swing resets back for vanishes + self.swing_reset_spacing = None + if self.swing_reset_spacing is not None: + white_swing_downtime += .5 / self.swing_reset_spacing + attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) + attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) + + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance + attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + + #Add in ruptures not previously covered + #Costs will need to be removed from dance values to avoid double counting + nightblade_count = len(nightblade_timeline) + attacks_per_second['nightblade'][self.finisher_thresholds['nightblade']] += float(nightblade_count)/self.settings.duration + self.cp_budget -= self.finisher_thresholds['nightblade'] * nightblade_count + self.energy_budget += (40 * (0.2 * self.finisher_thresholds['nightblade']) - self.get_spell_cost('nightblade')) * nightblade_count + self.dance_budget += (3. * self.finisher_thresholds['nightblade'] * nightblade_count)/60. + + finality_nightblade_count = len(finality_nb_timeline) + attacks_per_second['finality:nightblade'][self.finisher_thresholds['finality:nightblade']] += float(finality_nightblade_count)/self.settings.duration + self.cp_budget -= self.finisher_thresholds['finality:nightblade'] * finality_nightblade_count + self.energy_budget += (40 * (0.2 * self.finisher_thresholds['finality:nightblade']) - self.get_spell_cost('finality:nightblade')) * finality_nightblade_count + self.dance_budget += (3. * self.finisher_thresholds['finality:nightblade'] * finality_nightblade_count)/60. + + #Add in various cooldown abilities + #This could be made better with timelining but for now simple time average will do + if self.traits.goremaws_bite: + goremaws_bite_cd = self.get_spell_cd('goremaws_bite') + self.settings.response_time + attacks_per_second['goremaws_bite'] = 1./goremaws_bite_cd + self.cp_budget += 3 * (self.settings.duration/goremaws_bite_cd) + self.energy_budget += 30 * (self.settings.duration/goremaws_bite_cd) + + if self.talents.death_from_above: + dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + dfa_count = self.settings.duration/dfa_cd + + lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / haste_multiplier) + lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / haste_multiplier) + + attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd + attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd + + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['death_from_above_strike'][self.max_spend_cps] += 1./dfa_cd + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += 1./dfa_cd + + self.cp_budget -= self.max_spend_cps * dfa_count + self.energy_budget += (40 * (0.2 * self.max_spend_cps) - self.get_spell_cost('death_from_above')) * dfa_count + self.dance_budget += (3. * self.max_spend_cps * dfa_count)/60. + + + #Need to handle shadow techniques now to account for swing timer loss + shadow_techniques_cps_per_proc = 1 + (0.05 * self.traits.fortunes_bite) + shadow_techniques_procs = self.settings.duration * (attacks_per_second['mh_autoattack_hits'] + attacks_per_second['oh_autoattack_hits']) / 4 + shadow_techniques_cps = shadow_techniques_procs * shadow_techniques_cps_per_proc + self.cp_budget += shadow_techniques_cps + + cp_per_builder = 1 + self.shadow_blades_uptime + if self.settings.cycle.cp_builder == 'shuriken_storm': + cp_per_builder += self.settings.num_boss_adds + energy_per_cp = self.get_spell_cost(self.settings.cycle.cp_builder) /(cp_per_builder) + #Counter of evis and cp_builders implied to exist but not currently added + implied_builders = 0 + implied_evis = 0 + net_evis_cost = 40 - self.get_spell_cost('eviscerate') + # half of evis will be finality, half not + avg_evis_cps = (self.finisher_thresholds['finality:eviscerate'] + self.finisher_thresholds['eviscerate'])/2 + + #update the budgets to make sure we're still fine + #if we don't have enough dances build some cps and use some finishers + if self.dance_budget<0: + cps_required = abs(self.dance_budget) * 20 + implied_evis += cps_required/avg_evis_cps + self.energy_budget += net_evis_cost + #just subtract the cps because we'll fix those next + self.cp_budget -= cps_required + + #if we don't have enough cps lets build some + if self.cp_budget <0: + #can add since we know cp_budget is negative + self.energy_budget += self.cp_budget * energy_per_cp + implied_builders += abs(self.cp_budget) / cp_per_builder + self.cp_budget = 0 + #hopefully energy is still positive here, if not we're in trouble + energy_per_dance = net_evis_cost * (20./avg_evis_cps) - 20 * energy_per_cp + #print energy_per_dance + + #Iterate over dance finisher priority to schedule dances + for finisher in self.settings.cycle.dance_finisher_priority: + if finisher == 'finality:nightblade': + #Can we dance enough times to fit all of these in? + needed_dances = len(finality_nb_timeline) + #print needed_dances + #available_dances = + + + #print self.dance_budget + #print self.cp_budget + #print self.energy_budget + + #convert nightblade casts into nightblade ticks + for ability in ('finality:nightblade', 'nightblade'): + if ability in attacks_per_second: + tick_name = ability + '_ticks' + attacks_per_second[tick_name] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second[tick_name][cp] = (3 + cp) * attacks_per_second[ability][cp] + del attacks_per_second[ability] + return attacks_per_second, crit_rates, additional_info + ''' cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() shd_ambush_cost_modifier = 1. backstab_cost_mod = cost_modifier @@ -1803,7 +2001,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.dfa_cost = self.get_spell_stats('death_from_above', cost_mod=cost_modifier)[0] #haste and attack speed - haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + mastery_snd_speed = 1 + .4 * (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier * mastery_snd_speed / 1.4 @@ -1837,28 +2035,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): rupture_cd += 4 snd_cd += 6 - #sinister calling mechanic - sc_scaler = .5 / (.5 + self.sc_trigger_rate) - rupture_cd *= sc_scaler - hemo_cd *= sc_scaler - - #passive energy regen - energy_regen = base_energy_regen * haste_multiplier - if self.stats.gear_buffs.rogue_t17_4pc_lfr: - #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 - #https://twitter.com/Celestalon/status/525350819856535552 - energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - energy_regen *= 1.05 - energy_regen += self.bonus_energy_regen + max_energy / self.settings.duration + er_energy - energy_regen += self.get_bonus_energy_from_openers() - if self.stats.gear_buffs.rogue_t16_2pc_bonus(): - energy_regen += 2 * hat_cp_per_second - if self.stats.gear_buffs.rogue_t17_2pc: - energy_regen += 60. / shd_cd - if self.stats.gear_buffs.rogue_t17_4pc: - energy_regen -= (base_eviscerate_cost - 25) / shd_cd - #deal with extra subterfuge ambushes if self.talents.subterfuge: attacks_per_second['ambush'] += (1. / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] @@ -1871,23 +2047,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): cpg_costs_for_cycle = base_hemo_cost * 5 typical_cycle_size = cpg_costs_for_cycle + (base_eviscerate_cost - 25) - #swing timer - white_swing_downtime = 0 - if self.swing_reset_spacing is not None: - white_swing_downtime += .5 / self.swing_reset_spacing - attacks_per_second['mh_autoattacks'] = attack_speed_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) - attacks_per_second['oh_autoattacks'] = attack_speed_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) - if self.talents.death_from_above: - dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / attack_speed_multiplier) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / attack_speed_multiplier) - attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd - attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd - - attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance - attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance ##start consuming energy #base energy reductions @@ -2017,3 +2177,71 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): avg_cdr = 5 #assume all 5cp finishers self.vanish_cd_modifier = 1./((finishers_per_second * avg_cdr) + 1) return attacks_per_second, crit_rates, additional_info + ''' + #Computes the net energy and combo points from a shadow dance rotation + #Returns net_energy, net_cps, spent_cps, dict of attack counts + def get_dance_resources(self, use_sod=False, finisher=None): + net_energy = 0 + net_cps = 0 + spent_cps = 0 + + attack_counts = {} + + if self.talents.master_of_shadows: + net_energy += 30 + + cost_mod = 1.0 + if self.talents.shadow_focus: + cost_mod = 0.5 + + if use_sod: + net_energy -= self.get_spell_cost('symbols_of_death', cost_mod=cost_mod) + attack_counts['symbols_of_death'] = 1 + + if self.talents.subterfuge: + dance_gcds = 6 + else: + dance_gcds = 3 + + max_dance_energy = dance_gcds * self.energy_regen + self.max_energy + + if finisher: + net_energy += 40 * (0.2 * self.finisher_thresholds[finisher]) - self.get_spell_cost(finisher) + dance_gcds -=1 + net_cps -= self.finisher_thresholds[finisher] + attack_counts[finisher] = [0, 0, 0, 0, 0, 0, 0] + attack_counts[finisher][self.finisher_thresholds[finisher]] += 1 + spent_cps += self.finisher_thresholds[finisher] + #fill remaining gcds with shadowstrikes + cp_builder = self.settings.cycle.dance_cp_builder + cp_builder_cost = self.get_spell_cost(cp_builder, cost_mod=cost_mod) + attack_counts[cp_builder] = min(dance_gcds, math.floor((net_energy+max_dance_energy)/cp_builder_cost)) + net_energy -= attack_counts[cp_builder] * cp_builder_cost + if cp_builder == 'shadowstrike': + net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation) + self.shadow_blades_uptime + elif cp_builder == 'shuriken_storm': + net_cps += min(1 + self.settings.num_boss_adds, self.max_store_cps) + self.shadow_blades_uptime + + return net_energy, net_cps, spent_cps, attack_counts + + #Performs fuzzy matching, with specified delta on two lists. + #Returns 3 lists, match, and a and b with matches removed + #Only works for negative deltas for now. + def timeline_overlap(self, timeline_a, timeline_b, match_delta): + match_list = [] + #index of matches for removal + no_match_a = [] + for a in xrange(len(timeline_a)): + match = False + for b in xrange(len(timeline_b)): + #early termination for impossible matches + if timeline_b[b] > timeline_a[a]: + break + if timeline_b[b] > timeline_a[a] + match_delta and timeline_b[b] < timeline_a[a]: + match_list.append(timeline_b[b]) + match = True + if not match: + no_match_a.append(timeline_a[a]) + + return match_list, no_match_a, [x for x in timeline_b if x not in match_list] + diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index cd7c3a0..62b4775 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -4,7 +4,8 @@ class Settings(object): # Settings object for AldrianasRogueDamageCalculator. def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_params=None, - merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False): + merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False, + marked_for_death_resets=0): self.cycle = cycle self.response_time = response_time self.latency = latency @@ -15,6 +16,7 @@ def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_param self.num_boss_adds = max(num_boss_adds, 0) self.adv_params = self.interpret_adv_params(adv_params) self.default_ep_stat = default_ep_stat + self.marked_for_death_resets=marked_for_death_resets def interpret_adv_params(self, s=""): data = {} @@ -81,7 +83,7 @@ def __init__(self, cp_builder='backstab', dance_cp_builder='shadowstrike', posit eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, dance_finisher_priority=[]): self.cp_builder = cp_builder #Allowed values: fok, backstab, gloomblade - self.dance_cp_builder = dance_cp_builder #Allowed values: fok, shadowstrike + self.dance_cp_builder = dance_cp_builder #Allowed values: shuriken_storm, shadowstrike self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index e430439..6816b06 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -27,7 +27,8 @@ class RogueDamageCalculator(DamageCalculator): 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', - 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', 'shadowstrike', + 'backstab', 'eviscerate', 'finality:eviscerate', 'gloomblade', + 'goremaws_bite', 'nightblade', 'finality:nightblade', 'shadowstrike', 'shadow_blade', 'shuriken_storm', 'shuriken_toss'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', @@ -41,11 +42,12 @@ class RogueDamageCalculator(DamageCalculator): 'eviscerate', 'nightblade'] #All damage sources that deal damage with both hands dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', - 'goremaws_bite, shadow_blades'] + 'goremaws_bite', 'shadow_blades'] #All damage sources that scale with cps - cp_scaling_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + finisher_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'envenom', 'rupture_ticks', 'between_the_eyes', - 'run_through', 'eviscerate'] + 'run_through', 'eviscerate', 'finality:eviscerate', + 'nightblade', 'finality:nightblade'] assassination_mastery_conversion = .035 combat_mastery_conversion = .022 @@ -79,34 +81,37 @@ class RogueDamageCalculator(DamageCalculator): #subtlety 'backstab': (35., 'strike'), 'eviscerate': (35., 'strike'), + 'finality:eviscerate': (35., 'strike'), 'gloomblade': (35., 'strike'), 'nightblade': (25., 'strike'), + 'finality:nightblade': (25., 'strike'), + 'shadowstrike': (40., 'strike'), 'shuriken_storm': (35., 'strike'), 'shuriken_toss': (40., 'strike'), 'symbols_of_death': (20., 'buff'), } ability_cds = { #general - 'crimson_vial': 30, - 'death_from_above': 20, - 'kick': 15, - 'marked_for_death': 60, - 'sprint': 60, - 'tricks_of_the_trade': 30, - 'vanish': 120, + 'crimson_vial': 30., + 'death_from_above': 20., + 'kick': 15., + 'marked_for_death': 60., + 'sprint': 60., + 'tricks_of_the_trade': 30., + 'vanish': 120., #assassination - 'exsanguinate': 45, - 'kingsbane': 45, - 'vendetta': 120, + 'exsanguinate': 45., + 'kingsbane': 45., + 'vendetta': 120., #outlaw - 'adrenaline_rush': 180, - 'cannonball_barrage': 60, - 'curse_of_the_dreadblades': 90, - 'killing_spree': 120, + 'adrenaline_rush': 180., + 'cannonball_barrage': 60., + 'curse_of_the_dreadblades': 90., + 'killing_spree': 120., #subtlety - 'goremaws_bite': 60, - 'shadow_dance': 60, - 'shadow_blades': 120, + 'goremaws_bite': 60., + 'shadow_dance': 60., + 'shadow_blades': 120., } def __setattr__(self, name, value): @@ -134,7 +139,7 @@ def oh_penalty(self): def get_base_modifier(self, current_stats): base_modifier = self.damage_modifier_cache - base_modifier *= (self.stats.get_versatility_multiplier_from_rating(rating=current_stats['versatility']) + self.buffs.versatility_bonus()) + base_modifier *= self.stats.get_versatility_multiplier_from_rating(rating=current_stats['versatility']) return base_modifier def get_dps_contribution(self, base_damage, crit_rate, frequency, crit_modifier): @@ -158,7 +163,7 @@ def get_ability_dps(self, ap, ability, attacks_per_second, crit_rate, modifier, for i in xrange(1, cps+1): for a in ability_list: base_damage = self.get_formula(a)(ap, i) * modifier - dps += self.get_dps_contribution(base_damage, crit_rate[i], attacks_per_second[i]. crit_modifier) + dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second[i], crit_modifier) return dps def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): @@ -190,12 +195,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da oh_hit_rate = self.dw_oh_hit_chance - crit_rates['oh_autoattacks'] average_oh_hit = oh_hit_rate * oh_base_damage + crit_rates['oh_autoattacks'] * oh_base_damage * crit_damage_modifier oh_dps_tuple = average_oh_hit * attacks_per_second['oh_autoattacks'] - if self.settings.merge_damage: - damage_breakdown['autoattack'] = mh_dps_tuple + oh_dps_tuple - else: - damage_breakdown['mh_autoattack'] = mh_dps_tuple - damage_breakdown['oh_autoattack'] = oh_dps_tuple - + damage_breakdown['autoattack'] = mh_dps_tuple + oh_dps_tuple for proc in damage_procs: if proc.proc_name not in damage_breakdown: @@ -208,12 +208,14 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da potent_poisons_mod = (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) * base_modifier for ability in self.assassination_damage_sources: + if ability not in attacks_per_second: + continue aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier modifier = base_modifier both_hands = ability in self.dual_wield_damage_sources - cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + cps = max_cps if ability in self.finisher_damage_sources else 0 if ability in self.physical_damage_sources: modifier *= armor_modifier @@ -230,12 +232,14 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if self.spec == 'outlaw': for ability in self.outlaw_damage_sources: + if ability not in attacks_per_second: + continue aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier modifier = base_modifier both_hands = ability in self.dual_wield_damage_sources - cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + cps = max_cps if ability in self.finisher_damage_sources else 0 if ability in self.physical_damage_sources: modifier *= armor_modifier @@ -256,12 +260,14 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da shadow_fangs_mod = (1 + (0.04 * self.traits.shadow_fangs)) for ability in self.subtlety_damage_sources: + if ability not in attacks_per_second: + continue aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier modifier = base_modifier both_hands = ability in self.dual_wield_damage_sources - cps = max_cps if ability in self.cp_scaling_damage_sources else 0 + cps = max_cps if ability in self.finisher_damage_sources else 0 if ability in self.physical_damage_sources: modifier *= armor_modifier @@ -382,6 +388,8 @@ def backstab_damage(self, ap): def eviscerate_damage(self, ap, cp): return 1.28 * cp * ap + def finality_eviscerate_damage(self, ap, cp): + return 1.5 * cp * ap def gloomblade_damage(self, ap): return 4.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) @@ -392,8 +400,11 @@ def mh_goremaws_bite_damage(self, ap): def oh_goremaws_bite_damage(self, ap): return 5 * self.oh_penalty() * self.get_weapon_damage('oh', ap) - def nightblade_tick_damage(self, ap): + #Nightblade doesn't actually scale with cps but passing cps for simplicity + def nightblade_tick_damage(self, ap, cp): return 1.2 * ap * (1 + (0.05 * self.traits.demon_kiss)) + def finality_nightblade_tick_damage(self, ap, cp): + return 1.4 * ap * (1 + (0.05 * self.traits.demon_kiss)) def shadowstrike_damage(self, ap): return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) @@ -413,54 +424,58 @@ def shuriken_toss_damage(self, ap): def get_formula(self, name): formulas = { #general - 'mh_autoattack': self.mh_damage, - 'oh_autoattack': self.oh_damage, - 'death_from_above_pulse':self.death_from_above_pulse_damage, + 'mh_autoattack': self.mh_damage, + 'oh_autoattack': self.oh_damage, + 'death_from_above_pulse': self.death_from_above_pulse_damage, #assassination - 'deadly_poison': self.deadly_poison_tick_damage, - 'deadly_instant_poison': self.deadly_instant_poison_damage, - 'envenom': self.envenom_damage, - 'fan_of_knives_damage': self.fan_of_knives_damage, - 'garrote_ticks': self.garrote_tick_damage, - 'hemorrhage': self.hemorrhage_damage, - 'mh_kingsbane': self.mh_kingsbane_damage, - 'oh_kingsbane': self.oh_kingsbane_damage, - 'kingsbane_ticks': self.kingsbane_tick_damage, - 'mh_mutilate': self.mh_mutilate_damage, - 'oh_mutilate': self.oh_mutilate_damage, - 'poisoned_knife': self.poisoned_knife_damage, - 'rupture_ticks': self.rupture_tick_damage, + 'deadly_poison': self.deadly_poison_tick_damage, + 'deadly_instant_poison': self.deadly_instant_poison_damage, + 'envenom': self.envenom_damage, + 'fan_of_knives_damage': self.fan_of_knives_damage, + 'garrote_ticks': self.garrote_tick_damage, + 'hemorrhage': self.hemorrhage_damage, + 'mh_kingsbane': self.mh_kingsbane_damage, + 'oh_kingsbane': self.oh_kingsbane_damage, + 'kingsbane_ticks': self.kingsbane_tick_damage, + 'mh_mutilate': self.mh_mutilate_damage, + 'oh_mutilate': self.oh_mutilate_damage, + 'poisoned_knife': self.poisoned_knife_damage, + 'rupture_ticks': self.rupture_tick_damage, #outlaw - 'ambush': self.ambush_damage, - 'between_the_eyes': self.between_the_eyes_damage, - 'blunderbuss': self.blunderbuss_damage, - 'cannonball_barrage': self.cannonball_barrage_damage, - 'ghostly_strike': self.ghostly_strike_damage, - 'mh_greed': self.mh_greed_damage, - 'oh_greed': self.oh_greed_damage, - 'mh_killing_spree': self.mh_killing_spree_damage, - 'oh_killing_spree': self.oh_killing_spree_damage, - 'main_gauche': self.main_gauche_damage, - 'pistol_shot': self.pistol_shot_damage, - 'run_through': self.run_through_damage, - 'saber_slash': self.saber_slash_damage, + 'ambush': self.ambush_damage, + 'between_the_eyes': self.between_the_eyes_damage, + 'blunderbuss': self.blunderbuss_damage, + 'cannonball_barrage': self.cannonball_barrage_damage, + 'ghostly_strike': self.ghostly_strike_damage, + 'mh_greed': self.mh_greed_damage, + 'oh_greed': self.oh_greed_damage, + 'mh_killing_spree': self.mh_killing_spree_damage, + 'oh_killing_spree': self.oh_killing_spree_damage, + 'main_gauche': self.main_gauche_damage, + 'pistol_shot': self.pistol_shot_damage, + 'run_through': self.run_through_damage, + 'saber_slash': self.saber_slash_damage, #subtlety - 'backstab': self.backstab_damage, - 'eviscerate': self.eviscerate_damage, - 'gloomblade': self.gloomblade_damage, - 'mh_goremaws_bite': self.mh_goremaws_bite_damage, - 'oh_goremaws_bite': self.oh_goremaws_bite_damage, - 'nightblade_ticks': self.nightblade_tick_damage, - 'shadowstrike': self.shadowstrike_damage, - 'mh_shadow_blades': self.mh_shadow_blades_damage, - 'oh_shadow_blades': self.oh_shadow_blades_damage, - 'shuriken_storm': self.shuriken_storm_damage, - 'shuriken_toss': self.shuriken_toss_damage, + 'backstab': self.backstab_damage, + 'eviscerate': self.eviscerate_damage, + 'finality:eviscerate': self.finality_eviscerate_damage, + 'gloomblade': self.gloomblade_damage, + 'mh_goremaws_bite': self.mh_goremaws_bite_damage, + 'oh_goremaws_bite': self.oh_goremaws_bite_damage, + 'nightblade_ticks': self.nightblade_tick_damage, + 'finality_nightblade_ticks': self.finality_nightblade_tick_damage, + 'shadowstrike': self.shadowstrike_damage, + 'mh_shadow_blades': self.mh_shadow_blades_damage, + 'oh_shadow_blades': self.oh_shadow_blades_damage, + 'shuriken_storm': self.shuriken_storm_damage, + 'shuriken_toss': self.shuriken_toss_damage, } return formulas[name] def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod + if ability == 'shadowstrike': + cost -= 0.25 * (5 * self.traits.energetic_stabbing) return cost def get_spell_cd(self, ability): @@ -471,4 +486,4 @@ def crit_rate(self, crit=None): # should be coded better? base_crit = .15 base_crit += self.stats.get_crit_from_rating(crit) - return base_crit + self.buffs.buff_all_crit() + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction + return base_crit + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction From f23bb54efc380b5d9c9d0c5c8d59cb704f688d6c Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 13 Aug 2016 11:02:50 -0400 Subject: [PATCH 035/265] Remove armor_mitigation Import --- shadowcraft/calcs/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 79a81a5..8314d8a 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -5,7 +5,6 @@ __builtin__._ = gettext.gettext from shadowcraft.core import exceptions -from shadowcraft.calcs import armor_mitigation from shadowcraft.objects import class_data from shadowcraft.objects import talents from shadowcraft.objects import artifact From 3aba6b7bce702781b36302ee8a9e4bae741c1327 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sat, 13 Aug 2016 10:04:07 -0500 Subject: [PATCH 036/265] - ignore project files - remove depreciated armor_mitigation module from DamageCalculator --- .gitignore | 4 ++++ shadowcraft/calcs/__init__.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 49aac70..f408e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ item_db.db /.project /.pydevproject +/ShadowCraft-Engine.pyproj +/ShadowCraft-Engine.pyproj.user +/ShadowCraft-Engine.sln +/.vs/ShadowCraft-Engine/v14/.suo diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 79a81a5..8314d8a 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -5,7 +5,6 @@ __builtin__._ = gettext.gettext from shadowcraft.core import exceptions -from shadowcraft.calcs import armor_mitigation from shadowcraft.objects import class_data from shadowcraft.objects import talents from shadowcraft.objects import artifact From 50af06ca2ed854841208e1af5ed17affe90b04a5 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sat, 13 Aug 2016 10:23:41 -0500 Subject: [PATCH 037/265] - outlaw profile scaffolding --- scripts/combat.py | 145 ---------------------------------------------- scripts/outlaw.py | 116 +++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 145 deletions(-) delete mode 100644 scripts/combat.py create mode 100644 scripts/outlaw.py diff --git a/scripts/combat.py b/scripts/combat.py deleted file mode 100644 index 3086072..0000000 --- a/scripts/combat.py +++ /dev/null @@ -1,145 +0,0 @@ -# Simple test program to debug + play with assassination models. -from os import path -import sys -sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs - -from shadowcraft.core import i18n - -import time - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. -test_language = 'local' -i18n.set_language(test_language) - -start = time.time() - -# Set up level/class/race -test_level = 100 -test_race = race.Race('troll') -test_class = 'rogue' - -# Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'haste_buff', - 'multistrike_buff', - 'versatility_buff', - 'attack_power_buff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - 'flask_wod_agi', - 'food_mop_agi' - ) - -# Set up weapons: dancing_steel mark_of_the_shattered_hand mark_of_warsong -test_mh = stats.Weapon(410., 2.6, 'sword', 'dancing_steel') -#test_mh = stats.Weapon(420.5, 1.8, 'dagger', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(410., 2.6, 'sword', 'dancing_steel') - -# Set up procs. -test_procs = procs.ProcsList(('assurance_of_consequence', 588), ('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', 'archmages_incandescence') #trinkets, other things (legendary procs) - -# Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization', 'rogue_t17_2pc', 'rogue_t17_4pc') #tier buffs located here - -# Set up a calcs object.. -test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=3650, - stam=2426, - crit=1039, - haste=1100, - mastery=1015, - readiness=0, - versatility=122, - multistrike=1034,) - -# Initialize talents.. -test_talents = talents.Talents('3111121', test_class, test_level) - -# Set up glyphs. -glyph_list = ['energy', 'disappearance'] -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) - -# Set up settings. -test_cycle = settings.CombatCycle(revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=True) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', - latency=.03, merge_damage=True, use_opener='always', opener_name='ambush', - num_boss_adds=0.0, adv_params="") # 0.2 = 20% of the fight is an add present - -# Build a DPS object. -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - -# Compute DPS Breakdown. -dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) - -# Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) -#tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) -#mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) - -trinkets_list = { - #5.4 - 'assurance_of_consequence': [(528,532,536),(540,544,548),(553,557,561),(559,563,567),(566,570,574),(572,576,580)], - 'haromms_talisman': [(528,532,536),(540,544,548),(553,557,561),(559,563,567),(566,570,574),(572,576,580)], - 'sigil_of_rampage': [(528,532,536),(540,544,548),(553,557,561),(559,563,567),(566,570,574),(572,576,580)], - 'ticking_ebon_detonator': [(528,532,536),(540,544,548),(553,557,561),(559,563,567),(566,570,574),(572,576,580)], - 'thoks_tail_tip': [(528,532,536),(540,544,548),(553,557,561),(559,563,567),(566,570,574),(572,576,580)], - 'discipline_of_xuen': [(496,500,504),(535,539,543)], -} -#trinkets_ep_value = calculator.get_upgrades_ep_fast(trinkets_list) -#glyph_values = calculator.get_glyphs_ranking() - -# Compute weapon type modifier. -#weapon_type_mod = calculator.get_oh_weapon_modifier() -talent_ranks = calculator.get_talents_ranking() - -def max_length(dict_list): - max_len = 0 - for i in dict_list: - dict_values = i.items() - if max_len < max(len(entry[0]) for entry in dict_values): - max_len = max(len(entry[0]) for entry in dict_values) - - return max_len - -def pretty_print(dict_list, total_sum = 1., show_percent=False): - max_len = max_length(dict_list) - - for i in dict_list: - dict_values = i.items() - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' - else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) - -dicts_for_pretty_print = [ - ep_values, - #tier_ep_values, - #mh_enchants_and_dps_ep_values, - #oh_enchants_and_dps_ep_values, - #trinkets_ep_value, - #glyph_values, - talent_ranks, -] -pretty_print(dicts_for_pretty_print) -pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") -print "Request time: %s sec" % (time.time() - start) diff --git a/scripts/outlaw.py b/scripts/outlaw.py new file mode 100644 index 0000000..a6b3d75 --- /dev/null +++ b/scripts/outlaw.py @@ -0,0 +1,116 @@ +# Simple test program to debug + play with assassination models. +from os import path +import sys +sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) + +from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator +from shadowcraft.calcs.rogue.Aldriana import settings + +from shadowcraft.objects import buffs +from shadowcraft.objects import race +from shadowcraft.objects import stats +from shadowcraft.objects import procs +from shadowcraft.objects import talents +from shadowcraft.objects import artifact + +from shadowcraft.core import i18n + +import time + +# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. +test_language = 'local' +i18n.set_language(test_language) + +# Set up level/class/race +test_level = 110 +test_race = race.Race('pandaren') +test_class = 'rogue' +test_spec = 'outlaw' + +# Set up buffs. +test_buffs = buffs.Buffs( + 'short_term_haste_buff', + 'flask_wod_agi', + 'food_wod_versatility' + ) + +# Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand +test_mh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') +test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') + +# Set up procs. +#test_procs = procs.ProcsList(('assurance_of_consequence', 588), ('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', 'archmages_incandescence') #trinkets, other things (legendary procs) +test_procs = procs.ProcsList() + +# Set up gear buffs. +test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here + +# Set up a calcs object.. +test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, + agi=7655, + stam=19566, + crit=2665, + haste=1594, + mastery=3350, + versatility=6522,) + +# Initialize talents.. +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) + +#initialize artifact traits.. +test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') + +# Set up settings. +test_cycle = settings.CombatCycle(revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=True) +test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, + adv_params="", is_demon=True, num_boss_adds=0) + +# Build a DPS object. +calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) + +# Compute DPS Breakdown. +dps_breakdown = calculator.get_dps_breakdown() +total_dps = sum(entry[1] for entry in dps_breakdown.items()) + +# Compute EP values. +#ep_values = calculator.get_ep(baseline_dps=total_dps) +#tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) +#mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) + +# Compute weapon type modifier. +#talent_ranks = calculator.get_talents_ranking() +#trait_ranks = calculator.get_trait_ranking() + +def max_length(dict_list): + max_len = 0 + for i in dict_list: + dict_values = i.items() + if max_len < max(len(entry[0]) for entry in dict_values): + max_len = max(len(entry[0]) for entry in dict_values) + + return max_len + +def pretty_print(dict_list, total_sum = 1., show_percent=False): + max_len = max_length(dict_list) + + for i in dict_list: + dict_values = i.items() + dict_values.sort(key=lambda entry: entry[1], reverse=True) + for value in dict_values: + #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + if show_percent and ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': + print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' + else: + print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + print '-' * (max_len + 15) + +dicts_for_pretty_print = [ + #ep_values, + #tier_ep_values, + #talent_ranks, + #trinkets_ep_value, + dps_breakdown, + trait_ranks +] +pretty_print(dicts_for_pretty_print) +print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") From c43a97c081e0b4a444c3553aae3fcc96d80de87a Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sat, 13 Aug 2016 11:12:35 -0500 Subject: [PATCH 038/265] - removed readiness from outlaw model - removed vanish glyph references from outlaw model - removed lemon vest references from outlaw model - removed poison references from outlaw model - changed get_spell_stats() to get_spell_cost() in outlaw model - changed ability references to current outlaw abilities in outlaw model - disabled opener modeling in outlaw model - disabled shadow_reflection modeling in outlaw model -disabled special multistrike modeling for blade flurry in outlaw - disabled trait and talent ranking on outlaw model --- scripts/outlaw.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 76 +++++++++----------- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index a6b3d75..4266ba2 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -110,7 +110,7 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 854171e..5be1e6e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -44,8 +44,7 @@ def get_dps_breakdown(self): return 0 #return self.assassination_dps_breakdown() elif self.spec == 'outlaw': - return 0 - #return self.combat_dps_breakdown() + return self.combat_dps_breakdown() elif self.spec == 'subtlety': return self.subtlety_dps_breakdown() else: @@ -1219,14 +1218,7 @@ def combat_dps_breakdown(self): raise InputNotModeledException(_('You must specify a combat cycle to match your combat spec.')) #set readiness coefficient - self.readiness_spec_conversion = self.combat_readiness_conversion - self.spec_convergence_stats = ['haste', 'mastery', 'readiness'] - - #spec specific glyph behaviour - if self.glyphs.disappearance: - self.ability_cds['vanish'] = 60 - else: - self.ability_cds['vanish'] = 120 + self.spec_convergence_stats = ['haste', 'mastery'] #update spec specific proc rates if getattr(self.stats.procs, 'legendary_capacitive_meta'): @@ -1244,10 +1236,6 @@ def combat_dps_breakdown(self): self.max_energy = 100. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): self.max_energy += 30 - if self.talents.lemon_zest: - self.max_energy += 15 - if self.glyphs.energy: - self.max_energy += 20 if self.stats.gear_buffs.rogue_t18_4pc_lfr: self.max_energy += 20 if self.race.expansive_mind: @@ -1262,8 +1250,6 @@ def combat_dps_breakdown(self): if self.stats.gear_buffs.rogue_t17_2pc: self.extra_cp_chance += 0.2 self.rvs_duration = 24 - if self.settings.dmg_poison == 'dp' and self.level == 100: - self.settings.dmg_poison = 'sp' self.set_constants() self.stat_multipliers['haste'] *= 1.05 @@ -1344,8 +1330,8 @@ def combat_dps_breakdown(self): #combat gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! - multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) - multistrike_multiplier = min(.6, multistrike_multiplier) + #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) + #multistrike_multiplier = min(.6, multistrike_multiplier) for ability in damage_breakdown: damage_breakdown[ability] *=maalus_mod if 'sr_' not in ability: @@ -1354,7 +1340,7 @@ def combat_dps_breakdown(self): #Fel Lash doesn't MS if ability == 'Fel Lash': continue - damage_breakdown[ability] *= (1 + multistrike_multiplier) + #damage_breakdown[ability] *= (1 + multistrike_multiplier) if ability in ('eviscerate', 'sr_eviscerate'): damage_breakdown[ability] *= evis_multiplier @@ -1423,8 +1409,6 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): if ar: self.attack_speed_increase *= 1.2 self.base_energy_regen *= 2.0 - if self.talents.lemon_zest: - self.base_energy_regen *= 1 + .05 * (1 + min(self.settings.num_boss_adds, 2)) gcd_size = 1.0 + self.settings.latency if ar: gcd_size -= .2 @@ -1441,14 +1425,14 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG cost_reducer = main_gauche_proc_rate * combat_potency_from_mg - eviscerate_energy_cost = self.get_spell_stats('eviscerate', cost_mod=cost_modifier)[0] + eviscerate_energy_cost = self.get_spell_cost('run_through', cost_mod=cost_modifier) eviscerate_energy_cost -= cost_reducer eviscerate_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp - revealing_strike_energy_cost = self.get_spell_stats('revealing_strike', cost_mod=cost_modifier)[0] + revealing_strike_energy_cost = self.get_spell_cost('pistol_shot', cost_mod=cost_modifier) revealing_strike_energy_cost -= cost_reducer - sinister_strike_energy_cost = self.get_spell_stats('sinister_strike', cost_mod=cost_modifier)[0] + sinister_strike_energy_cost = self.get_spell_cost('saber_slash', cost_mod=cost_modifier) sinister_strike_energy_cost -= cost_reducer - death_from_above_energy_cost = self.get_spell_stats('death_from_above', cost_mod=cost_modifier)[0] + death_from_above_energy_cost = self.get_spell_cost('death_from_above', cost_mod=cost_modifier) death_from_above_energy_cost -= cost_reducer * (2 + self.settings.num_boss_adds) #need to reduce the cost of DFA by the strike's MG proc ... #but also the MG procs from the AOE which hits the main target plus each additional add (strike + aoe) @@ -1458,6 +1442,8 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): ## Base CPs and Attacks #Autoattacks white_swing_downtime = 0 + #TODO: Add swing resets back for vanishes + self.swing_reset_spacing = None if self.swing_reset_spacing is not None and not ar: white_swing_downtime += self.settings.response_time / self.swing_reset_spacing #from vanish swing_timer_mh = self.stats.mh.speed / self.attack_speed_increase @@ -1479,10 +1465,14 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): combat_potency_regen = attacks_per_second['oh_autoattack_hits'] * combat_potency_regen_per_oh #Base energy - bonus_energy_from_openers = self.get_bonus_energy_from_openers('sinister_strike', 'revealing_strike') - if self.settings.opener_name in ('ambush', 'garrote'): - attacks_per_second[self.settings.opener_name] = self.total_openers_per_second - attacks_per_second['main_gauche'] += self.total_openers_per_second * main_gauche_proc_rate + + #TODO handle openers + #bonus_energy_from_openers = self.get_bonus_energy_from_openers('sinister_strike', 'revealing_strike') + bonus_energy_from_openers = 0 + #if self.settings.opener_name in ('ambush', 'garrote'): + # attacks_per_second[self.settings.opener_name] = self.total_openers_per_second + # attacks_per_second['main_gauche'] += self.total_openers_per_second * main_gauche_proc_rate + if self.talents.death_from_above and not ar: attacks_per_second['main_gauche'] += (1 + self.settings.num_boss_adds) * main_gauche_proc_rate / dfa_cd combat_potency_regen += combat_potency_from_mg * attacks_per_second['main_gauche'] @@ -1610,20 +1600,20 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['oh_killing_spree'] = (1 + 2*ks_duration) / (final_ks_cd + self.settings.response_time) attacks_per_second['main_gauche'] += attacks_per_second['mh_killing_spree'] * main_gauche_proc_rate - if self.talents.shadow_reflection: - sr_uptime = 8. / self.get_spell_cd('shadow_reflection') - lst = ('sinister_strike', 'eviscerate', 'revealing_strike') - if not ar: - lst += ('mh_killing_spree', 'oh_killing_spree') - for ability in lst: - if type(attacks_per_second[ability]) in (tuple, list): - attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] - for i in xrange(1, 6): - attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] - else: - attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] - - self.get_poison_counts(attacks_per_second, current_stats) + #if self.talents.shadow_reflection: + # sr_uptime = 8. / self.get_spell_cd('shadow_reflection') + # lst = ('sinister_strike', 'eviscerate', 'revealing_strike') + # if not ar: + # lst += ('mh_killing_spree', 'oh_killing_spree') + # for ability in lst: + # if type(attacks_per_second[ability]) in (tuple, list): + # attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] + # for i in xrange(1, 6): + # attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] + # else: + # attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] + + #self.get_poison_counts(attacks_per_second, current_stats) #print attacks_per_second return attacks_per_second, crit_rates, additional_info From 42061be618002b68bdfe12d090d0cff79f2b3bf8 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sat, 13 Aug 2016 13:06:25 -0500 Subject: [PATCH 039/265] - rename combat functions to outlaw - first pass over outlaw model revamp --- scripts/outlaw.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 162 +++++++++---------- shadowcraft/calcs/rogue/Aldriana/settings.py | 3 +- shadowcraft/calcs/rogue/__init__.py | 2 +- 4 files changed, 82 insertions(+), 87 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 4266ba2..01dde5a 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -61,7 +61,7 @@ test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. -test_cycle = settings.CombatCycle(revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=True) +test_cycle = settings.CombatCycle(blade_flurry=False, dfa_during_ar=True) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 5be1e6e..2038560 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -31,8 +31,7 @@ def get_dps(self): return 0 #return self.assassination_dps_estimate() elif self.spec == 'outlaw': - return 0 - #return self.combat_dps_estimate() + return self.outlaw_dps_estimate() elif self.spec == 'subtlety': return self.subtlety_dps_estimate() else: @@ -44,7 +43,7 @@ def get_dps_breakdown(self): return 0 #return self.assassination_dps_breakdown() elif self.spec == 'outlaw': - return self.combat_dps_breakdown() + return self.outlaw_dps_breakdown() elif self.spec == 'subtlety': return self.subtlety_dps_breakdown() else: @@ -490,7 +489,7 @@ def get_poison_counts(self, attacks_per_second, current_stats): else: return proc_multiplier = 1 - if self.settings.is_combat_rogue(): + if self.spec == 'outlaw': if self.settings.cycle.blade_flurry: ms_value = 1 + min(2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()), 2) proc_multiplier += min(self.settings.num_boss_adds, [4, 999][self.level==100]) * ms_value @@ -662,7 +661,7 @@ def determine_stats(self, attack_counts_function): current_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] #only have to converge with specific procs - #check if... assassination:crit/haste, combat:mastery/haste, sub:haste/mastery + #check if... assassination:crit/haste, outlaw:mastery/haste, sub:haste/mastery if not convergence_stats and not self.spec_needs_converge: break @@ -735,7 +734,7 @@ def determine_stats(self, attack_counts_function): def compute_damage_from_aps(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): # this method exists solely to let us use cached values you would get from determine stats - # really only useful for combat calculations (restless blades calculations) + # really only useful for outlaw calculations (restless blades calculations) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) return damage_breakdown, additional_info @@ -1207,17 +1206,16 @@ def assassination_attack_counts_execute(self, current_stats, crit_rates=None): return self.assassination_attack_counts(current_stats, 'dispatch', self.settings.cycle.min_envenom_size_execute, crit_rates=crit_rates) ########################################################################### - # Combat DPS functions + # Outlaw DPS functions ########################################################################### - def combat_dps_estimate(self): - return sum(self.combat_dps_breakdown().values()) + def outlaw_dps_estimate(self): + return sum(self.outlaw_dps_breakdown().values()) - def combat_dps_breakdown(self): - if not self.settings.is_combat_rogue(): - raise InputNotModeledException(_('You must specify a combat cycle to match your combat spec.')) + def outlaw_dps_breakdown(self): + if not self.spec == 'outlaw': + raise InputNotModeledException(_('You must specify a combat cycle to match your outlaw spec.')) - #set readiness coefficient self.spec_convergence_stats = ['haste', 'mastery'] #update spec specific proc rates @@ -1226,13 +1224,8 @@ def combat_dps_breakdown(self): if getattr(self.stats.procs, 'fury_of_xuen'): getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.15 - #combat specific constants - self.max_bandits_guile_buff = 1.3 - self.combat_cd_delay = 0 #this is for DFA convergence, mostly - if self.level == 100: - self.max_bandits_guile_buff += .2 - self.dw_miss_penalty = 0 - self.recalculate_hit_constants() + #outlaw specific constants + self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly self.max_energy = 100. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): self.max_energy += 30 @@ -1245,11 +1238,8 @@ def combat_dps_breakdown(self): # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex if self.stats.gear_buffs.rogue_t18_2pc: self.ar_duration = 17.8571 - self.revealing_strike_multiplier = 1.35 - self.extra_cp_chance = .25 # Assume all casts during RvS if self.stats.gear_buffs.rogue_t17_2pc: self.extra_cp_chance += 0.2 - self.rvs_duration = 24 self.set_constants() self.stat_multipliers['haste'] *= 1.05 @@ -1264,7 +1254,7 @@ def combat_dps_breakdown(self): # actual damage calculations here phases = {} #AR phase - stats, aps, crits, procs, additional_info = self.determine_stats(self.combat_attack_counts_ar) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts_ar) ar_tuple = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) phases['ar'] = (self.ar_duration, self.update_with_bandits_guile(ar_tuple[0], ar_tuple[1])) for e in cds: @@ -1273,7 +1263,7 @@ def combat_dps_breakdown(self): #none self.tmp_ks_cd = cds['ks'] self.tmp_phase_length = cds['ar'] #This is to approximate the value of a full energy bar to be used when not during AR or SB - stats, aps, crits, procs, additional_info = self.determine_stats(self.combat_attack_counts_none) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts_none) none_tuple = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) phases['none'] = (self.rb_actual_cds(aps, cds)['ar'] + self.settings.response_time + self.major_cd_delay, self.update_with_bandits_guile(none_tuple[0], none_tuple[1]) ) @@ -1303,7 +1293,7 @@ def combat_dps_breakdown(self): damage_breakdown['blade_flurry'] = 0 for key in damage_breakdown: if key in self.melee_attacks: - if key == "eviscerate": + if key == "run_through": damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) * evis_multiplier else: damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) @@ -1327,7 +1317,7 @@ def combat_dps_breakdown(self): maalus_val = maalus.value['damage_mod']/10000. maalus_mod = 1 + (15.0/120* maalus_val) #super hackish - #combat gets it's own MS calculation due to BF mechanics + #outlaw gets it's own MS calculation due to BF mechanics #calculate multistrike here, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) @@ -1364,27 +1354,30 @@ def update_with_bandits_guile(self, damage_breakdown, additional_info): #http://www.wolframalpha.com/input/?i=%28sum+of+1.5*1.1%5Ex+from+x%3D1+to+7%29+%2F+%281.5*7%29 # No need to use anything other than a constant. Yay for convenience! damage_breakdown[key] *= 1.49084 - elif key in ('sinister_strike', 'revealing_strike'): + elif key in ('saber_slash', 'pistol_shot'): damage_breakdown[key] *= self.bandits_guile_multiplier - elif key in ('eviscerate', ): - damage_breakdown[key] *= self.bandits_guile_multiplier * self.revealing_strike_multiplier + elif key in ('run_through', ): + damage_breakdown[key] *= self.bandits_guile_multiplier #* self.revealing_strike_multiplier else: damage_breakdown[key] *= self.bandits_guile_multiplier #* self.ksp_multiplier return damage_breakdown - def combat_cpg_per_finisher(self, current_cp, ability_count): + def outlaw_cpg_per_finisher(self, current_cp, ability_count): if current_cp >= 5: return ability_count new_count = copy(ability_count) new_count += 1 - normal = self.combat_cpg_per_finisher(current_cp+1, new_count) - rvs_proc = self.combat_cpg_per_finisher(current_cp+2, new_count) + normal = self.outlaw_cpg_per_finisher(current_cp+1, new_count) - return (1 - self.extra_cp_chance)*normal + self.extra_cp_chance*rvs_proc + #disabled rvs modeling because i dont understand how it works anyway + #rvs_proc = self.outlaw_cpg_per_finisher(current_cp+2, new_count) + + #return (1 - self.extra_cp_chance)*normal + self.extra_cp_chance*rvs_proc + return normal - def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): + def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second = {} additional_info = {} # base_energy_regen needs to be reset here due to determine_stats method @@ -1399,7 +1392,7 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): self.attack_speed_increase = self.base_speed_multiplier * haste_multiplier - main_gauche_proc_rate = self.combat_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) + main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) combat_potency_regen_per_oh = 15 * .2 * self.stats.oh.speed / 1.4 # the new "normalized" formula combat_potency_from_mg = 15 * .2 @@ -1425,19 +1418,19 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG cost_reducer = main_gauche_proc_rate * combat_potency_from_mg - eviscerate_energy_cost = self.get_spell_cost('run_through', cost_mod=cost_modifier) - eviscerate_energy_cost -= cost_reducer - eviscerate_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp - revealing_strike_energy_cost = self.get_spell_cost('pistol_shot', cost_mod=cost_modifier) - revealing_strike_energy_cost -= cost_reducer - sinister_strike_energy_cost = self.get_spell_cost('saber_slash', cost_mod=cost_modifier) - sinister_strike_energy_cost -= cost_reducer + run_through_energy_cost = self.get_spell_cost('run_through', cost_mod=cost_modifier) + run_through_energy_cost -= cost_reducer + run_through_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp + pistol_shot_energy_cost = self.get_spell_cost('pistol_shot', cost_mod=cost_modifier) + pistol_shot_energy_cost -= cost_reducer + saber_slash_energy_cost = self.get_spell_cost('saber_slash', cost_mod=cost_modifier) + saber_slash_energy_cost -= cost_reducer death_from_above_energy_cost = self.get_spell_cost('death_from_above', cost_mod=cost_modifier) death_from_above_energy_cost -= cost_reducer * (2 + self.settings.num_boss_adds) #need to reduce the cost of DFA by the strike's MG proc ... #but also the MG procs from the AOE which hits the main target plus each additional add (strike + aoe) if self.stats.gear_buffs.rogue_t16_2pc_bonus(): - sinister_strike_energy_cost -= 15 * self.extra_cp_chance + saber_slash_energy_cost -= 15 * self.extra_cp_chance ## Base CPs and Attacks #Autoattacks @@ -1489,35 +1482,35 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): energy_regen += self.max_energy / self.settings.duration #Base actions - rvs_interval = self.rvs_duration - if self.settings.cycle.revealing_strike_pooling: - min_energy_while_pooling = energy_regen * gcd_size - max_energy_while_pooling = self.max_energy - 20 - rvs_interval += (max_energy_while_pooling - min_energy_while_pooling) / energy_regen #Minicycle sizes and cpg_per_finisher stats if self.talents.anticipation: ss_per_finisher = (FINISHER_SIZE - ruthlessness_value) / (cp_per_cpg + self.extra_cp_chance) else: - ss_per_finisher = self.combat_cpg_per_finisher(1, 0) + ss_per_finisher = self.outlaw_cpg_per_finisher(1, 0) cp_per_finisher = FINISHER_SIZE - energy_cost_for_cpgs = ss_per_finisher * sinister_strike_energy_cost - total_eviscerate_cost = energy_cost_for_cpgs + eviscerate_energy_cost + energy_cost_for_cpgs = ss_per_finisher * saber_slash_energy_cost + total_eviscerate_cost = energy_cost_for_cpgs + run_through_energy_cost ss_per_snd = ss_per_finisher snd_size = FINISHER_SIZE snd_base_cost = 25 - snd_cost = ss_per_snd * sinister_strike_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp + snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp snd_duration = 6 + 6 * (snd_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) energy_spent_on_snd = snd_cost / snd_duration #Base Actions + + #TODO model pistol shot procs + pistol_shot_interval = 10 + #marked for death CD - self.combat_cd_delay = (.5 * total_eviscerate_cost) / (2 * energy_regen) - marked_for_death_cd = self.get_spell_cd('marked_for_death') + self.combat_cd_delay + self.settings.response_time + self.outlaw_cd_delay = (.5 * total_eviscerate_cost) / (2 * energy_regen) + marked_for_death_cd = self.get_spell_cd('marked_for_death') + self.outlaw_cd_delay + self.settings.response_time if self.talents.marked_for_death: energy_regen -= 10. / marked_for_death_cd - energy_regen -= revealing_strike_energy_cost / rvs_interval + + energy_regen -= pistol_shot_energy_cost / pistol_shot_interval energy_for_dfa = 0 if self.talents.death_from_above and not ar: @@ -1532,15 +1525,15 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval * (self.settings.num_boss_adds+1)] #Base CPGs - attacks_per_second['sinister_strike_base'] = ss_per_snd / snd_duration + attacks_per_second['saber_slash_base'] = ss_per_snd / snd_duration if self.talents.death_from_above and not ar: - attacks_per_second['sinister_strike_base'] += ss_per_finisher / (1/dfa_interval) + attacks_per_second['saber_slash_base'] += ss_per_finisher / (1/dfa_interval) - attacks_per_second['revealing_strike'] = 1. / rvs_interval - extra_finishers_per_second = attacks_per_second['revealing_strike'] / 5. + attacks_per_second['pistol_shot'] = 1. / pistol_shot_interval + extra_finishers_per_second = attacks_per_second['pistol_shot'] / 5. #Scaling CPGs free_gcd = 1./gcd_size - free_gcd -= 1./snd_duration + (attacks_per_second['sinister_strike_base'] + attacks_per_second['revealing_strike'] + extra_finishers_per_second) + free_gcd -= 1./snd_duration + (attacks_per_second['saber_slash_base'] + attacks_per_second['pistol_shot'] + extra_finishers_per_second) if self.talents.marked_for_death: free_gcd -= (1. / marked_for_death_cd) #2 seconds is an approximation of GCD loss while in air @@ -1553,41 +1546,44 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): #http://www.wolframalpha.com/input/?i=sum+of+.2%5Ex+from+x%3D1+to+inf #This increases the frequency of Eviscerates by 25% for every Evisc cast evisc_actions_per_second += total_evis_per_second * .25 - attacks_per_second['sinister_strike'] = total_evis_per_second * ss_per_finisher + attacks_per_second['saber_slash'] = total_evis_per_second * ss_per_finisher # If GCD capped if evisc_actions_per_second > free_gcd: gcd_cap_mod = evisc_actions_per_second / free_gcd - attacks_per_second['sinister_strike'] = attacks_per_second['sinister_strike'] / gcd_cap_mod + attacks_per_second['saber_slash'] = attacks_per_second['saber_slash'] / gcd_cap_mod total_evis_per_second = total_evis_per_second / gcd_cap_mod # Reintroduce flat gcds - attacks_per_second['sinister_strike'] += attacks_per_second['sinister_strike_base'] - attacks_per_second['main_gauche'] += (attacks_per_second['sinister_strike'] + attacks_per_second['revealing_strike'] + + attacks_per_second['saber_slash'] += attacks_per_second['saber_slash_base'] + attacks_per_second['main_gauche'] += (attacks_per_second['saber_slash'] + attacks_per_second['pistol_shot'] + total_evis_per_second) * main_gauche_proc_rate if self.talents.death_from_above and not ar: attacks_per_second['main_gauche'] += attacks_per_second['death_from_above_strike'][5] * main_gauche_proc_rate #attacks_per_second['eviscerate'] = [finisher_chance * total_evis_per_second for finisher_chance in finisher_size_breakdown] - attacks_per_second['eviscerate'] = [0,0,0,0,0,total_evis_per_second] + attacks_per_second['run_through'] = [0,0,0,0,0,total_evis_per_second] for opener, cps in [('ambush', 2), ('garrote', 1)]: if opener in attacks_per_second: extra_finishers_per_second += attacks_per_second[opener] * cps / 5 - attacks_per_second['eviscerate'][5] += extra_finishers_per_second + attacks_per_second['run_through'][5] += extra_finishers_per_second if self.talents.marked_for_death: - attacks_per_second['eviscerate'][5] += 1. / marked_for_death_cd + attacks_per_second['run_through'][5] += 1. / marked_for_death_cd if self.stats.gear_buffs.rogue_t17_4pc: - attacks_per_second['eviscerate'][5] *= 1.25 + attacks_per_second['run_through'][5] *= 1.25 #self.current_variables['cp_spent_on_damage_finishers_per_second'] = (total_evis_per_second) * cp_per_finisher if 'garrote' in attacks_per_second: attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] - time_at_level = 4 / attacks_per_second['sinister_strike'] + time_at_level = 4 / attacks_per_second['saber_slash'] cycle_duration = 3 * time_at_level + 15 - if self.level == 100: - self.bandits_guile_multiplier = 1 + (0*time_at_level + .1*time_at_level + .2*time_at_level + .5 * 15) / cycle_duration - else: - avg_stacks = (3 * time_at_level + 45) / cycle_duration #45 is the duration (15s) multiplied by the stack power (30% BG) - self.bandits_guile_multiplier = 1 + .1 * avg_stacks + #if self.level == 100: + # self.bandits_guile_multiplier = 1 + (0*time_at_level + .1*time_at_level + .2*time_at_level + .5 * 15) / cycle_duration + #else: + # avg_stacks = (3 * time_at_level + 45) / cycle_duration #45 is the duration (15s) multiplied by the stack power (30% BG) + # self.bandits_guile_multiplier = 1 + .1 * avg_stacks + + #hack bg multiplier until it can be removed later + self.bandits_guile_multiplier = 1.0 if not ar: ks_duration = 3 @@ -1620,27 +1616,27 @@ def combat_attack_counts(self, current_stats, ar=False, crit_rates=None): def rb_actual_cds(self, attacks_per_second, base_cds, avg_rb_effect=10): final_cds = {} - # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster + # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster for cd_name in base_cds: final_cds[cd_name] = base_cds[cd_name] * self.rb_cd_modifier(attacks_per_second) return final_cds def rb_actual_cd(self, attacks_per_second, base_cd, avg_rb_effect=10): - # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster + # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster return base_cd * self.rb_cd_modifier(attacks_per_second) def rb_cd_modifier(self, attacks_per_second, avg_rb_effect=10): - # If it's best to always use 5CP finishers as combat now, it should continue to be so, this is simpler and faster - offensive_finisher_rate = attacks_per_second['eviscerate'][5] + # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster + offensive_finisher_rate = attacks_per_second['run_through'][5] if 'death_from_above' in attacks_per_second: offensive_finisher_rate += attacks_per_second['death_from_above'] return (1./avg_rb_effect) / (offensive_finisher_rate + (1./avg_rb_effect)) - def combat_attack_counts_ar(self, current_stats, crit_rates=None): - return self.combat_attack_counts(current_stats, ar=True, crit_rates=crit_rates) + def outlaw_attack_counts_ar(self, current_stats, crit_rates=None): + return self.outlaw_attack_counts(current_stats, ar=True, crit_rates=crit_rates) - def combat_attack_counts_none(self, current_stats, crit_rates=None): - return self.combat_attack_counts(current_stats, crit_rates=crit_rates) + def outlaw_attack_counts_none(self, current_stats, crit_rates=None): + return self.outlaw_attack_counts(current_stats, crit_rates=crit_rates) ########################################################################### # Subtlety DPS functions diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 62b4775..0a85e2d 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -70,10 +70,9 @@ def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5): class CombatCycle(Cycle): _cycle_type = 'combat' - def __init__(self, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=False, dfa_during_ar=False): + def __init__(self, ksp_immediately=True, blade_flurry=False, dfa_during_ar=False): self.blade_flurry = bool(blade_flurry) self.ksp_immediately = bool(ksp_immediately) # Determines whether to KSp the instant it comes off cool or wait until Bandit's Guile stacks up. - self.revealing_strike_pooling = bool(revealing_strike_pooling) self.dfa_during_ar = bool(dfa_during_ar) class SubtletyCycle(Cycle): diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 6816b06..13fd623 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -50,7 +50,7 @@ class RogueDamageCalculator(DamageCalculator): 'nightblade', 'finality:nightblade'] assassination_mastery_conversion = .035 - combat_mastery_conversion = .022 + outlaw_mastery_conversion = .022 subtlety_mastery_conversion = .0276 ability_info = { From 92d82942c96b5f7cd776f0ecce247ad87f178eb0 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 13 Aug 2016 16:10:11 -0400 Subject: [PATCH 040/265] Remove Multistrike from All Procs --- scripts/subtlety.py | 10 +++++----- shadowcraft/objects/proc_data.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index fed5286..13318ae 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -33,8 +33,8 @@ ) # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand -test_mh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') +test_mh = stats.Weapon(812.0, 1.8, 'dagger', None) +test_oh = stats.Weapon(812.0, 1.8, 'dagger', None) # Set up procs. - trinkets, other things (legendary procs) #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), @@ -77,7 +77,7 @@ #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) #talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -107,7 +107,7 @@ def pretty_print(dict_list): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") +print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 2a15b35..7d7f405 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -341,7 +341,7 @@ }, 'scales_of_doom': { 'stat': 'stats', - 'value': {'multistrike':1743}, + 'value': {'mastery':1743}, 'duration': 10, 'proc_name': 'Scales of Doom', 'upgradable': True, @@ -355,7 +355,7 @@ }, 'blackheart_enforcers_medallion': { 'stat': 'stats', - 'value': {'multistrike':1665}, + 'value': {'haste':1665}, 'duration': 10, 'proc_name': 'Blackheart Enforcers Medallion', 'upgradable': True, @@ -383,7 +383,7 @@ }, 'beating_heart_of_the_mountain': { 'stat': 'stats', - 'value': {'multistrike':1467}, + 'value': {'crit':1467}, 'duration': 20, 'proc_name': 'Beating Heart of the Mountain', 'upgradable': True, @@ -439,7 +439,7 @@ }, 'gorashans_lodestone_spike': { 'stat': 'stats', - 'value': {'multistrike':1060}, + 'value': {'crit':1060}, 'duration': 15, 'proc_name': 'Gorashans Lodestone Spike', 'upgradable': True, @@ -611,7 +611,7 @@ #6.0 'mark_of_the_frostwolf': { 'stat': 'stats', - 'value': {'multistrike':500}, + 'value': {'crit':500}, 'duration': 6, 'max_stacks': 2, 'proc_name': 'Mark of the Frostwolf', From 1064e37a11c6375ec9d4ddde75f7d730dc8c0e35 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 15 Aug 2016 11:29:33 -0400 Subject: [PATCH 041/265] Updated Sub Cycle Settings -New, simplfiied sub model --- shadowcraft/calcs/rogue/Aldriana/settings.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 62b4775..e48e541 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -80,22 +80,24 @@ class SubtletyCycle(Cycle): _cycle_type = 'subtlety' def __init__(self, cp_builder='backstab', dance_cp_builder='shadowstrike', positional_uptime=1.0, symbols_policy='just', - eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, - dance_finisher_priority=[]): + eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, dfa_cps = 5, + dance_finishers_allowed=[], vanish_ability='shadowstrike'): self.cp_builder = cp_builder #Allowed values: fok, backstab, gloomblade self.dance_cp_builder = dance_cp_builder #Allowed values: shuriken_storm, shadowstrike self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) #'just' - Only use SoD when needed to refresh + self.vanish_ability = vanish_ability #Allowed values: 'shadowstrike', 'shuriken_storm', 'symbols_of_death' #Finisher thresholds for each finisher, Allowed Values: 0, 1, 2, 3, 4, 5, 6 self.eviscerate_cps = eviscerate_cps self.finality_eviscerate_cps = finality_eviscerate_cps self.nightblade_cps = nightblade_cps self.finality_nightblade_cps = finality_nightblade_cps + self.death_from_above_cps = dfa_cps #List of following keys: 'eviscerate', 'nightblade', 'finality:eviscerate', 'finality:nightblade' #Priority of finisher usage during dance #Keys not included will not be used during dance - self.dance_finisher_priority = dance_finisher_priority + self.dance_finishers_allowed= dance_finishers_allowed From e06c0970530d5cd70bf0a21eb5c5213b2c87376c Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 15 Aug 2016 11:32:58 -0400 Subject: [PATCH 042/265] Updated Subtlety Cycle Settings --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 308 ++++++------------- 1 file changed, 90 insertions(+), 218 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 854171e..4bf224c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1670,7 +1670,6 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): #Artifact: # 'flickering_shadows', # 'second_shuriken', - # 'akarris_soul', # 'shadow_nova', # 'legionblade' @@ -1942,42 +1941,66 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Counter of evis and cp_builders implied to exist but not currently added implied_builders = 0 implied_evis = 0 - net_evis_cost = 40 - self.get_spell_cost('eviscerate') + self.net_evis_cost = 40 - self.get_spell_cost('eviscerate') # half of evis will be finality, half not - avg_evis_cps = (self.finisher_thresholds['finality:eviscerate'] + self.finisher_thresholds['eviscerate'])/2 + self.avg_evis_cps = (self.finisher_thresholds['finality:eviscerate'] + self.finisher_thresholds['eviscerate'])/2 #update the budgets to make sure we're still fine - #if we don't have enough dances build some cps and use some finishers - if self.dance_budget<0: - cps_required = abs(self.dance_budget) * 20 - implied_evis += cps_required/avg_evis_cps - self.energy_budget += net_evis_cost - #just subtract the cps because we'll fix those next - self.cp_budget -= cps_required - - #if we don't have enough cps lets build some - if self.cp_budget <0: - #can add since we know cp_budget is negative - self.energy_budget += self.cp_budget * energy_per_cp - implied_builders += abs(self.cp_budget) / cp_per_builder - self.cp_budget = 0 - #hopefully energy is still positive here, if not we're in trouble - energy_per_dance = net_evis_cost * (20./avg_evis_cps) - 20 * energy_per_cp - #print energy_per_dance + builders, evis, sane = self.sanitize_budgets(attacks_per_second) + implied_builders += builders + implied_evis += evis #Iterate over dance finisher priority to schedule dances + out_of_resouces = False + use_sod = False + if self.settings.cycle.symbols_policy == 'always': + use_sod = True for finisher in self.settings.cycle.dance_finisher_priority: - if finisher == 'finality:nightblade': - #Can we dance enough times to fit all of these in? - needed_dances = len(finality_nb_timeline) - #print needed_dances - #available_dances = + if not out_of_resouces: + break + #generate our dance rotation + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=finisher) + dances_per_second = 1./self.settings.duration + if finisher in ('finality:nightblade', 'nightblade'): + #remove finisher costs to prevent double counting + net_energy -= 40 * (0.2 * self.finisher_thresholds[finisher]) - self.get_spell_cost(finisher) + net_cps += self.finisher_thresholds[finisher] + del attack_counts[finisher] + if finisher == 'finality:nightblade': + needed_dances = len(finality_nb_timeline) + elif finisher == 'nightblade': + needed_dances = len(nightblade_timeline) + + #loop over dances until we can't anymore + for dance in xrange(needed_dances): + self.energy_budget += net_energy + self.cp_budget += net_cps + self.dance_budget -= 1 + builders, evis, sane = self.sanitize_budgets(attacks_per_second) + implied_builders += builders + implied_evis += evis + #add attack_counts into APS + for ability in attack_counts: + if ability in self.finisher_damage_sources: + for cp in xrange(7): + attacks_per_second[ability][cp] += dances_per_second * attack_counts[ability][cp] + else: + attacks_per_second[ability] += dances_per_second * attack_counts[ability] + + if not sane: + out_of_resouces = True + break + dance_count = dance + + #elif finisher == 'finality:eviscerate': + + #print self.dance_budget #print self.cp_budget #print self.energy_budget - + #convert nightblade casts into nightblade ticks for ability in ('finality:nightblade', 'nightblade'): if ability in attacks_per_second: @@ -1986,198 +2009,18 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): for cp in xrange(7): attacks_per_second[tick_name][cp] = (3 + cp) * attacks_per_second[ability][cp] del attacks_per_second[ability] - return attacks_per_second, crit_rates, additional_info - ''' - cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() - shd_ambush_cost_modifier = 1. - backstab_cost_mod = cost_modifier - base_eviscerate_cost = self.get_spell_stats('eviscerate', cost_mod=cost_modifier)[0] - base_rupture_cost = self.get_spell_stats('rupture', cost_mod=cost_modifier)[0] - base_hemo_cost = self.get_spell_stats('hemorrhage', cost_mod=cost_modifier)[0] - base_backstab_energy_cost = self.get_spell_stats('backstab', cost_mod=backstab_cost_mod)[0] - sd_ambush_cost = self.get_spell_stats('ambush', cost_mod=shd_ambush_cost_modifier)[0] - 20 - normal_ambush_cost = self.get_spell_stats('ambush')[0] - if self.talents.death_from_above: - self.dfa_cost = self.get_spell_stats('death_from_above', cost_mod=cost_modifier)[0] - - #haste and attack speed - - mastery_snd_speed = 1 + .4 * (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) - attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier * mastery_snd_speed / 1.4 - - cpg_name = 'backstab' - if self.settings.cycle.use_hemorrhage == 'always': - cpg_name = 'hemorrhage' - - #constant and base values - hat_triggers_per_second = self.settings.cycle.raid_crits_per_second - hat_cp_per_second = 1. / (2 + 1. / hat_triggers_per_second) - er_energy = 8. / 2 #8 energy every 2 seconds, assumed full SnD uptime - fw_duration = 10. #17.5s - if self.settings.cycle.clip_fw: - fw_duration -= .5 - attacks_per_second['eviscerate'] = [0,0,0,0,0,0] - attacks_per_second['rupture_ticks'] = [0,0,0,0,0,0] - attacks_per_second['ambush'] = self.total_openers_per_second - attacks_per_second['backstab'] = 0 - attacks_per_second['hemorrhage'] = 0 - cp_per_ambush = 2 - vanish_bonus_stealth = 0 + 3 * self.talents.subterfuge * [1, 2][self.glyphs.vanish] - rupture_ticks_per_cast = 12. - rupture_cd = 24. - hemo_cd = 24. - snd_cd = 36. - base_cp_per_second = hat_cp_per_second * (shd_cd-8.)/shd_cd + self.total_openers_per_second * 2 - if self.stats.gear_buffs.rogue_t18_2pc: - base_cp_per_second += 5 / self.get_spell_cd('vanish') - if self.stats.gear_buffs.rogue_t15_2pc: - rupture_ticks_per_cast += 2 - rupture_cd += 4 - snd_cd += 6 - - #deal with extra subterfuge ambushes - if self.talents.subterfuge: - attacks_per_second['ambush'] += (1. / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] - energy_regen -= (normal_ambush_cost / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] - base_cp_per_second += (2. / self.get_spell_cd('vanish')) * [1., 2.][self.glyphs.vanish] - ##calculations dependent on energy regen - cpg_costs_for_cycle = base_backstab_energy_cost * 5 - if self.settings.cycle.use_hemorrhage == 'always': - cpg_costs_for_cycle = base_hemo_cost * 5 - typical_cycle_size = cpg_costs_for_cycle + (base_eviscerate_cost - 25) + #convert some white swings into shadowblades + #since weapon speeds are now fixed just handle a single shadowblades + attacks_per_second['shadow_blades'] = self.shadow_blades_uptime * attacks_per_second['mh_autoattacks'] + attacks_per_second['mh_autoattacks'] -= attacks_per_second['shadow_blades'] + attacks_per_second['oh_autoattacks'] -= attacks_per_second['shadow_blades'] + if self.traits.akarris_soul: + attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] - - ##start consuming energy - #base energy reductions - marked_for_death_cd = self.get_spell_cd('marked_for_death') + (.5 * typical_cycle_size / energy_regen) + self.settings.response_time - if self.talents.marked_for_death: - energy_regen -= (base_eviscerate_cost - 25) / marked_for_death_cd - attacks_per_second['eviscerate'][5] += 1. / marked_for_death_cd - shadowmeld_ambushes = 0. - if self.race.shadowmeld: - shadowmeld_ambushes = 1. / (self.get_spell_cd('shadowmeld') + self.settings.response_time) - shadowmeld_ambushes *= ((self.settings.duration - fw_duration * 3 - 8) / self.settings.duration) - attacks_per_second['ambush'] += shadowmeld_ambushes - energy_regen -= normal_ambush_cost * shadowmeld_ambushes - base_cp_per_second += shadowmeld_ambushes * 2 - - #base CPs, CPGs, and finishers - if self.settings.cycle.use_hemorrhage != 'always' and self.settings.cycle.use_hemorrhage != 'never': - if self.settings.cycle.use_hemorrhage == 'uptime': - hemo_per_second = 1. / hemo_cd - else: - hemo_per_second = 1. / float(self.settings.cycle.use_hemorrhage) - energy_regen -= hemo_per_second * base_hemo_cost - base_cp_per_second += hemo_per_second - attacks_per_second['hemorrhage'] += hemo_per_second - #premed - base_cp_per_second += 2. / self.settings.duration #start of the fight - base_cp_per_second += 2. / shd_cd * (self.settings.duration-25.)/self.settings.duration - base_cp_per_second += 2. / self.get_spell_cd('vanish') * (self.settings.duration-50.)/self.settings.duration - #rupture - attacks_per_second['rupture'] = 1. / rupture_cd - attacks_per_second['rupture_ticks'][5] = rupture_ticks_per_cast / rupture_cd - #attacks_per_second['rupture_ticks_sc'] = [0,0,0,0,0, (1 - sc_scaler) * rupture_ticks_per_cast / rupture_cd] - base_cp_per_second -= 5. / rupture_cd - energy_regen -= (base_rupture_cost - 25) / rupture_cd - #no need to add slice and dice to attacks per second - base_cp_per_second -= 5. / snd_cd - - energy_for_dfa = 0 - if self.talents.death_from_above: - #dfa_gap probably should be handled more accurately especially in the non-anticipation case - dfa_interval = 1./(dfa_cd) - energy_for_dfa = typical_cycle_size + self.dfa_cost - base_eviscerate_cost - - attacks_per_second['death_from_above'] = dfa_interval - attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, dfa_interval] - attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval * (self.settings.num_boss_adds+1)] - attacks_per_second[cpg_name] += dfa_interval * 5 - energy_regen -= energy_for_dfa / dfa_cd - - base_cp_per_second += self.vanish_rate * 2 - #if we've consumed more CP's than we have for base functionality, lets generate some more CPs - if base_cp_per_second < 0: - cpg_per_second = math.fabs(base_cp_per_second) - base_cp_per_second += cpg_per_second - attacks_per_second[cpg_name] += cpg_per_second - if cpg_name == 'backstab': - energy_regen -= base_backstab_energy_cost * cpg_per_second - elif cpg_name == 'hemorrhage': - energy_regen -= base_hemo_cost * cpg_per_second - extra_evisc = base_cp_per_second / 5 - energy_regen -= (base_eviscerate_cost - 25) * extra_evisc - attacks_per_second['eviscerate'][5] += extra_evisc - if energy_regen < 0: - raise InputNotModeledException(_('Catastrophic failure: cycle not sustainable.')) - - #calculate shd ambush cycles - shd_energy = (max_energy - self.get_adv_param('max_pool_reduct', 10, min_bound=0, max_bound=50)) + energy_regen * shd_duration #lasts 8s, assume we pool to ~10 energy below max - shd_cycle_cost = 2 * sd_ambush_cost + (base_eviscerate_cost - 25) - shd_eviscerates = min(shd_energy / shd_cycle_cost, 8./3) #8/3 is the max GCDs - shd_ambushes = shd_eviscerates * 2 - attacks_per_second['ambush'] += (shd_ambushes / shd_cd) * ((self.settings.duration - fw_duration) / self.settings.duration) - attacks_per_second['eviscerate'][5] += (shd_eviscerates / shd_cd) * ((self.settings.duration - fw_duration) / self.settings.duration) - energy_regen -= (shd_cycle_cost * shd_eviscerates) / shd_cd * ((self.settings.duration - fw_duration) / self.settings.duration) - - #calculate percentage of ambushes with FW - ambush_no_fw = shadowmeld_ambushes + 1. / shd_cd - 1. / self.settings.duration - if not self.settings.cycle.clip_fw: - ambush_no_fw += self.total_openers_per_second + 1. / self.settings.duration - additional_info['ambush_no_fw_rate'] = ambush_no_fw / attacks_per_second['ambush'] - #calculate percentage of backstabs with FW - additional_info['backstab_fw_rate'] = (fw_duration - 1) / self.settings.duration #start of fight - additional_info['backstab_fw_rate'] += (fw_duration - 1) / shd_cd * (1. - fw_duration / self.settings.duration) - additional_info['backstab_fw_rate'] += (fw_duration + vanish_bonus_stealth - 1) / self.get_spell_cd('vanish') * ((self.settings.duration - fw_duration * 2 - 8) / self.settings.duration) - if self.race.shadowmeld: - additional_info['backstab_fw_rate'] += (fw_duration - 1) / self.get_spell_cd('shadowmeld') * ((self.settings.duration - fw_duration * 3 - 8) / self.settings.duration) - #accounts for the fact that backstab isn't evenly distributed - additional_info['backstab_fw_rate'] = additional_info['backstab_fw_rate'] / ((shd_cd - 8.) / shd_cd) - #calculate FW uptime overall - additional_info['fw_uptime'] = fw_duration / self.settings.duration #start of fight - additional_info['fw_uptime'] += (fw_duration + 7.5) / shd_cd * ((self.settings.duration - fw_duration) / self.settings.duration) - additional_info['fw_uptime'] += (fw_duration + vanish_bonus_stealth) / self.get_spell_cd('vanish') * ((self.settings.duration - fw_duration * 2 - 8) / self.settings.duration) - if self.race.shadowmeld: - additional_info['fw_uptime'] += fw_duration / self.get_spell_cd('shadowmeld') * ((self.settings.duration - fw_duration * 3 - 8) / self.settings.duration) - #allocate the remaining energy - filler_cycles_per_second = energy_regen / typical_cycle_size - attacks_per_second[cpg_name] += filler_cycles_per_second * 5 - attacks_per_second['eviscerate'][5] += filler_cycles_per_second - if self.stats.gear_buffs.rogue_t17_4pc: - attacks_per_second['eviscerate'][5] += 1. / shd_cd - - #Hemo ticks - if 'hemorrhage' in attacks_per_second and self.settings.cycle.use_hemorrhage != 'never': - if self.settings.cycle.use_hemorrhage == 'always': - hemo_gap = 1 / attacks_per_second['hemorrhage'] - else: - hemo_gap = hemo_cd - ticks_per_second = min(1. / (3 * sc_scaler), 8. / hemo_gap) - attacks_per_second['hemorrhage_ticks'] = ticks_per_second - - sc_ms_chance = min(2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()), 2) - #this is a cache for convergence - self.sc_trigger_rate = attacks_per_second['ambush'] * sc_ms_chance - if 'backstab' in attacks_per_second: - self.sc_trigger_rate += attacks_per_second['backstab'] * sc_ms_chance - self.sc_trigger_rate = min(self.sc_trigger_rate, 2) - - if self.talents.shadow_reflection: - sr_cd = self.get_spell_cd('shadow_reflection') - attacks_per_second['sr_eviscerate'] = [0,0,0,0,0, shd_eviscerates / sr_cd] - attacks_per_second['sr_rupture_ticks'] = [0,0,0,0,0, 12. / sr_cd] - attacks_per_second['sr_ambush'] = shd_ambushes / sr_cd - - self.get_poison_counts(attacks_per_second, current_stats) - - if self.stats.gear_buffs.rogue_t18_4pc: - finishers_per_second = sum(attacks_per_second['eviscerate']) + attacks_per_second['rupture'] - avg_cdr = 5 #assume all 5cp finishers - self.vanish_cd_modifier = 1./((finishers_per_second * avg_cdr) + 1) return attacks_per_second, crit_rates, additional_info - ''' + #Computes the net energy and combo points from a shadow dance rotation #Returns net_energy, net_cps, spent_cps, dict of attack counts def get_dance_resources(self, use_sod=False, finisher=None): @@ -2192,16 +2035,16 @@ def get_dance_resources(self, use_sod=False, finisher=None): cost_mod = 1.0 if self.talents.shadow_focus: - cost_mod = 0.5 + cost_mod = 0.7 if use_sod: net_energy -= self.get_spell_cost('symbols_of_death', cost_mod=cost_mod) attack_counts['symbols_of_death'] = 1 + dance_gcds = 3 if self.talents.subterfuge: - dance_gcds = 6 - else: - dance_gcds = 3 + dance_gcds += 2 + max_dance_energy = dance_gcds * self.energy_regen + self.max_energy @@ -2245,3 +2088,32 @@ def timeline_overlap(self, timeline_a, timeline_b, match_delta): return match_list, no_match_a, [x for x in timeline_b if x not in match_list] + #Attempts to sanitize the budget + #returns added implied evis, implied builder and if santization possible + def sanitize_budgets(self, attacks_per_second): + implied_builders = 0 + implied_evis = 0 + sane = True + #if we don't have enough dances build some cps and use some finishers + if self.dance_budget<0: + cps_required = abs(self.dance_budget) * 20 + implied_evis += cps_required/self.avg_evis_cps + self.energy_budget += self.net_evis_cost + #just subtract the cps because we'll fix those next + self.cp_budget -= cps_required + + #if we don't have enough cps lets build some + if self.cp_budget <0: + #can add since we know cp_budget is negative + self.energy_budget += self.cp_budget * self.energy_per_cp + implied_builders += abs(self.cp_budget) / self.cp_per_builder + self.cp_budget = 0 + + #if we don't have enough energy back off implied builders and evis + if self.energy_budget < 0: + deficit = abs(self.energy_budget) + builder_backoff = deficit / (self.cp_budget * self.energy_per_cp) + implied_builders -= builder_backoff + implied_evis -= builder_backoff/self.avg_evis_cps + sane = False + return implied_builders, implied_evis, sane From 6bd5f12b2c7fd41aad51812d9798e70fca11f91d Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 16 Aug 2016 15:50:29 -0400 Subject: [PATCH 043/265] Full Subtlety Model -Need to add damage modifiers -Numbers don't make a ton of sense --- scripts/subtlety.py | 6 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 339 ++++++++++--------- shadowcraft/calcs/rogue/Aldriana/settings.py | 12 +- shadowcraft/calcs/rogue/__init__.py | 24 +- 4 files changed, 209 insertions(+), 172 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 13318ae..06a0325 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -54,13 +54,13 @@ versatility=6522,) # Initialize talents.. -test_talents = talents.Talents('0200000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') +test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') # Set up settings. -test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', dance_finisher_priority=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate']) +test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', dance_finishers_allowed=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate']) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4bf224c..7c04388 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -17,6 +17,10 @@ class InputNotModeledException(exceptions.InvalidInputException): # I'll return these when inputs don't make sense to the model. pass +class ConvergenceErrorException(exceptions.InvalidInputException): + # Return this if a convergence loop goes too long + pass + class AldrianasRogueDamageCalculator(RogueDamageCalculator): ########################################################################### @@ -1661,9 +1665,7 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): #Talents: #T1-MoS #T1-Weaponmaster - #T1-Gloomblade #T2:NS - #T3:DS #T3:Ancitipcation #T6:Alacrity @@ -1680,9 +1682,8 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): #Legendaries #Rotation details: - #Openers #Combo Point loss - #Non-dance stealths + #SoD auto crit def subtlety_dps_estimate(self): return sum(self.subtlety_dps_breakdown().values()) @@ -1694,7 +1695,7 @@ def subtlety_dps_breakdown(self): #check to make sure cycle is sane: if self.settings.cycle.cp_builder == 'gloomblade' and not self.talents.gloomblade: raise InputNotModeledException(_('Gloomblade must be talented to be priamry cp builder')) - #Raise finality error here? + self.max_spend_cps = 5 if self.talents.deeper_strategem: @@ -1710,7 +1711,6 @@ def subtlety_dps_breakdown(self): } self.set_constants() - #sinister calling requires convergence to calculate (for now?) #self.spec_needs_converge = True stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) @@ -1767,6 +1767,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) + use_sod = False + if self.settings.cycle.symbols_policy == 'always': + use_sod = True + #Set up initial energy budget base_energy_regen = 10. haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod @@ -1779,40 +1783,30 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.max_energy += 50 self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy + #set initial dance budget + self.dance_budget = 3 + self.settings.duration/60. + shadow_blades_duration = 15. + (3.3333 * self.traits.soul_shadows) self.shadow_blades_uptime = shadow_blades_duration/self.get_spell_cd('shadow_blades') #swing timer white_swing_downtime = 0 - - #TODO: Add swing resets back for vanishes - self.swing_reset_spacing = None + self.swing_reset_spacing = self.get_spell_cd('vanish') if self.swing_reset_spacing is not None: white_swing_downtime += .5 / self.swing_reset_spacing attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) - attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance - attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - #Set up initial combo point budget mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5 + self.settings.marked_for_death_resets * 5) self.cp_budget = mfd_cps + #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps + #2 net energy per 6 seconds from relentless strikes if self.talents.enveloping_shadows: self.cp_budget += self.settings.duration/6. - - - #set initial dance budget - self.dance_budget = 3 + self.settings.duration/60. - - #print self.dance_budget - #print self.cp_budget - #print self.energy_budget - #print "-----" - - #finality evis tracking, should be 0 at end of rotation - finality_evis_count = 0 + self.energy_budget += (2./6) * self.settings.duration + self.dance_budget += (0.5 * self.settings.duration)/60 #setup timelines sod_duration = 35 @@ -1822,42 +1816,46 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Add attacks that could occur during first pass to aps attacks_per_second[self.settings.cycle.dance_cp_builder] = 0 attacks_per_second['symbols_of_death'] = 0 + attacks_per_second['shadow_dance'] = 0 + attacks_per_second['vanish'] = 0 #Leaving space for opener handling for the first cast - sod_timeline = range(sod_duration, self.settings.duration, sod_duration) + sod_timeline = range(0, self.settings.duration, sod_duration) if self.traits.finality: - finality_nb_timeline = range(nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) - nightblade_timeline = range(finality_nightblade_duration + nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) + finality_nb_timeline = range(0, self.settings.duration, finality_nightblade_duration + nightblade_duration) + nightblade_timeline = range(nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) else: finality_nb_timeline = [] nightblade_timeline = range(nightblade_duration, self.settings.duration, nightblade_duration) - #First timeline pass, since we're doing timeline matching use fixed priority + dance_finality_nb_uptime = 0.0 + dance_nb_uptime = 0.0 + #Timeline match of ruptures, fill in rest with either finality:eviscerate for finisher in ['finality:nightblade', 'nightblade', 'finality:eviscerate', 'eviscerate', None]: - attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] dance_count = 0 - if finisher in self.settings.cycle.dance_finisher_priority: - if finisher == 'finality:nightblade': + if finisher in self.settings.cycle.dance_finishers_allowed: + attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] + if finisher == 'finality:nightblade' and self.traits.finality: #Allow SoDs to be used on pandemic for match purposes joint, sod_timeline, finality_nb_timeline = self.timeline_overlap(sod_timeline, finality_nb_timeline, -0.3 * sod_duration) #if there is overlap compute a dance rotation for this combo dance_count = len(joint) + dance_finality_nb_uptime = dance_count/len(finality_nb_timeline) elif finisher == 'nightblade': joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) dance_count = len(joint) - #Assume finality evis will be available for half of these - if finisher == 'finality:eviscerate' and self.traits.finality: - dance_count = len(sod_timeline)/2 - sod_timeline = sod_timeline[dance_count:] - finality_evis_count += dance_count - if finisher == 'eviscerate': + dance_nb_uptime = dance_count/len(nightblade_timeline) + elif (finisher == 'finality:eviscerate') and self.traits.finality: dance_count = len(sod_timeline) - sod_timeline = sod_timeline[dance_count:] - finality_evis_count -= dance_count - #Whatever is left over is computed without finishers - if finisher is None: + sod_timeline = [] + elif finisher == 'eviscerate': + dance_count = len(sod_timeline) + sod_timeline = [] + + #Not using finishers during dance + if finisher is None and not self.settings.cycle.dance_finishers_allowed: dance_count = len(sod_timeline) - sod_timeline = sod_timeline[dance_count:] + sod_timeline = [] if dance_count: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=True, finisher=finisher) @@ -1865,29 +1863,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget += dance_count * net_cps self.dance_budget += ((3. * spent_cps* dance_count)/60) - dance_count #merge attack counts into attacks_per_second - dances_per_second = float(dance_count)/self.settings.duration - for ability in attack_counts: - if ability in self.finisher_damage_sources: - for cp in xrange(7): - attacks_per_second[ability][cp] += dances_per_second * attack_counts[ability][cp] - else: - attacks_per_second[ability] += dances_per_second * attack_counts[ability] - - #Add in white swings - white_swing_downtime = 0 - - #TODO: Add swing resets back for vanishes - self.swing_reset_spacing = None - if self.swing_reset_spacing is not None: - white_swing_downtime += .5 / self.swing_reset_spacing - attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) - attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) - - attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance - attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + self.rotation_merge(attacks_per_second, attack_counts, dance_count) #Add in ruptures not previously covered - #Costs will need to be removed from dance values to avoid double counting nightblade_count = len(nightblade_timeline) attacks_per_second['nightblade'][self.finisher_thresholds['nightblade']] += float(nightblade_count)/self.settings.duration self.cp_budget -= self.finisher_thresholds['nightblade'] * nightblade_count @@ -1927,80 +1905,119 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget += (40 * (0.2 * self.max_spend_cps) - self.get_spell_cost('death_from_above')) * dfa_count self.dance_budget += (3. * self.max_spend_cps * dfa_count)/60. - #Need to handle shadow techniques now to account for swing timer loss + attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance + attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + shadow_techniques_cps_per_proc = 1 + (0.05 * self.traits.fortunes_bite) shadow_techniques_procs = self.settings.duration * (attacks_per_second['mh_autoattack_hits'] + attacks_per_second['oh_autoattack_hits']) / 4 shadow_techniques_cps = shadow_techniques_procs * shadow_techniques_cps_per_proc self.cp_budget += shadow_techniques_cps + #vanish handling + vanish_count = self.settings.duration/self.get_spell_cd('vanish') + #Treat subterfuge as a mini-dance + if self.talents.subterfuge: + if 'finality:eviscerate' in self.settings.cycle.dance_finishers_allowed and self.traits.finality: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='finality:eviscerate', vanish=True) + else: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) + else: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) + self.energy_budget += vanish_count * net_energy + self.cp_budget += vanish_count * net_cps + self.dance_budget += ((3. * spent_cps* vanish_count)/60) + self.rotation_merge(attacks_per_second, attack_counts, vanish_count) + + #Generate one final dance template + if 'finality:eviscerate' in self.settings.cycle.dance_finishers_allowed and self.traits.finality: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='finality:eviscerate') + elif 'eviscerate' in self.settings.cycle.dance_finishers_allowed: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate') + else: + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None) + + #Now lets make sure all our budgets are positive cp_per_builder = 1 + self.shadow_blades_uptime if self.settings.cycle.cp_builder == 'shuriken_storm': cp_per_builder += self.settings.num_boss_adds energy_per_cp = self.get_spell_cost(self.settings.cycle.cp_builder) /(cp_per_builder) - #Counter of evis and cp_builders implied to exist but not currently added - implied_builders = 0 - implied_evis = 0 - self.net_evis_cost = 40 - self.get_spell_cost('eviscerate') - # half of evis will be finality, half not - self.avg_evis_cps = (self.finisher_thresholds['finality:eviscerate'] + self.finisher_thresholds['eviscerate'])/2 - - #update the budgets to make sure we're still fine - builders, evis, sane = self.sanitize_budgets(attacks_per_second) - implied_builders += builders - implied_evis += evis - - #Iterate over dance finisher priority to schedule dances - out_of_resouces = False - use_sod = False - if self.settings.cycle.symbols_policy == 'always': - use_sod = True - for finisher in self.settings.cycle.dance_finisher_priority: - if not out_of_resouces: - break - #generate our dance rotation - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=finisher) - dances_per_second = 1./self.settings.duration - if finisher in ('finality:nightblade', 'nightblade'): - #remove finisher costs to prevent double counting - net_energy -= 40 * (0.2 * self.finisher_thresholds[finisher]) - self.get_spell_cost(finisher) - net_cps += self.finisher_thresholds[finisher] - del attack_counts[finisher] - if finisher == 'finality:nightblade': - needed_dances = len(finality_nb_timeline) - elif finisher == 'nightblade': - needed_dances = len(nightblade_timeline) - - #loop over dances until we can't anymore - for dance in xrange(needed_dances): - self.energy_budget += net_energy - self.cp_budget += net_cps - self.dance_budget -= 1 - builders, evis, sane = self.sanitize_budgets(attacks_per_second) - implied_builders += builders - implied_evis += evis - #add attack_counts into APS - for ability in attack_counts: - if ability in self.finisher_damage_sources: - for cp in xrange(7): - attacks_per_second[ability][cp] += dances_per_second * attack_counts[ability][cp] - else: - attacks_per_second[ability] += dances_per_second * attack_counts[ability] - - if not sane: - out_of_resouces = True - break - dance_count = dance - - #elif finisher == 'finality:eviscerate': - + extra_evis = 0 + extra_builders = 0 + #Not enough dances, generate some more + if self.dance_budget<0: + cps_required = abs(self.dance_budget) * 20 + extra_evis += cps_required/self.avg_evis_cps + self.energy_budget += self.net_evis_cost + #just subtract the cps because we'll fix those next + self.cp_budget -= cps_required + self.dance_budget = 0 + #If we have too many dances just spend them now + elif self.dance_budget > 0: + #quick convergence loop + loop_counter = 0 + while dance_count > 0.0001: + if loop_counter > 100: + raise ConvergenceErrorException(_('Dance fixup failed to converge.')) + self.energy_budget += dance_count * net_energy + self.cp_budget += dance_count * net_cps + self.dance_budget += ((3. * spent_cps* dance_count)/60) - dance_count + #merge attack counts into attacks_per_second + self.rotation_merge(attacks_per_second, attack_counts, dance_count) + loop_counter += 1 + #if we don't have enough cps lets build some + if self.cp_budget <0: + #can add since we know cp_budget is negative + self.energy_budget += self.cp_budget * energy_per_cp + extra_builders += abs(self.cp_budget) / cp_per_builder + self.cp_budget = 0 + #TODO: Handle extra cps here - #print self.dance_budget - #print self.cp_budget - #print self.energy_budget + if self.settings.cycle.cp_builder == 'shuriken_storm': + attacks_per_second['shuriken_storm-no-dance'] = extra_builders / self.settings.duration + else: + attacks_per_second[self.settings.cycle.cp_builder] = extra_builders / self.settings.duration + attacks_per_second['eviscerate'][self.finisher_thresholds['eviscerate']] += extra_evis + #Hopefully energy budget here isn't negative, if it is we're in trouble + #Now we convert all the energy we have left into mini-cycles + #Each mini-cycle contains enough 1 dance and generators+finishers for one dance + cps_per_dance = 20 + if self.traits.finality: + finishers_per_minicycle = cps_per_dance/(0.5 * self.finisher_thresholds['finality:eviscerate'] + 0.5 * self.finisher_thresholds['eviscerate']) + else: + finishers_per_minicycle = cps_per_dance/self.finisher_thresholds['eviscerate'] + + attack_counts_mini_cycle = attack_counts + attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] + loop_counter = 0 + while self.energy_budget > 0: + if loop_counter > 20: + raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) + loop_counter += 1 + cps_to_generate = max(cps_per_dance - self.cp_budget, 0) + builders_per_minicycle = cps_to_generate / cp_per_builder + mini_cycle_energy = 5 * finishers_per_minicycle - (cps_to_generate * energy_per_cp) + #add in dance energy + mini_cycle_energy += net_energy + if cps_to_generate: + mini_cycle_count = float(self.energy_budget) / abs(mini_cycle_energy) + else: + mini_cycle_count = 1 + #build the minicycle attack_counts + if self.settings.cycle.cp_builder == 'shuriken_storm': + attack_counts_mini_cycle['shuriken_storm-no-dance'] = builders_per_minicycle + else: + attack_counts_mini_cycle[self.settings.cycle.cp_builder] = builders_per_minicycle + attack_counts_mini_cycle['eviscerate'][self.finisher_thresholds['eviscerate']] += finishers_per_minicycle + self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) + self.energy_budget += mini_cycle_energy * mini_cycle_count + self.cp_budget += net_cps - 20 + #Update energy budget with alacrity and haste procs + + #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks for ability in ('finality:nightblade', 'nightblade'): if ability in attacks_per_second: @@ -2018,12 +2035,30 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.akarris_soul: attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] + if self.traits.shadow_nova: + attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] + + if self.settings.cycle.dance_cp_builder == 'shuriken_storm': + self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) + attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] + del attacks_per_second['shuriken_storm-no-dance'] + + #Full additive assumption for now + if self.talents.master_of_subtlety: + stealth_time = 9 * attacks_per_second['shadow_dance'] + 6 * attacks_per_second['vanish'] + if self.talents.subterfuge: + stealth_time = 11 * attacks_per_second['shadow_dance'] + 9 * attacks_per_second['vanish'] + self.mos_time = float(stealth_time)/self.settings.duration + + if self.talents.nightstalker: + self.dance_finality_nb_uptime = dance_finality_nb_uptime + self.dance_nb_uptime = dance_nb_uptime return attacks_per_second, crit_rates, additional_info #Computes the net energy and combo points from a shadow dance rotation #Returns net_energy, net_cps, spent_cps, dict of attack counts - def get_dance_resources(self, use_sod=False, finisher=None): + def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): net_energy = 0 net_cps = 0 spent_cps = 0 @@ -2043,8 +2078,12 @@ def get_dance_resources(self, use_sod=False, finisher=None): dance_gcds = 3 if self.talents.subterfuge: - dance_gcds += 2 - + if vanish: + dance_gcds += 1 + else: + dance_gcds += 2 + elif vanish: + dance_gcds = 1 max_dance_energy = dance_gcds * self.energy_regen + self.max_energy @@ -2058,7 +2097,14 @@ def get_dance_resources(self, use_sod=False, finisher=None): #fill remaining gcds with shadowstrikes cp_builder = self.settings.cycle.dance_cp_builder cp_builder_cost = self.get_spell_cost(cp_builder, cost_mod=cost_mod) - attack_counts[cp_builder] = min(dance_gcds, math.floor((net_energy+max_dance_energy)/cp_builder_cost)) + builder_count = min(dance_gcds, math.floor((net_energy+max_dance_energy)/cp_builder_cost)) + if vanish is True: + attack_counts[cp_builder] = min(builder_count, self.settings.cycle.max_vanish_builders) + attack_counts['vanish'] = 1 + else: + attack_counts[cp_builder] = min(builder_count, self.settings.cycle.max_dance_builders) + attack_counts['shadow_dance'] = 1 + net_energy -= attack_counts[cp_builder] * cp_builder_cost if cp_builder == 'shadowstrike': net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation) + self.shadow_blades_uptime @@ -2088,32 +2134,13 @@ def timeline_overlap(self, timeline_a, timeline_b, match_delta): return match_list, no_match_a, [x for x in timeline_b if x not in match_list] - #Attempts to sanitize the budget - #returns added implied evis, implied builder and if santization possible - def sanitize_budgets(self, attacks_per_second): - implied_builders = 0 - implied_evis = 0 - sane = True - #if we don't have enough dances build some cps and use some finishers - if self.dance_budget<0: - cps_required = abs(self.dance_budget) * 20 - implied_evis += cps_required/self.avg_evis_cps - self.energy_budget += self.net_evis_cost - #just subtract the cps because we'll fix those next - self.cp_budget -= cps_required - - #if we don't have enough cps lets build some - if self.cp_budget <0: - #can add since we know cp_budget is negative - self.energy_budget += self.cp_budget * self.energy_per_cp - implied_builders += abs(self.cp_budget) / self.cp_per_builder - self.cp_budget = 0 - - #if we don't have enough energy back off implied builders and evis - if self.energy_budget < 0: - deficit = abs(self.energy_budget) - builder_backoff = deficit / (self.cp_budget * self.energy_per_cp) - implied_builders -= builder_backoff - implied_evis -= builder_backoff/self.avg_evis_cps - sane = False - return implied_builders, implied_evis, sane + #Takes in the full attacks per second dict and a raw attack counts dict + #adds attack countes into the rotation at global scope + def rotation_merge (self, attacks_per_second, attack_counts, count): + rotations_per_second = float(count)/self.settings.duration + for ability in attack_counts: + if ability in self.finisher_damage_sources: + for cp in xrange(7): + attacks_per_second[ability][cp] += rotations_per_second * attack_counts[ability][cp] + else: + attacks_per_second[ability] += rotations_per_second * attack_counts[ability] diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index e48e541..b21d9b3 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -81,23 +81,21 @@ class SubtletyCycle(Cycle): def __init__(self, cp_builder='backstab', dance_cp_builder='shadowstrike', positional_uptime=1.0, symbols_policy='just', eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, dfa_cps = 5, - dance_finishers_allowed=[], vanish_ability='shadowstrike'): + dance_finishers_allowed=[], symbols_during_vanish=True, max_vanish_builders=3, max_dance_builders=4): self.cp_builder = cp_builder #Allowed values: fok, backstab, gloomblade self.dance_cp_builder = dance_cp_builder #Allowed values: shuriken_storm, shadowstrike self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) #'just' - Only use SoD when needed to refresh - self.vanish_ability = vanish_ability #Allowed values: 'shadowstrike', 'shuriken_storm', 'symbols_of_death' - #Finisher thresholds for each finisher, Allowed Values: 0, 1, 2, 3, 4, 5, 6 self.eviscerate_cps = eviscerate_cps self.finality_eviscerate_cps = finality_eviscerate_cps self.nightblade_cps = nightblade_cps self.finality_nightblade_cps = finality_nightblade_cps self.death_from_above_cps = dfa_cps + self.max_dance_builders = max_dance_builders #0-4 with subter, 0-3 without + self.max_vanish_builders = max_vanish_builders #0-3 with subter, 0,1 without - #List of following keys: 'eviscerate', 'nightblade', 'finality:eviscerate', 'finality:nightblade' - #Priority of finisher usage during dance + #List of following keys: 'eviscerate', 'nightblade', 'finality:eviscerate', 'finality:nightblade, death_from_above' #Keys not included will not be used during dance - self.dance_finishers_allowed= dance_finishers_allowed - + self.dance_finishers_allowed= dance_finishers_allowed \ No newline at end of file diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 6816b06..325aa17 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -27,9 +27,11 @@ class RogueDamageCalculator(DamageCalculator): 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', - 'backstab', 'eviscerate', 'finality:eviscerate', 'gloomblade', + 'backstab', 'eviscerate', 'finality:eviscerate', 'gloomblade', 'goremaws_bite', 'nightblade', 'finality:nightblade', 'shadowstrike', - 'shadow_blade', 'shuriken_storm', 'shuriken_toss'] + 'shadow_blades', 'shuriken_storm', 'shuriken_toss', + 'nightblade_ticks', 'finality:nightblade_ticks', + 'soul_rip', 'shadow_nova'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', @@ -39,7 +41,8 @@ class RogueDamageCalculator(DamageCalculator): 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] #All damage sources the scale with mastery (assn or sub) mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', - 'eviscerate', 'nightblade'] + 'eviscerate', 'finality:eviscerate', 'nightblade_ticks', + 'finality:nightblade_ticks'] #All damage sources that deal damage with both hands dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', 'goremaws_bite', 'shadow_blades'] @@ -47,7 +50,8 @@ class RogueDamageCalculator(DamageCalculator): finisher_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'envenom', 'rupture_ticks', 'between_the_eyes', 'run_through', 'eviscerate', 'finality:eviscerate', - 'nightblade', 'finality:nightblade'] + 'nightblade', 'finality:nightblade', + 'nightblade_ticks', 'finality:nightblade_ticks'] assassination_mastery_conversion = .035 combat_mastery_conversion = .022 @@ -111,7 +115,7 @@ class RogueDamageCalculator(DamageCalculator): #subtlety 'goremaws_bite': 60., 'shadow_dance': 60., - 'shadow_blades': 120., + 'shadow_blades': 180., } def __setattr__(self, name, value): @@ -421,6 +425,12 @@ def shuriken_storm_damage(self, ap): def shuriken_toss_damage(self, ap): return 1.2 * ap + def soul_rip_damage(self, ap): + return 1.5 * ap + + def shadow_nova_damage(self, ap): + return 1.5 * ap + def get_formula(self, name): formulas = { #general @@ -463,12 +473,14 @@ def get_formula(self, name): 'mh_goremaws_bite': self.mh_goremaws_bite_damage, 'oh_goremaws_bite': self.oh_goremaws_bite_damage, 'nightblade_ticks': self.nightblade_tick_damage, - 'finality_nightblade_ticks': self.finality_nightblade_tick_damage, + 'finality:nightblade_ticks': self.finality_nightblade_tick_damage, 'shadowstrike': self.shadowstrike_damage, 'mh_shadow_blades': self.mh_shadow_blades_damage, 'oh_shadow_blades': self.oh_shadow_blades_damage, 'shuriken_storm': self.shuriken_storm_damage, 'shuriken_toss': self.shuriken_toss_damage, + 'soul_rip': self.soul_rip_damage, + 'shadow_nova': self.shadow_nova_damage, } return formulas[name] From 1bdad174c26dfbb8d43d194fe26309c812ab4050 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Tue, 16 Aug 2016 15:37:33 -0500 Subject: [PATCH 044/265] - more outlaw profile fixes - rename combatcycle() yo outlawcycle() --- scripts/combat_import.py | 2 +- scripts/outlaw.py | 43 +++++++++----------- shadowcraft/calcs/rogue/Aldriana/__init__.py | 6 ++- shadowcraft/calcs/rogue/Aldriana/settings.py | 4 +- shadowcraft/core/jsoninput.py | 2 +- test_ui/testing_ui.py | 2 +- 6 files changed, 30 insertions(+), 29 deletions(-) diff --git a/scripts/combat_import.py b/scripts/combat_import.py index fdf8dc8..faf27a6 100755 --- a/scripts/combat_import.py +++ b/scripts/combat_import.py @@ -94,7 +94,7 @@ # Set up settings. if character_data.get_mh_type() == 'dagger': print "\nALERT: Dagger found. Playing combat with a dagger should be a last resort, and is not recommended. \n\n" -test_cycle = settings.CombatCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) +test_cycle = settings.OulawCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 01dde5a..9f3cc7a 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -15,9 +15,7 @@ from shadowcraft.core import i18n -import time - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. +# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. test_language = 'local' i18n.set_language(test_language) @@ -28,18 +26,18 @@ test_spec = 'outlaw' # Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'flask_wod_agi', - 'food_wod_versatility' - ) +test_buffs = buffs.Buffs('short_term_haste_buff', + 'flask_wod_agi', + 'food_wod_versatility') -# Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand -test_mh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') +# Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand +test_mh = stats.Weapon(812.0, 2.6, 'sword', 'mark_of_the_shattered_hand') +test_oh = stats.Weapon(812.0, 2.6, 'sword', 'mark_of_the_shattered_hand') # Set up procs. -#test_procs = procs.ProcsList(('assurance_of_consequence', 588), ('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', 'archmages_incandescence') #trinkets, other things (legendary procs) +#test_procs = procs.ProcsList(('assurance_of_consequence', 588), +#('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', +#'archmages_incandescence') #trinkets, other things (legendary procs) test_procs = procs.ProcsList() # Set up gear buffs. @@ -61,7 +59,7 @@ test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. -test_cycle = settings.CombatCycle(blade_flurry=False, dfa_during_ar=True) +test_cycle = settings.OulawCycle(blade_flurry=False, dfa_during_ar=True) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) @@ -73,14 +71,13 @@ total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -#ep_values = calculator.get_ep(baseline_dps=total_dps) +ep_values = calculator.get_ep(baseline_dps=total_dps) #tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) -#mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) +#mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = +#calculator.get_weapon_ep(dps=True, enchants=True) -# Compute weapon type modifier. #talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() - def max_length(dict_list): max_len = 0 for i in dict_list: @@ -90,22 +87,22 @@ def max_length(dict_list): return max_len -def pretty_print(dict_list, total_sum = 1., show_percent=False): +def pretty_print(dict_list, total_sum=1., show_percent=False): max_len = max_length(dict_list) for i in dict_list: dict_values = i.items() dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' + #print value[0] + ':' + ' ' * (max_len - len(value[0])), + #str(value[1]) + if show_percent and ("{0:.2f}".format(float(value[1]) / total_dps)) != '0.00': + print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' (' + str("{0:.2f}".format(100 * float(value[1]) / total_sum)) + '%)' else: print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) print '-' * (max_len + 15) -dicts_for_pretty_print = [ - #ep_values, +dicts_for_pretty_print = [ep_values, #tier_ep_values, #talent_ranks, #trinkets_ep_value, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2038560..f3142f7 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1214,7 +1214,7 @@ def outlaw_dps_estimate(self): def outlaw_dps_breakdown(self): if not self.spec == 'outlaw': - raise InputNotModeledException(_('You must specify a combat cycle to match your outlaw spec.')) + raise InputNotModeledException(_('You must specify a outlaw cycle to match your outlaw spec.')) self.spec_convergence_stats = ['haste', 'mastery'] @@ -1226,6 +1226,8 @@ def outlaw_dps_breakdown(self): #outlaw specific constants self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly + + self.max_energy = 100. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): self.max_energy += 30 @@ -1233,6 +1235,8 @@ def outlaw_dps_breakdown(self): self.max_energy += 20 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) + + self.ar_duration = 15 # recurance relation of 0.16*x until convergence # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 0a85e2d..294c712 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -67,8 +67,8 @@ def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5): assert min_envenom_size_execute in self.allowed_values self.min_envenom_size_execute = min_envenom_size_execute -class CombatCycle(Cycle): - _cycle_type = 'combat' +class OulawCycle(Cycle): + _cycle_type = 'outlaw' def __init__(self, ksp_immediately=True, blade_flurry=False, dfa_during_ar=False): self.blade_flurry = bool(blade_flurry) diff --git a/shadowcraft/core/jsoninput.py b/shadowcraft/core/jsoninput.py index b506c17..2c042d9 100644 --- a/shadowcraft/core/jsoninput.py +++ b/shadowcraft/core/jsoninput.py @@ -28,7 +28,7 @@ def from_json(json_string, character_class='rogue'): elif settings_type == 'combat': # CombatCycle(self, use_rupture=True, use_revealing_strike='sometimes', ksp_immediately=False) c = s.get('cycle', {}) - cycle = settings.CombatCycle(c.get('use_rupture', True), c.get('use_revealing_strike', 'sometimes'), c.get('ksp_immediately', False)) + cycle = settings.OulawCycle(c.get('use_rupture', True), c.get('use_revealing_strike', 'sometimes'), c.get('ksp_immediately', False)) elif settings_type == 'subtlety': # SubletySycle(raid_crits_per_second, clip_recuperate=False) c = s['cycle'] diff --git a/test_ui/testing_ui.py b/test_ui/testing_ui.py index 4698b1e..88e8cd7 100644 --- a/test_ui/testing_ui.py +++ b/test_ui/testing_ui.py @@ -567,7 +567,7 @@ def get_cycle(self): if cur_cycle == "Assassination": cycle = settings.AssassinationCycle() elif cur_cycle == "Combat": - cycle = settings.CombatCycle() + cycle = settings.OulawCycle() elif cur_cycle == "Subtlety": cycle = settings.SubtletyCycle() return cycle From c719f9090e156b34cd441656c98643bba5c26a93 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Tue, 16 Aug 2016 15:55:21 -0500 Subject: [PATCH 045/265] - fix outlawcycle rename --- scripts/combat_import.py | 2 +- scripts/outlaw.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- shadowcraft/calcs/rogue/Aldriana/settings.py | 2 +- shadowcraft/core/jsoninput.py | 2 +- test_ui/testing_ui.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/combat_import.py b/scripts/combat_import.py index faf27a6..7d32248 100755 --- a/scripts/combat_import.py +++ b/scripts/combat_import.py @@ -94,7 +94,7 @@ # Set up settings. if character_data.get_mh_type() == 'dagger': print "\nALERT: Dagger found. Playing combat with a dagger should be a last resort, and is not recommended. \n\n" -test_cycle = settings.OulawCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) +test_cycle = settings.OutlawCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 9f3cc7a..53da60a 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -59,7 +59,7 @@ test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. -test_cycle = settings.OulawCycle(blade_flurry=False, dfa_during_ar=True) +test_cycle = settings.OutlawCycle(blade_flurry=False, dfa_during_ar=True) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f3142f7..c6f9075 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1227,7 +1227,7 @@ def outlaw_dps_breakdown(self): #outlaw specific constants self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly - + # calculate max energy (this should probably be a function in the rogue module and overridden in spec modules) self.max_energy = 100. if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): self.max_energy += 30 @@ -1241,7 +1241,7 @@ def outlaw_dps_breakdown(self): # recurance relation of 0.16*x until convergence # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex if self.stats.gear_buffs.rogue_t18_2pc: - self.ar_duration = 17.8571 + self.ar_duration = 17.8571 # it not clear what this means if self.stats.gear_buffs.rogue_t17_2pc: self.extra_cp_chance += 0.2 diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 294c712..dfa62e2 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -67,7 +67,7 @@ def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5): assert min_envenom_size_execute in self.allowed_values self.min_envenom_size_execute = min_envenom_size_execute -class OulawCycle(Cycle): +class OutlawCycle(Cycle): _cycle_type = 'outlaw' def __init__(self, ksp_immediately=True, blade_flurry=False, dfa_during_ar=False): diff --git a/shadowcraft/core/jsoninput.py b/shadowcraft/core/jsoninput.py index 2c042d9..5ad33cc 100644 --- a/shadowcraft/core/jsoninput.py +++ b/shadowcraft/core/jsoninput.py @@ -28,7 +28,7 @@ def from_json(json_string, character_class='rogue'): elif settings_type == 'combat': # CombatCycle(self, use_rupture=True, use_revealing_strike='sometimes', ksp_immediately=False) c = s.get('cycle', {}) - cycle = settings.OulawCycle(c.get('use_rupture', True), c.get('use_revealing_strike', 'sometimes'), c.get('ksp_immediately', False)) + cycle = settings.OutlawCycle(c.get('use_rupture', True), c.get('use_revealing_strike', 'sometimes'), c.get('ksp_immediately', False)) elif settings_type == 'subtlety': # SubletySycle(raid_crits_per_second, clip_recuperate=False) c = s['cycle'] diff --git a/test_ui/testing_ui.py b/test_ui/testing_ui.py index 88e8cd7..92988b9 100644 --- a/test_ui/testing_ui.py +++ b/test_ui/testing_ui.py @@ -567,7 +567,7 @@ def get_cycle(self): if cur_cycle == "Assassination": cycle = settings.AssassinationCycle() elif cur_cycle == "Combat": - cycle = settings.OulawCycle() + cycle = settings.OutlawCycle() elif cur_cycle == "Subtlety": cycle = settings.SubtletyCycle() return cycle From 6fe643afc0f3862891c8387072c242de89532e6c Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 16 Aug 2016 17:59:37 -0400 Subject: [PATCH 046/265] Decent match on AMR Sim Interval Check --- scripts/subtlety.py | 20 +++++++++++--------- shadowcraft/calcs/rogue/Aldriana/__init__.py | 19 +++++++++++++++---- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 06a0325..11f0a65 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -33,8 +33,8 @@ ) # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand -test_mh = stats.Weapon(812.0, 1.8, 'dagger', None) -test_oh = stats.Weapon(812.0, 1.8, 'dagger', None) +test_mh = stats.Weapon(4821.0, 1.8, 'dagger', None) +test_oh = stats.Weapon(4821.0, 1.8, 'dagger', None) # Set up procs. - trinkets, other things (legendary procs) #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), @@ -46,12 +46,12 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=7655, + agi=20909, stam=19566, - crit=2665, - haste=1594, - mastery=3350, - versatility=6522,) + crit=5248, + haste=5150, + mastery=5115, + versatility=844,) # Initialize talents.. test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) @@ -60,8 +60,10 @@ test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') # Set up settings. -test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', dance_finishers_allowed=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate']) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, +test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', + #dance_finishers_allowed=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate'], + ) +test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, adv_params="", is_demon=True, num_boss_adds=0) # Build a DPS object. diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 7c04388..f867cda 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1832,6 +1832,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): dance_nb_uptime = 0.0 #Timeline match of ruptures, fill in rest with either finality:eviscerate for finisher in ['finality:nightblade', 'nightblade', 'finality:eviscerate', 'eviscerate', None]: + attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] dance_count = 0 if finisher in self.settings.cycle.dance_finishers_allowed: attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] @@ -1865,6 +1866,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) + del attacks_per_second[None] #Add in ruptures not previously covered nightblade_count = len(nightblade_timeline) attacks_per_second['nightblade'][self.finisher_thresholds['nightblade']] += float(nightblade_count)/self.settings.duration @@ -1945,6 +1947,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): extra_evis = 0 extra_builders = 0 + #Not enough dances, generate some more if self.dance_budget<0: cps_required = abs(self.dance_budget) * 20 @@ -1957,12 +1960,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): elif self.dance_budget > 0: #quick convergence loop loop_counter = 0 - while dance_count > 0.0001: - if loop_counter > 100: + while self.dance_budget > 0.0001: + if loop_counter > 20: raise ConvergenceErrorException(_('Dance fixup failed to converge.')) + dance_count = abs(self.dance_budget) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += ((3. * spent_cps* dance_count)/60) - dance_count + self.dance_budget += ((3. * spent_cps* dance_count)/60.) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) loop_counter += 1 @@ -1973,7 +1977,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget += self.cp_budget * energy_per_cp extra_builders += abs(self.cp_budget) / cp_per_builder self.cp_budget = 0 - #TODO: Handle extra cps here if self.settings.cycle.cp_builder == 'shuriken_storm': attacks_per_second['shuriken_storm-no-dance'] = extra_builders / self.settings.duration @@ -2054,6 +2057,14 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.dance_finality_nb_uptime = dance_finality_nb_uptime self.dance_nb_uptime = dance_nb_uptime + for ability in attacks_per_second.keys(): + if not attacks_per_second[ability]: + del attacks_per_second[ability] + elif isinstance(attacks_per_second[ability], list) and not any(attacks_per_second[ability]): + del attacks_per_second[ability] + + print attacks_per_second + return attacks_per_second, crit_rates, additional_info #Computes the net energy and combo points from a shadow dance rotation From ddb78fff2791e088f6d7f66b91917451e3392f12 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Tue, 16 Aug 2016 17:36:40 -0500 Subject: [PATCH 047/265] - fix blade flurry damage replication list - fix base run_through damage calc --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 51 +++++++++++--------- shadowcraft/calcs/rogue/__init__.py | 45 +++++++++++------ 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c6f9075..1c55b6f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -324,13 +324,13 @@ def get_mh_procs_per_second(self, proc, attacks_per_second, crit_rates): if 'mh_autoattack_hits' in attacks_per_second: triggers_per_second += attacks_per_second['mh_autoattack_hits'] if proc.procs_off_strikes(): - for ability in ('mutilate', 'dispatch', 'backstab', 'revealing_strike', 'sinister_strike', 'ambush', 'hemorrhage', 'mh_killing_spree', 'shuriken_toss'): + for ability in ('mutilate', 'dispatch', 'backstab', 'pistol_shot', 'saber_slash', 'ambush', 'hemorrhage', 'mh_killing_spree', 'shuriken_toss'): if ability in attacks_per_second: if proc.procs_off_crit_only(): triggers_per_second += attacks_per_second[ability] * crit_rates[ability] else: triggers_per_second += attacks_per_second[ability] - for ability in ('envenom', 'eviscerate'): + for ability in ('envenom', 'eviscerate', 'run_through'): if ability in attacks_per_second: if proc.procs_off_crit_only(): triggers_per_second += sum(attacks_per_second[ability]) * crit_rates[ability] @@ -1284,23 +1284,20 @@ def outlaw_dps_breakdown(self): #average it together damage_breakdown = self.average_damage_breakdowns(phases, denom = total_duration) - evis_multiplier = 1 + run_through_multiplier = 1 if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - evis_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 + run_through_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 bf_mod = .35 - bf_max_targets = 4 - if self.level == 100: - bf_max_targets = 999 #this is the "no more target cap" limit, screw extra if statements if self.settings.cycle.blade_flurry: damage_breakdown['blade_flurry'] = 0 for key in damage_breakdown: - if key in self.melee_attacks: + if key in self.blade_flurry_damage_sources: if key == "run_through": - damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) * evis_multiplier + damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * self.settings.num_boss_adds * run_through_multiplier else: - damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * min(self.settings.num_boss_adds, bf_max_targets) + damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * self.settings.num_boss_adds soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): @@ -1326,6 +1323,7 @@ def outlaw_dps_breakdown(self): #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) #multistrike_multiplier = min(.6, multistrike_multiplier) + for ability in damage_breakdown: damage_breakdown[ability] *=maalus_mod if 'sr_' not in ability: @@ -1335,8 +1333,8 @@ def outlaw_dps_breakdown(self): if ability == 'Fel Lash': continue #damage_breakdown[ability] *= (1 + multistrike_multiplier) - if ability in ('eviscerate', 'sr_eviscerate'): - damage_breakdown[ability] *= evis_multiplier + if ability == 'run_through': + damage_breakdown[ability] *= run_through_multiplier #add maalus burst if maalus_mod > 1.0: @@ -1535,36 +1533,41 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second['pistol_shot'] = 1. / pistol_shot_interval extra_finishers_per_second = attacks_per_second['pistol_shot'] / 5. + #Scaling CPGs free_gcd = 1./gcd_size free_gcd -= 1./snd_duration + (attacks_per_second['saber_slash_base'] + attacks_per_second['pistol_shot'] + extra_finishers_per_second) if self.talents.marked_for_death: free_gcd -= (1. / marked_for_death_cd) + #2 seconds is an approximation of GCD loss while in air if self.talents.death_from_above and not ar: free_gcd -= dfa_interval * (2. / gcd_size) #wowhead claims a 2s GCD - energy_available_for_evis = energy_regen - energy_spent_on_snd - energy_for_dfa - total_evis_per_second = energy_available_for_evis / total_eviscerate_cost - evisc_actions_per_second = (total_evis_per_second * ss_per_finisher + total_evis_per_second) + energy_available_for_run_through = energy_regen - energy_spent_on_snd - energy_for_dfa + total_run_through_per_second = energy_available_for_run_through / total_eviscerate_cost + run_through_actions_per_second = (total_run_through_per_second * ss_per_finisher + total_run_through_per_second) if self.stats.gear_buffs.rogue_t17_4pc: #http://www.wolframalpha.com/input/?i=sum+of+.2%5Ex+from+x%3D1+to+inf #This increases the frequency of Eviscerates by 25% for every Evisc cast - evisc_actions_per_second += total_evis_per_second * .25 - attacks_per_second['saber_slash'] = total_evis_per_second * ss_per_finisher + run_through_actions_per_second += total_run_through_per_second * .25 + attacks_per_second['saber_slash'] = total_run_through_per_second * ss_per_finisher + # If GCD capped - if evisc_actions_per_second > free_gcd: - gcd_cap_mod = evisc_actions_per_second / free_gcd + if run_through_actions_per_second > free_gcd: + gcd_cap_mod = run_through_actions_per_second / free_gcd attacks_per_second['saber_slash'] = attacks_per_second['saber_slash'] / gcd_cap_mod - total_evis_per_second = total_evis_per_second / gcd_cap_mod + total_run_through_per_second = total_run_through_per_second / gcd_cap_mod + # Reintroduce flat gcds attacks_per_second['saber_slash'] += attacks_per_second['saber_slash_base'] attacks_per_second['main_gauche'] += (attacks_per_second['saber_slash'] + attacks_per_second['pistol_shot'] + - total_evis_per_second) * main_gauche_proc_rate + total_run_through_per_second) * main_gauche_proc_rate if self.talents.death_from_above and not ar: attacks_per_second['main_gauche'] += attacks_per_second['death_from_above_strike'][5] * main_gauche_proc_rate #attacks_per_second['eviscerate'] = [finisher_chance * total_evis_per_second for finisher_chance in finisher_size_breakdown] - attacks_per_second['run_through'] = [0,0,0,0,0,total_evis_per_second] + attacks_per_second['run_through'] = [0,0,0,0,0,total_run_through_per_second] + for opener, cps in [('ambush', 2), ('garrote', 1)]: if opener in attacks_per_second: extra_finishers_per_second += attacks_per_second[opener] * cps / 5 @@ -1580,6 +1583,8 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): time_at_level = 4 / attacks_per_second['saber_slash'] cycle_duration = 3 * time_at_level + 15 + + #if self.level == 100: # self.bandits_guile_multiplier = 1 + (0*time_at_level + .1*time_at_level + .2*time_at_level + .5 * 15) / cycle_duration #else: @@ -1587,6 +1592,8 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): # self.bandits_guile_multiplier = 1 + .1 * avg_stacks #hack bg multiplier until it can be removed later + + self.bandits_guile_multiplier = 1.0 if not ar: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 13fd623..3b539de 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -7,11 +7,11 @@ from shadowcraft.core import exceptions class RogueDamageCalculator(DamageCalculator): - # Functions of general use to rogue damage calculation go here. If a + # Functions of general use to rogue damage calculation go here. If a # calculation will reasonably used for multiple classes, it should go in - # calcs.DamageCalculator instead. If its a specific intermediate + # calcs.DamageCalculator instead. If its a specific intermediate # value useful to only your calculations, when you extend this you should - # put the calculations in your object. But there are things - like + # put the calculations in your object. But there are things - like # backstab damage as a function of AP - that (almost) any rogue damage # calculator will need to know, so things like that go here. @@ -48,6 +48,10 @@ class RogueDamageCalculator(DamageCalculator): 'envenom', 'rupture_ticks', 'between_the_eyes', 'run_through', 'eviscerate', 'finality:eviscerate', 'nightblade', 'finality:nightblade'] + #All damage source that are replicated by Blade Flurry + blade_flurry_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', + 'ambush', 'between_the_eyes', 'blunderbuss', 'ghostly_strike', 'greed', 'killing_spree', + 'main_gauche','pistol_shot', 'run_through', 'saber_slash'] assassination_mastery_conversion = .035 outlaw_mastery_conversion = .022 @@ -120,8 +124,10 @@ def __setattr__(self, name, value): self._set_constants_for_level() def _set_constants_for_level(self): - # this calls _set_constants_for_level() in calcs/__init__.py because this supercedes it, this is how inheretence in python works - # any modules that expand on rogue/__init__.py and use this should do the same + # this calls _set_constants_for_level() in calcs/__init__.py because + # this supercedes it, this is how inheretence in python works + # any modules that expand on rogue/__init__.py and use this should do + # the same super(RogueDamageCalculator, self)._set_constants_for_level() self.normalize_ep_stat = self.get_adv_param('norm_ep_stat', self.settings.default_ep_stat, ignore_bounds=True) self.damage_modifier_cache = 1 @@ -160,7 +166,7 @@ def get_ability_dps(self, ap, ability, attacks_per_second, crit_rate, modifier, base_damage = self.get_formula(a)(ap) * modifier dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second, crit_modifier) else: - for i in xrange(1, cps+1): + for i in xrange(1, cps + 1): for a in ability_list: base_damage = self.get_formula(a)(ap, i) * modifier dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second[i], crit_modifier) @@ -178,13 +184,15 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da base_modifier = self.get_base_modifier(current_stats) armor_modifier = self.armor_mitigation_multiplier() - # this removes keys with empty values, prevents errors from: attacks_per_second['sinister_strike'] = None + # this removes keys with empty values, prevents errors from: + # attacks_per_second['sinister_strike'] = None for key in attacks_per_second.keys(): if not attacks_per_second[key]: del attacks_per_second[key] if 'mh_autoattacks' in attacks_per_second: - # Assumes mh and oh attacks are both active at the same time. As they should always be. + # Assumes mh and oh attacks are both active at the same time. As + # they should always be. # Friends don't let friends raid without gear. mh_base_damage = self.mh_damage(average_ap) * armor_modifier * base_modifier mh_hit_rate = self.dw_mh_hit_chance - crit_rates['mh_autoattacks'] @@ -223,8 +231,10 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da modifier *= potent_poisons_mod #override for "weird" abilities - #death from above strike is actually an envenom with 1.5 modifier - #manually add in base modifier because DfA strike is in physical sources + #death from above strike is actually an envenom with 1.5 + #modifier + #manually add in base modifier because DfA strike is in + #physical sources if ability == 'death_from_above_strike': modifier = base_modifier * 1.5 * potent_poisons_mod ability = 'envenom' @@ -248,9 +258,10 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da #death from above strike is actually an evis with 1.5 modifier if ability == 'death_from_above_strike': modifier *= 1.5 - ability = 'eviscerate' + ability = 'run_through' #between the eyes has additional crit damage - #Damage modifier 3 explained here: http://beta.askmrrobot.com/wow/simulator/docs/critdamage + #Damage modifier 3 explained here: + #http://beta.askmrrobot.com/wow/simulator/docs/critdamage if ability == 'between_the_eyes': crit_mod = self.crit_damage_modifiers(3) damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) @@ -271,14 +282,16 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability in self.physical_damage_sources: modifier *= armor_modifier - #assume for now that all non-physical damage sources are shadow damage + #assume for now that all non-physical damage sources are shadow + #damage else: modifier *= shadow_fangs_mod if ability in self.mastery_scaling_damage_sources: modifier *= executioner_mod #override for "weird" abilities - #death from above strike is actually an evis with 1.5 modifier and dfa pulse needs mastery + #death from above strike is actually an evis with 1.5 modifier + #and dfa pulse needs mastery if ability == 'death_from_above_strike': modifier *= 1.5 * executioner_mod ability = 'eviscerate' @@ -367,7 +380,7 @@ def mh_killing_spree_damage(self, ap): return 2.108 * self.get_weapon_damage('mh', ap) def oh_killing_spree_damage(self, ap): - return 2.018* self.oh_penalty() * self.get_weapon_damage('oh', ap) + return 2.018 * self.oh_penalty() * self.get_weapon_damage('oh', ap) def main_gauche_damage(self, ap): return 2.1 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.1 * self.traits.fortunes_strike)) @@ -376,7 +389,7 @@ def pistol_shot_damage(self, ap): return 1.5 * ap def run_through_damage(self, ap, cp): - return 1.2 * ap * cp * (1 * (0.08 * self.traits.fates_thirst)) + return 1.5 * ap * cp * (1 + (0.08 * self.traits.fates_thirst)) def saber_slash_damage(self, ap): return 2.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) From c67494f80c7f8bd7fde0f352fa345c919f6d1624 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Tue, 16 Aug 2016 21:05:20 -0500 Subject: [PATCH 048/265] -talent ranking -trait ranking -disabled get_talents_ranking override -extracted energy cap calcuation -added support for vigor -fixed combat potency multiplier -remove relentless strike calcs --- scripts/outlaw.py | 9 +-- shadowcraft/calcs/rogue/Aldriana/__init__.py | 73 +++++++++++--------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 53da60a..9fe42f4 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -76,8 +76,9 @@ #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = #calculator.get_weapon_ep(dps=True, enchants=True) -#talent_ranks = calculator.get_talents_ranking() -#trait_ranks = calculator.get_trait_ranking() +talent_ranks = calculator.get_talents_ranking() +trait_ranks = calculator.get_trait_ranking() + def max_length(dict_list): max_len = 0 for i in dict_list: @@ -104,10 +105,10 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): dicts_for_pretty_print = [ep_values, #tier_ep_values, - #talent_ranks, + talent_ranks, #trinkets_ep_value, dps_breakdown, - #trait_ranks + trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1c55b6f..ac8e83d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -73,20 +73,21 @@ def are_close_enough(self, old_dist, new_dist, precision=PRECISION_REQUIRED): # setups that we are really modeling. ########################################################################### - def get_talents_ranking(self, list=None): - if list is None: - list = [ - 'nightstalker', - 'subterfuge', - 'shadow_focus', - #'shuriken_toss', - 'marked_for_death', - 'anticipation', - 'lemon_zest', - 'death_from_above', - 'shadow_reflection', - ] - return super(AldrianasRogueDamageCalculator, self).get_talents_ranking(list) + #i don't know why this is overridden, but I disabled it to fix talent ranking + #def get_talents_ranking(self, list=None): + # if list is None: + # list = [ + # 'nightstalker', + # 'subterfuge', + # 'shadow_focus', + # #'shuriken_toss', + # 'marked_for_death', + # 'anticipation', + # 'lemon_zest', + # 'death_from_above', + # 'shadow_reflection', + # ] + # return super(AldrianasRogueDamageCalculator, self).get_talents_ranking(list) def get_oh_weapon_modifier(self, setups=None): if setups is None: @@ -1227,21 +1228,11 @@ def outlaw_dps_breakdown(self): #outlaw specific constants self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly - # calculate max energy (this should probably be a function in the rogue module and overridden in spec modules) - self.max_energy = 100. - if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): - self.max_energy += 30 - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - self.max_energy += 20 - if self.race.expansive_mind: - self.max_energy = round(self.max_energy * 1.05, 0) - - self.ar_duration = 15 # recurance relation of 0.16*x until convergence # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex if self.stats.gear_buffs.rogue_t18_2pc: - self.ar_duration = 17.8571 # it not clear what this means + self.ar_duration = 17.8571 # it not clear what this means, magic number? if self.stats.gear_buffs.rogue_t17_2pc: self.extra_cp_chance += 0.2 @@ -1382,8 +1373,11 @@ def outlaw_cpg_per_finisher(self, current_cp, ability_count): def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): attacks_per_second = {} additional_info = {} + # base_energy_regen needs to be reset here due to determine_stats method - self.base_energy_regen = 12. + self.base_energy_regen = 12. # should extract this to a function + if self.talents.vigor: + self.base_energy_regen *= 0.1 if self.settings.cycle.blade_flurry: self.base_energy_regen *= .8 @@ -1396,8 +1390,8 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) - combat_potency_regen_per_oh = 15 * .2 * self.stats.oh.speed / 1.4 # the new "normalized" formula - combat_potency_from_mg = 15 * .2 + combat_potency_regen_per_oh = 15 * .3 * self.stats.oh.speed / 1.4 # the new "normalized" formula + combat_potency_from_mg = 15 * .3 FINISHER_SIZE = 5 ruthlessness_value = 1 # 1CP gained at 20% chance per CP spent (5CP spent means 1 is always added) @@ -1420,15 +1414,17 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG cost_reducer = main_gauche_proc_rate * combat_potency_from_mg + #these should probably extracted to functions. run_through_energy_cost = self.get_spell_cost('run_through', cost_mod=cost_modifier) run_through_energy_cost -= cost_reducer - run_through_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp + #run_through_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp pistol_shot_energy_cost = self.get_spell_cost('pistol_shot', cost_mod=cost_modifier) pistol_shot_energy_cost -= cost_reducer saber_slash_energy_cost = self.get_spell_cost('saber_slash', cost_mod=cost_modifier) saber_slash_energy_cost -= cost_reducer death_from_above_energy_cost = self.get_spell_cost('death_from_above', cost_mod=cost_modifier) death_from_above_energy_cost -= cost_reducer * (2 + self.settings.num_boss_adds) + #need to reduce the cost of DFA by the strike's MG proc ... #but also the MG procs from the AOE which hits the main target plus each additional add (strike + aoe) if self.stats.gear_buffs.rogue_t16_2pc_bonus(): @@ -1481,7 +1477,7 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): energy_regen += self.bonus_energy_regen + combat_potency_regen + bonus_energy_from_openers #Rough idea to factor in a full energy bar if not ar: - energy_regen += self.max_energy / self.settings.duration + energy_regen += self.get_max_energy() / self.settings.duration #Base actions @@ -1497,7 +1493,8 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): ss_per_snd = ss_per_finisher snd_size = FINISHER_SIZE snd_base_cost = 25 - snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp + #snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp + snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost snd_duration = 6 + 6 * (snd_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) energy_spent_on_snd = snd_cost / snd_duration @@ -1519,7 +1516,7 @@ def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): #dfa_gap probably should be handled more accurately especially in the non-anticipation case dfa_interval = 1./(dfa_cd) energy_for_dfa = energy_cost_for_cpgs + death_from_above_energy_cost - energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp + #energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp energy_for_dfa *= dfa_interval attacks_per_second['death_from_above'] = dfa_interval @@ -1649,6 +1646,18 @@ def outlaw_attack_counts_ar(self, current_stats, crit_rates=None): def outlaw_attack_counts_none(self, current_stats, crit_rates=None): return self.outlaw_attack_counts(current_stats, crit_rates=crit_rates) + def get_max_energy(self): + self.max_energy = 100 + if self.talents.vigor: + self.max_energy += 50 + if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): + self.max_energy += 30 + if self.stats.gear_buffs.rogue_t18_4pc_lfr: + self.max_energy += 20 + if self.race.expansive_mind: + self.max_energy = round(self.max_energy * 1.05, 0) + return self.max_energy + ########################################################################### # Subtlety DPS functions ########################################################################### From 22e514b0a780292c6be59a6cd08df752088c13e5 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 13:10:18 -0400 Subject: [PATCH 049/265] Update Sub Model -Add nightstalker and MoS damage modifiers -Fix evis overcounting on convergence loop -Weaponmaster support --- .gitignore | 2 + scripts/subtlety.py | 14 ++-- shadowcraft/calcs/rogue/Aldriana/__init__.py | 84 +++++++++++++++----- 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 49aac70..ecabc29 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ item_db.db /.project /.pydevproject +*.sublime-workspace +*.sublime-project diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 11f0a65..4f823ae 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -32,7 +32,7 @@ 'food_wod_versatility' ) -# Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand +# Set up weapons. test_mh = stats.Weapon(4821.0, 1.8, 'dagger', None) test_oh = stats.Weapon(4821.0, 1.8, 'dagger', None) @@ -48,20 +48,22 @@ test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, agi=20909, stam=19566, - crit=5248, + crit=4402, haste=5150, - mastery=5115, - versatility=844,) + #haste=0, + mastery=5999, + versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2000020', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, '110000000000000000') # Set up settings. test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', #dance_finishers_allowed=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate'], + #dance_finishers_allowed =['nightblade'] ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, adv_params="", is_demon=True, num_boss_adds=0) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f867cda..874ef95 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -525,10 +525,10 @@ def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 for finisher in self.finisher_damage_sources: #Don't double count DfA - if finisher != 'death_from_above_pulse': + if finisher in attacks_per_second and finisher != 'death_from_above_pulse': for cp in xrange(7): stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] - stack_time = stacks_per_second/20 + stack_time = 20/stacks_per_second if stack_time > self.settings.duration: max_stacks = self.settings.duration * stacks_per_second return max_stacks/2 @@ -1663,16 +1663,11 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): #Legion TODO: #Talents: - #T1-MoS - #T1-Weaponmaster - #T2:NS #T3:Ancitipcation - #T6:Alacrity #Artifact: # 'flickering_shadows', # 'second_shuriken', - # 'shadow_nova', # 'legionblade' #Items: @@ -1683,7 +1678,9 @@ def combat_attack_counts_none(self, current_stats, crit_rates=None): #Rotation details: #Combo Point loss - #SoD auto crit + #Finality evis stealth modifier handlings + #Shuriken storm dances details + #weaponmaster bonus cp gen def subtlety_dps_estimate(self): return sum(self.subtlety_dps_breakdown().values()) @@ -1711,7 +1708,9 @@ def subtlety_dps_breakdown(self): } self.set_constants() - #self.spec_needs_converge = True + + #symbols of death + self.damage_modifier_cache = 1.2 stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -1739,19 +1738,41 @@ def subtlety_dps_breakdown(self): maalus_val = maalus.value['damage_mod']/10000. maalus_mod = 1 + (15.0/120* maalus_val) #super hackish - vanish_damage_mod = 1.0 - if self.stats.gear_buffs.rogue_t18_2pc: - vanish_damage_buff_uptime = 10/self.get_spell_cd('vanish') - vanish_damage_mod += vanish_damage_buff_uptime * 0.3 + #nightstalker + if self.talents.nightstalker: + ns_full_multiplier = 0.12 + for key in damage_breakdown: + if key == 'shadowstrike': + damage_breakdown[key] *= ns_full_multiplier + elif key == 'shuriken_storm': + damage_breakdown[key] *= 1 + (0.12 * self.stealth_shuriken_uptime) + elif key == 'finality_nightblade_ticks': + damage_breakdown[key] *= 1 + (0.12 * self.dance_finality_nb_uptime) + elif key == 'nightblade_ticks': + damage_breakdown[key] *= 1 + (0.12 * self.dance_nb_uptime) + + #master of subtlety + if self.talents.master_of_subtlety: + mos_full_multiplier = 1.1 + mos_uptime_multipler = 1. + (0.1 * self.mos_time) + + for key in damage_breakdown: + if key == 'shadowstrike': + damage_breakdown[key] *= mos_full_multiplier + elif key == 'shuriken_storm': + damage_breakdown[key] *= 1 + (0.1 * self.stealth_shuriken_uptime) + else: + damage_breakdown[key] *= mos_uptime_multipler for key in damage_breakdown: - damage_breakdown[key] *= vanish_damage_mod damage_breakdown[key] *= maalus_mod - if key in ('ambush', 'garrote', 'sr_ambush'): + if key == 'shadowstrike': damage_breakdown[key] *=trinket_multiplier if "sr_" not in key: damage_breakdown[key] *= soul_cap_mod damage_breakdown[key] *= infallible_trinket_mod + if key == 'shuriken_storm': + damage_breakdown[key] *= (1 + self.stealth_shuriken_uptime * 3) #if "Mirror" not in key: # damage_breakdown[key] *= mos_multiplier @@ -1961,7 +1982,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #quick convergence loop loop_counter = 0 while self.dance_budget > 0.0001: - if loop_counter > 20: + if loop_counter > 100: raise ConvergenceErrorException(_('Dance fixup failed to converge.')) dance_count = abs(self.dance_budget) self.energy_budget += dance_count * net_energy @@ -1996,6 +2017,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attack_counts_mini_cycle = attack_counts attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] loop_counter = 0 + + alacrity_stacks = 0 while self.energy_budget > 0: if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) @@ -2009,16 +2032,24 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): mini_cycle_count = float(self.energy_budget) / abs(mini_cycle_energy) else: mini_cycle_count = 1 + mini_cycle_count = 1 #build the minicycle attack_counts if self.settings.cycle.cp_builder == 'shuriken_storm': attack_counts_mini_cycle['shuriken_storm-no-dance'] = builders_per_minicycle else: attack_counts_mini_cycle[self.settings.cycle.cp_builder] = builders_per_minicycle - attack_counts_mini_cycle['eviscerate'][self.finisher_thresholds['eviscerate']] += finishers_per_minicycle + attack_counts_mini_cycle['eviscerate'][self.finisher_thresholds['eviscerate']] = finishers_per_minicycle self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) self.energy_budget += mini_cycle_energy * mini_cycle_count - self.cp_budget += net_cps - 20 + self.cp_budget += net_cps - 20 + cps_to_generate #Update energy budget with alacrity and haste procs + if self.talents.alacrity: + old_alacrity_regen = self.energy_regen * (1 + (alacrity_stacks *0.01)) + new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) + new_alacrity_regen = self.energy_regen * (1 + (new_alacrity_stacks *0.01)) + self.energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration + alacrity_stacks = new_alacrity_stacks + #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks @@ -2048,9 +2079,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Full additive assumption for now if self.talents.master_of_subtlety: - stealth_time = 9 * attacks_per_second['shadow_dance'] + 6 * attacks_per_second['vanish'] + stealth_time = 9. * attacks_per_second['shadow_dance'] + 6 * attacks_per_second['vanish'] if self.talents.subterfuge: - stealth_time = 11 * attacks_per_second['shadow_dance'] + 9 * attacks_per_second['vanish'] + stealth_time = 11. * attacks_per_second['shadow_dance'] + 9 * attacks_per_second['vanish'] self.mos_time = float(stealth_time)/self.settings.duration if self.talents.nightstalker: @@ -2065,6 +2096,19 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): print attacks_per_second + #add SoD auto crits + sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] + crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes + #print crit_rates + + if self.talents.weaponmaster: + for ability in attacks_per_second: + if isinstance(attacks_per_second[ability], list): + for cp in xrange(7): + attacks_per_second[ability][cp] *= 1.06 + else: + attacks_per_second[ability] *=1.06 + return attacks_per_second, crit_rates, additional_info #Computes the net energy and combo points from a shadow dance rotation From 3620eb57d7bfe74265cc713445b071fd1606171a Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 13:13:43 -0400 Subject: [PATCH 050/265] Removed APS Print --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 874ef95..92f2381 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2094,7 +2094,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): elif isinstance(attacks_per_second[ability], list) and not any(attacks_per_second[ability]): del attacks_per_second[ability] - print attacks_per_second + #print attacks_per_second #add SoD auto crits sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] From eb7d49cd2054e3e5a2c77107e0ddb4e7cc7d0816 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 13:56:58 -0400 Subject: [PATCH 051/265] Fix Shuriken Storm Rotations --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 92f2381..dd2b568 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2019,7 +2019,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): loop_counter = 0 alacrity_stacks = 0 - while self.energy_budget > 0: + while self.energy_budget > 0.1: if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 @@ -2029,10 +2029,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #add in dance energy mini_cycle_energy += net_energy if cps_to_generate: - mini_cycle_count = float(self.energy_budget) / abs(mini_cycle_energy) + mini_cycle_count = 0.9*float(self.energy_budget) / abs(mini_cycle_energy) else: mini_cycle_count = 1 - mini_cycle_count = 1 + #mini_cycle_count = 1 #build the minicycle attack_counts if self.settings.cycle.cp_builder == 'shuriken_storm': attack_counts_mini_cycle['shuriken_storm-no-dance'] = builders_per_minicycle @@ -2072,7 +2072,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.shadow_nova: attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] - if self.settings.cycle.dance_cp_builder == 'shuriken_storm': + self.stealth_shuriken_uptime = 0. + if self.settings.cycle.dance_cp_builder == 'shuriken_storm' and self.settings.cycle.cp_builder == 'shuriken_storm': self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] del attacks_per_second['shuriken_storm-no-dance'] @@ -2097,8 +2098,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #print attacks_per_second #add SoD auto crits - sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] - crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes + if 'shadowstrike' in attacks_per_second: + sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] + crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes #print crit_rates if self.talents.weaponmaster: From 635c352d7398631e3c07d89085a26b4fde0bcd6c Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 14:08:02 -0400 Subject: [PATCH 052/265] Add Feast Buff --- shadowcraft/objects/buffs.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index 78a47cb..1a91930 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -55,8 +55,10 @@ class Buffs(object): 'food_legion_damage_1', # Spiced Rib Roast 'food_legion_damage_2', # Drogbar-Style Salmon 'food_legion_damage_3', # Fishbrul Special + 'food_legion_feast_150', + 'food_legion_feast_200', ]) - + buffs_debuffs = frozenset([ 'short_term_haste_buff', # Heroism/Blood Lust, Time Warp #'stat_multiplier_buff', # Mark of the Wild, Blessing of Kings, Legacy of the Emperor @@ -94,8 +96,10 @@ def buff_agi(self, race=False): bonus_agi += 200 * self.flask_wod_agi_200 bonus_agi += 250 * self.flask_wod_agi bonus_agi += 1300 * self.flask_legion_agi + bonus_agi += 150 * self.food_legion_feast_150 * [1, 2][race] + bonus_agi += 200 * self.food_legion_feast_200 * [1, 2][race] return bonus_agi - + def buff_haste(self, race=False): bonus_haste = 0 bonus_haste += 125 * self.food_wod_haste_125 * [1, 2][race] @@ -105,7 +109,7 @@ def buff_haste(self, race=False): bonus_haste += 300 * self.food_legion_haste_300 * [1, 2][race] bonus_haste += 375 * self.food_legion_haste_375 * [1, 2][race] return bonus_haste - + def buff_crit(self, race=False): bonus_crit = 0 bonus_crit += 125 * self.food_wod_crit_125 * [1, 2][race] @@ -115,7 +119,7 @@ def buff_crit(self, race=False): bonus_crit += 300 * self.food_legion_crit_300 * [1, 2][race] bonus_crit += 375 * self.food_legion_crit_375 * [1, 2][race] return bonus_crit - + def buff_mast(self, race=False): bonus_mastery = 0 bonus_mastery += 125 * self.food_wod_mastery_125 * [1, 2][race] @@ -125,7 +129,7 @@ def buff_mast(self, race=False): bonus_mastery += 300 * self.food_legion_mastery_300 * [1, 2][race] bonus_mastery += 375 * self.food_legion_mastery_375 * [1, 2][race] return bonus_mastery - + def buff_versatility(self, race=False): bonus_versatility = 0 bonus_versatility += 125 * self.food_wod_versatility_125 * [1, 2][race] From 029220a91215ae88b3629747c4302ebe4c1a7128 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Wed, 17 Aug 2016 15:02:38 -0500 Subject: [PATCH 053/265] - quick, check it in before it breaks again! - asn script and model first pass - dispatched dispatch --- scripts/assassination.py | 96 ++++-------- shadowcraft/calcs/rogue/Aldriana/__init__.py | 153 +++++++++---------- shadowcraft/objects/proc_data.py | 2 +- 3 files changed, 102 insertions(+), 149 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index fad5f0f..fd3346a 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -11,37 +11,25 @@ from shadowcraft.objects import stats from shadowcraft.objects import procs from shadowcraft.objects import talents -from shadowcraft.objects import glyphs +from shadowcraft.objects import artifact from shadowcraft.core import i18n -import time - # Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. test_language = 'local' i18n.set_language(test_language) -start = time.time() - # Set up level/class/race test_level = 100 test_race = race.Race('none') test_class = 'rogue' +test_spec = 'assassination' # Set up buffs. test_buffs = buffs.Buffs( - #'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'haste_buff', - 'multistrike_buff', - 'versatility_buff', - 'attack_power_buff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - 'agi_flask_mop', - 'food_mop_agi' + 'short_term_haste_buff', + 'flask_wod_agi', + 'food_wod_versatility' ) # Set up weapons. @@ -49,66 +37,46 @@ test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_frostwolf') # Set up procs. -test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), - 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') +#test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), +# 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') +test_procs = procs.ProcsList() # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization') # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=3650, - stam=2426, - crit=1539, - haste=0, - mastery=1615, - readiness=0, - versatility=122, - multistrike=1034,) + agi=7655, + stam=19566, + crit=2665, + haste=1594, + mastery=3350, + versatility=6522,) # Initialize talents.. -test_talents = talents.Talents('3322122', test_class, test_level) +test_talents = talents.Talents('0200000', test_spec, test_class, level=test_level) -# Set up glyphs. -glyph_list = ['disappearance', 'sprint', 'vendetta'] #just to have something -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) +#initialize artifact traits.. +test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. test_cycle = settings.AssassinationCycle(min_envenom_size_non_execute=4, min_envenom_size_execute=5) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=False, - use_opener='always', opener_name='mutilate') +test_settings = settings.Settings(test_cycle, response_time=.5, duration=360) # Build a DPS object. -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) +calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in dps_breakdown.items()) -calculator.init_assassination() -non_execute_breakdown = calculator.assassination_dps_breakdown_non_execute() -non_execute_total = sum(entry[1] for entry in non_execute_breakdown.items()) -calculator.init_assassination() -execute_breakdown = calculator.assassination_dps_breakdown_execute() -execute_total = sum(entry[1] for entry in execute_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) +#ep_values = calculator.get_ep(baseline_dps=total_dps) #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) -#mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = calculator.get_weapon_ep(dps=True, enchants=True) - -trinkets_list = [ - #5.4 - 'assurance_of_consequence', - 'haromms_talisman', - 'sigil_of_rampage', - 'ticking_ebon_detonator', - 'thoks_tail_tip', - 'discipline_of_xuen', - 'fury_of_xuen', -] -# trinkets_ep_value = calculator.get_upgrades_ep_fast(trinkets_list) + #talent_ranks = calculator.get_talents_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -134,26 +102,14 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, + #ep_values, #tier_ep_values, - #mh_enchants_and_dps_ep_values, - #oh_enchants_and_dps_ep_values, - #trinkets_ep_value, - #glyph_values, #talent_ranks, + #trinkets_ep_value, + dps_breakdown, + #trait_ranks ] pretty_print(dicts_for_pretty_print) pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) print ' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.") -print '' -print "Request time: %s sec" % (time.time() - start) - - -print 'non-execute breakdown: ' -pretty_print([non_execute_breakdown], total_sum=non_execute_total, show_percent=True) -print ' ' * (max_length([non_execute_breakdown]) + 1), non_execute_total, _("total damage per second.") - -print 'execute breakdown: ' -pretty_print([execute_breakdown], total_sum=execute_total, show_percent=True) -print ' ' * (max_length([execute_breakdown]) + 1), execute_total, _("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ac8e83d..fef6c96 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -39,9 +39,8 @@ def get_dps(self): def get_dps_breakdown(self): if self.spec == 'assassination': - self.init_assassination() - return 0 - #return self.assassination_dps_breakdown() + self.init_assassination() # why the special init? + return self.assassination_dps_breakdown() elif self.spec == 'outlaw': return self.outlaw_dps_breakdown() elif self.spec == 'subtlety': @@ -645,8 +644,8 @@ def determine_stats(self, attack_counts_function): 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], 'haste': self.base_stats['haste'] * self.stat_multipliers['haste'], 'mastery': self.base_stats['mastery'] * self.stat_multipliers['mastery'], - 'readiness': self.base_stats['readiness'] * self.stat_multipliers['readiness'], - 'multistrike': self.base_stats['multistrike'] * self.stat_multipliers['multistrike'], + #'readiness': self.base_stats['readiness'] * self.stat_multipliers['readiness'], + #'multistrike': self.base_stats['multistrike'] * self.stat_multipliers['multistrike'], 'versatility': self.base_stats['versatility'] * self.stat_multipliers['versatility'], } for k in static_proc_stats: @@ -761,9 +760,7 @@ def init_assassination(self): if self.stats.mh.type != 'dagger' or self.stats.oh.type != 'dagger': raise InputNotModeledException(_('Assassination modeling requires daggers in both hands')) - #set readiness coefficient - self.readiness_spec_conversion = self.assassination_readiness_conversion - self.spec_convergence_stats = ['haste', 'crit', 'readiness'] + self.spec_convergence_stats = ['haste', 'crit'] # Assassasins's Resolve self.damage_modifier_cache = 1.17 @@ -774,11 +771,7 @@ def init_assassination(self): if getattr(self.stats.procs, 'fury_of_xuen'): getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.55 - #spec specific glyph behaviour - if self.glyphs.disappearance: - self.ability_cds['vanish'] = 60 - else: - self.ability_cds['vanish'] = 120 + self.ability_cds['vanish'] = 120 self.base_energy_regen = 10 self.max_energy = 120. @@ -787,8 +780,6 @@ def init_assassination(self): if self.talents.lemon_zest: self.base_energy_regen *= 1 + .05 * (1 + min(self.settings.num_boss_adds, 2)) self.max_energy += 15 - if self.glyphs.energy: - self.max_energy += 20 if self.stats.gear_buffs.rogue_t18_4pc_lfr: self.max_energy += 20 if self.race.expansive_mind: @@ -802,9 +793,9 @@ def init_assassination(self): spec_needs_converge = True self.envenom_crit_modifier = 0.0 - self.vendetta_duration = 20 + 10 * self.glyphs.vendetta + self.vendetta_duration = 20 #+ 10 * self.glyphs.vendetta self.vendetta_uptime = self.vendetta_duration / (self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay) - self.vendetta_multiplier = .3 - .05 * self.glyphs.vendetta + self.vendetta_multiplier = .3 #- .05 * self.glyphs.vendetta self.vendetta_mult = 1 + self.vendetta_multiplier * self.vendetta_uptime def assassination_dps_estimate(self): @@ -840,8 +831,8 @@ def assassination_dps_breakdown(self): def update_assassination_breakdown_with_modifiers(self, damage_breakdown, current_stats): #calculate multistrike here for Sub and Assassination, really cheap to calculate #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! - multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()) - multistrike_multiplier = min(.6, multistrike_multiplier) + #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()) + #multistrike_multiplier = min(.6, multistrike_multiplier) soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): @@ -869,7 +860,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren #Fel Lash doesn't MS if key == 'Fel Lash': continue - damage_breakdown[key] *= 1 + multistrike_multiplier + damage_breakdown[key] *= 1 #mirror of the blademaster doesn't get any player buffs if key == 'Mirror of the Blademaster': continue @@ -911,15 +902,15 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi final_cp = min(current_cp, 5) current_sizes[final_cp] += 1 return final_cp, blindside_proc, ability_count, current_sizes - avg_count = {'mutilate':0, 'dispatch':0} + avg_count = {'mutilate':0, 'hemorrhage':0} avg_breakdown = [0,0,0,0,0,0] new_count = copy(ability_count) if blindside_proc or execute: - new_count['dispatch'] += 1 + new_count['hemorrhage'] += 1 # these hemos are dispatch hacks - n_chance = 1 - crit_rates['dispatch'] - c_chance = crit_rates['dispatch'] + n_chance = 1 - crit_rates['hemorrhage'] + c_chance = crit_rates['hemorrhage'] if self.stats.gear_buffs.rogue_t18_4pc: n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+4, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) @@ -957,12 +948,13 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_rates=None): + self.settings.time_in_execute_range = 1 #hack old settings attacks_per_second = {} additional_info = {} #can't rely on a cache, due to the Cold Blood perk crit_rates = self.get_crit_rates(current_stats) for key in crit_rates: - if key in ('mutilate', 'dispatch'): + if key in ('mutilate', 'garrote', 'hemorrhage'): crit_rates[key]+=self.envenom_crit_modifier crit_rates[key] = min(crit_rates[key], 1.0) @@ -977,7 +969,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if self.stats.gear_buffs.rogue_t18_4pc_lfr: energy_regen *= 1.05 energy_regen += self.bonus_energy_regen - if cpg == 'dispatch': + if cpg == 'hemorrhage': # dispatch hack #this is for the effects of pooling going into execute phase energy_regen += (self.max_energy - 10) / (self.settings.duration * self.settings.time_in_execute_range) @@ -994,13 +986,13 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier self.attack_speed_increase = attack_speed_multiplier - seal_fate_proc_rate = crit_rates['dispatch'] + seal_fate_proc_rate = crit_rates['hemorrhage'] # just hacked in to get rid of dispatch errors if cpg == 'mutilate': seal_fate_proc_rate *= blindside_proc_rate seal_fate_proc_rate += 1 - (1 - crit_rates['mutilate']) ** 2 mutilate_cps = 3 - (1 - crit_rates['mutilate']) ** 2 # 1 - (1 - crit_rates['mutilate']) ** 2 is the Seal Fate CP - dispatch_cps = 1 + crit_rates['dispatch'] + dispatch_cps = 1 + crit_rates['hemorrhage'] #hack get rid of dispatch errors if self.stats.gear_buffs.rogue_t18_4pc: dispatch_cps += 2 @@ -1018,7 +1010,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra avg_cpgs_per_finisher = cp_needed_per_finisher / avg_cp_per_cpg else: - ability_count = {'mutilate':0, 'dispatch':0} + ability_count = {'mutilate':0, 'hemorrhage':0} #hack to get rid of dispatch errors finisher_size_breakdown = [0,0,0,0,0,0] #This is incredibly verbose, but functional. It exhaustively calculates the potential finisher size outcomes using recursion. @@ -1029,7 +1021,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra execute = False base_cp = 0 min_finisher_size = self.settings.cycle.min_envenom_size_non_execute - if cpg == 'dispatch': + if cpg == 'hemorrhage': #hack to get rid of dispatch errors min_finisher_size = self.settings.cycle.min_envenom_size_execute execute = True if self.stats.gear_buffs.rogue_t17_4pc: @@ -1050,7 +1042,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra avg_cpgs_per_finisher = avg_count[cpg] avg_cp_per_cpg = avg_finisher_size / avg_cpgs_per_finisher - cpg_energy_cost = self.get_spell_stats(cpg, cost_mod=ability_cost_modifier)[0] + cpg_energy_cost = self.get_spell_cost(cpg, cost_mod=ability_cost_modifier) cpg_cost_reduction = 0 if self.stats.gear_buffs.rogue_t17_2pc: cpg_cost_reduction = 8 * crit_rates['mutilate'] #4 per hand, double crit is 2 procs @@ -1058,38 +1050,41 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra cpg_cost_reduction = 6 * seal_fate_proc_rate cpg_energy_cost -= cpg_cost_reduction - current_opener_name = self.settings.opener_name - if self.settings.opener_name == 'cpg': - current_opener_name = cpg - - cp_generated = 0 - if current_opener_name == 'envenom': - opener_net_cost = self.get_spell_stats('envenom', cost_mod=ability_cost_modifier*(1-self.get_shadow_focus_multiplier()))[0] - energy_regen += opener_net_cost * self.total_openers_per_second - elif current_opener_name == cpg: - opener_net_cost = self.get_spell_stats(current_opener_name, cost_mod=(1-self.get_shadow_focus_multiplier()))[0] - opener_net_cost += cpg_cost_reduction - cp_generated = avg_cp_per_cpg - energy_regen += opener_net_cost * self.total_openers_per_second - else: - opener_net_cost = self.get_spell_stats(current_opener_name, cost_mod=self.get_shadow_focus_multiplier())[0] - attacks_per_second[current_opener_name] = self.total_openers_per_second - if current_opener_name == 'mutilate': - attacks_per_second['dispatch'] += self.total_openers_per_second * blindside_proc_rate - if current_opener_name in ('mutilate', 'dispatch', 'cpg'): - cp_generated = mutilate_cps + dispatch_cps * blindside_proc_rate - elif current_opener_name == 'ambush': - cp_generated = 2 + crit_rates['ambush'] - energy_regen -= opener_net_cost * self.total_openers_per_second - for i in xrange(1,6): - attacks_per_second['envenom'][i] = self.total_openers_per_second * cp_generated / i * avg_size_breakdown[i] + ##hardcode an opner for now + #current_opener_name = 'garrote' + ##if self.settings.opener_name == 'cpg': + #current_opener_name = cpg + + #cp_generated = 0 + #if current_opener_name == 'envenom': + # opener_net_cost = self.get_spell_cost('envenom', cost_mod=ability_cost_modifier*(1-self.get_shadow_focus_multiplier())) + # energy_regen += opener_net_cost * self.total_openers_per_second + #elif current_opener_name == cpg: + # opener_net_cost = self.get_spell_cost(current_opener_name, cost_mod=(1-self.get_shadow_focus_multiplier())) + # opener_net_cost += cpg_cost_reduction + # cp_generated = avg_cp_per_cpg + # #energy_regen += opener_net_cost * self.total_openers_per_second + # energy_regen += opener_net_cost * 1 #hack opener per sec calc + #else: + # opener_net_cost = self.get_spell_cost(current_opener_name, cost_mod=self.get_shadow_focus_multiplier()) + # #attacks_per_second[current_opener_name] = self.total_openers_per_second + # attacks_per_second[current_opener_name] = self.total_openers_per_second + # if current_opener_name == 'mutilate': + # attacks_per_second['hemorrhage'] += self.total_openers_per_second * blindside_proc_rate # more dispatch hacks + # if current_opener_name in ('mutilate', 'hemorrhage', 'cpg'): #another dispatch hack + # cp_generated = mutilate_cps + dispatch_cps * blindside_proc_rate + # elif current_opener_name == 'ambush': + # cp_generated = 2 + crit_rates['ambush'] + # energy_regen -= opener_net_cost * self.total_openers_per_second + #for i in xrange(1,6): + # attacks_per_second['envenom'][i] = self.total_openers_per_second * cp_generated / i * avg_size_breakdown[i] attacks_per_second['venomous_wounds'] = .5 energy_regen_with_rupture = energy_regen + .5 * vw_energy_return avg_cycle_length = 4. * (1 + avg_finisher_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_stats('rupture', cost_mod=ability_cost_modifier)[0] + energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('rupture', cost_mod=ability_cost_modifier) energy_for_rupture -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp attacks_per_second['rupture'] = 1. / avg_cycle_length @@ -1112,7 +1107,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa - envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_stats('envenom', cost_mod=ability_cost_modifier)[0] + envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('envenom', cost_mod=ability_cost_modifier) envenom_energy_cost -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost @@ -1125,8 +1120,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attacks_per_second[cpg] += cpgs_per_second else: attacks_per_second[cpg] = cpgs_per_second - if cpg == 'mutilate': - attacks_per_second['dispatch'] += cpgs_per_second * blindside_proc_rate + #if cpg == 'mutilate': + # attacks_per_second['hemorrhage'] += cpgs_per_second * blindside_proc_rate #another dispatch hack attacks_per_second['rupture_ticks'] = [0,0,0,0,0,.5] if self.talents.anticipation: @@ -1157,22 +1152,23 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) - if self.talents.shadow_reflection: - sr_uptime = 8. / self.get_spell_cd('shadow_reflection') - for ability in ('rupture_ticks', 'dispatch'): - if type(attacks_per_second[ability]) in (tuple, list): - attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] - for i in xrange(1, 6): - attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] - else: - attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] - envenom_per_sr = 1.5 * sum(attacks_per_second['envenom']) - attacks_per_second['sr_envenom'] = [finisher_chance * envenom_per_sr / self.get_spell_cd('shadow_reflection') for finisher_chance in avg_size_breakdown] - crit_rates['sr_envenom'] = 1./envenom_per_sr + (1-envenom_per_sr)/envenom_per_sr * crit_rates['envenom'] - if 'mutilate' in attacks_per_second: - attacks_per_second['sr_mh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] - attacks_per_second['sr_oh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] - + #if self.talents.shadow_reflection: + # sr_uptime = 8. / self.get_spell_cd('shadow_reflection') + # for ability in ('rupture_ticks', 'dispatch'): + # if type(attacks_per_second[ability]) in (tuple, list): + # attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] + # for i in xrange(1, 6): + # attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] + # else: + # attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] + # envenom_per_sr = 1.5 * sum(attacks_per_second['envenom']) + # attacks_per_second['sr_envenom'] = [finisher_chance * envenom_per_sr / self.get_spell_cd('shadow_reflection') for finisher_chance in avg_size_breakdown] + # crit_rates['sr_envenom'] = 1./envenom_per_sr + (1-envenom_per_sr)/envenom_per_sr * crit_rates['envenom'] + # if 'mutilate' in attacks_per_second: + # attacks_per_second['sr_mh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] + # attacks_per_second['sr_oh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] + + self.swing_reset_spacing = None #hack resets for now white_swing_downtime = 0 if self.swing_reset_spacing is not None: white_swing_downtime += .5 / self.swing_reset_spacing @@ -1188,7 +1184,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - + + self.settings.dmg_poison = 'dp' #hack in deadly poison for now self.get_poison_counts(attacks_per_second, current_stats) if self.level == 100: @@ -1204,7 +1201,7 @@ def assassination_attack_counts_non_execute(self, current_stats, crit_rates=None return self.assassination_attack_counts(current_stats, 'mutilate', self.settings.cycle.min_envenom_size_non_execute, crit_rates=crit_rates) def assassination_attack_counts_execute(self, current_stats, crit_rates=None): - return self.assassination_attack_counts(current_stats, 'dispatch', self.settings.cycle.min_envenom_size_execute, crit_rates=crit_rates) + return self.assassination_attack_counts(current_stats, 'hemorrhage', self.settings.cycle.min_envenom_size_execute, crit_rates=crit_rates) ########################################################################### # Outlaw DPS functions diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 2a15b35..a92fd01 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -611,7 +611,7 @@ #6.0 'mark_of_the_frostwolf': { 'stat': 'stats', - 'value': {'multistrike':500}, + 'value': {'crit':500}, 'duration': 6, 'max_stacks': 2, 'proc_name': 'Mark of the Frostwolf', From 12883ee24db431f3dd588d17f0d6a8b6492f4f58 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 17:50:31 -0400 Subject: [PATCH 054/265] Update Human Racial --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index dd2b568..cac2ad4 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -186,15 +186,12 @@ def set_constants(self): 'str': 1., 'agi': self.stats.gear_buffs.gear_specialization_multiplier(), 'ap': 1, - 'crit': 1., - 'haste': 1., - 'mastery': 1., - 'versatility': 1., + 'crit': 1. + (0.02 * self.race.human_spirit), + 'haste': 1. + (0.02 * self.race.human_spirit), + 'mastery': 1. + (0.02 * self.race.human_spirit), + 'versatility': 1. + (0.02 * self.race.human_spirit), } - if self.race.human_spirit: - self.base_stats['versatility'] += self.race.versatility_bonuses[self.level] - for boost in self.race.get_racial_stat_boosts(): if boost['stat'] in self.base_stats: self.base_stats[boost['stat']] += boost['value'] * boost['duration'] * 1.0 / (boost['cooldown'] + self.settings.response_time) @@ -1693,7 +1690,6 @@ def subtlety_dps_breakdown(self): if self.settings.cycle.cp_builder == 'gloomblade' and not self.talents.gloomblade: raise InputNotModeledException(_('Gloomblade must be talented to be priamry cp builder')) - self.max_spend_cps = 5 if self.talents.deeper_strategem: self.max_spend_cps += 1 @@ -2020,6 +2016,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = 0 while self.energy_budget > 0.1: + #print self.energy_budget if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 From a2af91d601b5c0b14227ac90d95a46c9c15745ef Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 17 Aug 2016 22:35:34 -0400 Subject: [PATCH 055/265] Trait Updates -Fix demon_kiss trait to demons_kiss -Traits will now properly rank for binary traits when selected --- shadowcraft/calcs/__init__.py | 7 +++++- shadowcraft/calcs/rogue/__init__.py | 4 ++-- shadowcraft/objects/artifact.py | 6 ++++- shadowcraft/objects/artifact_data.py | 35 ++++++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 8314d8a..c1af7b6 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -558,9 +558,14 @@ def get_trait_ranking(self, list=None): else: trait_list = list + single_rank = self.traits.get_single_rank_trait_list() + for trait in trait_list: base_trait_rank = getattr(self.traits, trait) - setattr(self.traits, trait, base_trait_rank+1) + if trait in single_rank and base_trait_rank: + setattr(self.traits, trait, 0) + else: + setattr(self.traits, trait, base_trait_rank+1) try: new_dps = self.get_dps() if new_dps != baseline_dps: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 325aa17..d96ac89 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -406,9 +406,9 @@ def oh_goremaws_bite_damage(self, ap): #Nightblade doesn't actually scale with cps but passing cps for simplicity def nightblade_tick_damage(self, ap, cp): - return 1.2 * ap * (1 + (0.05 * self.traits.demon_kiss)) + return 1.2 * ap * (1 + (0.05 * self.traits.demons_kiss)) def finality_nightblade_tick_damage(self, ap, cp): - return 1.4 * ap * (1 + (0.05 * self.traits.demon_kiss)) + return 1.4 * ap * (1 + (0.05 * self.traits.demons_kiss)) def shadowstrike_damage(self, ap): return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index c4a6fd9..076f5cc 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -7,6 +7,7 @@ class InvalidTraitException(exceptions.InvalidInputException): class Artifact(object): def __init__(self, class_spec, game_class, trait_string='', trait_dict= {}): self.allowed_traits = artifact_data.traits[(game_class, class_spec)] + self.single_rank_traits = artifact_data.single_rank[(game_class, class_spec)] if trait_string: self.initialize_traits(trait_string) @@ -38,4 +39,7 @@ def initialize_traits(self, trait_string): self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) def get_trait_list(self): - return list(self.allowed_traits) \ No newline at end of file + return list(self.allowed_traits) + + def get_single_rank_trait_list(self): + return list(self.single_rank_traits) \ No newline at end of file diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 410845b..1bc714f 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -59,4 +59,39 @@ 'shadow_nova', 'legionblade' ), +} + +#Single Rank Traits for each spec +#Used for binary trait ranking +single_rank = { + ('rogue', 'assassination'): ( + 'kingsbane', + 'assassins_blades', + 'urge_to_kill', + 'surge_of_toxins', + 'shadow_swiftness', + 'bag_of_tricks', + 'from_the_shadows', + 'blood_of_the_assassinated', + ), + ('rogue', 'outlaw'): ( + 'curse_of_the_dreadblades', + 'cursed_edges', + 'hidden_blade', + 'deception', + 'greed', + 'blurred_time', + 'blademaster', + 'blunderbuss', + ), + ('rogue', 'subtlety'): ( + 'goremaws_bite', + 'shadow_fangs', + 'embrace_of_darkness', + 'flickering_shadows', + 'second_shuriken', + 'finality', + 'akarris_soul', + 'shadow_nova', + ), } \ No newline at end of file From 2bac2f9c54f4da6d5653a51b82fa56d1874d4cfc Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Thu, 18 Aug 2016 17:22:04 -0500 Subject: [PATCH 056/265] - more asn cleanup --- scripts/assassination.py | 18 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 181 +++++-------------- shadowcraft/calcs/rogue/Aldriana/settings.py | 9 +- 3 files changed, 55 insertions(+), 153 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index fd3346a..593e4ce 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -54,13 +54,13 @@ versatility=6522,) # Initialize talents.. -test_talents = talents.Talents('0200000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') # Set up settings. -test_cycle = settings.AssassinationCycle(min_envenom_size_non_execute=4, min_envenom_size_execute=5) +test_cycle = settings.AssassinationCycle() test_settings = settings.Settings(test_cycle, response_time=.5, duration=360) # Build a DPS object. @@ -71,12 +71,12 @@ total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -#ep_values = calculator.get_ep(baseline_dps=total_dps) +ep_values = calculator.get_ep(baseline_dps=total_dps) #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) -#talent_ranks = calculator.get_talents_ranking() -#trait_ranks = calculator.get_trait_ranking() +talent_ranks = calculator.get_talents_ranking() +trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -102,14 +102,14 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): print '-' * (max_len + 15) dicts_for_pretty_print = [ - #ep_values, + ep_values, #tier_ep_values, - #talent_ranks, + talent_ranks, #trinkets_ep_value, dps_breakdown, - #trait_ranks + trait_ranks ] pretty_print(dicts_for_pretty_print) -pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) +#pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) print ' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index fef6c96..9bc8efc 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -27,9 +27,8 @@ class AldrianasRogueDamageCalculator(RogueDamageCalculator): def get_dps(self): super(AldrianasRogueDamageCalculator, self).get_dps() if self.spec == 'assassination': - self.init_assassination() - return 0 - #return self.assassination_dps_estimate() + self.init_assassination() # why the special init? - aeriwen + return self.assassination_dps_estimate() elif self.spec == 'outlaw': return self.outlaw_dps_estimate() elif self.spec == 'subtlety': @@ -39,7 +38,7 @@ def get_dps(self): def get_dps_breakdown(self): if self.spec == 'assassination': - self.init_assassination() # why the special init? + self.init_assassination() # why the special init? - aeriwen return self.assassination_dps_breakdown() elif self.spec == 'outlaw': return self.outlaw_dps_breakdown() @@ -72,7 +71,7 @@ def are_close_enough(self, old_dist, new_dist, precision=PRECISION_REQUIRED): # setups that we are really modeling. ########################################################################### - #i don't know why this is overridden, but I disabled it to fix talent ranking + #i don't know why this is overridden, but I disabled it to fix talent ranking -aeriwen #def get_talents_ranking(self, list=None): # if list is None: # list = [ @@ -644,8 +643,6 @@ def determine_stats(self, attack_counts_function): 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], 'haste': self.base_stats['haste'] * self.stat_multipliers['haste'], 'mastery': self.base_stats['mastery'] * self.stat_multipliers['mastery'], - #'readiness': self.base_stats['readiness'] * self.stat_multipliers['readiness'], - #'multistrike': self.base_stats['multistrike'] * self.stat_multipliers['multistrike'], 'versatility': self.base_stats['versatility'] * self.stat_multipliers['versatility'], } for k in static_proc_stats: @@ -799,34 +796,7 @@ def init_assassination(self): self.vendetta_mult = 1 + self.vendetta_multiplier * self.vendetta_uptime def assassination_dps_estimate(self): - non_execute_dps = self.assassination_dps_estimate_non_execute() * (1 - self.settings.time_in_execute_range) - execute_dps = self.assassination_dps_estimate_execute() * self.settings.time_in_execute_range - return non_execute_dps + execute_dps - - def assassination_dps_estimate_execute(self): - return sum(self.assassination_dps_breakdown_execute().values()) - - def assassination_dps_estimate_non_execute(self): - return sum(self.assassination_dps_breakdown_non_execute().values()) - - def assassination_dps_breakdown(self): - non_execute_dps_breakdown = self.assassination_dps_breakdown_non_execute() - execute_dps_breakdown = self.assassination_dps_breakdown_execute() - - non_execute_weight = 1 - self.settings.time_in_execute_range - execute_weight = self.settings.time_in_execute_range - - dps_breakdown = {} - for source, quantity in non_execute_dps_breakdown.items(): - dps_breakdown[source] = quantity * non_execute_weight - - for source, quantity in execute_dps_breakdown.items(): - if source in dps_breakdown: - dps_breakdown[source] += quantity * execute_weight - else: - dps_breakdown[source] = quantity * execute_weight - - return dps_breakdown + return sum(self.assassination_dps_breakdown().values()) def update_assassination_breakdown_with_modifiers(self, damage_breakdown, current_stats): #calculate multistrike here for Sub and Assassination, really cheap to calculate @@ -872,42 +842,30 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren damage_breakdown[key] *= 1 + self.vendetta_multiplier if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): damage_breakdown[key] *= self.emp_envenom_percentage - if self.stats.gear_buffs.rogue_t18_2pc: - if key == 'dispatch': - damage_breakdown[key]*= 1+(0.25 * (1+(self.stats.get_mastery_from_rating(rating=current_stats['mastery'])*self.assassination_mastery_conversion))) #add maalus burst if maalus_mod > 1.0: damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) - def assassination_dps_breakdown_non_execute(self): - #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_non_execute) + def assassination_dps_breakdown(self): current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_non_execute) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) return damage_breakdown - def assassination_dps_breakdown_execute(self): - #damage_breakdown, additional_info = self.compute_damage(self.assassination_attack_counts_execute) - current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_execute) - damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) - - self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) - return damage_breakdown - def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, ability_count, size_breakdown, cp_limit=4, blindside_proc=0, execute=False): current_sizes = copy(size_breakdown) if (current_cp >= cp_limit and not blindside_proc and not execute) or current_cp >= 5: final_cp = min(current_cp, 5) current_sizes[final_cp] += 1 return final_cp, blindside_proc, ability_count, current_sizes - avg_count = {'mutilate':0, 'hemorrhage':0} + avg_count = {'mutilate':0, 'hemorrhage':0} #hemo is here as a hack for dispatch, but maybe there are times to use hemo to cap cp? avg_breakdown = [0,0,0,0,0,0] new_count = copy(ability_count) - if blindside_proc or execute: - new_count['hemorrhage'] += 1 # these hemos are dispatch hacks + if blindside_proc or execute: #leaving this in because this logic may be useful for using hemo/garrote/fok to optimize finisher cp counts? idk, this is voodoo to me -aeriwen + new_count['hemorrhage'] += 1 # these hemos are dispatch hacks - aeriwen n_chance = 1 - crit_rates['hemorrhage'] c_chance = crit_rates['hemorrhage'] @@ -948,69 +906,41 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_rates=None): - self.settings.time_in_execute_range = 1 #hack old settings + attacks_per_second = {} additional_info = {} + #can't rely on a cache, due to the Cold Blood perk crit_rates = self.get_crit_rates(current_stats) - for key in crit_rates: - if key in ('mutilate', 'garrote', 'hemorrhage'): + for key in crit_rates: #not sure that this is needed anymore -aeriwen + if key in ('mutilate', 'hemorrhage'): crit_rates[key]+=self.envenom_crit_modifier crit_rates[key] = min(crit_rates[key], 1.0) - haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - ability_cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_reduced_cost() - - energy_regen = self.base_energy_regen * haste_multiplier - if self.stats.gear_buffs.rogue_t17_4pc_lfr: - #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 - #https://twitter.com/Celestalon/status/525350819856535552 - energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - energy_regen *= 1.05 - energy_regen += self.bonus_energy_regen - if cpg == 'hemorrhage': # dispatch hack - #this is for the effects of pooling going into execute phase - energy_regen += (self.max_energy - 10) / (self.settings.duration * self.settings.time_in_execute_range) - vw_energy_return = 10 vw_energy_per_bleed_tick = vw_energy_return blindside_proc_rate = [0, .3][cpg == 'mutilate'] attacks_per_second['envenom'] = [0,0,0,0,0,0] attacks_per_second['dispatch'] = 0 + - if self.talents.marked_for_death: - energy_regen -= 10. / self.get_spell_cd('marked_for_death') # 35-25 - - attack_speed_multiplier = self.base_speed_multiplier * haste_multiplier + attack_speed_multiplier = self.base_speed_multiplier * self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod self.attack_speed_increase = attack_speed_multiplier - seal_fate_proc_rate = crit_rates['hemorrhage'] # just hacked in to get rid of dispatch errors - if cpg == 'mutilate': - seal_fate_proc_rate *= blindside_proc_rate - seal_fate_proc_rate += 1 - (1 - crit_rates['mutilate']) ** 2 - mutilate_cps = 3 - (1 - crit_rates['mutilate']) ** 2 # 1 - (1 - crit_rates['mutilate']) ** 2 is the Seal Fate CP - dispatch_cps = 1 + crit_rates['hemorrhage'] #hack get rid of dispatch errors - if self.stats.gear_buffs.rogue_t18_4pc: - dispatch_cps += 2 if self.talents.anticipation: avg_finisher_size = 5 avg_size_breakdown = [0,0,0,0,0,1.] #this is for determining the % likelyhood of sizes, not frequency of the sizes cp_needed_per_finisher = 5 - if self.stats.gear_buffs.rogue_t17_4pc: - cp_needed_per_finisher -= 1 if cpg == 'mutilate': - avg_cp_per_cpg = mutilate_cps + dispatch_cps * blindside_proc_rate - else: - avg_cp_per_cpg = dispatch_cps + avg_cp_per_cpg = mutilate_cps avg_cpgs_per_finisher = cp_needed_per_finisher / avg_cp_per_cpg else: - ability_count = {'mutilate':0, 'hemorrhage':0} #hack to get rid of dispatch errors + ability_count = {'mutilate':0, 'hemorrhage':0} #hemo is a hack to get rid of dispatch errors, i don't understand what this is doing - aeriwen finisher_size_breakdown = [0,0,0,0,0,0] #This is incredibly verbose, but functional. It exhaustively calculates the potential finisher size outcomes using recursion. @@ -1020,12 +950,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra #avg_breakdown - frequency of finisher sizes (should sum to 100% or 1) execute = False base_cp = 0 - min_finisher_size = self.settings.cycle.min_envenom_size_non_execute - if cpg == 'hemorrhage': #hack to get rid of dispatch errors - min_finisher_size = self.settings.cycle.min_envenom_size_execute - execute = True - if self.stats.gear_buffs.rogue_t17_4pc: - base_cp = 1 + min_finisher_size = self.settings.cycle.min_envenom_size + avg_finisher_size, avg_bs, avg_count, avg_size_breakdown = self.assassination_cp_distribution_for_finisher(base_cp, crit_rates, ability_count, finisher_size_breakdown, cp_limit=min_finisher_size, execute=execute) if avg_bs > 0: @@ -1042,22 +968,15 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra avg_cpgs_per_finisher = avg_count[cpg] avg_cp_per_cpg = avg_finisher_size / avg_cpgs_per_finisher - cpg_energy_cost = self.get_spell_cost(cpg, cost_mod=ability_cost_modifier) - cpg_cost_reduction = 0 - if self.stats.gear_buffs.rogue_t17_2pc: - cpg_cost_reduction = 8 * crit_rates['mutilate'] #4 per hand, double crit is 2 procs - if self.stats.gear_buffs.rogue_t16_2pc_bonus(): - cpg_cost_reduction = 6 * seal_fate_proc_rate - cpg_energy_cost -= cpg_cost_reduction + cpg_energy_cost = self.get_spell_cost(cpg) - ##hardcode an opner for now - #current_opener_name = 'garrote' - ##if self.settings.opener_name == 'cpg': + #disable opener logic for now -aeriwen + #if self.settings.opener_name == 'cpg': #current_opener_name = cpg #cp_generated = 0 #if current_opener_name == 'envenom': - # opener_net_cost = self.get_spell_cost('envenom', cost_mod=ability_cost_modifier*(1-self.get_shadow_focus_multiplier())) + # opener_net_cost = self.get_spell_cost('envenom', cost_mod=1-self.get_shadow_focus_multiplier())) # energy_regen += opener_net_cost * self.total_openers_per_second #elif current_opener_name == cpg: # opener_net_cost = self.get_spell_cost(current_opener_name, cost_mod=(1-self.get_shadow_focus_multiplier())) @@ -1070,8 +989,8 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra # #attacks_per_second[current_opener_name] = self.total_openers_per_second # attacks_per_second[current_opener_name] = self.total_openers_per_second # if current_opener_name == 'mutilate': - # attacks_per_second['hemorrhage'] += self.total_openers_per_second * blindside_proc_rate # more dispatch hacks - # if current_opener_name in ('mutilate', 'hemorrhage', 'cpg'): #another dispatch hack + # attacks_per_second['hemorrhage'] += self.total_openers_per_second * blindside_proc_rate # more hemo dispatch hacks -aeriwen + # if current_opener_name in ('mutilate', 'hemorrhage', 'cpg'): #another dispatch hack - aeriwen # cp_generated = mutilate_cps + dispatch_cps * blindside_proc_rate # elif current_opener_name == 'ambush': # cp_generated = 2 + crit_rates['ambush'] @@ -1080,12 +999,11 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra # attacks_per_second['envenom'][i] = self.total_openers_per_second * cp_generated / i * avg_size_breakdown[i] attacks_per_second['venomous_wounds'] = .5 - energy_regen_with_rupture = energy_regen + .5 * vw_energy_return + energy_regen_with_rupture = self.get_asassination_energy_regen(current_stats) + .5 * vw_energy_return avg_cycle_length = 4. * (1 + avg_finisher_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('rupture', cost_mod=ability_cost_modifier) - energy_for_rupture -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp + energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('rupture') attacks_per_second['rupture'] = 1. / avg_cycle_length energy_per_cycle = avg_cycle_length * energy_regen_with_rupture @@ -1095,8 +1013,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time dfa_cd += energy_for_rupture / (4 * energy_regen_with_rupture) dfa_interval = 1./dfa_cd - energy_for_dfa = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_stats('death_from_above', cost_mod=ability_cost_modifier)[0] - energy_for_dfa -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp + energy_for_dfa = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('death_from_above') attacks_per_second['death_from_above'] = dfa_interval attacks_per_second['death_from_above_strike'] = [finisher_chance * dfa_interval for finisher_chance in avg_size_breakdown] @@ -1107,29 +1024,29 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa - envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('envenom', cost_mod=ability_cost_modifier) - envenom_energy_cost -= avg_finisher_size * self.relentless_strikes_energy_return_per_cp + envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('envenom') envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost envenoms_per_second = envenoms_per_cycle / avg_cycle_length finishers_per_second = envenoms_per_second + attacks_per_second['rupture'] + if self.talents.death_from_above: finishers_per_second += attacks_per_second['death_from_above'] + cpgs_per_second = avg_cpgs_per_finisher * finishers_per_second + if cpg in attacks_per_second: attacks_per_second[cpg] += cpgs_per_second else: attacks_per_second[cpg] = cpgs_per_second - #if cpg == 'mutilate': - # attacks_per_second['hemorrhage'] += cpgs_per_second * blindside_proc_rate #another dispatch hack attacks_per_second['rupture_ticks'] = [0,0,0,0,0,.5] + if self.talents.anticipation: attacks_per_second['envenom'][5] += envenoms_per_second else: for i in xrange(1,6): attacks_per_second['envenom'][i] = envenoms_per_second * avg_size_breakdown[i] - #attacks_per_second['envenom'] = [finisher_chance * envenoms_per_second for finisher_chance in avg_size_breakdown] for i in xrange(1, 6): ticks_per_rupture = 2 * (1 + i + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) attacks_per_second['rupture_ticks'][i] = ticks_per_rupture * attacks_per_second['rupture'] * avg_size_breakdown[i] @@ -1139,6 +1056,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra if 'garrote' in attacks_per_second: attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] + attacks_per_second['envenom'][5] += 1. / 180 if self.level == 100: @@ -1152,23 +1070,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) - #if self.talents.shadow_reflection: - # sr_uptime = 8. / self.get_spell_cd('shadow_reflection') - # for ability in ('rupture_ticks', 'dispatch'): - # if type(attacks_per_second[ability]) in (tuple, list): - # attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] - # for i in xrange(1, 6): - # attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] - # else: - # attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] - # envenom_per_sr = 1.5 * sum(attacks_per_second['envenom']) - # attacks_per_second['sr_envenom'] = [finisher_chance * envenom_per_sr / self.get_spell_cd('shadow_reflection') for finisher_chance in avg_size_breakdown] - # crit_rates['sr_envenom'] = 1./envenom_per_sr + (1-envenom_per_sr)/envenom_per_sr * crit_rates['envenom'] - # if 'mutilate' in attacks_per_second: - # attacks_per_second['sr_mh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] - # attacks_per_second['sr_oh_mutilate'] = 2 * sr_uptime * attacks_per_second['mutilate'] - - self.swing_reset_spacing = None #hack resets for now + self.swing_reset_spacing = None #hack resets for now -aeriwen white_swing_downtime = 0 if self.swing_reset_spacing is not None: white_swing_downtime += .5 / self.swing_reset_spacing @@ -1185,7 +1087,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - self.settings.dmg_poison = 'dp' #hack in deadly poison for now + self.settings.dmg_poison = 'dp' #hack in deadly poison for now -aeriwen self.get_poison_counts(attacks_per_second, current_stats) if self.level == 100: @@ -1197,11 +1099,14 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra return attacks_per_second, crit_rates, additional_info - def assassination_attack_counts_non_execute(self, current_stats, crit_rates=None): - return self.assassination_attack_counts(current_stats, 'mutilate', self.settings.cycle.min_envenom_size_non_execute, crit_rates=crit_rates) + def assassination_attack_counts_non_execute(self, current_stats, crit_rates=None): #probably should be inlined -aeriwen + return self.assassination_attack_counts(current_stats, 'mutilate', self.settings.cycle.min_envenom_size, crit_rates=crit_rates) - def assassination_attack_counts_execute(self, current_stats, crit_rates=None): - return self.assassination_attack_counts(current_stats, 'hemorrhage', self.settings.cycle.min_envenom_size_execute, crit_rates=crit_rates) + def get_asassination_energy_regen(self, current_stats): + energy_regen = self.base_energy_regen * self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + self.bonus_energy_regen + if self.talents.marked_for_death: #not sure if this is the right model for dfa -aeriwen + energy_regen -= 10. / self.get_spell_cd('marked_for_death') # 35-25 + return energy_regen ########################################################################### # Outlaw DPS functions diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index dfa62e2..fa300ed 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -60,12 +60,9 @@ class AssassinationCycle(Cycle): allowed_values = (1, 2, 3, 4, 5) - def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5): - assert min_envenom_size_non_execute in self.allowed_values - self.min_envenom_size_non_execute = min_envenom_size_non_execute - - assert min_envenom_size_execute in self.allowed_values - self.min_envenom_size_execute = min_envenom_size_execute + def __init__(self, min_envenom_size=4): + assert min_envenom_size in self.allowed_values + self.min_envenom_size = min_envenom_size class OutlawCycle(Cycle): _cycle_type = 'outlaw' From 6f5712532ee2df4a643de6e3c3e2fc06cd6f113d Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Tue, 23 Aug 2016 07:53:02 -0500 Subject: [PATCH 057/265] - final cleanup for asn - removed execute model for asn --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 43 ++++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9bc8efc..1abbc4b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -772,13 +772,10 @@ def init_assassination(self): self.base_energy_regen = 10 self.max_energy = 120. - if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): - self.max_energy += 30 + if self.talents.lemon_zest: self.base_energy_regen *= 1 + .05 * (1 + min(self.settings.num_boss_adds, 2)) self.max_energy += 15 - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - self.max_energy += 20 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) @@ -790,20 +787,16 @@ def init_assassination(self): spec_needs_converge = True self.envenom_crit_modifier = 0.0 - self.vendetta_duration = 20 #+ 10 * self.glyphs.vendetta + self.vendetta_duration = 20 self.vendetta_uptime = self.vendetta_duration / (self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay) - self.vendetta_multiplier = .3 #- .05 * self.glyphs.vendetta + self.vendetta_multiplier = .3 self.vendetta_mult = 1 + self.vendetta_multiplier * self.vendetta_uptime def assassination_dps_estimate(self): return sum(self.assassination_dps_breakdown().values()) def update_assassination_breakdown_with_modifiers(self, damage_breakdown, current_stats): - #calculate multistrike here for Sub and Assassination, really cheap to calculate - #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! - #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()) - #multistrike_multiplier = min(.6, multistrike_multiplier) - + #not sure if this is still needed since the trinkets, multistike and sr stuff are gone -aeriwen soul_cap_mod = 1.0 if getattr(self.stats.procs, 'soul_capacitor'): soul_cap= getattr(self.stats.procs, 'soul_capacitor') @@ -834,13 +827,11 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren #mirror of the blademaster doesn't get any player buffs if key == 'Mirror of the Blademaster': continue - if ('sr_' not in key): - damage_breakdown[key] *= self.vendetta_mult - damage_breakdown[key] *= soul_cap_mod - damage_breakdown[key] *= infallible_trinket_mod - elif 'sr_' in key: - damage_breakdown[key] *= 1 + self.vendetta_multiplier - if self.level == 100 and key in ('mutilate', 'dispatch', 'sr_mutilate', 'sr_mh_mutilate', 'sr_oh_mutilate', 'sr_dispatch'): + + damage_breakdown[key] *= self.vendetta_mult + damage_breakdown[key] *= soul_cap_mod + damage_breakdown[key] *= infallible_trinket_mod + if self.level == 100 and key in ('mutilate', 'hemorrhage'): #hacked with hemo in place of dispatch for now -aeriwen damage_breakdown[key] *= self.emp_envenom_percentage #add maalus burst @@ -848,7 +839,7 @@ def update_assassination_breakdown_with_modifiers(self, damage_breakdown, curren damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) def assassination_dps_breakdown(self): - current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts_non_execute) + current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts) damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) @@ -905,8 +896,9 @@ def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, abi avg_breakdown[i] = n_chance*n_breakdown[i] + n_bs_chance*n_bs_breakdown[i] + c_chance*c_breakdown[i] + c_bs_chance*c_bs_breakdown[i] return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown - def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_rates=None): - + def assassination_attack_counts(self, current_stats, crit_rates=None): + #several previous settings are now hadcoded, appropriate settings will need to be exposed for release -aeriwen + cpg = 'mutilate' attacks_per_second = {} additional_info = {} @@ -1070,7 +1062,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) - self.swing_reset_spacing = None #hack resets for now -aeriwen + self.swing_reset_spacing = None #hack resets for now, since it is broken -aeriwen white_swing_downtime = 0 if self.swing_reset_spacing is not None: white_swing_downtime += .5 / self.swing_reset_spacing @@ -1099,10 +1091,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra return attacks_per_second, crit_rates, additional_info - def assassination_attack_counts_non_execute(self, current_stats, crit_rates=None): #probably should be inlined -aeriwen - return self.assassination_attack_counts(current_stats, 'mutilate', self.settings.cycle.min_envenom_size, crit_rates=crit_rates) - - def get_asassination_energy_regen(self, current_stats): + def get_asassination_energy_regen(self, current_stats): #this should probably be handled in a super class -aeriwen energy_regen = self.base_energy_regen * self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + self.bonus_energy_regen if self.talents.marked_for_death: #not sure if this is the right model for dfa -aeriwen energy_regen -= 10. / self.get_spell_cd('marked_for_death') # 35-25 @@ -1548,7 +1537,7 @@ def outlaw_attack_counts_ar(self, current_stats, crit_rates=None): def outlaw_attack_counts_none(self, current_stats, crit_rates=None): return self.outlaw_attack_counts(current_stats, crit_rates=crit_rates) - def get_max_energy(self): + def get_max_energy(self): #this may be best handled as a method in a super class self.max_energy = 100 if self.talents.vigor: self.max_energy += 50 From 23634a635030ded320affe24c3b902943e3b850a Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 23 Aug 2016 09:30:40 -0400 Subject: [PATCH 058/265] Update Settings Object --- shadowcraft/calcs/rogue/Aldriana/settings.py | 58 +++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index c95fa5a..6265bdd 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -5,7 +5,7 @@ class Settings(object): def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_params=None, merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False, - marked_for_death_resets=0): + marked_for_death_resets=0, finisher_threshold=5): self.cycle = cycle self.response_time = response_time self.latency = latency @@ -16,8 +16,12 @@ def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_param self.num_boss_adds = max(num_boss_adds, 0) self.adv_params = self.interpret_adv_params(adv_params) self.default_ep_stat = default_ep_stat + #per minute self.marked_for_death_resets=marked_for_death_resets + #TODO: can be overridden by spec specific finisher thresholds + self.finisher_threshold = finisher_threshold + def interpret_adv_params(self, s=""): data = {} max_effects = 8 @@ -58,43 +62,45 @@ class Cycle(object): class AssassinationCycle(Cycle): _cycle_type = 'assassination' - allowed_values = (1, 2, 3, 4, 5) - - def __init__(self, min_envenom_size_non_execute=4, min_envenom_size_execute=5): - assert min_envenom_size_non_execute in self.allowed_values - self.min_envenom_size_non_execute = min_envenom_size_non_execute - - assert min_envenom_size_execute in self.allowed_values - self.min_envenom_size_execute = min_envenom_size_execute + def __init__(self, kingsbane_with_vendetta ='just', exsang_with_vendetta='just', cp_builder='mutilate'): + self.cp_builder = cp_builder #Allowed values: 'mutilate', 'fan_of_knives' + #Cooldown scheduling and usage settings + #Allowed values: 'just': Use cooldown if it aligns with vendetta but don't delay usages + # 'only': Only use cooldown with vendetta + self.kingsbane_with_vendetta = kingsbane_with_vendetta + self.exsang_with_vendetta = exsang_with_vendetta class OutlawCycle(Cycle): _cycle_type = 'outlaw' - def __init__(self, ksp_immediately=True, blade_flurry=False, dfa_during_ar=False): + def __init__(self, blade_flurry=False, between_the_eyes_policy='shark', + jolly_roger_reroll=0, grand_melee_reroll=0, shark_reroll=0, + true_bearing_reroll=0, buried_treasure_reroll=0, broadsides_reroll=0): self.blade_flurry = bool(blade_flurry) - self.ksp_immediately = bool(ksp_immediately) # Determines whether to KSp the instant it comes off cool or wait until Bandit's Guile stacks up. - self.dfa_during_ar = bool(dfa_during_ar) + self.between_the_eyes_policy = between_the_eyes_policy #Allowed values: 'shark', 'always', 'never' + # RtB reroll thresholds, 0, 1, 2, 3 + # 0 means never reroll combos with this buff + # 1 means reroll singles of buff + # 2 means reroll doubles containing this buff + # 3 means reroll triples containing this buff + self.jolly_roger_reroll = jolly_roger_reroll + self.grand_melee_reroll = grand_melee_reroll + self.shark_reroll = shark_reroll + self.true_bearing_reroll = true_bearing_reroll + self.buried_treasure_reroll = buried_treasure_reroll + self.broadsides_reroll = broadsides_reroll + class SubtletyCycle(Cycle): _cycle_type = 'subtlety' - def __init__(self, cp_builder='backstab', dance_cp_builder='shadowstrike', positional_uptime=1.0, symbols_policy='just', + def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy='just', eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, dfa_cps = 5, - dance_finishers_allowed=[], symbols_during_vanish=True, max_vanish_builders=3, max_dance_builders=4): - self.cp_builder = cp_builder #Allowed values: fok, backstab, gloomblade - self.dance_cp_builder = dance_cp_builder #Allowed values: shuriken_storm, shadowstrike + dance_finishers_allowed=True): + self.cp_builder = cp_builder #Allowed values: 'shuriken_storm', 'backstab' (implies gloomblade if selected and ssk during dance) self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) #'just' - Only use SoD when needed to refresh - self.eviscerate_cps = eviscerate_cps - self.finality_eviscerate_cps = finality_eviscerate_cps - self.nightblade_cps = nightblade_cps - self.finality_nightblade_cps = finality_nightblade_cps - self.death_from_above_cps = dfa_cps - self.max_dance_builders = max_dance_builders #0-4 with subter, 0-3 without - self.max_vanish_builders = max_vanish_builders #0-3 with subter, 0,1 without - - #List of following keys: 'eviscerate', 'nightblade', 'finality:eviscerate', 'finality:nightblade, death_from_above' - #Keys not included will not be used during dance + #Allow finishers to be scheduled during dance self.dance_finishers_allowed= dance_finishers_allowed \ No newline at end of file From 18ddd4130c6acd0502c97842d53e117484da7a0a Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Thu, 25 Aug 2016 12:54:46 -0500 Subject: [PATCH 059/265] - talent ranking format changed - modified spec test scripts to pprint talent ranking --- scripts/assassination.py | 5 ++++- scripts/outlaw.py | 5 ++++- scripts/subtlety.py | 6 ++++-- shadowcraft/calcs/__init__.py | 28 +++++++++++++++++++--------- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index fd3346a..f3acca9 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -1,6 +1,7 @@ # Simple test program to debug + play with assassination models. from os import path import sys +from pprint import pprint sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator @@ -75,7 +76,7 @@ #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) -#talent_ranks = calculator.get_talents_ranking() +talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): @@ -113,3 +114,5 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) print ' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.") + +pprint(talent_ranks) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 9fe42f4..0f51c7e 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -1,6 +1,7 @@ # Simple test program to debug + play with assassination models. from os import path import sys +from pprint import pprint sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator @@ -105,10 +106,12 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): dicts_for_pretty_print = [ep_values, #tier_ep_values, - talent_ranks, + #talent_ranks, #trinkets_ep_value, dps_breakdown, trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") + +pprint(talent_ranks) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 4f823ae..2fe3a75 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -1,6 +1,7 @@ # Simple test program to debug + play with subtlety models. from os import path import sys +from pprint import pprint sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator @@ -80,7 +81,7 @@ #ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) -#talent_ranks = calculator.get_talents_ranking() +talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): @@ -108,10 +109,11 @@ def pretty_print(dict_list): dicts_for_pretty_print = [ #ep_values, #tier_ep_values, - #talent_ranks, #trinkets_ep_value, dps_breakdown, #trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") + +pprint(talent_ranks) \ No newline at end of file diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index c1af7b6..7271c3f 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -528,6 +528,8 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) def get_talents_ranking(self, list=None): talents_ranking = {} + tier_ranking = {} + tier_levels = [15, 30, 45, 60, 75, 90, 100] #names for our tiers baseline_dps = self.get_dps() talent_list = [] @@ -536,15 +538,23 @@ def get_talents_ranking(self, list=None): else: talent_list = list - for talent in talent_list: - setattr(self.talents, talent, not getattr(self.talents, talent)) - try: - new_dps = self.get_dps() - if new_dps != baseline_dps: - talents_ranking[talent] = abs(new_dps - baseline_dps) - except: - talents_ranking[talent] = _('not implemented') - setattr(self.talents, talent, not getattr(self.talents, talent)) + n = 0 #hacky index to name our talent tiers + for tier in self.talents.class_talents: + for talent in tier: + if talent in talent_list: + setattr(self.talents, talent, not getattr(self.talents, talent)) + try: + new_dps = self.get_dps() + if new_dps != baseline_dps: + tier_ranking[talent] = abs(new_dps - baseline_dps) + else: + tier_ranking[talent] = 'not implemented' # two unique null results for debug purposes + except: + tier_ranking[talent] = 'implementation error' # two unique null results for debug purposes + setattr(self.talents, talent, not getattr(self.talents, talent)) + talents_ranking[tier_levels[n]] = tier_ranking + n += 1 #increment the tier name + tier_ranking = {} return talents_ranking From 602893fe6fc88c1aa23f8fe6a673c51285bcbbe0 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Thu, 25 Aug 2016 16:36:17 -0500 Subject: [PATCH 060/265] - refactor get_talents_ranking --- shadowcraft/calcs/__init__.py | 40 ++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 7271c3f..0ff2fee 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -528,33 +528,39 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) def get_talents_ranking(self, list=None): talents_ranking = {} - tier_ranking = {} - tier_levels = [15, 30, 45, 60, 75, 90, 100] #names for our tiers - baseline_dps = self.get_dps() - talent_list = [] + tier_levels = [15, 30, 45, 60, 75, 90, 100] #list of levels for our tiers, because it is not in the talent data + baseline_dps = self.get_dps() #cache + allowed_talent_list = self.talents.get_allowed_talents_for_level() if list == None else list #cache - if list is None: - talent_list = self.talents.get_allowed_talents_for_level() - else: - talent_list = list + for tier, level in zip(self.talents.class_talents, tier_levels): - n = 0 #hacky index to name our talent tiers - for tier in self.talents.class_talents: + tier_ranking = {} #reinitialized to clear dict for each new tier + for talent in tier: - if talent in talent_list: - setattr(self.talents, talent, not getattr(self.talents, talent)) + + if talent in allowed_talent_list: + + setattr(self.talents, talent, not getattr(self.talents, talent)) # this is a rather arcane way to handle state :/ + try: + new_dps = self.get_dps() + if new_dps != baseline_dps: + tier_ranking[talent] = abs(new_dps - baseline_dps) + else: - tier_ranking[talent] = 'not implemented' # two unique null results for debug purposes + + tier_ranking[talent] = 'not implemented' #unique error: no dps delta for this talent + except: - tier_ranking[talent] = 'implementation error' # two unique null results for debug purposes + + tier_ranking[talent] = 'implementation error' #unique error: error attempting to calc dps with this talent + setattr(self.talents, talent, not getattr(self.talents, talent)) - talents_ranking[tier_levels[n]] = tier_ranking - n += 1 #increment the tier name - tier_ranking = {} + + talents_ranking[level] = tier_ranking #place each tier into the talent tree return talents_ranking From cbf28f23ebbe601b1c4199707500546863320853 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sat, 27 Aug 2016 21:07:57 -0500 Subject: [PATCH 061/265] - handle damage proc scaling - added some trinkets - ignore test output --- .gitignore | 1 + shadowcraft/objects/proc_data.py | 30 ++++++++++++++++++++++++++++++ shadowcraft/objects/procs.py | 10 ++++++++-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 27e5caa..31e0ed1 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ item_db.db /ShadowCraft-Engine.pyproj.user /ShadowCraft-Engine.sln /.vs/ShadowCraft-Engine/v14/.suo +/TestResults diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 7d7f405..3ed5739 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -158,6 +158,36 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, + #7.0 procs + 'arcanogolem_digit': { + 'stat':'spell_damage', + 'value': 37356, + 'duration': 0, + 'proc_name': 'Arcane Swipe', + 'scaling': 12, + 'item_level': 875, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 1, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'stormsinger_fulmination_charge': { + 'stat':'stats', + 'value': {'mastery': 505}, #has buff ramping and decay mechanic, may not be accurately valued + 'duration': 10, + 'proc_name': 'Focused Lightning', + 'scaling': 0.35815, + 'item_level': 790, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': .7, + 'icd': 20, + 'trigger': 'all_attacks' + }, #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index f3e3ae3..90c40d3 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -44,10 +44,16 @@ def update_proc_value(self): tools = class_data.Util() #http://forums.elitistjerks.com/topic/130561-shadowcraft-for-mists-of-pandaria/page-3 #see above for stat value initialization + #not sure if this is the correct way to handle damage procs. Most seem to have both disabled scaling and raw value or both enabled scaling and an {object:value}, + #they should probably all use the same notation either way. If we want both, the scaling property should probably be set by value property instead of manually configured. + #the other option is to always handle it deeper into the calc module, but that is coupling object responsibilities and not ideal. if self.scaling: if self.source in ('trinket',): - for e in self.value: - self.value[e] = round(self.scaling * tools.get_random_prop_point(self.item_level)) + if hasattr(self.value,'__iter__'): #handle object value + for e in self.value: + self.value[e] = round(self.scaling * tools.get_random_prop_point(self.item_level)) + else: #handle raw value + self.value = round(self.scaling * tools.get_random_prop_point(self.item_level)) def procs_off_auto_attacks(self): if self.trigger in ('all_attacks', 'auto_attacks', 'all_spells_and_attacks', 'all_melee_attacks'): From 9df482df5c266d8218ab4434c25a61635952121d Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sun, 28 Aug 2016 19:26:18 -0500 Subject: [PATCH 062/265] - first pass heroic trinkets --- shadowcraft/objects/proc_data.py | 219 +++++++++++++++++++++++++++++-- 1 file changed, 211 insertions(+), 8 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 3ed5739..d621fb0 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -145,6 +145,7 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, + 'archmages_greater_incandescence': { 'stat':'stats_modifier', 'value': {'agi':.15}, @@ -159,9 +160,9 @@ 'trigger': 'all_attacks' }, #7.0 procs - 'arcanogolem_digit': { + 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for 37356 Arcane damage. 'stat':'spell_damage', - 'value': 37356, + 'value': 37356, #multiple targets not modeled 'duration': 0, 'proc_name': 'Arcane Swipe', 'scaling': 12, @@ -175,17 +176,219 @@ 'trigger': 'all_attacks' }, - 'stormsinger_fulmination_charge': { + 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by 55, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. 'stat':'stats', - 'value': {'mastery': 505}, #has buff ramping and decay mechanic, may not be accurately valued - 'duration': 10, - 'proc_name': 'Focused Lightning', - 'scaling': 0.35815, + 'value': {'agi': 42}, + 'duration': 24, #decays 1/s after 4s + 'max_stacks': 20, + 'proc_name': 'Chaotic Energy', + 'scaling': 0.029595, 'item_level': 790, + 'source': 'trinket', + 'type': 'icd', + 'icd': 1, + 'proc_rate': 1000000, #idk how to force a stack every autoattack, and messing with icd gives wonky behavior + 'trigger': 'all_attacks', + }, + + 'chrono_shard': { #Equip: Your spells and abilities have a chance to grant you 5112 Haste and 15% movement speed for 10 sec. + 'stat':'stats', + 'value': {'haste': 5112}, + 'duration': 10, + 'proc_name': 'Acceleration', + 'scaling': 2.741159, + 'item_level': 820, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1, + 'haste_scales': True, + 'trigger': 'all_attacks', + }, + + 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause 54933 additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) + 'stat':'spell_damage', + 'value': 0, + 'duration': 15, + 'proc_name': 'Sheathed in Frost', #need special handling + 'scaling': 29.454582, + 'item_level': 820, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 120, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to 233125 damage for 8 sec. When the bubble is consumed or expires, it explodes and deals 111900 Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) + 'stat':'spell_damage', + 'value': 111900, #multiple targets not modeled + 'duration': 0, + 'proc_name': 'Gaseous Bubble', + 'scaling': 60, + 'item_level': 820, + 'type': 'icd', + 'source': 'trinket', + 'icd': 60, + 'proc_rate': 1, + 'can_crit': True, + 'trigger': 'all_attacks', + }, + + 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by 2798 for 30 sec. (2 Min Cooldown) + 'stat':'stats', + 'value': {'agi': 2798}, + 'duration': 30, + 'proc_name': "Valarjar's Path", + 'scaling': 1.2, + 'item_level': 820, + 'type': 'icd', + 'source': 'trinket', + 'icd': 120, + 'proc_rate': 1, + 'trigger': 'all_attacks', + }, + + 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing 43597 Physical damage to all enemies directly in front of you. + 'stat':'spell_damage', + 'value': 43597, #multiple targets not modeled + 'duration': 0, + 'proc_name': 'Landslide', + 'scaling': 23.376637, + 'item_level': 820, 'type': 'rppm', 'source': 'trinket', + 'proc_rate': 4, + 'icd': 1, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'memento_of_angerboda': { #Equip: Your melee attacks have a chance to activate Screams of the Dead, granting you a random combat enhancement for 8 sec. + 'stat':'stats', + 'value': {'mastery': 1189}, # actually 1-3 stat buffs each time, only modeled one + 'duration': 8, + 'proc_name': 'Memento of Angerboda', + 'scaling': 0.637533, #only one data point 1189stat@ilvl820 + 'item_level': 820, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1.5, + 'trigger': 'all_attacks', + }, + + 'nightmare_egg_shell': { #Equip: Your melee attacks have a chance to grant you 184 Haste every 1 sec for 20 sec. + 'stat':'stats', + 'value': {'haste': 184}, + 'duration': 8, + 'proc_name': 'Down Draft', + 'scaling': 0.098396, + 'item_level': 820, + 'source': 'trinket', + 'type': 'rppm', 'proc_rate': .7, - 'icd': 20, + 'trigger': 'all_attacks', + }, + + 'spiked_counterweight': { #Your melee attacks have a chance to deal 90047 Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to 271840 extra damage dealt. + 'stat':'physical_damage', + 'value': 103562, #initial hit portion + 'duration': 0, + 'proc_name': 'Brutal Haymaker', + 'scaling': 53, + 'item_level': 825, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': .92, + }, + + 'spiked_counterweight': { #Your melee attacks have a chance to deal 90047 Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to 271840 extra damage dealt. + 'stat':'physical_damage', + 'value': 312640, #modeled as all direct damage since bonus has a cap + 'duration': 0, + 'proc_name': 'Brutal Haymaker', + 'scaling': 160, + 'item_level': 825, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': .92, + }, + + 'tempered_egg_of_serpentrix': { #Equip: Your attacks have a chance to summon a Spawn of Serpentrix to assist you. + 'stat':'physical_damage', + 'value': 0, #unmodeled + 'duration': 0, + 'proc_name': 'Spawn of Serpentrix', + 'scaling': 0, + 'item_level': 820, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1, + 'haste_scales': True, + 'trigger': 'all_attacks', + }, + + 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal 86952 Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. + 'stat':'spell_damage', + 'value': 695616, #multiple targets not modeled, assuming 4 hits out and 4 in + 'duration': 0, + 'proc_name': 'Shadow Wave', + 'scaling': 372.986208, #46.623276 * 8 + 'item_level': 820, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 1, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting [6228 * (1 + $versadmg)] Nature damage per Goo consumed. (20 Sec Cooldown) + 'stat':'spell_damage', + 'value': 37368, #4709 per stack * 6 stacks + 'duration': 0, + #'max_stacks': 6, + 'proc_name': 'Fetid Regurgitation', + 'scaling': 3.339412, + 'item_level': 620, + 'type': 'icd', #actually rppm + 'source': 'trinket', + #'proc_rate': 3, + 'icd': 20, #modeled as used on cooldown with max stacks every time + #'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) + 'stat':'spell_damage', + 'value': 0, + 'duration': 15, + 'proc_name': 'Darkstrikes', #need special handling + 'scaling': 20.829683, + 'item_level': 820, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 75, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting (7 * 40619) Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) + 'stat':'spell_damage', + 'value': 284333, #multiple targets not modeled + 'duration': 0, + 'proc_name': 'Slicing Maelstrom', + 'scaling': 152.455464, + 'item_level': 790, + 'type': 'icd', + 'source': 'trinket', + 'icd': 120, + 'proc_rate': 1, + 'can_crit': True, 'trigger': 'all_attacks' }, #6.2.3 procs From 1839d43c7c7fd22661c15ffbf801979e3167f204 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 28 Aug 2016 17:48:09 -0700 Subject: [PATCH 063/265] Sub Model Updated for New Settings -Outlaw model WIP --- scripts/subtlety.py | 14 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 803 ++++++++----------- shadowcraft/calcs/rogue/Aldriana/settings.py | 49 +- shadowcraft/calcs/rogue/__init__.py | 9 +- 4 files changed, 403 insertions(+), 472 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 4f823ae..4cb6989 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -21,7 +21,7 @@ # Set up level/class/race test_level = 110 -test_race = race.Race('pandaren') +test_race = race.Race('human') test_class = 'rogue' test_spec = 'subtlety' @@ -29,7 +29,8 @@ test_buffs = buffs.Buffs( 'short_term_haste_buff', 'flask_wod_agi', - 'food_wod_versatility' + 'food_wod_versatility', + #'food_legion_feast_150' ) # Set up weapons. @@ -55,18 +56,17 @@ versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('2000020', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3200000', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '110000000000000000') # Set up settings. -test_cycle = settings.SubtletyCycle(dance_cp_builder='shadowstrike', - #dance_finishers_allowed=['finality:nightblade','nightblade', 'finality:eviscerate', 'eviscerate'], - #dance_finishers_allowed =['nightblade'] +test_cycle = settings.SubtletyCycle(cp_builder='shuriken_storm', + dance_finishers_allowed=True, ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, - adv_params="", is_demon=True, num_boss_adds=0) + adv_params="", is_demon=True, num_boss_adds=10) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0a58cdb..4839e8c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -645,8 +645,7 @@ def determine_stats(self, attack_counts_function): 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], 'haste': self.base_stats['haste'] * self.stat_multipliers['haste'], 'mastery': self.base_stats['mastery'] * self.stat_multipliers['mastery'], - #'readiness': self.base_stats['readiness'] * self.stat_multipliers['readiness'], - #'multistrike': self.base_stats['multistrike'] * self.stat_multipliers['multistrike'], + 'versatility': self.base_stats['versatility'] * self.stat_multipliers['versatility'], } for k in static_proc_stats: @@ -1185,7 +1184,7 @@ def assassination_attack_counts(self, current_stats, cpg, finisher_size, crit_ra attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - + self.settings.dmg_poison = 'dp' #hack in deadly poison for now self.get_poison_counts(attacks_per_second, current_stats) @@ -1208,6 +1207,30 @@ def assassination_attack_counts_execute(self, current_stats, crit_rates=None): # Outlaw DPS functions ########################################################################### + #Legion TODO: + + #Talents: + #T1:Ghostly Strike + #T3:Anticipation + #T5:Cannonball Barrage + #T5:Alacrity + #T5:Killing Spree + #T6:Marked for Death + + #Artifact: + # 'curse_of_the_dreadblades', + # 'hidden_blade', (ambush proc weirdness) + # 'blurred_time', + + #Items: + #Class hall set bonus + #Tier bonus + #Trinkets + #Legendaries + + #Rotation details: + # Roll the Bones handling + def outlaw_dps_estimate(self): return sum(self.outlaw_dps_breakdown().values()) @@ -1215,84 +1238,92 @@ def outlaw_dps_breakdown(self): if not self.spec == 'outlaw': raise InputNotModeledException(_('You must specify a outlaw cycle to match your outlaw spec.')) - self.spec_convergence_stats = ['haste', 'mastery'] - - #update spec specific proc rates - if getattr(self.stats.procs, 'legendary_capacitive_meta'): - getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.136 - if getattr(self.stats.procs, 'fury_of_xuen'): - getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.15 - #outlaw specific constants self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly + self.damage_modifier_cache = 1 + (0.005 * self.traits.cursed_steel) + self.ar_duration = 15 - # recurance relation of 0.16*x until convergence - # https://www.wolframalpha.com/input/?i=15%2Bsum%28x%3D1+to+inf%29+of+15*.16%5Ex - if self.stats.gear_buffs.rogue_t18_2pc: - self.ar_duration = 17.8571 # it not clear what this means, magic number? - if self.stats.gear_buffs.rogue_t17_2pc: - self.extra_cp_chance += 0.2 + self.ar_cd = self.get_spell_cd('adrenaline_rush') self.set_constants() - self.stat_multipliers['haste'] *= 1.05 - self.stat_multipliers['ap'] *= 1.50 - if self.talents.death_from_above: - self.spec_needs_converge = True - - cds = {'ar':self.get_spell_cd('adrenaline_rush'), - 'ks':self.get_spell_cd('killing_spree')} - - # actual damage calculations here - phases = {} - #AR phase - stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts_ar) - ar_tuple = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - phases['ar'] = (self.ar_duration, self.update_with_bandits_guile(ar_tuple[0], ar_tuple[1])) - for e in cds: - cds[e] -= self.ar_duration / self.rb_cd_modifier(aps) - - #none - self.tmp_ks_cd = cds['ks'] - self.tmp_phase_length = cds['ar'] #This is to approximate the value of a full energy bar to be used when not during AR or SB - stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts_none) - none_tuple = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - phases['none'] = (self.rb_actual_cds(aps, cds)['ar'] + self.settings.response_time + self.major_cd_delay, - self.update_with_bandits_guile(none_tuple[0], none_tuple[1]) ) - - if self.stats.gear_buffs.rogue_t18_4pc: - for key in phases['ar'][1]: - phases['ar'][1][key] *=1.15 - for key in phases['none'][1]: - #15% damage boost with 16% uptime - phases['none'][1][key] *= 1.024 - - - total_duration = phases['ar'][0] + phases['none'][0] - #average it together - damage_breakdown = self.average_damage_breakdowns(phases, denom = total_duration) - - run_through_multiplier = 1 - if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - run_through_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.6784929152)/10000 + #table of minicycle ability amounts + #indexed by (min_spend_cps, deeper_strat, quick_draw, swordmaster, broadside, jollyroger) + #values are (ss_per_min_cycle, ps_per_min_cycle, finisher_cp_list) + #TODO: 60 element table is probably a bit much, should probably be condensed + self.minicycle_table = { + (4, True, True, False, True, True) : (0.92778015, 0.5566681, [0, 0, 0, 0, 0.46230870485305786, 0.40208783745765686, 0.13560345768928528]) , + (4, True, True, False, True, False) : (1.2831669, 0.44910839, [0, 0, 0, 0, 0.35908344388008118, 0.49529376626014709, 0.14562278985977173]) , + (4, True, True, False, False, True) : (1.3207548, 0.79245281, [0, 0, 0, 0, 0.37735849618911743, 0.62264150381088257, 0.0]) , + (4, True, True, False, False, False) : (1.7271835, 0.60451424, [0, 0, 0, 0, 0.57409226894378662, 0.42590776085853577, 0.0]) , + (4, True, False, True, True, True) : (1.7995313, 1.2596719, [0, 0, 0, 0, 0.19270744919776917, 0.39063876867294312, 0.41665378212928772]) , + (4, True, False, True, True, False) : (1.759297, 0.79168367, [0, 0, 0, 0, 0.13849352300167084, 0.56256377696990967, 0.29894271492958069]) , + (4, True, False, True, False, True) : (1.3918972, 0.97432804, [0, 0, 0, 0, 0.82430845499038696, 0.17569157481193542, 0.0]) , + (4, True, False, True, False, False) : (1.7689608, 0.79603237, [0, 0, 0, 0, 0.7987181544303894, 0.20128187537193298, 0.0]) , + (4, True, False, False, True, True) : (1.7663901, 1.059834, [0, 0, 0, 0, 0.17100141942501068, 0.45841407775878906, 0.37058448791503906]) , + (4, True, False, False, True, False) : (1.7791812, 0.62271339, [0, 0, 0, 0, 0.11556066572666168, 0.63698828220367432, 0.24745103716850281]) , + (4, True, False, False, False, True) : (1.5257645, 0.91545868, [0, 0, 0, 0, 0.80414772033691406, 0.19585229456424713, 0.0]) , + (4, True, False, False, False, False) : (1.9706308, 0.68972075, [0, 0, 0, 0, 0.81240963935852051, 0.1875903457403183, 0.0]) , + (4, False, True, False, True, True) : (0.90085906, 0.54051542, [0, 0, 0, 0, 0.46230870485305786, 0.53769129514694214, 0]) , + (4, False, True, False, True, False) : (1.2441286, 0.43544501, [0, 0, 0, 0, 0.35908344388008118, 0.64091658592224121, 0]) , + (4, False, True, False, False, True) : (1.3207548, 0.79245281, [0, 0, 0, 0, 0.37735849618911743, 0.62264150381088257, 0]) , + (4, False, True, False, False, False) : (1.7271835, 0.60451424, [0, 0, 0, 0, 0.57409226894378662, 0.42590776085853577, 0]) , + (4, False, False, True, True, True) : (1.6560036, 1.1592025, [0, 0, 0, 0, 0.19270744919776917, 0.80729258060455322, 0]) , + (4, False, False, True, True, False) : (1.6573817, 0.74582177, [0, 0, 0, 0, 0.13849352300167084, 0.86150646209716797, 0]) , + (4, False, False, True, False, True) : (1.3918972, 0.97432804, [0, 0, 0, 0, 0.82430845499038696, 0.17569157481193542, 0]) , + (4, False, False, True, False, False) : (1.7689608, 0.79603237, [0, 0, 0, 0, 0.7987181544303894, 0.20128187537193298, 0]) , + (4, False, False, False, True, True) : (1.640496, 0.98429757, [0, 0, 0, 0, 0.17100141942501068, 0.82899856567382812, 0]) , + (4, False, False, False, True, False) : (1.693392, 0.59268725, [0, 0, 0, 0, 0.11556066572666168, 0.88443934917449951, 0]) , + (4, False, False, False, False, True) : (1.5257645, 0.91545868, [0, 0, 0, 0, 0.80414772033691406, 0.19585229456424713, 0]) , + (4, False, False, False, False, False) : (1.9706308, 0.68972075, [0, 0, 0, 0, 0.81240963935852051, 0.1875903457403183, 0]) , + (5, True, True, False, True, True) : (1.5440897, 0.92645377, [0, 0, 0, 0, 0, 0.47792428731918335, 0.52207571268081665]) , + (5, True, True, False, True, False) : (1.6837471, 0.58931148, [0, 0, 0, 0, 0, 0.52392536401748657, 0.47607460618019104]) , + (5, True, True, False, False, True) : (1.509434, 0.90566039, [0, 0, 0, 0, 0, 0.71698111295700073, 0.28301885724067688]) , + (5, True, True, False, False, False) : (2.0673864, 0.72358519, [0, 0, 0, 0, 0, 0.70232254266738892, 0.29767745733261108]) , + (5, True, False, True, True, True) : (2.7676663, 1.9373665, [0, 0, 0, 0, 0, 0.32654938101768494, 0.67345058917999268]) , + (5, True, False, True, True, False) : (2.0575211, 0.92588449, [0, 0, 0, 0, 0, 0.53625214099884033, 0.46374788880348206]) , + (5, True, False, True, False, True) : (1.7693849, 1.2385694, [0, 0, 0, 0, 0, 0.69184529781341553, 0.30815470218658447]) , + (5, True, False, True, False, False) : (2.1994596, 0.98975676, [0, 0, 0, 0, 0, 0.7762836217880249, 0.22371639311313629]) , + (5, True, False, False, True, True) : (2.3502514, 1.4101509, [0, 0, 0, 0, 0, 0.41270622611045837, 0.58729374408721924]) , + (5, True, False, False, True, False) : (1.9709414, 0.68982947, [0, 0, 0, 0, 0, 0.62002801895141602, 0.37997198104858398]) , + (5, True, False, False, False, True) : (1.9163667, 1.14982, [0, 0, 0, 0, 0, 0.72999167442321777, 0.27000829577445984]) , + (5, True, False, False, False, False) : (2.4447069, 0.85564739, [0, 0, 0, 0, 0, 0.80499798059463501, 0.19500201940536499]) , + (5, False, True, False, True, True) : (1.475865, 0.88551903, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, True, False, True, False) : (1.6334157, 0.57169551, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, True, False, False, True) : (1.509434, 0.90566039, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, True, False, False, False) : (2.0673864, 0.72358519, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, True, True, True) : (2.5490196, 1.7843137, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, True, True, False) : (1.9435737, 0.87460816, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, True, False, True) : (1.7693849, 1.2385694, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, True, False, False) : (2.1994596, 0.98975676, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, False, True, True) : (2.1875, 1.3125, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, False, True, False) : (1.8803419, 0.65811968, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, False, False, True) : (1.9163667, 1.14982, [0, 0, 0, 0, 0, 1.0, 0]) , + (5, False, False, False, False, False) : (2.4447069, 0.85564739, [0, 0, 0, 0, 0, 1.0, 0]) , + (6, True, True, False, True, True) : (2.7550187, 1.6530112, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, True, False, True, False) : (2.4767113, 0.86684889, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, True, False, False, True) : (1.8489302, 1.1093582, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, True, False, False, False) : (2.4813204, 0.86846215, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, True, True, True) : (1.8811882, 1.3168317, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, True, True, False) : (2.0423892, 0.91907513, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, True, False, True) : (2.1186955, 1.4830868, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, True, False, False) : (2.6321666, 1.1844751, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, False, True, True) : (1.9298246, 1.1578947, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, False, True, False) : (2.1415608, 0.74954629, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, False, False, True) : (2.2952538, 1.3771522, [0, 0, 0, 0, 0, 0, 1.0]) , + (6, True, False, False, False, False) : (2.9230175, 1.0230561, [0, 0, 0, 0, 0, 0, 1.0]) , + } + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) bf_mod = .35 if self.settings.cycle.blade_flurry: damage_breakdown['blade_flurry'] = 0 for key in damage_breakdown: if key in self.blade_flurry_damage_sources: - if key == "run_through": - damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * self.settings.num_boss_adds * run_through_multiplier - else: - damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * self.settings.num_boss_adds - - soul_cap_mod = 1.0 - if getattr(self.stats.procs, 'soul_capacitor'): - soul_cap= getattr(self.stats.procs, 'soul_capacitor') - self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) + damage_breakdown['blade_flurry'] += bf_mod * damage_breakdown[key] * self.settings.num_boss_adds infallible_trinket_mod = 1.0 if self.settings.is_demon: @@ -1301,357 +1332,208 @@ def outlaw_dps_breakdown(self): self.set_rppm_uptime(ift) infallible_trinket_mod = 1+(ift.uptime *0.10) - maalus_mod = 1.0 - if getattr(self.stats.procs,'maalus'): - maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000. - maalus_mod = 1 + (15.0/120* maalus_val) #super hackish - - #outlaw gets it's own MS calculation due to BF mechanics - #calculate multistrike here, really cheap to calculate - #turns out the 2 chance system yields a very basic linear pattern, the damage modifier is 30% of the multistrike %! - #multistrike_multiplier = .3 * 2 * (self.stats.get_multistrike_chance_from_rating(rating=stats['multistrike']) + self.buffs.multistrike_bonus()) - #multistrike_multiplier = min(.6, multistrike_multiplier) - for ability in damage_breakdown: - damage_breakdown[ability] *=maalus_mod - if 'sr_' not in ability: - damage_breakdown[ability] *= soul_cap_mod - damage_breakdown[ability] *= infallible_trinket_mod - #Fel Lash doesn't MS - if ability == 'Fel Lash': - continue - #damage_breakdown[ability] *= (1 + multistrike_multiplier) - if ability == 'run_through': - damage_breakdown[ability] *= run_through_multiplier - - #add maalus burst - if maalus_mod > 1.0: - damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) - - return damage_breakdown - - def update_with_bandits_guile(self, damage_breakdown, additional_info): - for key in damage_breakdown: - if key in ('Mirror of the Blademaster', 'Fel Lash'): - continue - if key in ('killing_spree', 'mh_killing_spree', 'oh_killing_spree'): - if self.settings.cycle.ksp_immediately: - damage_breakdown[key] *= self.bandits_guile_multiplier - else: - damage_breakdown[key] *= self.max_bandits_guile_buff - if self.stats.gear_buffs.rogue_t16_4pc_bonus(): - #http://elitistjerks.com/f78/t132793-5_4_changes_discussion/p2/#post2301780 - #http://www.wolframalpha.com/input/?i=%28sum+of+1.5*1.1%5Ex+from+x%3D1+to+7%29+%2F+%281.5*7%29 - # No need to use anything other than a constant. Yay for convenience! - damage_breakdown[key] *= 1.49084 - elif key in ('saber_slash', 'pistol_shot'): - damage_breakdown[key] *= self.bandits_guile_multiplier - elif key in ('run_through', ): - damage_breakdown[key] *= self.bandits_guile_multiplier #* self.revealing_strike_multiplier - else: - damage_breakdown[key] *= self.bandits_guile_multiplier #* self.ksp_multiplier + damage_breakdown[ability] *= infallible_trinket_mod return damage_breakdown - def outlaw_cpg_per_finisher(self, current_cp, ability_count): - if current_cp >= 5: - return ability_count - new_count = copy(ability_count) - new_count += 1 - - normal = self.outlaw_cpg_per_finisher(current_cp+1, new_count) - - #disabled rvs modeling because i dont understand how it works anyway - #rvs_proc = self.outlaw_cpg_per_finisher(current_cp+2, new_count) - - #return (1 - self.extra_cp_chance)*normal + self.extra_cp_chance*rvs_proc - return normal - - def outlaw_attack_counts(self, current_stats, ar=False, crit_rates=None): + def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second = {} additional_info = {} - # base_energy_regen needs to be reset here due to determine_stats method - self.base_energy_regen = 12. # should extract this to a function + #Compute values that are true through all RtB variations + self.base_energy_regen = 12. if self.talents.vigor: self.base_energy_regen *= 0.1 if self.settings.cycle.blade_flurry: - self.base_energy_regen *= .8 + self.base_energy_regen *= .8 + (0.03333 * self.traits.blade_dancer) if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - self.attack_speed_increase = self.base_speed_multiplier * haste_multiplier + combat_potency_proc_energy = 15 + (1 * self.traits.fortune_strikes) + self.combat_potency_regen_per_oh = combat_potency_proc_energy * .3 * self.stats.oh.speed / 1.4 # the new "normalized" formula + self.combat_potency_from_mg = combat_potency_proc_energy * .3 - main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) + self.main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) + cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg - combat_potency_regen_per_oh = 15 * .3 * self.stats.oh.speed / 1.4 # the new "normalized" formula - combat_potency_from_mg = 15 * .3 - FINISHER_SIZE = 5 - ruthlessness_value = 1 # 1CP gained at 20% chance per CP spent (5CP spent means 1 is always added) + #compute MG lumped ability costs + self.run_through_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer + self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer + self.saber_slash_energy_cost = self. self.get_spell_cost('saber_slash') - cost_reducer + self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (4 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) + if self.talents.slice_and_dice: + self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (4 * self.traits.fatebringer) + else: + self.roll_the_bones_cost = self.get_spell_cost('roll_the_bones') - (4 * self.traits.fatebringer) + if self.talents.ghostly_strike: + self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer - if ar: - self.attack_speed_increase *= 1.2 - self.base_energy_regen *= 2.0 - gcd_size = 1.0 + self.settings.latency - if ar: - gcd_size -= .2 - cp_per_cpg = 1. - dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - - if self.stats.gear_buffs.rogue_t18_2pc and not ar: - self.attack_speed_increase *= 1 + (0.16 *0.2) - self.base_energy_regen *= 1.16 - gcd_size -= (0.16 * 0.2) - - # Combine energy cost scalers to reduce function calls (ie, 40% reduced energy cost). Assume multiplicative. - cost_modifier = self.stats.gear_buffs.rogue_t15_4pc_modifier() - # Turn the cost of the ability into the net loss of energy by reducing it by the energy gained from MG - cost_reducer = main_gauche_proc_rate * combat_potency_from_mg - - #these should probably extracted to functions. - run_through_energy_cost = self.get_spell_cost('run_through', cost_mod=cost_modifier) - run_through_energy_cost -= cost_reducer - #run_through_energy_cost -= FINISHER_SIZE * self.relentless_strikes_energy_return_per_cp - pistol_shot_energy_cost = self.get_spell_cost('pistol_shot', cost_mod=cost_modifier) - pistol_shot_energy_cost -= cost_reducer - saber_slash_energy_cost = self.get_spell_cost('saber_slash', cost_mod=cost_modifier) - saber_slash_energy_cost -= cost_reducer - death_from_above_energy_cost = self.get_spell_cost('death_from_above', cost_mod=cost_modifier) - death_from_above_energy_cost -= cost_reducer * (2 + self.settings.num_boss_adds) - - #need to reduce the cost of DFA by the strike's MG proc ... - #but also the MG procs from the AOE which hits the main target plus each additional add (strike + aoe) - if self.stats.gear_buffs.rogue_t16_2pc_bonus(): - saber_slash_energy_cost -= 15 * self.extra_cp_chance + self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') + #compute dps phases each non-rerolling rtb buff combo (excluding tb and shark) ar and not + phases = {} - ## Base CPs and Attacks - #Autoattacks - white_swing_downtime = 0 - #TODO: Add swing resets back for vanishes - self.swing_reset_spacing = None - if self.swing_reset_spacing is not None and not ar: - white_swing_downtime += self.settings.response_time / self.swing_reset_spacing #from vanish - swing_timer_mh = self.stats.mh.speed / self.attack_speed_increase - swing_timer_mh = self.stats.oh.speed / self.attack_speed_increase - attacks_per_second['mh_autoattacks'] = self.attack_speed_increase / self.stats.mh.speed * (1 - white_swing_downtime) - attacks_per_second['oh_autoattacks'] = self.attack_speed_increase / self.stats.oh.speed * (1 - white_swing_downtime) - #swing delays should be handled here - if self.talents.death_from_above and not ar: - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / self.attack_speed_increase) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / self.attack_speed_increase) - attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd - attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd - attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance - attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance - attacks_per_second['main_gauche'] = attacks_per_second['mh_autoattack_hits'] * main_gauche_proc_rate - combat_potency_regen = attacks_per_second['oh_autoattack_hits'] * combat_potency_regen_per_oh - #Base energy - #TODO handle openers - #bonus_energy_from_openers = self.get_bonus_energy_from_openers('sinister_strike', 'revealing_strike') - bonus_energy_from_openers = 0 - #if self.settings.opener_name in ('ambush', 'garrote'): - # attacks_per_second[self.settings.opener_name] = self.total_openers_per_second - # attacks_per_second['main_gauche'] += self.total_openers_per_second * main_gauche_proc_rate + if self.traints.greed: + attacks_per_second['greed'] = 0.35 * attacks_per_second['run_through'] - if self.talents.death_from_above and not ar: - attacks_per_second['main_gauche'] += (1 + self.settings.num_boss_adds) * main_gauche_proc_rate / dfa_cd - combat_potency_regen += combat_potency_from_mg * attacks_per_second['main_gauche'] - energy_regen = self.base_energy_regen * haste_multiplier - if self.stats.gear_buffs.rogue_t17_4pc_lfr: - #http://www.wolframalpha.com/input/?i=1.1307+*+%281+-+e+**+%28-1+*+1.1+*+6%2F+60%29%29 - #https://twitter.com/Celestalon/status/525350819856535552 - energy_regen *= 1 + (.11778034322021550695 * .3) #11% uptime on 30% boost) - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - energy_regen *= 1.05 - energy_regen += self.bonus_energy_regen + combat_potency_regen + bonus_energy_from_openers - #Rough idea to factor in a full energy bar - if not ar: - energy_regen += self.get_max_energy() / self.settings.duration + if self.traits.blunderbuss: + attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] + attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - #Base actions + return attacks_per_second, crit_rates, additional_info - #Minicycle sizes and cpg_per_finisher stats - if self.talents.anticipation: - ss_per_finisher = (FINISHER_SIZE - ruthlessness_value) / (cp_per_cpg + self.extra_cp_chance) - else: - ss_per_finisher = self.outlaw_cpg_per_finisher(1, 0) - cp_per_finisher = FINISHER_SIZE - energy_cost_for_cpgs = ss_per_finisher * saber_slash_energy_cost - total_eviscerate_cost = energy_cost_for_cpgs + run_through_energy_cost - - ss_per_snd = ss_per_finisher - snd_size = FINISHER_SIZE - snd_base_cost = 25 - #snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost - snd_size * self.relentless_strikes_energy_return_per_cp - snd_cost = ss_per_snd * saber_slash_energy_cost + snd_base_cost - snd_duration = 6 + 6 * (snd_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - energy_spent_on_snd = snd_cost / snd_duration - - #Base Actions - - #TODO model pistol shot procs - pistol_shot_interval = 10 - - #marked for death CD - self.outlaw_cd_delay = (.5 * total_eviscerate_cost) / (2 * energy_regen) - marked_for_death_cd = self.get_spell_cd('marked_for_death') + self.outlaw_cd_delay + self.settings.response_time - if self.talents.marked_for_death: - energy_regen -= 10. / marked_for_death_cd + def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, + jolly=False, melee=False, buried=False, broadsides=False, duration=30): - energy_regen -= pistol_shot_energy_cost / pistol_shot_interval + attack_speed_multiplier = self.haste_multiplier + if melee: + attack_speed_multiplier *= 1.5 + if snd: + attack_speed_multiplier *= 1.9 - energy_for_dfa = 0 - if self.talents.death_from_above and not ar: - #dfa_gap probably should be handled more accurately especially in the non-anticipation case - dfa_interval = 1./(dfa_cd) - energy_for_dfa = energy_cost_for_cpgs + death_from_above_energy_cost - #energy_for_dfa -= cp_per_finisher * self.relentless_strikes_energy_return_per_cp - energy_for_dfa *= dfa_interval + energy_regen = self.base_energy_regen + if buried: + energy_regen *= 1.25 - attacks_per_second['death_from_above'] = dfa_interval - attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, dfa_interval] - attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, dfa_interval * (self.settings.num_boss_adds+1)] + if ar: + attack_speed_multiplier *= 1.2 + energy_regen *= 2.0 - #Base CPGs - attacks_per_second['saber_slash_base'] = ss_per_snd / snd_duration - if self.talents.death_from_above and not ar: - attacks_per_second['saber_slash_base'] += ss_per_finisher / (1/dfa_interval) + gcd_size = 1.0 + self.settings.latency + if ar: + gcd_size -= .2 - attacks_per_second['pistol_shot'] = 1. / pistol_shot_interval - extra_finishers_per_second = attacks_per_second['pistol_shot'] / 5. + #fetch minicycle value + minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_strategem), bool(self.talents.quick_draw), + bool(self.talents.swordmaster), broadsides, jolly) + ss_count, ps_count, finisher_list = self.minicycle_table[minicycle_key] - #Scaling CPGs - free_gcd = 1./gcd_size - free_gcd -= 1./snd_duration + (attacks_per_second['saber_slash_base'] + attacks_per_second['pistol_shot'] + extra_finishers_per_second) - if self.talents.marked_for_death: - free_gcd -= (1. / marked_for_death_cd) + # set up our initial budgets + energy_budget = duration * energy_regen + gcd_budget = duration/gcd_size - #2 seconds is an approximation of GCD loss while in air - if self.talents.death_from_above and not ar: - free_gcd -= dfa_interval * (2. / gcd_size) #wowhead claims a 2s GCD - energy_available_for_run_through = energy_regen - energy_spent_on_snd - energy_for_dfa - total_run_through_per_second = energy_available_for_run_through / total_eviscerate_cost - run_through_actions_per_second = (total_run_through_per_second * ss_per_finisher + total_run_through_per_second) - if self.stats.gear_buffs.rogue_t17_4pc: - #http://www.wolframalpha.com/input/?i=sum+of+.2%5Ex+from+x%3D1+to+inf - #This increases the frequency of Eviscerates by 25% for every Evisc cast - run_through_actions_per_second += total_run_through_per_second * .25 - attacks_per_second['saber_slash'] = total_run_through_per_second * ss_per_finisher - - # If GCD capped - if run_through_actions_per_second > free_gcd: - gcd_cap_mod = run_through_actions_per_second / free_gcd - attacks_per_second['saber_slash'] = attacks_per_second['saber_slash'] / gcd_cap_mod - total_run_through_per_second = total_run_through_per_second / gcd_cap_mod - - # Reintroduce flat gcds - attacks_per_second['saber_slash'] += attacks_per_second['saber_slash_base'] - attacks_per_second['main_gauche'] += (attacks_per_second['saber_slash'] + attacks_per_second['pistol_shot'] + - total_run_through_per_second) * main_gauche_proc_rate + #since artifacts we'll just compute a one handed swing timer + swing_timer = self.stats.mh_speed / attack_speed_multiplier + swings = duration/swing_timer + swings *= (1 - self.white_swing_downtime) if self.talents.death_from_above and not ar: - attacks_per_second['main_gauche'] += attacks_per_second['death_from_above_strike'][5] * main_gauche_proc_rate - - #attacks_per_second['eviscerate'] = [finisher_chance * total_evis_per_second for finisher_chance in finisher_size_breakdown] - attacks_per_second['run_through'] = [0,0,0,0,0,total_run_through_per_second] - - for opener, cps in [('ambush', 2), ('garrote', 1)]: - if opener in attacks_per_second: - extra_finishers_per_second += attacks_per_second[opener] * cps / 5 - attacks_per_second['run_through'][5] += extra_finishers_per_second - if self.talents.marked_for_death: - attacks_per_second['run_through'][5] += 1. / marked_for_death_cd - if self.stats.gear_buffs.rogue_t17_4pc: - attacks_per_second['run_through'][5] *= 1.25 - - #self.current_variables['cp_spent_on_damage_finishers_per_second'] = (total_evis_per_second) * cp_per_finisher - if 'garrote' in attacks_per_second: - attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] + dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + dfa_count = duration/dfa_cd + dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, swing_timer) + swings -= dfa_lost_swings * dfa_count - time_at_level = 4 / attacks_per_second['saber_slash'] - cycle_duration = 3 * time_at_level + 15 + swing_hits = swings * self.dw_mh_hit_chance + combat_potency_energy = swing_hits * self.combat_potency_regen_per_oh + combat_potency_energy += swing_hits * self.main_gauche_proc_rate * self.combat_potency_from_mg + energy_budget += combat_potency_energy - #if self.level == 100: - # self.bandits_guile_multiplier = 1 + (0*time_at_level + .1*time_at_level + .2*time_at_level + .5 * 15) / cycle_duration - #else: - # avg_stacks = (3 * time_at_level + 45) / cycle_duration #45 is the duration (15s) multiplied by the stack power (30% BG) - # self.bandits_guile_multiplier = 1 + .1 * avg_stacks + attacks_per_second = {} - #hack bg multiplier until it can be removed later + #consider the cost of building to max cps and using rtb + energy_budget -= ss_count * self.saber_slash_energy_cost + #don't account for ps energy becuase ps is free + energy_budget -= self.roll_the_bones_cost + gcd_budget -= (ss_count + ps_count + 1) + attacks_per_second['saber_slash'] = float(ss_count)/duration + attacks_per_second['pistol_shot'] = float(ps_count)/duration + attacks_per_second['roll_the_bones'] = float(finisher_list)/duration + #consider DfA + if self.talents.death_from_above and not ar: + energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost + energy_budget -= dfa_count * self.death_from_above_energy_cost + attacks_per_second['saber_slash'] = float(ss_count * dfa_count)/duration + attacks_per_second['pistol_shot'] = float(ps_count * dfa_count)/duration + attacks_per_second['death_from_above_strike'] = finisher_list + attacks_per_second['death_from_above_pulse'] = finisher_list + for cp in xrange(7): + attacks_per_second['death_from_above_strike'][cp] *= float(dfa_count)/duration + attacks_per_second['death_from_above_pulse'][cp] *= float(dfa_count)/duration + #DfA forces a 2 second GCD + gcd_budget -= dfa_count * (ss_count + ps_count + 2) + + #consider ghostly strike + if self.talents.ghostly_strike: + gs_count = duration/15. + gs_cps = gs_count * (1 + broadsides) + gs_energy = self.ghostly_strike_cost * gs_count + #TODO: use these ghostly strike cps + energy_budget -= gs_energy + gcd_budget -= gs_count + attacks_per_second['ghostly_strike'] = float(gs_count)/duration + + #Burn the rest of our energy until you run out of energy or gcds + gcds_per_minicycle = ss_count + ps_count + 1 + energy_per_minicycle = ss_count * self.saber_slash_energy_cost + self.run_through_energy_cost + minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) - self.bandits_guile_multiplier = 1.0 + alacrity_stacks = 0 + loop_counter = 0 + while energy_budget > 0.1 and gcd_budget > 0.1: + if loop_counter > 20: + raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) - if not ar: - ks_duration = 3 - if self.stats.gear_buffs.rogue_pvp_wod_4pc: - ks_duration += 1 - final_ks_cd = self.rb_actual_cd(attacks_per_second, self.tmp_ks_cd) + self.major_cd_delay + self.ar_duration/2. - if not self.settings.cycle.ksp_immediately: - final_ks_cd += (3 * time_at_level)/2 * (3 * time_at_level)/cycle_duration - attacks_per_second['mh_killing_spree'] = (1 + 2*ks_duration) / (final_ks_cd + self.settings.response_time) - attacks_per_second['oh_killing_spree'] = (1 + 2*ks_duration) / (final_ks_cd + self.settings.response_time) - attacks_per_second['main_gauche'] += attacks_per_second['mh_killing_spree'] * main_gauche_proc_rate + loop_counter += 1 + minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) + + attacks_per_second['saber_slash'] += float(minicycle_count * ss_count)/duration + attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count)/duration + attacks_per_second['run_through'] = finisher_list + for cp in xrange(7): + attacks_per_second['run_through'][cp] *= float(minicycle_count)/duration + #Don't need to converge if we don't have alacrity + if not self.talents.alacrity: + break + else: + energy_budget -= minicycle_count * energy_per_minicycle + gcd_budget -= minicycle_count * gcds_per_minicycle - #if self.talents.shadow_reflection: - # sr_uptime = 8. / self.get_spell_cd('shadow_reflection') - # lst = ('sinister_strike', 'eviscerate', 'revealing_strike') - # if not ar: - # lst += ('mh_killing_spree', 'oh_killing_spree') - # for ability in lst: - # if type(attacks_per_second[ability]) in (tuple, list): - # attacks_per_second['sr_'+ability] = [0,0,0,0,0,0] - # for i in xrange(1, 6): - # attacks_per_second['sr_'+ability][i] = sr_uptime * attacks_per_second[ability][i] - # else: - # attacks_per_second['sr_'+ability] = sr_uptime * attacks_per_second[ability] + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) + new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) + energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration + #compute new MG regen + #TODO: Compute new MG regen in alacrity loop + alacrity_stacks = new_alacrity_stacks - #self.get_poison_counts(attacks_per_second, current_stats) + #Add in swings and MG APS + attacks_per_second['mh_autoattack'] = float(swings)/duration + attacks_per_second['mh_autoattack_hits'] = float(swing_hits)/duration + attacks_per_second['oh_autoattack'] = float(swings)/duration + attacks_per_second['oh_autoattack_hits'] = float(swing_hits)/duration - #print attacks_per_second - return attacks_per_second, crit_rates, additional_info + attacks_per_second['main_gauche'] = attacks_per_second['mh_autoattack_hits'] * self.main_gauche_proc_rate + aps_mg_sources = attacks_per_second['saber_slash'] + attacks_per_second['pistol_shot'] + \ + sum(attacks_per_second['run_through']) + if self.talents.death_from_above: + aps_mg_sources += sum(attacks_per_second['death_from_above_strike']) + \ + (1 + self.settings.num_boss_adds) * attacks_per_second['death_from_above_pulse'] + if self.talents.ghostly_strike: + aps_mg_sources += attacks_per_second['ghostly_strike'] - def rb_actual_cds(self, attacks_per_second, base_cds, avg_rb_effect=10): - final_cds = {} - # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster - for cd_name in base_cds: - final_cds[cd_name] = base_cds[cd_name] * self.rb_cd_modifier(attacks_per_second) - return final_cds + attacks_per_second['main_gauche'] += aps_mg_sources * self.main_gauche_proc_rate - def rb_actual_cd(self, attacks_per_second, base_cd, avg_rb_effect=10): - # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster - return base_cd * self.rb_cd_modifier(attacks_per_second) + return attacks_per_second - def rb_cd_modifier(self, attacks_per_second, avg_rb_effect=10): - # If it's best to always use 5CP finishers as outlaw now, it should continue to be so, this is simpler and faster - offensive_finisher_rate = attacks_per_second['run_through'][5] - if 'death_from_above' in attacks_per_second: - offensive_finisher_rate += attacks_per_second['death_from_above'] - return (1./avg_rb_effect) / (offensive_finisher_rate + (1./avg_rb_effect)) + def outlaw_attack_counts_reroll(self, current_stats, ar=False, + jolly=False, melee=False, buried=False, broadsides=False): + return 8 - def outlaw_attack_counts_ar(self, current_stats, crit_rates=None): - return self.outlaw_attack_counts(current_stats, ar=True, crit_rates=crit_rates) - def outlaw_attack_counts_none(self, current_stats, crit_rates=None): - return self.outlaw_attack_counts(current_stats, crit_rates=crit_rates) def get_max_energy(self): self.max_energy = 100 if self.talents.vigor: self.max_energy += 50 - if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): - self.max_energy += 30 - if self.stats.gear_buffs.rogue_t18_4pc_lfr: - self.max_energy += 20 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) return self.max_energy @@ -1662,13 +1544,8 @@ def get_max_energy(self): #Legion TODO: - #Talents: - #T3:Ancitipcation - #Artifact: # 'flickering_shadows', - # 'second_shuriken', - # 'legionblade' #Items: #Class hall set bonus @@ -1678,7 +1555,6 @@ def get_max_energy(self): #Rotation details: #Combo Point loss - #Finality evis stealth modifier handlings #Shuriken storm dances details #weaponmaster bonus cp gen @@ -1689,9 +1565,16 @@ def subtlety_dps_breakdown(self): if not self.settings.is_subtlety_rogue(): raise InputNotModeledException(_('You must specify a subtlety cycle to match your subtlety spec.')) - #check to make sure cycle is sane: - if self.settings.cycle.cp_builder == 'gloomblade' and not self.talents.gloomblade: - raise InputNotModeledException(_('Gloomblade must be talented to be priamry cp builder')) + self.cp_builder = self.settings.cycle.cp_builder + if self.cp_builder == 'shuriken_storm': + self.dance_cp_builder = 'shuriken_storm' + elif self.cp_builder == 'backstab': + self.dance_cp_builder = 'shadowstrike' + else: + raise InputNotModeledException(_("{} is not a valid cp_builder").format(self.cp_builder)) + + if self.cp_builder == 'backstab' and self.talents.gloomblade: + self.cp_builder = 'gloomblade' self.max_spend_cps = 5 if self.talents.deeper_strategem: @@ -1700,30 +1583,14 @@ def subtlety_dps_breakdown(self): if self.talents.anticipation: self.max_store_cps += 3 - self.finisher_thresholds = {'eviscerate': min(self.settings.cycle.eviscerate_cps, self.max_spend_cps), - 'finality:eviscerate': min(self.settings.cycle.finality_eviscerate_cps, self.max_spend_cps), - 'nightblade': min(self.settings.cycle.nightblade_cps, self.max_spend_cps), - 'finality:nightblade': min(self.settings.cycle.finality_nightblade_cps, self.max_spend_cps), - } - self.set_constants() #symbols of death - self.damage_modifier_cache = 1.2 + self.damage_modifier_cache = 1.2 * (1 +(0.005 * self.traits.legionblade)) stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - trinket_multiplier = 1 - if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - trinket_multiplier = 1+round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod']*1.940260238)/10000 - - soul_cap_mod = 1.0 - if getattr(self.stats.procs, 'soul_capacitor'): - soul_cap= getattr(self.stats.procs, 'soul_capacitor') - self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) - infallible_trinket_mod = 1.0 if self.settings.is_demon: if getattr(self.stats.procs, 'infallible_tracking_charm_mod'): @@ -1731,12 +1598,6 @@ def subtlety_dps_breakdown(self): self.set_rppm_uptime(ift) infallible_trinket_mod = 1+(ift.uptime *0.10) - maalus_mod = 1.0 - if getattr(self.stats.procs,'maalus'): - maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000. - maalus_mod = 1 + (15.0/120* maalus_val) #super hackish - #nightstalker if self.talents.nightstalker: ns_full_multiplier = 0.12 @@ -1749,6 +1610,8 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= 1 + (0.12 * self.dance_finality_nb_uptime) elif key == 'nightblade_ticks': damage_breakdown[key] *= 1 + (0.12 * self.dance_nb_uptime) + elif key in ('eviscerate', 'finality:eviscerate'): + damage_breakdown[key] *= 1 + (0.12 * self.stealth_evis_uptime) #master of subtlety if self.talents.master_of_subtlety: @@ -1760,24 +1623,31 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= mos_full_multiplier elif key == 'shuriken_storm': damage_breakdown[key] *= 1 + (0.1 * self.stealth_shuriken_uptime) + elif key in ('eviscerate', 'finality:eviscerate'): + damage_breakdown[key] *= 1 + (0.1 * self.stealth_evis_uptime) else: damage_breakdown[key] *= mos_uptime_multipler + ds_multiplier = 1.0 + if self.talents.deeper_strategem: + ds_multiplier = 1.1 + for key in damage_breakdown: - damage_breakdown[key] *= maalus_mod - if key == 'shadowstrike': - damage_breakdown[key] *=trinket_multiplier - if "sr_" not in key: - damage_breakdown[key] *= soul_cap_mod - damage_breakdown[key] *= infallible_trinket_mod + damage_breakdown[key] *= infallible_trinket_mod if key == 'shuriken_storm': damage_breakdown[key] *= (1 + self.stealth_shuriken_uptime * 3) - #if "Mirror" not in key: - # damage_breakdown[key] *= mos_multiplier + if key in self.finisher_damage_sources: + damage_breakdown[key] *= ds_multiplier - #add maalus burst - if maalus_mod > 1.0: - damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) + #add AoE damage sources: + if self.settings.num_boss_adds: + for key in damage_breakdown: + if key == 'shuriken_toss': + damage_breakdown[key] *= 1 + self.settings.num_boss_adds + elif key == 'second_shuriken': + damage_breakdown[key] *= 1 + self.settings.num_boss_adds + elif key == 'shadow_nova': + damage_breakdown *= 1 + self.settings.num_boss_adds return damage_breakdown @@ -1818,9 +1688,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) #Set up initial combo point budget - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5 + self.settings.marked_for_death_resets * 5) + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5 * (1 + self.settings.marked_for_death_resets)) self.cp_budget = mfd_cps + + #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps #2 net energy per 6 seconds from relentless strikes if self.talents.enveloping_shadows: @@ -1830,14 +1702,15 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #setup timelines sod_duration = 35 - nightblade_duration = 6 + (2 * self.finisher_thresholds['nightblade']) - finality_nightblade_duration = 6 + (2 * self.finisher_thresholds['finality:nightblade']) + nightblade_duration = 6 + (2 * self.settings.finisher_threshold) + finality_nightblade_duration = 6 + (2 * self.settings.finisher_threshold) #Add attacks that could occur during first pass to aps - attacks_per_second[self.settings.cycle.dance_cp_builder] = 0 + attacks_per_second[self.dance_cp_builder] = 0 attacks_per_second['symbols_of_death'] = 0 attacks_per_second['shadow_dance'] = 0 attacks_per_second['vanish'] = 0 + #Leaving space for opener handling for the first cast sod_timeline = range(0, self.settings.duration, sod_duration) @@ -1850,12 +1723,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): dance_finality_nb_uptime = 0.0 dance_nb_uptime = 0.0 - #Timeline match of ruptures, fill in rest with either finality:eviscerate - for finisher in ['finality:nightblade', 'nightblade', 'finality:eviscerate', 'eviscerate', None]: + for finisher in ['finality:nightblade', 'nightblade', 'eviscerate']: attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] - dance_count = 0 - if finisher in self.settings.cycle.dance_finishers_allowed: - attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] + #Timeline match of ruptures, fill in rest with eviscerate + if self.settings.cycle.dance_finishers_allowed: + dance_count = 0 if finisher == 'finality:nightblade' and self.traits.finality: #Allow SoDs to be used on pandemic for match purposes joint, sod_timeline, finality_nb_timeline = self.timeline_overlap(sod_timeline, finality_nb_timeline, -0.3 * sod_duration) @@ -1866,15 +1738,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) dance_count = len(joint) dance_nb_uptime = dance_count/len(nightblade_timeline) - elif (finisher == 'finality:eviscerate') and self.traits.finality: - dance_count = len(sod_timeline) - sod_timeline = [] elif finisher == 'eviscerate': dance_count = len(sod_timeline) sod_timeline = [] #Not using finishers during dance - if finisher is None and not self.settings.cycle.dance_finishers_allowed: + else: + finisher=None dance_count = len(sod_timeline) sod_timeline = [] @@ -1886,19 +1756,18 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) - del attacks_per_second[None] #Add in ruptures not previously covered nightblade_count = len(nightblade_timeline) - attacks_per_second['nightblade'][self.finisher_thresholds['nightblade']] += float(nightblade_count)/self.settings.duration - self.cp_budget -= self.finisher_thresholds['nightblade'] * nightblade_count - self.energy_budget += (40 * (0.2 * self.finisher_thresholds['nightblade']) - self.get_spell_cost('nightblade')) * nightblade_count - self.dance_budget += (3. * self.finisher_thresholds['nightblade'] * nightblade_count)/60. + attacks_per_second['nightblade'][self.settings.finisher_threshold] += float(nightblade_count)/self.settings.duration + self.cp_budget -= self.settings.finisher_threshold * nightblade_count + self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('nightblade')) * nightblade_count + self.dance_budget += (3. * self.settings.finisher_threshold * nightblade_count)/60. finality_nightblade_count = len(finality_nb_timeline) - attacks_per_second['finality:nightblade'][self.finisher_thresholds['finality:nightblade']] += float(finality_nightblade_count)/self.settings.duration - self.cp_budget -= self.finisher_thresholds['finality:nightblade'] * finality_nightblade_count - self.energy_budget += (40 * (0.2 * self.finisher_thresholds['finality:nightblade']) - self.get_spell_cost('finality:nightblade')) * finality_nightblade_count - self.dance_budget += (3. * self.finisher_thresholds['finality:nightblade'] * finality_nightblade_count)/60. + attacks_per_second['finality:nightblade'][self.settings.finisher_threshold] += float(finality_nightblade_count)/self.settings.duration + self.cp_budget -= self.settings.finisher_threshold * finality_nightblade_count + self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('finality:nightblade')) * finality_nightblade_count + self.dance_budget += (3. * self.settings.finisher_threshold * finality_nightblade_count)/60. #Add in various cooldown abilities #This could be made better with timelining but for now simple time average will do @@ -1940,30 +1809,25 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): vanish_count = self.settings.duration/self.get_spell_cd('vanish') #Treat subterfuge as a mini-dance if self.talents.subterfuge: - if 'finality:eviscerate' in self.settings.cycle.dance_finishers_allowed and self.traits.finality: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='finality:eviscerate', vanish=True) - else: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) else: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy self.cp_budget += vanish_count * net_cps self.dance_budget += ((3. * spent_cps* vanish_count)/60) self.rotation_merge(attacks_per_second, attack_counts, vanish_count) - #Generate one final dance template - if 'finality:eviscerate' in self.settings.cycle.dance_finishers_allowed and self.traits.finality: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='finality:eviscerate') - elif 'eviscerate' in self.settings.cycle.dance_finishers_allowed: + #Generate one final dance templates + if self.settings.cycle.dance_finishers_allowed: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate') else: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None) #Now lets make sure all our budgets are positive cp_per_builder = 1 + self.shadow_blades_uptime - if self.settings.cycle.cp_builder == 'shuriken_storm': + if self.cp_builder == 'shuriken_storm': cp_per_builder += self.settings.num_boss_adds - energy_per_cp = self.get_spell_cost(self.settings.cycle.cp_builder) /(cp_per_builder) + energy_per_cp = self.get_spell_cost(self.cp_builder) /(cp_per_builder) extra_evis = 0 extra_builders = 0 @@ -1996,22 +1860,20 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #can add since we know cp_budget is negative self.energy_budget += self.cp_budget * energy_per_cp extra_builders += abs(self.cp_budget) / cp_per_builder + print extra_builders, energy_per_cp self.cp_budget = 0 - if self.settings.cycle.cp_builder == 'shuriken_storm': + if self.cp_builder == 'shuriken_storm': attacks_per_second['shuriken_storm-no-dance'] = extra_builders / self.settings.duration else: - attacks_per_second[self.settings.cycle.cp_builder] = extra_builders / self.settings.duration - attacks_per_second['eviscerate'][self.finisher_thresholds['eviscerate']] += extra_evis + attacks_per_second[self.cp_builder] = extra_builders / self.settings.duration + attacks_per_second['eviscerate'][self.settings.finisher_threshold] += extra_evis #Hopefully energy budget here isn't negative, if it is we're in trouble #Now we convert all the energy we have left into mini-cycles #Each mini-cycle contains enough 1 dance and generators+finishers for one dance cps_per_dance = 20 - if self.traits.finality: - finishers_per_minicycle = cps_per_dance/(0.5 * self.finisher_thresholds['finality:eviscerate'] + 0.5 * self.finisher_thresholds['eviscerate']) - else: - finishers_per_minicycle = cps_per_dance/self.finisher_thresholds['eviscerate'] + finishers_per_minicycle = cps_per_dance/self.settings.finisher_threshold attack_counts_mini_cycle = attack_counts attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] @@ -2034,11 +1896,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): mini_cycle_count = 1 #mini_cycle_count = 1 #build the minicycle attack_counts - if self.settings.cycle.cp_builder == 'shuriken_storm': + if self.cp_builder == 'shuriken_storm': attack_counts_mini_cycle['shuriken_storm-no-dance'] = builders_per_minicycle else: - attack_counts_mini_cycle[self.settings.cycle.cp_builder] = builders_per_minicycle - attack_counts_mini_cycle['eviscerate'][self.finisher_thresholds['eviscerate']] = finishers_per_minicycle + attack_counts_mini_cycle[self.cp_builder] = builders_per_minicycle + attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] = finishers_per_minicycle self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) self.energy_budget += mini_cycle_energy * mini_cycle_count self.cp_budget += net_cps - 20 + cps_to_generate @@ -2050,7 +1912,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks for ability in ('finality:nightblade', 'nightblade'): @@ -2073,7 +1934,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] self.stealth_shuriken_uptime = 0. - if self.settings.cycle.dance_cp_builder == 'shuriken_storm' and self.settings.cycle.cp_builder == 'shuriken_storm': + if self.cp_builder == 'shuriken_storm': self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] del attacks_per_second['shuriken_storm-no-dance'] @@ -2095,13 +1956,29 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): elif isinstance(attacks_per_second[ability], list) and not any(attacks_per_second[ability]): del attacks_per_second[ability] + #determine how many evis used during dance + if self.settings.cycle.dance_finishers_allowed: + stealth_evis = attacks_per_second['shadow_dance'] + if self.talents.subterfuge: + stealth_evis += attacks_per_second['vanish'] + else: + stealth_evis = 0 + self.stealth_evis_uptime = stealth_evis/sum(attacks_per_second['eviscerate']) + + #convert half of evis to finality + if self.traits.finality: + for cp in xrange(7): + attacks_per_second['finality:eviscerate'][cp] = attacks_per_second['eviscerate'][cp] * 0.5 + attacks_per_second['eviscerate'][cp] *= 0.5 + + if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: + attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] #print attacks_per_second #add SoD auto crits if 'shadowstrike' in attacks_per_second: sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes - #print crit_rates if self.talents.weaponmaster: for ability in attacks_per_second: @@ -2145,21 +2022,21 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): max_dance_energy = dance_gcds * self.energy_regen + self.max_energy if finisher: - net_energy += 40 * (0.2 * self.finisher_thresholds[finisher]) - self.get_spell_cost(finisher) - dance_gcds -=1 - net_cps -= self.finisher_thresholds[finisher] + net_energy += 40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost(finisher) + dance_gcds -= 1 + net_cps -= self.settings.finisher_threshold attack_counts[finisher] = [0, 0, 0, 0, 0, 0, 0] - attack_counts[finisher][self.finisher_thresholds[finisher]] += 1 - spent_cps += self.finisher_thresholds[finisher] + attack_counts[finisher][self.settings.finisher_threshold] += 1 + spent_cps += self.settings.finisher_threshold #fill remaining gcds with shadowstrikes - cp_builder = self.settings.cycle.dance_cp_builder - cp_builder_cost = self.get_spell_cost(cp_builder, cost_mod=cost_mod) - builder_count = min(dance_gcds, math.floor((net_energy+max_dance_energy)/cp_builder_cost)) - if vanish is True: - attack_counts[cp_builder] = min(builder_count, self.settings.cycle.max_vanish_builders) + cp_builder = self.dance_cp_builder + cp_builder_cost = float(self.get_spell_cost(cp_builder, cost_mod=cost_mod)) + builder_count = min(dance_gcds, (net_energy+max_dance_energy)/cp_builder_cost) + if vanish: + attack_counts[cp_builder] = builder_count attack_counts['vanish'] = 1 else: - attack_counts[cp_builder] = min(builder_count, self.settings.cycle.max_dance_builders) + attack_counts[cp_builder] = builder_count attack_counts['shadow_dance'] = 1 net_energy -= attack_counts[cp_builder] * cp_builder_cost diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 6265bdd..259b196 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -72,6 +72,30 @@ def __init__(self, kingsbane_with_vendetta ='just', exsang_with_vendetta='just', class OutlawCycle(Cycle): _cycle_type = 'outlaw' + #Generated by: + #from itertools import combinations + #single = ['jr', 'gm', 's', 'tb', 'bt', 'b'] + #list(combinations(single, 6)) + list(combinations(single, 3)) + list(combinations(single, 2)) + list(combinations(single, 1)) + rtb_combos = [('jr', 'gm', 's', 'tb', 'bt', 'b'), + #3 buffs + ('jr', 'gm', 's'), ('jr', 'gm', 'tb'), + ('jr', 'gm', 'bt'), ('jr', 'gm', 'b'), + ('jr', 's', 'tb'), ('jr', 's', 'bt'), + ('jr', 's', 'b'), ('jr', 'tb', 'bt'), + ('jr', 'tb', 'b'), ('jr', 'bt', 'b'), + ('gm', 's', 'tb'), ('gm', 's', 'bt'), + ('gm', 's', 'b'), ('gm', 'tb', 'bt'), + ('gm', 'tb', 'b'), ('gm', 'bt', 'b'), + ('s', 'tb', 'bt'), ('s', 'tb', 'b'), + ('s', 'bt', 'b'), ('tb', 'bt', 'b'), + #2 buffs + ('jr', 'gm'), ('jr', 's'), ('jr', 'tb'), + ('jr', 'bt'), ('jr', 'b'), ('gm', 's'), + ('gm', 'tb'), ('gm', 'bt'), ('gm', 'b'), + ('s', 'tb'), ('s', 'bt'), ('s', 'b'), + ('tb', 'bt'), ('tb', 'b'), ('bt', 'b'), + #single buffs + ('jr',), ('gm',), ('s',), ('tb',), ('bt',), ('b',)] def __init__(self, blade_flurry=False, between_the_eyes_policy='shark', jolly_roger_reroll=0, grand_melee_reroll=0, shark_reroll=0, @@ -90,12 +114,35 @@ def __init__(self, blade_flurry=False, between_the_eyes_policy='shark', self.buried_treasure_reroll = buried_treasure_reroll self.broadsides_reroll = broadsides_reroll + #build reroll lists here + self.reroll_list = [] + self.keep_list = [] + for combo in self.rtb_combos: + buffs = len(combo) + if 'jr' in combo and buffs <= self.jolly_roger_reroll: + self.reroll_list.append(combo) + continue + if 'gm' in combo and buffs <= self.grand_melee_reroll: + self.reroll_list.append(combo) + continue + if 's' in combo and buffs <= self.shark_reroll: + self.reroll_list.append(combo) + continue + if 'tb' in combo and buffs <= self.true_bearing_reroll: + self.reroll_list.append(combo) + continue + if 'bt' in combo and buffs <= self.buried_treasure_reroll: + self.reroll_list.append(combo) + continue + if 'b' in combo and buffs <= self.broadsides_reroll: + self.reroll_list.append(combo) + continue + self.keep_list.append(combo) class SubtletyCycle(Cycle): _cycle_type = 'subtlety' def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy='just', - eviscerate_cps=5, finality_eviscerate_cps=5, nightblade_cps=5, finality_nightblade_cps=5, dfa_cps = 5, dance_finishers_allowed=True): self.cp_builder = cp_builder #Allowed values: 'shuriken_storm', 'backstab' (implies gloomblade if selected and ssk during dance) self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 92da2e0..5b03f68 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -360,7 +360,7 @@ def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) def between_the_eyes_damage(self, ap, cp): - return .75 * cp * ap * (1 + (0.08 / self.traits.black_powder)) + return .75 * cp * ap * (1 + (0.08 * self.traits.black_powder)) #7*55% AP def blunderbuss_damage(self, ap): @@ -432,6 +432,9 @@ def mh_shadow_blades_damage(self, ap): def oh_shadow_blades_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) + def second_shuriken_damage(self, ap): + return 0.264 * ap + def shuriken_storm_damage(self, ap): return 0.5544 * ap @@ -490,6 +493,7 @@ def get_formula(self, name): 'shadowstrike': self.shadowstrike_damage, 'mh_shadow_blades': self.mh_shadow_blades_damage, 'oh_shadow_blades': self.oh_shadow_blades_damage, + 'second_shuriken': self.second_shuriken_damage, 'shuriken_storm': self.shuriken_storm_damage, 'shuriken_toss': self.shuriken_toss_damage, 'soul_rip': self.soul_rip_damage, @@ -504,6 +508,9 @@ def get_spell_cost(self, ability, cost_mod=1.0): return cost def get_spell_cd(self, ability): + cd = self.ability_cds[ability] + if ability == 'adrenaline_rush': + cd -= 10 * self.traits.fortunes_boon return self.ability_cds[ability] def crit_rate(self, crit=None): From 527a1b8c195b535de1bd00d63b0ced1becd6a293 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Sun, 28 Aug 2016 21:08:03 -0500 Subject: [PATCH 064/265] -first pass raid trinkets --- shadowcraft/objects/proc_data.py | 104 +++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index d621fb0..db5b56a 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -176,6 +176,19 @@ 'trigger': 'all_attacks' }, + 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by 3399 for 10 sec. This effect occurs more often against targets at low health. + 'stat':'stats', + 'value': {'haste': 3399}, + 'duration': 10, + 'proc_name': 'Bloodthirsty Instinct', + 'scaling': 1.378349, + 'item_level': 850, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 3, + 'trigger': 'all_attacks', + }, + 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by 55, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. 'stat':'stats', 'value': {'agi': 42}, @@ -205,6 +218,46 @@ 'trigger': 'all_attacks', }, + 'convergence_of_fates': { #Equip: Your attacks have a chance to reduce the remaining cooldown on one of your powerful abilities by 5 sec. + 'stat':'stats', + 'value': 5, #needs special modeling + 'duration': 0, + 'proc_name': 'Prescience', # reduce cd of shadow blades, vendetta, adrenaline rush + 'scaling': 0, #no values appear to scale ¯\_(ツ)_/¯ + 'item_level': 875, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 3, + 'trigger': 'all_attacks', + }, + + 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing 124520 damage to a random nearby enemy every second for 8 sec. You cannot use abilities during your rage, and your movement speed is slowed by 30%. (2 Min Cooldown) + 'stat':'stats', + 'value': 996160, #124520 * 8 + 'duration': 0, + 'proc_name': 'Fel-Crazed Rage', + 'scaling': 40, + 'item_level': 875, + 'source': 'trinket', + 'type': 'icd', + 'icd': 120, + 'proc_rate': 1, + 'trigger': 'all_attacks', + }, + + 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant Fiery, Frost, or Arcane enchants for 8 sec. + 'stat':'stats', + 'value': {'haste': 0}, #needs special modeling + 'duration': 8, + 'proc_name': 'Triumvirate', + 'scaling': 1.5, + 'item_level': 875, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1, + 'trigger': 'all_attacks', + }, + 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause 54933 additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) 'stat':'spell_damage', 'value': 0, @@ -278,6 +331,32 @@ 'trigger': 'all_attacks', }, + 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 8 sec. + 'stat':'stats', + 'value': {'haste': 0}, #needs special modeling + 'duration': 8, + 'proc_name': 'Allies of Nature', + 'scaling': 1.98186, + 'item_level': 850, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 2, + 'trigger': 'all_attacks', + }, + + 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional 1557 damage and increase the intensity of Recursive Strikes. + 'stat':'stats', + 'value': 1557, #needs special modeling + 'duration': 15, + 'proc_name': 'Recursive Strikes', + 'scaling': 0.5001606, + 'item_level': 875, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1, + 'trigger': 'all_attacks', + }, + 'nightmare_egg_shell': { #Equip: Your melee attacks have a chance to grant you 184 Haste every 1 sec for 20 sec. 'stat':'stats', 'value': {'haste': 184}, @@ -291,6 +370,19 @@ 'trigger': 'all_attacks', }, + 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing 18798 Shadow damage to enemies in the area each second. While you remain in this area, you gain 1540 Leech. (1 Min Cooldown) + 'stat':'spell_damage', + 'value': 18798, #multiple targets not modeled + 'duration': 10, + 'proc_name': 'Infested Ground', + 'scaling': 7.622871, + 'item_level': 850, + 'type': 'icd', + 'icd': 60, + 'source': 'trinket', + 'proc_rate': 1, + }, + 'spiked_counterweight': { #Your melee attacks have a chance to deal 90047 Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to 271840 extra damage dealt. 'stat':'physical_damage', 'value': 103562, #initial hit portion @@ -315,6 +407,18 @@ 'proc_rate': .92, }, + 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for 15132 Physical damage every 0.75 sec. + 'stat':'physical_damage', + 'value': 0, #needs special handling + 'duration': 12, + 'proc_name': 'Horrific Appendages', + 'scaling': 6.136254, + 'item_level': 850, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': .7, + }, + 'tempered_egg_of_serpentrix': { #Equip: Your attacks have a chance to summon a Spawn of Serpentrix to assist you. 'stat':'physical_damage', 'value': 0, #unmodeled From eed5d78aa3c668b7ee2f7ea29aec82cb4b2985bb Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 28 Aug 2016 21:49:00 -0700 Subject: [PATCH 065/265] Replace avg_evis_cps with finisher_threshold --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 38 +++++++++----------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4839e8c..1006b70 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1229,7 +1229,6 @@ def assassination_attack_counts_execute(self, current_stats, crit_rates=None): #Legendaries #Rotation details: - # Roll the Bones handling def outlaw_dps_estimate(self): return sum(self.outlaw_dps_breakdown().values()) @@ -1375,6 +1374,21 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') #compute dps phases each non-rerolling rtb buff combo (excluding tb and shark) ar and not phases = {} + ar_phases = {} + + for phase in self.settings.keep_list: + jolly = 'jr' in phase + melee = 'gm' in phase + buried = 'bt' in phase + broadsides = 'b' in phase + true_bearing = 'tb' in phase + shark = 's' in phase + + chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] + aps = outlaw_attack_counts_mincycle(current_stats, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides) + + @@ -1505,31 +1519,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, #TODO: Compute new MG regen in alacrity loop alacrity_stacks = new_alacrity_stacks - #Add in swings and MG APS - attacks_per_second['mh_autoattack'] = float(swings)/duration - attacks_per_second['mh_autoattack_hits'] = float(swing_hits)/duration - attacks_per_second['oh_autoattack'] = float(swings)/duration - attacks_per_second['oh_autoattack_hits'] = float(swing_hits)/duration - - attacks_per_second['main_gauche'] = attacks_per_second['mh_autoattack_hits'] * self.main_gauche_proc_rate - aps_mg_sources = attacks_per_second['saber_slash'] + attacks_per_second['pistol_shot'] + \ - sum(attacks_per_second['run_through']) - if self.talents.death_from_above: - aps_mg_sources += sum(attacks_per_second['death_from_above_strike']) + \ - (1 + self.settings.num_boss_adds) * attacks_per_second['death_from_above_pulse'] - if self.talents.ghostly_strike: - aps_mg_sources += attacks_per_second['ghostly_strike'] - - attacks_per_second['main_gauche'] += aps_mg_sources * self.main_gauche_proc_rate - + #skip white swings and mg procs because we can do those later return attacks_per_second def outlaw_attack_counts_reroll(self, current_stats, ar=False, jolly=False, melee=False, buried=False, broadsides=False): return 8 - - def get_max_energy(self): self.max_energy = 100 if self.talents.vigor: @@ -1835,7 +1831,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Not enough dances, generate some more if self.dance_budget<0: cps_required = abs(self.dance_budget) * 20 - extra_evis += cps_required/self.avg_evis_cps + extra_evis += cps_required/self.settings.finisher_threshold self.energy_budget += self.net_evis_cost #just subtract the cps because we'll fix those next self.cp_budget -= cps_required From eec630a7c151d863af7e3a650305336e0c0d4a83 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sun, 28 Aug 2016 22:17:57 -0700 Subject: [PATCH 066/265] Plumb positional_uptime Setting Fix finality eviscerate handling --- scripts/subtlety.py | 13 +++++++------ shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 ++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 4cb6989..67f496a 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -56,17 +56,18 @@ versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('3200000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2200002', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '110000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, '110000000000100000') # Set up settings. -test_cycle = settings.SubtletyCycle(cp_builder='shuriken_storm', +test_cycle = settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, + positional_uptime=0.9 ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, - adv_params="", is_demon=True, num_boss_adds=10) + adv_params="", is_demon=True, num_boss_adds=0, marked_for_death_resets=2.0) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) @@ -81,7 +82,7 @@ #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) #talent_ranks = calculator.get_talents_ranking() -#trait_ranks = calculator.get_trait_ranking() +trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -111,7 +112,7 @@ def pretty_print(dict_list): #talent_ranks, #trinkets_ep_value, dps_breakdown, - #trait_ranks + trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1006b70..0afb245 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1634,6 +1634,8 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= (1 + self.stealth_shuriken_uptime * 3) if key in self.finisher_damage_sources: damage_breakdown[key] *= ds_multiplier + if key == 'backstab': + damage_breakdown[key] *= 1 + (0.3 * self.settings.cycle.positional_uptime) #add AoE damage sources: if self.settings.num_boss_adds: @@ -1684,11 +1686,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) #Set up initial combo point budget - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5 * (1 + self.settings.marked_for_death_resets)) + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5. * (1. + self.settings.marked_for_death_resets)) self.cp_budget = mfd_cps - #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps #2 net energy per 6 seconds from relentless strikes if self.talents.enveloping_shadows: @@ -1827,7 +1828,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): extra_evis = 0 extra_builders = 0 - #Not enough dances, generate some more if self.dance_budget<0: cps_required = abs(self.dance_budget) * 20 @@ -1963,6 +1963,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #convert half of evis to finality if self.traits.finality: + attacks_per_second['finality:eviscerate'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): attacks_per_second['finality:eviscerate'][cp] = attacks_per_second['eviscerate'][cp] * 0.5 attacks_per_second['eviscerate'][cp] *= 0.5 From 10c6c617b0c342abd1a2ee6149eb7e445ce8c7d8 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Mon, 29 Aug 2016 01:56:33 -0500 Subject: [PATCH 067/265] -profession trinkets --- shadowcraft/objects/proc_data.py | 72 ++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index db5b56a..906f575 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -176,7 +176,7 @@ 'trigger': 'all_attacks' }, - 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by 3399 for 10 sec. This effect occurs more often against targets at low health. + 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by 3399 for 10 sec. This effect occurs more often against targets at low health. 'stat':'stats', 'value': {'haste': 3399}, 'duration': 10, @@ -189,7 +189,7 @@ 'trigger': 'all_attacks', }, - 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by 55, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. + 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by 55, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. 'stat':'stats', 'value': {'agi': 42}, 'duration': 24, #decays 1/s after 4s @@ -219,11 +219,11 @@ }, 'convergence_of_fates': { #Equip: Your attacks have a chance to reduce the remaining cooldown on one of your powerful abilities by 5 sec. - 'stat':'stats', - 'value': 5, #needs special modeling + 'stat':'ability_modifier', + 'value': 0, #needs special modeling 'duration': 0, 'proc_name': 'Prescience', # reduce cd of shadow blades, vendetta, adrenaline rush - 'scaling': 0, #no values appear to scale ¯\_(ツ)_/¯ + 'scaling': 0, #no values appear to scale 'item_level': 875, 'source': 'trinket', 'type': 'rppm', @@ -231,8 +231,22 @@ 'trigger': 'all_attacks', }, - 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing 124520 damage to a random nearby enemy every second for 8 sec. You cannot use abilities during your rage, and your movement speed is slowed by 30%. (2 Min Cooldown) - 'stat':'stats', + #removed the ":" not sure which way it should be + 'darkmoon_deck_dominion': { #Equip: Increase critical strike by 668-1336. The amount of critical strike depends on the topmost card in the deck. Equip: Periodically shuffle the deck while in combat. + 'stat': 'stats', + 'value': {'crit':1336}, #not accurate, it should be shuffled every proc, may also stack + 'duration': 20, + 'proc_name': 'Dominion Deck', #this does some wierd shuffling crit values /wrists + 'scaling': 0.750315, #only valid for the 1336 draw + 'item_level': 815, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 1, + 'trigger': 'all_attacks' + }, + + 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing 124520 damage to a random nearby enemy every second for 8 sec. You cannot use abilities during your rage, and your movement speed is slowed by 30%. (2 Min Cooldown) + 'stat':'spell_damage', 'value': 996160, #124520 * 8 'duration': 0, 'proc_name': 'Fel-Crazed Rage', @@ -247,7 +261,7 @@ 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant Fiery, Frost, or Arcane enchants for 8 sec. 'stat':'stats', - 'value': {'haste': 0}, #needs special modeling + 'value': {'haste': 0}, #needs special modeling 'duration': 8, 'proc_name': 'Triumvirate', 'scaling': 1.5, @@ -258,7 +272,7 @@ 'trigger': 'all_attacks', }, - 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause 54933 additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) + 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause 54933 additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) 'stat':'spell_damage', 'value': 0, 'duration': 15, @@ -273,7 +287,7 @@ 'trigger': 'all_attacks' }, - 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to 233125 damage for 8 sec. When the bubble is consumed or expires, it explodes and deals 111900 Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) + 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to 233125 damage for 8 sec. When the bubble is consumed or expires, it explodes and deals 111900 Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) 'stat':'spell_damage', 'value': 111900, #multiple targets not modeled 'duration': 0, @@ -288,7 +302,7 @@ 'trigger': 'all_attacks', }, - 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by 2798 for 30 sec. (2 Min Cooldown) + 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by 2798 for 30 sec. (2 Min Cooldown) 'stat':'stats', 'value': {'agi': 2798}, 'duration': 30, @@ -302,6 +316,19 @@ 'trigger': 'all_attacks', }, + 'infernal_alchemist_stone': { #Equip: When you heal or deal damage you have a chance to increase your Strength, Agility, or Intellect by 3275 for 15 sec. Your highest stat is always chosen. + 'stat': 'stats', + 'value': {'agi':3275}, + 'duration': 15, + 'proc_name': 'Infernal Alchemist Stone', + 'scaling': 1.839772, + 'item_level': 815, + 'type': 'rppm', #yes, it is rppm now + 'source': 'trinket', + 'proc_rate': 1, + 'trigger': 'all_attacks' + }, + 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing 43597 Physical damage to all enemies directly in front of you. 'stat':'spell_damage', 'value': 43597, #multiple targets not modeled @@ -333,7 +360,7 @@ 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 8 sec. 'stat':'stats', - 'value': {'haste': 0}, #needs special modeling + 'value': {'haste': 0}, #needs special modeling 'duration': 8, 'proc_name': 'Allies of Nature', 'scaling': 1.98186, @@ -344,9 +371,9 @@ 'trigger': 'all_attacks', }, - 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional 1557 damage and increase the intensity of Recursive Strikes. - 'stat':'stats', - 'value': 1557, #needs special modeling + 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec,causing your auto attacks to deal an additional 1557 damage and increase the intensity of Recursive Strikes. + 'stat':'physical_damage', + 'value': 1557, #needs special modeling 'duration': 15, 'proc_name': 'Recursive Strikes', 'scaling': 0.5001606, @@ -370,7 +397,7 @@ 'trigger': 'all_attacks', }, - 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing 18798 Shadow damage to enemies in the area each second. While you remain in this area, you gain 1540 Leech. (1 Min Cooldown) + 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing 18798 Shadow damage to enemies in the area each second. While you remain in this area, you gain 1540 Leech. (1 Min Cooldown) 'stat':'spell_damage', 'value': 18798, #multiple targets not modeled 'duration': 10, @@ -433,7 +460,7 @@ 'trigger': 'all_attacks', }, - 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal 86952 Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. + 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal 86952 Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. 'stat':'spell_damage', 'value': 695616, #multiple targets not modeled, assuming 4 hits out and 4 in 'duration': 0, @@ -449,7 +476,7 @@ 'trigger': 'all_attacks' }, - 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting [6228 * (1 + $versadmg)] Nature damage per Goo consumed. (20 Sec Cooldown) + 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting [6228 * (1 + $versadmg)] Nature damage per Goo consumed. (20 Sec Cooldown) 'stat':'spell_damage', 'value': 37368, #4709 per stack * 6 stacks 'duration': 0, @@ -466,7 +493,7 @@ 'trigger': 'all_attacks' }, - 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) + 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) 'stat':'spell_damage', 'value': 0, 'duration': 15, @@ -481,7 +508,7 @@ 'trigger': 'all_attacks' }, - 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting (7 * 40619) Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) + 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting (7 * 40619) Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) 'stat':'spell_damage', 'value': 284333, #multiple targets not modeled 'duration': 0, @@ -495,6 +522,7 @@ 'can_crit': True, 'trigger': 'all_attacks' }, + #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', @@ -524,8 +552,6 @@ 'haste_scales': False, 'trigger': 'all_attacks' }, - - #6.2 procs 'maalus': { 'stat': 'damage_modifier', @@ -998,7 +1024,7 @@ }, 'mark_of_warsong': { 'stat': 'stats', - 'value': {'haste':5.5*100}, + 'value': {'haste':5.5 * 100}, 'duration': 20, 'proc_name': 'Mark of the Bleeding Hollow', 'type': 'rppm', From c889fdfd283c9e95800a1e3c63c71846bfe105de Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Mon, 29 Aug 2016 02:38:08 -0500 Subject: [PATCH 068/265] -neck enchant procs --- shadowcraft/objects/proc_data.py | 40 +++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 906f575..d40df0d 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -159,7 +159,45 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, - #7.0 procs + #7.0 neck enchants + 'mark_of_the_hidden_satyr ': { #191259 Deals 41626 to 48375 damage. + 'stat':'spell_damage', + 'value': 45000, #average 41626 to 48375 + 'duration': 0, + 'proc_name': 'Mark of the Hidden Satyr', + 'type': 'rppm', + 'source': 'neck', + 'proc_rate': 2.5, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + #aoe proc? ranged only? + 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 41628 to 48375 damage over 1.5 sec. + 'stat':'physical_damage', + 'value': 45000, #average 41626 to 48375 + 'duration': 0, + 'proc_name': 'Mark of the Distant Army', + 'type': 'rppm', + 'source': 'neck', + 'proc_rate': 2.5, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'mark_of_the_claw': { #Permanently enchants a necklace to sometimes increase critical strike and haste by 550 for 6 sec. + 'stat':'stats', + 'value': {'haste': 550, 'crit': 550}, + 'duration': 6, + 'proc_name': 'Mark of the Claw', + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 2.5, + 'trigger': 'all_attacks', + }, + #7.0 trinket procs 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for 37356 Arcane damage. 'stat':'spell_damage', 'value': 37356, #multiple targets not modeled From 4c000b1b2c306f2290b6cb9f4bf1d82396c644d6 Mon Sep 17 00:00:00 2001 From: wavefunctionp Date: Mon, 29 Aug 2016 02:43:35 -0500 Subject: [PATCH 069/265] -fix mark_of_the_hidden_satyr --- shadowcraft/objects/proc_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index d40df0d..01347bf 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -160,7 +160,7 @@ 'trigger': 'all_attacks' }, #7.0 neck enchants - 'mark_of_the_hidden_satyr ': { #191259 Deals 41626 to 48375 damage. + 'mark_of_the_hidden_satyr': { #191259 Deals 41626 to 48375 damage. 'stat':'spell_damage', 'value': 45000, #average 41626 to 48375 'duration': 0, From c4066120966e6691f486a371bd8ce590c5d5a98e Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 04:16:53 -0700 Subject: [PATCH 070/265] Outlaw Model SnD SnD only outlaw model, still debugging RtB --- scripts/outlaw.py | 36 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 340 +++++++++++++++---- shadowcraft/calcs/rogue/Aldriana/settings.py | 4 +- shadowcraft/calcs/rogue/__init__.py | 20 +- 4 files changed, 317 insertions(+), 83 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 9fe42f4..047b1a2 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -31,8 +31,8 @@ 'food_wod_versatility') # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand -test_mh = stats.Weapon(812.0, 2.6, 'sword', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(812.0, 2.6, 'sword', 'mark_of_the_shattered_hand') +test_mh = stats.Weapon(4821.0, 2.6, 'sword', None) +test_oh = stats.Weapon(4821.0, 2.6, 'sword', None) # Set up procs. #test_procs = procs.ProcsList(('assurance_of_consequence', 588), @@ -45,21 +45,27 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=7655, + agi=20909, stam=19566, - crit=2665, - haste=1594, - mastery=3350, - versatility=6522,) + crit=4402, + haste=5150, + mastery=5999, + versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000001', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') +test_traits = artifact.Artifact(test_spec, test_class, '000000000001000000') # Set up settings. -test_cycle = settings.OutlawCycle(blade_flurry=False, dfa_during_ar=True) +test_cycle = settings.OutlawCycle(blade_flurry=False, + jolly_roger_reroll=1, + grand_melee_reroll=1, + shark_reroll=1, + true_bearing_reroll=1, + buried_treasure_reroll=1, + broadsides_reroll=1) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) @@ -71,12 +77,12 @@ total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) +#ep_values = calculator.get_ep(baseline_dps=total_dps) #tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = #calculator.get_weapon_ep(dps=True, enchants=True) -talent_ranks = calculator.get_talents_ranking() +#talent_ranks = calculator.get_talents_ranking() trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): @@ -103,12 +109,12 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) print '-' * (max_len + 15) -dicts_for_pretty_print = [ep_values, +dicts_for_pretty_print = [#ep_values, #tier_ep_values, - talent_ranks, + #talent_ranks, #trinkets_ep_value, dps_breakdown, trait_ranks ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") +print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0afb245..2894ea2 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1210,11 +1210,7 @@ def assassination_attack_counts_execute(self, current_stats, crit_rates=None): #Legion TODO: #Talents: - #T1:Ghostly Strike #T3:Anticipation - #T5:Cannonball Barrage - #T5:Alacrity - #T5:Killing Spree #T6:Marked for Death #Artifact: @@ -1343,7 +1339,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #Compute values that are true through all RtB variations self.base_energy_regen = 12. if self.talents.vigor: - self.base_energy_regen *= 0.1 + self.base_energy_regen *= 1.1 if self.settings.cycle.blade_flurry: self.base_energy_regen *= .8 + (0.03333 * self.traits.blade_dancer) @@ -1353,16 +1349,17 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod combat_potency_proc_energy = 15 + (1 * self.traits.fortune_strikes) - self.combat_potency_regen_per_oh = combat_potency_proc_energy * .3 * self.stats.oh.speed / 1.4 # the new "normalized" formula - self.combat_potency_from_mg = combat_potency_proc_energy * .3 + self.combat_potency_regen_per_oh = combat_potency_proc_energy * 3. * self.stats.oh.speed / 1.4 # the new "normalized" formula + self.combat_potency_from_mg = combat_potency_proc_energy * 3. self.main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg #compute MG lumped ability costs self.run_through_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer + self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (4 * self.traits.fatebringer) - cost_reducer self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer - self.saber_slash_energy_cost = self. self.get_spell_cost('saber_slash') - cost_reducer + self.saber_slash_energy_cost = self.get_spell_cost('saber_slash') - cost_reducer self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (4 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) if self.talents.slice_and_dice: self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (4 * self.traits.fatebringer) @@ -1372,48 +1369,194 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') - #compute dps phases each non-rerolling rtb buff combo (excluding tb and shark) ar and not + #compute dps phases each non-rerolling rtb buff combo ar and not phases = {} ar_phases = {} - for phase in self.settings.keep_list: - jolly = 'jr' in phase - melee = 'gm' in phase - buried = 'bt' in phase - broadsides = 'b' in phase - true_bearing = 'tb' in phase - shark = 's' in phase - - chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] - aps = outlaw_attack_counts_mincycle(current_stats, jolly=jolly, - melee=melee, buried=buried, broadsides=broadsides) - - - - - - - + keep_chance = 0.0 + keep_tb_uptime = 0.0 + keep_shark_uptime = 0.0 + keep_gm_uptime = 0.0 + maintainence_buff_duration = 6 * (1 + self.settings.finisher_threshold) + if self.talents.slice_and_dice: + aps_normal = self.outlaw_attack_counts_mincycle(current_stats, snd=True, duration=maintainence_buff_duration) + aps_ar = self.outlaw_attack_counts_mincycle(current_stats, snd=True, ar=True, duration=self.ar_duration) + else: + for phase in self.settings.cycle.keep_list: + jolly = 'jr' in phase + melee = 'gm' in phase + buried = 'bt' in phase + broadsides = 'b' in phase + true_bearing = 'tb' in phase + shark = 's' in phase + + chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] + aps = self.outlaw_attack_counts_mincycle(current_stats, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, + duration=maintainence_buff_duration) + aps_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, + duration=self.ar_duration) + + phases[phase] = (chance, aps) + ar_phases[phase] = (chance, aps_ar) + keep_chance += chance + if melee: + keep_gm_uptime += chance + if true_bearing: + keep_tb_uptime += chance + if shark: + keep_shark_uptime += chance + #merge ar and non-ar into single phases + aps_normal = self.merge_attacks_per_second(phases, total_time=keep_chance) + aps_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) + #technically there is a convergence relationship here but ignoring it + if self.talents.alacrity: + alacrity_stacks = self.get_average_alacrity(aps_normal) + alacrity_stacks_ar = self.get_average_alacrity(aps_ar) + else: + alacrity_stacks = 0 + alacrity_stacks_ar = 0 + #now compute the average time for each reroll + phases = {} + ar_phases = {} + net_reroll_time = 0.0 + net_reroll_time_ar = 0.0 + reroll_tb_uptime = 0.0 + reroll_shark_uptime = 0.0 + reroll_gm_uptime = 0.0 + for phase in self.settings.cycle.reroll_list: + jolly = 'jr' in phase + melee = 'gm' in phase + buried = 'bt' in phase + broadsides = 'b' in phase + true_bearing = 'tb' in phase + shark = 's' in phase + + chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] + aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks) + aps_ar, reroll_time_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks = alacrity_stacks_ar) + phases[phase] = (chance * reroll_time, aps) + ar_phases[phase] = (chance * reroll_time_ar, aps_ar) + net_reroll_time += chance * reroll_time + net_reroll_time_ar += chance * reroll_time_ar + if true_bearing: + reroll_tb_uptime += chance * reroll_time + if shark: + reroll_shark_uptime += chance * reroll_time + if melee: + reroll_gm_uptime += chance * reroll_time + + reroll_tb_uptime *= 1./net_reroll_time + reroll_shark_uptime *= 1./net_reroll_time + reroll_gm_uptime *= 1./net_reroll_time + + aps_reroll = self.merge_attacks_per_second(phases, total_time=net_reroll_time) + aps_reroll_ar = self.merge_attacks_per_second(phases, total_time=net_reroll_time_ar) + #now combine the reroll and keep dicts + rtb_keep_duration = 6 * (1+ self.settings.finisher_threshold) + #will pandemic into rtb based on keep_chance + rtb_keep_duration *= 1 + (0.3 * keep_chance) + reroll_duration = net_reroll_time * len(self.settings.cycle.reroll_list) + ar_reroll_duration = net_reroll_time_ar * len(self.settings.cycle.reroll_list) + phases = {'keep': (rtb_keep_duration, aps_normal), + 'reroll': (reroll_duration, aps_reroll)} + aps_normal = self.merge_attacks_per_second(phases, rtb_keep_duration + reroll_duration) + phases = {'keep': (rtb_keep_duration, aps_ar), + 'reroll': (ar_reroll_duration, aps_reroll_ar)} + aps_ar = self.merge_attacks_per_second(phases, rtb_keep_duration + ar_reroll_duration) + + keep_uptime = rtb_keep_duration/(rtb_keep_duration + reroll_duration) + tb_uptime = (keep_uptime * keep_tb_uptime) + (1 - keep_uptime) * reroll_tb_uptime + gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime + shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime + + #determine ar uptime and merge the two distributions + attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), + 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) + ar_uptime = self.ar_duration / self.ar_cd + tb_seconds_per_second = 0 + #if rtb loop on ar cooldown + if not self.talents.slice_and_dice: + old_ar_cd = self.ar_duration + loop_counter = 0 + while (loop_counter < 20): + cp_spend_per_second = 0 + for ability in attacks_per_second: + if ability in self.finisher_damage_sources: + for cp in xrange(7): + cp_spend_per_second += attacks_per_second[ability][cp] * cp + tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime + new_ar_cd = self.ar_cd/(1 + tb_seconds_per_second) + #remerge the ars + attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), + 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) + + if old_ar_cd - new_ar_cd < 0.1: + break + else: + old_ar_cd = new_ar_cd + ar_uptime = self.ar_duration / new_ar_cd + + #add in cannonball and killing spree + if self.talents.killing_spree: + ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) + #ksp is 7 hits per hand + attacks_per_second['killing_spree'] = 7./ksp_cd + if self.talents.cannonball_barrage: + cannonball_barrage_cd = self.get_spell_cd('cannonball_barrage') / (1. + tb_seconds_per_second) + attacks_per_second['cannonball_barrage'] = 1./cannonball_barrage_cd + + + #figure swing timer and add mg + attack_speed_multiplier = self.haste_multiplier * (1 + (0.9 * self.talents.slice_and_dice)) + attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) + if not self.talents.slice_and_dice: + attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) + swing_timer = self.stats.mh.speed / attack_speed_multiplier + attacks_per_second['mh_autoattacks'] = 1./swing_timer + attacks_per_second['oh_autoattacks'] = 1./swing_timer + attacks_per_second['main_gauche'] = self.main_gauche_proc_rate * attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() + #add in mg + for ability in attacks_per_second: + if ability in ['ambush', 'ghostly_strike', 'killing_spree', 'saber_slash']: + attacks_per_second['main_gauche'] += self.main_gauche_proc_rate * attacks_per_second[ability] + elif ability in ['death_from_above_pulse', 'death_from_above_strike','run_through',]: + attacks_per_second['main_gauche'] += sum(attacks_per_second[ability]) * self.main_gauche_proc_rate + + if not self.talents.slice_and_dice: + crit_mod = 1 + (0.25 * shark_uptime) + for ability in crit_rates: + if ability == 'between_the_eyes' and self.settings.cycle.between_the_eyes_policy == 'shark': + crit_rates[ability] += 0.25 + else: + crit_rates[ability] += crit_mod - if self.traints.greed: - attacks_per_second['greed'] = 0.35 * attacks_per_second['run_through'] + if self.traits.greed: + attacks_per_second['greed'] = 0.35 * sum(attacks_per_second['run_through']) if self.traits.blunderbuss: attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] + #print attacks_per_second return attacks_per_second, crit_rates, additional_info def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, - jolly=False, melee=False, buried=False, broadsides=False, duration=30): - + jolly=False, melee=False, buried=False, broadsides=False, duration=30, + #probably don't actually need shark or tb here but simpler + shark=False, true_bearing=True): + maintainence_buff ='roll_the_bones' attack_speed_multiplier = self.haste_multiplier if melee: attack_speed_multiplier *= 1.5 if snd: attack_speed_multiplier *= 1.9 + maintainence_buff = 'slice_and_dice' - energy_regen = self.base_energy_regen + energy_regen = self.base_energy_regen * self.haste_multiplier if buried: energy_regen *= 1.25 @@ -1435,43 +1578,55 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, gcd_budget = duration/gcd_size #since artifacts we'll just compute a one handed swing timer - swing_timer = self.stats.mh_speed / attack_speed_multiplier - swings = duration/swing_timer - swings *= (1 - self.white_swing_downtime) if self.talents.death_from_above and not ar: - dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - (10 * true_bearing) dfa_count = duration/dfa_cd - dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, swing_timer) - swings -= dfa_lost_swings * dfa_count - - swing_hits = swings * self.dw_mh_hit_chance - - combat_potency_energy = swing_hits * self.combat_potency_regen_per_oh - combat_potency_energy += swing_hits * self.main_gauche_proc_rate * self.combat_potency_from_mg - energy_budget += combat_potency_energy + dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.mh.speed/attack_speed_multiplier) + dfa_energy_lost = dfa_lost_swings * (self.main_gauche_proc_rate * self.combat_potency_from_mg + self.combat_potency_regen_per_oh) + energy_budget -= dfa_energy_lost + + mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) * (1 - self.white_swing_downtime) + energy_budget += mg_cp_energy attacks_per_second = {} #consider the cost of building to max cps and using rtb energy_budget -= ss_count * self.saber_slash_energy_cost #don't account for ps energy becuase ps is free - energy_budget -= self.roll_the_bones_cost + if snd: + energy_budget -= self.slice_and_dice_cost + else: + energy_budget -= self.roll_the_bones_cost gcd_budget -= (ss_count + ps_count + 1) attacks_per_second['saber_slash'] = float(ss_count)/duration attacks_per_second['pistol_shot'] = float(ps_count)/duration - attacks_per_second['roll_the_bones'] = float(finisher_list)/duration + + attacks_per_second[maintainence_buff] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second[maintainence_buff][cp] += finisher_list[cp]/duration + + if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': + bte_count = duration/(20 + self.settings.response_time - (10 * true_bearing)) + attacks_per_second['between_the_eyes'] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second['between_the_eyes'][cp] += float(finisher_list[cp] * bte_count)/duration + attacks_per_second['pistol_shot'] += float(bte_count * ps_count)/duration + attacks_per_second['saber_slash'] += float(bte_count * ss_count)/duration + energy_budget -= (bte_count * ss_count) * self.saber_slash_energy_cost + energy_budget -= bte_count * self.between_the_eyes_energy_cost + gcd_budget -= bte_count * (ss_count + ps_count + 1) #consider DfA if self.talents.death_from_above and not ar: energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost energy_budget -= dfa_count * self.death_from_above_energy_cost - attacks_per_second['saber_slash'] = float(ss_count * dfa_count)/duration - attacks_per_second['pistol_shot'] = float(ps_count * dfa_count)/duration - attacks_per_second['death_from_above_strike'] = finisher_list - attacks_per_second['death_from_above_pulse'] = finisher_list + attacks_per_second['saber_slash'] += float(ss_count * dfa_count)/duration + attacks_per_second['pistol_shot'] += float(ps_count * dfa_count)/duration + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): - attacks_per_second['death_from_above_strike'][cp] *= float(dfa_count)/duration - attacks_per_second['death_from_above_pulse'][cp] *= float(dfa_count)/duration + attacks_per_second['death_from_above_strike'][cp] *= float(finisher_list[cp] * dfa_count)/duration + attacks_per_second['death_from_above_pulse'][cp] *= float(finisher_list[cp] * dfa_count)/duration #DfA forces a 2 second GCD gcd_budget -= dfa_count * (ss_count + ps_count + 2) @@ -1488,7 +1643,6 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, #Burn the rest of our energy until you run out of energy or gcds gcds_per_minicycle = ss_count + ps_count + 1 energy_per_minicycle = ss_count * self.saber_slash_energy_cost + self.run_through_energy_cost - minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) alacrity_stacks = 0 loop_counter = 0 @@ -1498,12 +1652,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, loop_counter += 1 minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) - + attacks_per_second['saber_slash'] += float(minicycle_count * ss_count)/duration attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count)/duration - attacks_per_second['run_through'] = finisher_list + attacks_per_second['run_through'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): - attacks_per_second['run_through'][cp] *= float(minicycle_count)/duration + attacks_per_second['run_through'][cp] += float(minicycle_count * finisher_list[cp])/duration + #Don't need to converge if we don't have alacrity if not self.talents.alacrity: break @@ -1511,20 +1666,77 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, energy_budget -= minicycle_count * energy_per_minicycle gcd_budget -= minicycle_count * gcds_per_minicycle - old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) + #ar doubles the effect of alacrity while up + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) * (1 + int(ar)) new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) - new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) - energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration - #compute new MG regen - #TODO: Compute new MG regen in alacrity loop + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) * (1 + int(ar)) + energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration + #compute new CP/MG regen + old_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * alacrity_stacks)) + new_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * new_alacrity_stacks)) + energy_budget += new_cp_mg - old_cp_mg alacrity_stacks = new_alacrity_stacks #skip white swings and mg procs because we can do those later return attacks_per_second - def outlaw_attack_counts_reroll(self, current_stats, ar=False, - jolly=False, melee=False, buried=False, broadsides=False): - return 8 + def outlaw_attack_counts_reroll(self, current_stats, ar=False, + jolly=False, melee=False, buried=False, broadsides=False, alacrity_stacks=0): + #fetch minicycle value + minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_strategem), bool(self.talents.quick_draw), + bool(self.talents.swordmaster), broadsides, jolly) + ss_count, ps_count, finisher_list = self.minicycle_table[minicycle_key] + reroll_energy_cost = (ss_count * self.saber_slash_energy_cost) + self.roll_the_bones_cost + energy_regen = self.base_energy_regen * (self.haste_multiplier + 0.01 * alacrity_stacks) + if buried: + energy_regen *= 1.25 + attack_speed_multiplier = self.haste_multiplier + 0.01 * alacrity_stacks + if melee: + attack_speed_multiplier *= 1.5 + if ar: + energy_regen *= 2.0 + attack_speed_multiplier *= 1.2 + + mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) + total_regen = energy_regen + mg_cp_energy + reroll_time = reroll_energy_cost / total_regen + attacks_per_second = {} + attacks_per_second['saber_slash'] = float(ss_count)/reroll_time + attacks_per_second['pistol_shot'] = float(ps_count)/reroll_time + attacks_per_second['roll_the_bones'] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second['roll_the_bones'][cp] *= finisher_list[cp]/reroll_time + return attacks_per_second, reroll_time + + + #dict of (probability, aps) pairs + def merge_attacks_per_second(self, aps_dicts, total_time=1.0): + attacks_per_second = {} + for key in aps_dicts: + proportion, aps = aps_dicts[key] + uptime = float(proportion)/total_time + + for ability in aps: + if ability in attacks_per_second: + if isinstance(attacks_per_second[ability], list): + for cp in xrange(7): + attacks_per_second[ability][cp] += uptime * aps[ability][cp] + else: + attacks_per_second[ability] += uptime * aps[ability] + else: + if isinstance(aps[ability], list): + attacks_per_second[ability] = aps[ability] + for cp in xrange(7): + attacks_per_second[ability][cp] *= uptime + else: + attacks_per_second[ability] = uptime * aps[ability] + return attacks_per_second + + def get_mg_cp_regen_from_haste(self, haste_multiplier): + swing_per_second = (self.stats.mh.speed * self.dw_mh_hit_chance)/haste_multiplier + mg_regen = self.main_gauche_proc_rate * self.combat_potency_from_mg * swing_per_second + cp_regen = self.combat_potency_regen_per_oh * swing_per_second + return mg_regen + cp_regen def get_max_energy(self): self.max_energy = 100 diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 259b196..63d9cc4 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -42,8 +42,8 @@ def interpret_adv_params(self, s=""): def is_assassination_rogue(self): return self.cycle._cycle_type == 'assassination' - def is_combat_rogue(self): - return self.cycle._cycle_type == 'combat' + def is_outlaw_rogue(self): + return self.cycle._cycle_type == 'outlaw' def is_subtlety_rogue(self): return self.cycle._cycle_type == 'subtlety' diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 5b03f68..9114c7d 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -51,12 +51,28 @@ class RogueDamageCalculator(DamageCalculator): 'envenom', 'rupture_ticks', 'between_the_eyes', 'run_through', 'eviscerate', 'finality:eviscerate', 'nightblade', 'finality:nightblade', - 'nightblade_ticks', 'finality:nightblade_ticks'] + 'nightblade_ticks', 'finality:nightblade_ticks', + 'roll_the_bones', 'slice_and_dice'] #All damage source that are replicated by Blade Flurry blade_flurry_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche','pistol_shot', 'run_through', 'saber_slash'] + #probability of getting X buffs with rtb + rtb_probabilities = { + 1: 0.5923, + 2: 0.3537, + 3: 0.0386, + 6: 0.0154, + } + #number of unique rtb buffs of each amount + rtb_buff_count = { + 1: 6, + 2: 15, + 3: 20, + 6: 1, + } + assassination_mastery_conversion = .035 outlaw_mastery_conversion = .022 subtlety_mastery_conversion = .0276 @@ -377,7 +393,7 @@ def mh_greed_damage(self, ap): return 3.5 * self.get_weapon_damage('mh', ap) def oh_greed_damage(self, ap): - return 3.5 * self.oh_penalty * self.get_weapon_damage('oh', ap) + return 3.5 * self.oh_penalty() * self.get_weapon_damage('oh', ap) #For KsP treat each hit individually def mh_killing_spree_damage(self, ap): From 28f1f058f0a755face8a3ed384f2322ca1e345c4 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 04:20:24 -0700 Subject: [PATCH 071/265] Outlaw RtB Model -Likely overcounting somewhere, but runs --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2894ea2..b481571 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1436,8 +1436,8 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks) - aps_ar, reroll_time_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, - melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks = alacrity_stacks_ar) + aps_ar, reroll_time_ar = self.outlaw_attack_counts_reroll(current_stats, ar=True, jolly=jolly, + melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks_ar) phases[phase] = (chance * reroll_time, aps) ar_phases[phase] = (chance * reroll_time_ar, aps_ar) net_reroll_time += chance * reroll_time From a955f15ea8407914db3a7320c02bf010d5bbc7c6 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 09:18:23 -0700 Subject: [PATCH 072/265] Fix Divide by Zero with RtB thresholds --- scripts/outlaw.py | 19 ++++++++++--------- shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 ++++++---- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 047b1a2..a14af83 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -53,19 +53,20 @@ versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000001', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '000000000001000000') +test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') # Set up settings. test_cycle = settings.OutlawCycle(blade_flurry=False, - jolly_roger_reroll=1, - grand_melee_reroll=1, + #jolly_roger_reroll=1, + #grand_melee_reroll=1, shark_reroll=1, - true_bearing_reroll=1, - buried_treasure_reroll=1, - broadsides_reroll=1) + #true_bearing_reroll=1, + #buried_treasure_reroll=1, + #broadsides_reroll=1 + ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) @@ -83,7 +84,7 @@ #calculator.get_weapon_ep(dps=True, enchants=True) #talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -114,7 +115,7 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b481571..a25ec86 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1449,9 +1449,10 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): if melee: reroll_gm_uptime += chance * reroll_time - reroll_tb_uptime *= 1./net_reroll_time - reroll_shark_uptime *= 1./net_reroll_time - reroll_gm_uptime *= 1./net_reroll_time + if net_reroll_time: + reroll_tb_uptime *= 1./net_reroll_time + reroll_shark_uptime *= 1./net_reroll_time + reroll_gm_uptime *= 1./net_reroll_time aps_reroll = self.merge_attacks_per_second(phases, total_time=net_reroll_time) aps_reroll_ar = self.merge_attacks_per_second(phases, total_time=net_reroll_time_ar) @@ -1473,6 +1474,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime + print shark_uptime #determine ar uptime and merge the two distributions attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) @@ -1541,7 +1543,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - #print attacks_per_second + print attacks_per_second return attacks_per_second, crit_rates, additional_info def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, From 0a77ac9dddf6d8315a116e11492e7c571e10cb9e Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 10:02:33 -0700 Subject: [PATCH 073/265] Fix Fortunes Boon and Fortune Strikes --- scripts/outlaw.py | 8 ++++---- shadowcraft/calcs/rogue/Aldriana/__init__.py | 12 ++++++++---- shadowcraft/calcs/rogue/__init__.py | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index a14af83..3f555da 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -60,9 +60,9 @@ # Set up settings. test_cycle = settings.OutlawCycle(blade_flurry=False, - #jolly_roger_reroll=1, + #jolly_roger_reroll=0, #grand_melee_reroll=1, - shark_reroll=1, + #shark_reroll=1, #true_bearing_reroll=1, #buried_treasure_reroll=1, #broadsides_reroll=1 @@ -84,7 +84,7 @@ #calculator.get_weapon_ep(dps=True, enchants=True) #talent_ranks = calculator.get_talents_ranking() -#trait_ranks = calculator.get_trait_ranking() +trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -115,7 +115,7 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - #trait_ranks + trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a25ec86..4345426 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1349,8 +1349,8 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod combat_potency_proc_energy = 15 + (1 * self.traits.fortune_strikes) - self.combat_potency_regen_per_oh = combat_potency_proc_energy * 3. * self.stats.oh.speed / 1.4 # the new "normalized" formula - self.combat_potency_from_mg = combat_potency_proc_energy * 3. + self.combat_potency_regen_per_oh = combat_potency_proc_energy * 0.3 * self.stats.oh.speed / 1.4 # the new "normalized" formula + self.combat_potency_from_mg = combat_potency_proc_energy * 0.3 self.main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg @@ -1449,10 +1449,15 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): if melee: reroll_gm_uptime += chance * reroll_time + #check for reroll time, to protect from divide by zero if net_reroll_time: reroll_tb_uptime *= 1./net_reroll_time reroll_shark_uptime *= 1./net_reroll_time reroll_gm_uptime *= 1./net_reroll_time + else: + reroll_tb_uptime = 0 + reroll_shark_uptime = 0 + reroll_gm_uptime = 0 aps_reroll = self.merge_attacks_per_second(phases, total_time=net_reroll_time) aps_reroll_ar = self.merge_attacks_per_second(phases, total_time=net_reroll_time_ar) @@ -1474,7 +1479,6 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime - print shark_uptime #determine ar uptime and merge the two distributions attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) @@ -1543,7 +1547,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - print attacks_per_second + #print attacks_per_second return attacks_per_second, crit_rates, additional_info def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 9114c7d..384c991 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -527,7 +527,7 @@ def get_spell_cd(self, ability): cd = self.ability_cds[ability] if ability == 'adrenaline_rush': cd -= 10 * self.traits.fortunes_boon - return self.ability_cds[ability] + return cd def crit_rate(self, crit=None): # all rogues get 10% bonus crit, .05 of base crit for everyone From 3c2488f6a45e34345fcd6835aa049a34c616c4ec Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 11:17:11 -0700 Subject: [PATCH 074/265] Outlaw Done For Now -RT counts are still low -Check comments for NYI list --- scripts/outlaw.py | 18 +++++++++--------- shadowcraft/calcs/rogue/Aldriana/__init__.py | 19 +++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 3f555da..69fbb03 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -53,19 +53,19 @@ versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000001', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') # Set up settings. test_cycle = settings.OutlawCycle(blade_flurry=False, - #jolly_roger_reroll=0, - #grand_melee_reroll=1, - #shark_reroll=1, - #true_bearing_reroll=1, - #buried_treasure_reroll=1, - #broadsides_reroll=1 + jolly_roger_reroll=1, + grand_melee_reroll=1, + shark_reroll=1, + true_bearing_reroll=1, + buried_treasure_reroll=1, + broadsides_reroll=1 ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, adv_params="", is_demon=True, num_boss_adds=0) @@ -84,7 +84,7 @@ #calculator.get_weapon_ep(dps=True, enchants=True) #talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -115,7 +115,7 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4345426..9505744 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1378,6 +1378,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): keep_shark_uptime = 0.0 keep_gm_uptime = 0.0 maintainence_buff_duration = 6 * (1 + self.settings.finisher_threshold) + if self.talents.slice_and_dice: aps_normal = self.outlaw_attack_counts_mincycle(current_stats, snd=True, duration=maintainence_buff_duration) aps_ar = self.outlaw_attack_counts_mincycle(current_stats, snd=True, ar=True, duration=self.ar_duration) @@ -1505,7 +1506,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): else: old_ar_cd = new_ar_cd ar_uptime = self.ar_duration / new_ar_cd - + #add in cannonball and killing spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1604,7 +1605,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, else: energy_budget -= self.roll_the_bones_cost gcd_budget -= (ss_count + ps_count + 1) - attacks_per_second['saber_slash'] = float(ss_count)/duration + attacks_per_second['saber_slash'] = float(ss_count + ps_count)/duration attacks_per_second['pistol_shot'] = float(ps_count)/duration attacks_per_second[maintainence_buff] = [0, 0, 0, 0, 0, 0, 0] @@ -1617,7 +1618,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, for cp in xrange(7): attacks_per_second['between_the_eyes'][cp] += float(finisher_list[cp] * bte_count)/duration attacks_per_second['pistol_shot'] += float(bte_count * ps_count)/duration - attacks_per_second['saber_slash'] += float(bte_count * ss_count)/duration + attacks_per_second['saber_slash'] += float(bte_count * (ss_count + ps_count))/duration energy_budget -= (bte_count * ss_count) * self.saber_slash_energy_cost energy_budget -= bte_count * self.between_the_eyes_energy_cost gcd_budget -= bte_count * (ss_count + ps_count + 1) @@ -1626,7 +1627,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, if self.talents.death_from_above and not ar: energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost energy_budget -= dfa_count * self.death_from_above_energy_cost - attacks_per_second['saber_slash'] += float(ss_count * dfa_count)/duration + attacks_per_second['saber_slash'] += float((ss_count + ps_count) * dfa_count)/duration attacks_per_second['pistol_shot'] += float(ps_count * dfa_count)/duration attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] @@ -1658,8 +1659,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, loop_counter += 1 minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) - - attacks_per_second['saber_slash'] += float(minicycle_count * ss_count)/duration + attacks_per_second['saber_slash'] += float(minicycle_count * (ss_count + ps_count))/duration attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count)/duration attacks_per_second['run_through'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): @@ -1707,7 +1707,7 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, total_regen = energy_regen + mg_cp_energy reroll_time = reroll_energy_cost / total_regen attacks_per_second = {} - attacks_per_second['saber_slash'] = float(ss_count)/reroll_time + attacks_per_second['saber_slash'] = float(ss_count + ps_count)/reroll_time attacks_per_second['pistol_shot'] = float(ps_count)/reroll_time attacks_per_second['roll_the_bones'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): @@ -1717,10 +1717,14 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, #dict of (probability, aps) pairs def merge_attacks_per_second(self, aps_dicts, total_time=1.0): + #print "CALL" + total = 0.0 attacks_per_second = {} for key in aps_dicts: proportion, aps = aps_dicts[key] uptime = float(proportion)/total_time + total+= uptime + #print uptime, total for ability in aps: if ability in attacks_per_second: @@ -2074,7 +2078,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #can add since we know cp_budget is negative self.energy_budget += self.cp_budget * energy_per_cp extra_builders += abs(self.cp_budget) / cp_per_builder - print extra_builders, energy_per_cp self.cp_budget = 0 if self.cp_builder == 'shuriken_storm': From c7d3b426179eabacf8ab76f740be04eaa103edee Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 19:55:09 -0700 Subject: [PATCH 075/265] Assassination Model With NYI -Need to plumb damage modifiers from talents --- scripts/assassination.py | 25 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 529 +++++++------------ shadowcraft/calcs/rogue/Aldriana/settings.py | 4 - shadowcraft/calcs/rogue/__init__.py | 4 +- shadowcraft/objects/talents_data.py | 4 +- 5 files changed, 201 insertions(+), 365 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 477d178..027eea4 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -34,8 +34,8 @@ ) # Set up weapons. -test_mh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_shattered_hand') -test_oh = stats.Weapon(812.0, 1.8, 'dagger', 'mark_of_the_frostwolf') +test_mh = stats.Weapon(4821.0, 1.8, 'dagger', None) +test_oh = stats.Weapon(4821.0, 1.8, 'dagger', None) # Set up procs. #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), @@ -47,18 +47,17 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=7655, + agi=20909, stam=19566, - crit=2665, - haste=1594, - mastery=3350, - versatility=6522,) - + crit=4402, + haste=5150, + mastery=5999, + versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3020020', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000100000') +test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') # Set up settings. test_cycle = settings.AssassinationCycle() @@ -72,7 +71,7 @@ total_dps = sum(entry[1] for entry in dps_breakdown.items()) # Compute EP values. -ep_values = calculator.get_ep(baseline_dps=total_dps) +#ep_values = calculator.get_ep(baseline_dps=total_dps) #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) @@ -103,9 +102,9 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, + #ep_values, #tier_ep_values, - talent_ranks, + #talent_ranks, #trinkets_ep_value, dps_breakdown, #trait_ranks diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b69c4c7..474b330 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -31,7 +31,6 @@ class AldrianasRogueDamageCalculator(RogueDamageCalculator): def get_dps(self): super(AldrianasRogueDamageCalculator, self).get_dps() if self.spec == 'assassination': - self.init_assassination() # why the special init? - aeriwen return self.assassination_dps_estimate() elif self.spec == 'outlaw': return self.outlaw_dps_estimate() @@ -42,7 +41,6 @@ def get_dps(self): def get_dps_breakdown(self): if self.spec == 'assassination': - self.init_assassination() # why the special init? - aeriwen return self.assassination_dps_breakdown() elif self.spec == 'outlaw': return self.outlaw_dps_breakdown() @@ -208,7 +206,6 @@ def set_constants(self): self.relentless_strikes_energy_return_per_cp = 5 #.20 * 25 #should only include bloodlust if the spec can average it in, deal with this later - self.base_speed_multiplier = 1.4 if self.race.berserking: self.true_haste_mod *= (1 + .15 * 10. / (180 + self.settings.response_time)) self.true_haste_mod *= 1 + self.race.get_racial_haste() #doesn't include Berserking @@ -685,41 +682,6 @@ def determine_stats(self, attack_counts_function): crit_rates = None attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats, crit_rates=crit_rates) - # the t16 4pc do not need to be in the main loop because mastery for assa is just increased damage - # and has no impact on the cycle - if self.stats.gear_buffs.rogue_t16_4pc_bonus() and self.settings.is_assassination_rogue(): - #20 stacks of 250 mastery, lasts 5 seconds - mas_per_stack = 38. - max_stacks = 20. - buff_duration = 5. - extra_duration = buff_duration - self.settings.response_time - ability_aps = 0 - mutilate_aps = 0 - for key in ('mutilate', 'dispatch', 'envenom'): - if key in attacks_per_second: - if key in ('envenom'): - ability_aps += sum(attacks_per_second[key]) - elif key == 'mutilate': - ability_aps += attacks_per_second[key] - mutilate_aps += attacks_per_second[key] - else: - ability_aps += attacks_per_second[key] - attack_spacing = 1 / ability_aps - # mutilate gives 2 stacks, so it needs to be included - avg_stacks_per_attack = 1 + mutilate_aps / ability_aps - res = 0. - if attack_spacing < 5: - time_to_max = max_stacks * attack_spacing / avg_stacks_per_attack - time_at_max = max(0., self.vendetta_duration - time_to_max) - max_stacks_able_to_reach = min(self.vendetta_duration / attack_spacing, max_stacks) - avg_stacks = max_stacks_able_to_reach / 2 - avg = time_to_max * avg_stacks + time_at_max * max_stacks + extra_duration * max_stacks_able_to_reach - res = avg * mas_per_stack / self.get_spell_cd('vendetta') - else: - uptime = buff_duration / attack_spacing - res = self.vendetta_duration * uptime * mas_per_stack * avg_stacks_per_attack / self.get_spell_cd('vendetta') - current_stats['mastery'] += res - #some procs need specific prep, think RoRO/VoS self.setup_unique_procs(current_stats, current_stats['agi']+current_stats['ap']) @@ -746,358 +708,235 @@ def compute_damage(self, attack_counts_function): # Assassination DPS functions ########################################################################### - def init_assassination(self): - # Call this before calling any of the assassination_dps functions - # directly. If you're just calling get_dps, you can ignore this as it - # happens automatically; however, if you're going to pull a damage - # breakdown or other sub-result, make sure to call this, as it - # initializes many values that are needed to perform the calculations. + #Legion TODO: - if not self.settings.is_assassination_rogue(): - raise InputNotModeledException(_('You must specify an assassination cycle to match your assassination spec.')) - if self.stats.mh.type != 'dagger' or self.stats.oh.type != 'dagger': - raise InputNotModeledException(_('Assassination modeling requires daggers in both hands')) + #Talents: + #T1:Master Poisoner + #T1:Elaborate Planning + #T2:Nightstalker + #T2:Subter + #T2:SF + #T3:Deeper Strat + #T5:Agonizing Poison - self.spec_convergence_stats = ['haste', 'crit'] + #Artifact: + # 'poison_knives', + # 'surge_of_toxins', + # 'bag_of_tricks', + # 'master_alchemist', + # 'from_the_shadows', + # 'blood_of_the_assassinated', - # Assassasins's Resolve - self.damage_modifier_cache = 1.17 + #Items: + #Class hall set bonus + #Tier bonus + #Trinkets + #Legendaries - #update spec specific proc rates - if getattr(self.stats.procs, 'legendary_capacitive_meta'): - getattr(self.stats.procs, 'legendary_capacitive_meta').proc_rate_modifier = 1.789 - if getattr(self.stats.procs, 'fury_of_xuen'): - getattr(self.stats.procs, 'fury_of_xuen').proc_rate_modifier = 1.55 + #Rotation details: - self.ability_cds['vanish'] = 120 + def assassination_dps_estimate(self): + return sum(self.assassination_dps_breakdown().values()) - self.base_energy_regen = 10 - self.max_energy = 120. + def assassination_dps_breakdown(self): + if not self.spec == 'assassination': + raise InputNotModeledException(_('You must specify a assassination cycle to match your assassination spec.')) - if self.talents.lemon_zest: - self.base_energy_regen *= 1 + .05 * (1 + min(self.settings.num_boss_adds, 2)) - self.max_energy += 15 - if self.race.expansive_mind: - self.max_energy = round(self.max_energy * 1.05, 0) + #outlaw specific constants + self.damage_modifier_cache = 1 + (0.005 * self.traits.slayers_precision) self.set_constants() - self.stat_multipliers['mastery'] *= 1.05 - - if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - spec_needs_converge = True - self.envenom_crit_modifier = 0.0 - - self.vendetta_duration = 20 - self.vendetta_uptime = self.vendetta_duration / (self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay) - self.vendetta_multiplier = .3 - self.vendetta_mult = 1 + self.vendetta_multiplier * self.vendetta_uptime - - def assassination_dps_estimate(self): - return sum(self.assassination_dps_breakdown().values()) - - def update_assassination_breakdown_with_modifiers(self, damage_breakdown, current_stats): - #not sure if this is still needed since the trinkets, multistike and sr stuff are gone -aeriwen - soul_cap_mod = 1.0 - if getattr(self.stats.procs, 'soul_capacitor'): - soul_cap= getattr(self.stats.procs, 'soul_capacitor') - self.set_rppm_uptime(soul_cap) - soul_cap_mod = 1+(soul_cap.uptime * soul_cap.value['damage_mod']/10000.) - infallible_trinket_mod = 1.0 - if self.settings.is_demon: - if getattr(self.stats.procs, 'infallible_tracking_charm_mod'): - ift = getattr(self.stats.procs, 'infallible_tracking_charm_mod') - self.set_rppm_uptime(ift) - infallible_trinket_mod = 1+(ift.uptime *0.10) + self.vendetta_cd = self.get_spell_cd('vendetta') + #cp stacking handlers + if self.settings.cycle.kingsbane_with_vendetta == 'only': + self.kingsbane_cd = min(self.vendetta_cd, self.get_spell_cd('kingsbane')) + kb_venn_uptime = 1.0 + else: + self.kingsbane_cd = self.get_spell_cd('kingsbane') + kb_venn_uptime = self.kingsbane_cd/self.vendetta_cd - maalus_mod = 1.0 - if getattr(self.stats.procs,'maalus'): - maalus = getattr(self.stats.procs, 'maalus') - maalus_val = maalus.value['damage_mod']/10000. - maalus_mod = 1 + (15.0/120* maalus_val) #super hackish - + if self.settings.cycle.exsang_with_vendetta == 'only': + self.exsang_cd = min(self.vendetta_cd), self.get_spell_cd('exsanguinate') + exsang_venn_uptime = 1.0 + else: + self.exsang_cd = self.get_spell_cd('exsanguinate') + exsang_venn_uptime = self.exsang_cd/self.vendetta_cd - for key in damage_breakdown: - damage_breakdown[key] *= maalus_mod - #Fel Lash doesn't MS - if key == 'Fel Lash': - continue - damage_breakdown[key] *= 1 - #mirror of the blademaster doesn't get any player buffs - if key == 'Mirror of the Blademaster': - continue - - damage_breakdown[key] *= self.vendetta_mult - damage_breakdown[key] *= soul_cap_mod - damage_breakdown[key] *= infallible_trinket_mod - if self.level == 100 and key in ('mutilate', 'hemorrhage'): #hacked with hemo in place of dispatch for now -aeriwen - damage_breakdown[key] *= self.emp_envenom_percentage - #add maalus burst - if maalus_mod > 1.0: - damage_breakdown['maalus'] = sum(damage_breakdown.values())*(maalus_mod-1.0) * (self.settings.num_boss_adds+1) + stats, aps, crits, procs, additional_info = self.determine_stats(self.assassination_attack_counts) + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - def assassination_dps_breakdown(self): - current_stats, attacks_per_second, crit_rates, damage_procs, additional_info = self.determine_stats(self.assassination_attack_counts) - damage_breakdown, additional_info = self.get_damage_breakdown(current_stats, attacks_per_second, crit_rates, damage_procs, additional_info) - self.update_assassination_breakdown_with_modifiers(damage_breakdown, current_stats) return damage_breakdown - def assassination_cp_distribution_for_finisher(self, current_cp, crit_rates, ability_count, size_breakdown, cp_limit=4, blindside_proc=0, execute=False): - current_sizes = copy(size_breakdown) - if (current_cp >= cp_limit and not blindside_proc and not execute) or current_cp >= 5: - final_cp = min(current_cp, 5) - current_sizes[final_cp] += 1 - return final_cp, blindside_proc, ability_count, current_sizes - avg_count = {'mutilate':0, 'hemorrhage':0} #hemo is here as a hack for dispatch, but maybe there are times to use hemo to cap cp? - avg_breakdown = [0,0,0,0,0,0] - new_count = copy(ability_count) - - if blindside_proc or execute: #leaving this in because this logic may be useful for using hemo/garrote/fok to optimize finisher cp counts? idk, this is voodoo to me -aeriwen - new_count['hemorrhage'] += 1 # these hemos are dispatch hacks - aeriwen - - n_chance = 1 - crit_rates['hemorrhage'] - c_chance = crit_rates['hemorrhage'] - if self.stats.gear_buffs.rogue_t18_4pc: - n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+4, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - else: - n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+1, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, execute=execute) - - avg_cp = n_chance*n_value + c_chance*c_value - avg_bs_afterwards = n_chance*n_proc + c_chance*c_proc - for key in new_count: - avg_count[key] = n_chance*n_count[key] + c_chance*c_count[key] - for i in xrange(1, 6): - avg_breakdown[i] = n_chance*n_breakdown[i] + c_chance*c_breakdown[i] - return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown - else: - bs_proc_rate = .3 - new_count['mutilate'] += 1 - - n_chance = ((1 - crit_rates['mutilate']) ** 2) * (1-bs_proc_rate) - n_value, n_proc, n_count, n_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit) - n_bs_chance = ((1 - crit_rates['mutilate']) ** 2) * bs_proc_rate - n_bs_value, n_bs_proc, n_bs_count, n_bs_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+2, crit_rates, new_count, current_sizes, cp_limit=cp_limit, blindside_proc=1.) - - c_chance = (1 - (1 - crit_rates['mutilate']) ** 2) * (1-bs_proc_rate) - c_value, c_proc, c_count, c_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit) - c_bs_chance = (1 - (1 - crit_rates['mutilate']) ** 2) * bs_proc_rate - c_bs_value, c_bs_proc, c_bs_count, c_bs_breakdown = self.assassination_cp_distribution_for_finisher(current_cp+3, crit_rates, new_count, current_sizes, cp_limit=cp_limit, blindside_proc=1.) - - avg_cp = n_chance*n_value + n_bs_chance*n_bs_value + c_chance*c_value + c_bs_chance*c_bs_value - avg_bs_afterwards = n_chance*n_proc + n_bs_chance*n_bs_proc + c_chance*c_proc + c_bs_chance*c_bs_proc - for key in new_count: - avg_count[key] = n_chance*n_count[key] + n_bs_chance*n_bs_count[key] + c_chance*c_count[key] + c_bs_chance*c_bs_count[key] - for i in xrange(1, 6): - avg_breakdown[i] = n_chance*n_breakdown[i] + n_bs_chance*n_bs_breakdown[i] + c_chance*c_breakdown[i] + c_bs_chance*c_bs_breakdown[i] - return avg_cp, avg_bs_afterwards, avg_count, avg_breakdown - def assassination_attack_counts(self, current_stats, crit_rates=None): - #several previous settings are now hadcoded, appropriate settings will need to be exposed for release -aeriwen - cpg = 'mutilate' attacks_per_second = {} additional_info = {} - #can't rely on a cache, due to the Cold Blood perk - crit_rates = self.get_crit_rates(current_stats) - for key in crit_rates: #not sure that this is needed anymore -aeriwen - if key in ('mutilate', 'hemorrhage'): - crit_rates[key]+=self.envenom_crit_modifier - crit_rates[key] = min(crit_rates[key], 1.0) - - vw_energy_return = 10 - vw_energy_per_bleed_tick = vw_energy_return - - blindside_proc_rate = [0, .3][cpg == 'mutilate'] - attacks_per_second['envenom'] = [0,0,0,0,0,0] - attacks_per_second['dispatch'] = 0 - - - attack_speed_multiplier = self.base_speed_multiplier * self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - self.attack_speed_increase = attack_speed_multiplier + if crit_rates == None: + crit_rates = self.get_crit_rates(current_stats) - mutilate_cps = 3 - (1 - crit_rates['mutilate']) ** 2 # 1 - (1 - crit_rates['mutilate']) ** 2 is the Seal Fate CP + # set up our finisher distributions + #unlike outlaw these depend on gear (crit) so they cannot be precomputed + self.cp_builder = self.settings.cycle.cp_builder + cp_builder_crit = crit_rates[self.cp_builder] + if self.cp_builder == 'mutilate': + cpg_cps = {2: (1 - cp_builder_crit) ** 2, + 3: 2 * (1 - cp_builder_crit) * cp_builder_crit, + 4: cp_builder_crit ** 2} + elif self.cp_builder == 'fan_of_knives': + raise InputNotModeledException(_('Fan of Knives cp builder unimplemented')) + else: + raise InputNotModeledException(_('Cp builder must be \'mutilate\' or \'fan_of_knives\'')) + #if anticipation we can just assume no waste if self.talents.anticipation: - avg_finisher_size = 5 - avg_size_breakdown = [0,0,0,0,0,1.] #this is for determining the % likelyhood of sizes, not frequency of the sizes - cp_needed_per_finisher = 5 - - if cpg == 'mutilate': - avg_cp_per_cpg = mutilate_cps - - avg_cpgs_per_finisher = cp_needed_per_finisher / avg_cp_per_cpg + avg_cp_per_builder = sum([cp * cpg_cps[cp] for cp in cpg_cps]) + builders_per_finisher = self.settings.finisher_threshold/avg_cp_per_builder + avg_finisher_size = self.settings.finisher_threshold + finisher_list = [0, 0, 0, 0, 0, 0, 0] + finisher_list[self.settings.finisher_threshold] = 1.0 + #otherwise we need to enumerate paths to determine amount of waste given cp threshold else: - ability_count = {'mutilate':0, 'hemorrhage':0} #hemo is a hack to get rid of dispatch errors, i don't understand what this is doing - aeriwen - finisher_size_breakdown = [0,0,0,0,0,0] - - #This is incredibly verbose, but functional. It exhaustively calculates the potential finisher size outcomes using recursion. - #avg_finisher_size - measures average finisher size - #avg_bs_afterwards - likelyhood of finishing with a blindside proc active - #avg_count - number of ability casts per finisher (dictionary of both Mutilate and Dispatch) - #avg_breakdown - frequency of finisher sizes (should sum to 100% or 1) - execute = False - base_cp = 0 - min_finisher_size = self.settings.cycle.min_envenom_size - - avg_finisher_size, avg_bs, avg_count, avg_size_breakdown = self.assassination_cp_distribution_for_finisher(base_cp, crit_rates, - ability_count, finisher_size_breakdown, cp_limit=min_finisher_size, execute=execute) - if avg_bs > 0: - mut_start_chance = 1/(1+avg_bs) - bs_start_chance = 1 - mut_start_chance - extra_tuple = self.assassination_cp_distribution_for_finisher(base_cp, crit_rates, ability_count, finisher_size_breakdown, cp_limit=4, blindside_proc=1) - - avg_finisher_size = avg_finisher_size*mut_start_chance + extra_tuple[0]*bs_start_chance - for key in ability_count: - avg_count[key] = avg_count[key]*mut_start_chance + extra_tuple[2][key]*bs_start_chance - for i in xrange(1,6): - avg_size_breakdown[i] = avg_size_breakdown[i]*mut_start_chance + extra_tuple[3][i]*bs_start_chance - - avg_cpgs_per_finisher = avg_count[cpg] - avg_cp_per_cpg = avg_finisher_size / avg_cpgs_per_finisher - - cpg_energy_cost = self.get_spell_cost(cpg) - - #disable opener logic for now -aeriwen - #if self.settings.opener_name == 'cpg': - #current_opener_name = cpg - - #cp_generated = 0 - #if current_opener_name == 'envenom': - # opener_net_cost = self.get_spell_cost('envenom', cost_mod=1-self.get_shadow_focus_multiplier())) - # energy_regen += opener_net_cost * self.total_openers_per_second - #elif current_opener_name == cpg: - # opener_net_cost = self.get_spell_cost(current_opener_name, cost_mod=(1-self.get_shadow_focus_multiplier())) - # opener_net_cost += cpg_cost_reduction - # cp_generated = avg_cp_per_cpg - # #energy_regen += opener_net_cost * self.total_openers_per_second - # energy_regen += opener_net_cost * 1 #hack opener per sec calc - #else: - # opener_net_cost = self.get_spell_cost(current_opener_name, cost_mod=self.get_shadow_focus_multiplier()) - # #attacks_per_second[current_opener_name] = self.total_openers_per_second - # attacks_per_second[current_opener_name] = self.total_openers_per_second - # if current_opener_name == 'mutilate': - # attacks_per_second['hemorrhage'] += self.total_openers_per_second * blindside_proc_rate # more hemo dispatch hacks -aeriwen - # if current_opener_name in ('mutilate', 'hemorrhage', 'cpg'): #another dispatch hack - aeriwen - # cp_generated = mutilate_cps + dispatch_cps * blindside_proc_rate - # elif current_opener_name == 'ambush': - # cp_generated = 2 + crit_rates['ambush'] - # energy_regen -= opener_net_cost * self.total_openers_per_second - #for i in xrange(1,6): - # attacks_per_second['envenom'][i] = self.total_openers_per_second * cp_generated / i * avg_size_breakdown[i] - - attacks_per_second['venomous_wounds'] = .5 - energy_regen_with_rupture = self.get_asassination_energy_regen(current_stats) + .5 * vw_energy_return - - avg_cycle_length = 4. * (1 + avg_finisher_size + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - - energy_for_rupture = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('rupture') - - attacks_per_second['rupture'] = 1. / avg_cycle_length - energy_per_cycle = avg_cycle_length * energy_regen_with_rupture - - energy_for_dfa = 0 - if self.talents.death_from_above: - dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_cd += energy_for_rupture / (4 * energy_regen_with_rupture) - dfa_interval = 1./dfa_cd - energy_for_dfa = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('death_from_above') - - attacks_per_second['death_from_above'] = dfa_interval - attacks_per_second['death_from_above_strike'] = [finisher_chance * dfa_interval for finisher_chance in avg_size_breakdown] - attacks_per_second['death_from_above_pulse'] = [finisher_chance * dfa_interval * self.settings.num_boss_adds for finisher_chance in avg_size_breakdown] - - #Normalize DfA energy intervals to rupture intervals - energy_for_dfa *= (avg_cycle_length)/(1./dfa_interval) - - energy_for_envenoms = energy_per_cycle - energy_for_rupture - energy_for_dfa - - envenom_energy_cost = avg_cpgs_per_finisher * cpg_energy_cost + self.get_spell_cost('envenom') - envenoms_per_cycle = energy_for_envenoms / envenom_energy_cost - - envenoms_per_second = envenoms_per_cycle / avg_cycle_length - finishers_per_second = envenoms_per_second + attacks_per_second['rupture'] + #TODO: This is nonsense + builders_per_finisher = 2.8 + avg_finisher_size = 5.2 + finisher_list = [0, 0, 0, 0, 0, 0.8, 0.2] + cp_builder_energy_per_finisher = builders_per_finisher * self.get_spell_cost(self.cp_builder) - if self.talents.death_from_above: - finishers_per_second += attacks_per_second['death_from_above'] - - cpgs_per_second = avg_cpgs_per_finisher * finishers_per_second + #set up our energy budget + self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + energy_regen = 10 * self.haste_multiplier + if self.talents.vigor: + energy_regen *= 1.1 + + #set up rupture + attacks_per_second['rupture'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['rupture_ticks'] = [0, 0, 0, 0, 0, 0, 0] + base_rupture_duration = 4 * (1 + avg_finisher_size) + if self.talents.exsanguinate: + #assume full pandemic on exsanged ruptures + exsang_rupture_duration = (1.3 * base_rupture_duration)/2 + #rupture we're pandemicing from + exsang_from_duration = 0.7 * base_rupture_duration + normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration)/base_rupture_duration + ruptures_per_second = (2. + normal_ruptures_per_exsang_cd) / self.exsang_cd + rupture_ticks_per_second = 1. * float(exsang_rupture_duration)/ self.exsang_cd + \ + 0.5 * float(self.exsang_cd - exsang_rupture_duration)/self.exsang_cd + else: + ruptures_per_second = 1. / base_rupture_duration + rupture_ticks_per_second = 0.5 - if cpg in attacks_per_second: - attacks_per_second[cpg] += cpgs_per_second + for cp in xrange(7): + attacks_per_second['rupture'][cp] = ruptures_per_second * finisher_list[cp] + attacks_per_second['rupture_ticks'][cp] = rupture_ticks_per_second * finisher_list[cp] + rupture_cost_per_second = self.get_spell_cost('rupture') * ruptures_per_second + rupture_cost_per_second += cp_builder_energy_per_finisher * ruptures_per_second + attacks_per_second[self.cp_builder] = ruptures_per_second * builders_per_finisher + + #set up garrote: + base_garrote_duration = 18. + garrote_cooldown = 15. + if self.talents.exsanguinate: + exsang_garrote_duration = base_garrote_duration / 2 + exsang_downtime = garrote_cooldown - exsang_garrote_duration + normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration + attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd + attacks_per_second['garrote_ticks'] = 1.5 * float(exsang_garrote_duration) / self.exsang_cd + \ + 3.0 * float(self.exsang_cd - exsang_garrote_duration) / self.exsang_cd else: - attacks_per_second[cpg] = cpgs_per_second + attacks_per_second['garrote'] = 1. / base_garrote_duration + attacks_per_second['garrote_ticks'] = 1. / 3 - attacks_per_second['rupture_ticks'] = [0,0,0,0,0,.5] + garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] - if self.talents.anticipation: - attacks_per_second['envenom'][5] += envenoms_per_second - else: - for i in xrange(1,6): - attacks_per_second['envenom'][i] = envenoms_per_second * avg_size_breakdown[i] - for i in xrange(1, 6): - ticks_per_rupture = 2 * (1 + i + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) - attacks_per_second['rupture_ticks'][i] = ticks_per_rupture * attacks_per_second['rupture'] * avg_size_breakdown[i] + #Now that ticks are done, we can compute VW regen + vw_energy_per_tick = 7 + 3 * self.talents.venom_rush + vw_regen_per_second = vw_energy_per_tick * (sum(attacks_per_second['rupture_ticks']) + attacks_per_second['garrote_ticks']) - if self.talents.marked_for_death: - attacks_per_second['envenom'][5] += 1. / self.get_spell_cd('marked_for_death') + net_energy_per_second = energy_regen + vw_regen_per_second + net_energy_per_second -= rupture_cost_per_second - garrote_cost_per_second - if 'garrote' in attacks_per_second: - attacks_per_second['garrote_ticks'] = 6 * attacks_per_second['garrote'] + #compute cooldowned talents: + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5. * (1. + self.settings.marked_for_death_resets)) + cp_budget = mfd_cps - attacks_per_second['envenom'][5] += 1. / 180 + if self.traits.kingsbane: + attacks_per_second['kingsbane'] = 1./self.kingsbane_cd + attacks_per_second['kingsbane_ticks'] = 7. / self.kingsbane_cd + net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] - if self.level == 100: - finisher_per_second = sum(attacks_per_second['envenom']) + attacks_per_second['rupture'] - if self.talents.death_from_above: - finisher_per_second += sum(attacks_per_second['death_from_above_strike']) - self.emp_envenom_percentage = 1 + .3 * (1 - attacks_per_second['rupture']/finisher_per_second) - crit_mod = 0 - if getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel'): - #may need a better model for envenom uptime at high cp gen - crit_mod = round(getattr(self.stats.procs, 'bleeding_hollow_toxin_vessel').value['ability_mod'])/10000 - self.envenom_crit_modifier = crit_mod * (1 - attacks_per_second['rupture']/finisher_per_second) - - self.swing_reset_spacing = None #hack resets for now, since it is broken -aeriwen - white_swing_downtime = 0 - if self.swing_reset_spacing is not None: - white_swing_downtime += .5 / self.swing_reset_spacing - attacks_per_second['mh_autoattacks'] = self.attack_speed_increase / self.stats.mh.speed * (1 - white_swing_downtime) - attacks_per_second['oh_autoattacks'] = self.attack_speed_increase / self.stats.oh.speed * (1 - white_swing_downtime) + if self.talents.hemorrhage: + hemos_per_second = 1./20 + attacks_per_second['hemorrhage'] = hemos_per_second + hemo_cps = (1 + crit_rates['hemorrhage']) * (20. / self.settings.duration) + cp_budget += hemo_cps + net_energy_per_second -= self.get_spell_cost('hemorrhage') * hemos_per_second if self.talents.death_from_above: - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / self.attack_speed_increase) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / self.attack_speed_increase) + dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + dfa_per_second = 1./self.dfa_cd + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second['death_from_above_pulse'] = dfa_per_second * finisher_list[cp] + attacks_per_second['death_from_above_strike'] = dfa_per_second * finisher_list[cp] + attacks_per_second[self.cp_builder] += dfa_per_second * builders_per_finisher + dfa_cost_per_second = self.get_spell_cost('death_from_above') * dfa_per_second + dfa_cost_per_second += cp_builder_energy_per_finisher * dfa_per_second + net_energy_per_second -= dfa_cost_per_second + + #form whats left into a budget + energy_budget = self.settings.duration * net_energy_per_second + max_energy = 120 + if self.talents.vigor: + max_energy += 50 + energy_budget += max_energy + #assume you get 50% of max energy back each time + if self.traits.urge_to_kill: + energy_budget += (self.settings.duration/self.vendetta_cd) * 0.5 * max_energy + + attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] + #spend those extra cps + if cp_budget > 0: + extra_envenom = float(cp_budget)/avg_finisher_size + energy_budget -= self.get_spell_cost('envenom') * extra_envenom + extra_envenom_per_second = extra_envenom/self.settings.duration + for cp in xrange(7): + attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] - attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd - attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd + #now burn whats left in a minicycle + mini_cycle_energy = self.get_spell_cost('envenom') + cp_builder_energy_per_finisher + loop_counter = 0 - attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance - attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + alacrity_stacks = 0 + while energy_budget > 0.1: + #print self.energy_budget + if loop_counter > 20: + raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) + loop_counter += 1 + + total_minicycles = float(energy_budget) / mini_cycle_energy + attacks_per_second[self.cp_builder] += float(total_minicycles * builders_per_finisher) / self.settings.duration + finishers_per_second = total_minicycles / self.settings.duration + for cp in xrange(7): + attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second + energy_budget -= total_minicycles * mini_cycle_energy - self.settings.dmg_poison = 'dp' #hack in deadly poison for now - self.get_poison_counts(attacks_per_second, current_stats) + if self.talents.alacrity: + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) + new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) + energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration + alacrity_stacks = new_alacrity_stacks - if self.level == 100: - #this is to update the crit rate for envenom due to the 'crit on Vendetta cast' perk, unlikely to ever be another ability - crit_uptime = (1./(self.get_spell_cd('vendetta') + self.settings.response_time + self.major_cd_delay)) / sum(attacks_per_second['envenom']) - #this takes the difference between normal and guaranteed crits (1 - crit_rate), and multiplies it by the "uptime" across all envenoms - #it's then added back to the original crit rate - crit_rates['envenom'] += crit_uptime * (1 - crit_rates['envenom']) + #print attacks_per_second return attacks_per_second, crit_rates, additional_info - def get_asassination_energy_regen(self, current_stats): #this should probably be handled in a super class -aeriwen - energy_regen = self.base_energy_regen * self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + self.bonus_energy_regen - if self.talents.marked_for_death: #not sure if this is the right model for dfa -aeriwen - energy_regen -= 10. / self.get_spell_cd('marked_for_death') # 35-25 - return energy_regen - ########################################################################### # Outlaw DPS functions ########################################################################### diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 924dc10..63d9cc4 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -70,10 +70,6 @@ def __init__(self, kingsbane_with_vendetta ='just', exsang_with_vendetta='just', self.kingsbane_with_vendetta = kingsbane_with_vendetta self.exsang_with_vendetta = exsang_with_vendetta - def __init__(self, min_envenom_size=4): - assert min_envenom_size in self.allowed_values - self.min_envenom_size = min_envenom_size - class OutlawCycle(Cycle): _cycle_type = 'outlaw' #Generated by: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 384c991..17bcbab 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -356,7 +356,7 @@ def hemorrhage_damage(self, ap): def mh_kingsbane_damage(self, ap): return 3 * self.get_weapon_damage('mh', ap) * (1 + (0.4 * self.talents.master_poisoner)) def oh_kingsbane_damage(self, ap): - return 3 * self.oh_penalty * self.get_weapon_damage('oh', ap) * (1 + (0.4 * self.talents.master_poisoner)) + return 3 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.4 * self.talents.master_poisoner)) def kingsbane_tick_damage(self, ap): return 0.45 * ap * (1 + (0.4 * self.talents.master_poisoner)) def mh_mutilate_damage(self, ap): @@ -527,6 +527,8 @@ def get_spell_cd(self, ability): cd = self.ability_cds[ability] if ability == 'adrenaline_rush': cd -= 10 * self.traits.fortunes_boon + elif ability == 'vendetta': + cd -= 10 * self.traits.master_assassin return cd def crit_rate(self, crit=None): diff --git a/shadowcraft/objects/talents_data.py b/shadowcraft/objects/talents_data.py index c56b064..9900159 100644 --- a/shadowcraft/objects/talents_data.py +++ b/shadowcraft/objects/talents_data.py @@ -76,8 +76,8 @@ ('deeper_strategem', 'anticipation', 'vigor'), ('leeching_poison', 'elusiveness', 'cheat_death'), ('thuggee', 'prey_on_the_weak', 'internal_bleeding'), - ('agonizing_poison', 'alacrity', 'blood_sweat'), - ('lemon_zest', 'marked_for_death', 'death_from_above') + ('agonizing_poison', 'alacrity', 'exsanguinate'), + ('venom_rush', 'marked_for_death', 'death_from_above') ), ('rogue', 'outlaw'): ( ('ghostly_strike', 'swordmaster', 'quick_draw'), From c0fbd06b350ba2bd88abdb1688ea2d069929d4e7 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 20:06:39 -0700 Subject: [PATCH 076/265] Fix Proc crash with Rupture --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 474b330..d5b807e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -335,7 +335,7 @@ def get_mh_procs_per_second(self, proc, attacks_per_second, crit_rates): triggers_per_second += sum(attacks_per_second[ability]) if proc.procs_off_apply_debuff() and not proc.procs_off_crit_only(): if 'rupture' in attacks_per_second: - triggers_per_second += attacks_per_second['rupture'] + triggers_per_second += sum(attacks_per_second['rupture']) if 'garrote' in attacks_per_second: triggers_per_second += attacks_per_second['garrote'] if 'hemorrhage_ticks' in attacks_per_second: From 0894cb150323c65c5ba0b9ba070a4705d4342fb7 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 20:51:11 -0700 Subject: [PATCH 077/265] Fix Death From Above handling --- scripts/assassination.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 027eea4..8119df2 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -54,7 +54,7 @@ mastery=5999, versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('3020020', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3020023', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d5b807e..2efe09a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -518,6 +518,7 @@ def get_average_alacrity(self, attacks_per_second): #Don't double count DfA if finisher in attacks_per_second and finisher != 'death_from_above_pulse': for cp in xrange(7): + print finisher stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] stack_time = 20/stacks_per_second if stack_time > self.settings.duration: @@ -878,12 +879,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_per_second = 1./self.dfa_cd + dfa_per_second = 1./dfa_cd attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): - attacks_per_second['death_from_above_pulse'] = dfa_per_second * finisher_list[cp] - attacks_per_second['death_from_above_strike'] = dfa_per_second * finisher_list[cp] + attacks_per_second['death_from_above_pulse'][cp] = dfa_per_second * finisher_list[cp] + attacks_per_second['death_from_above_strike'][cp] = dfa_per_second * finisher_list[cp] attacks_per_second[self.cp_builder] += dfa_per_second * builders_per_finisher dfa_cost_per_second = self.get_spell_cost('death_from_above') * dfa_per_second dfa_cost_per_second += cp_builder_energy_per_finisher * dfa_per_second From 7cdcf44aff0baa3acad240e083b1e7660be88185 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 21:21:20 -0700 Subject: [PATCH 078/265] By Hand Path Enumeration -Should be fixed to generate on the fly --- scripts/assassination.py | 5 +-- shadowcraft/calcs/rogue/Aldriana/__init__.py | 32 ++++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 8119df2..0580b4e 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -54,14 +54,15 @@ mastery=5999, versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('3020023', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3010023', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') # Set up settings. test_cycle = settings.AssassinationCycle() -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360) +test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, + finisher_threshold=5) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2efe09a..3e53ce0 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -518,7 +518,6 @@ def get_average_alacrity(self, attacks_per_second): #Don't double count DfA if finisher in attacks_per_second and finisher != 'death_from_above_pulse': for cp in xrange(7): - print finisher stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] stack_time = 20/stacks_per_second if stack_time > self.settings.duration: @@ -717,7 +716,6 @@ def compute_damage(self, attack_counts_function): #T2:Nightstalker #T2:Subter #T2:SF - #T3:Deeper Strat #T5:Agonizing Poison #Artifact: @@ -802,10 +800,32 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): finisher_list[self.settings.finisher_threshold] = 1.0 #otherwise we need to enumerate paths to determine amount of waste given cp threshold else: - #TODO: This is nonsense - builders_per_finisher = 2.8 - avg_finisher_size = 5.2 - finisher_list = [0, 0, 0, 0, 0, 0.8, 0.2] + #TODO: Super hackish, do this right + finisher_list = [0, 0, 0, 0, 0, 0, 0] + if self.settings.finisher_threshold == 4: + paths = [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4,)] + elif self.settings.finisher_threshold == 5: + paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + elif self.settings.finisher_threshold == 6: + paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 2), (2, 3, 3), (2, 3, 4), (2, 4), + (3, 2, 2), (3, 2, 3), (3, 2, 4), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + else: + raise InputNotModeledException(_('Finisher thresholds less than 4 unimplemented')) + max_cps = 5 + if self.talents.deeper_strategem: + max_cps = 6 + builders_per_finisher = 0.0 + avg_finisher_size = 0.0 + finisher_list = [0, 0, 0, 0, 0, 0, 0] + for path in paths: + chance = 1.0 + for step in path: + chance *= cpg_cps[step] + builders_per_finisher += chance * len(path) + size = min(max_cps, sum(path)) + avg_finisher_size += chance * size + finisher_list[size] += chance + cp_builder_energy_per_finisher = builders_per_finisher * self.get_spell_cost(self.cp_builder) #set up our energy budget From b9a4d5a43baeea3c0751a853d1076162ce1ae186 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 29 Aug 2016 22:43:23 -0700 Subject: [PATCH 079/265] Many Assn Talents and Traits Implmented --- scripts/assassination.py | 6 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 119 ++++++++++++++----- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 0580b4e..bf5618a 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -54,7 +54,7 @@ mastery=5999, versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('3010023', test_spec, test_class, level=test_level) +test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') @@ -77,7 +77,7 @@ talent_ranks = calculator.get_talents_ranking() -#trait_ranks = calculator.get_trait_ranking() +trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -108,7 +108,7 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - #trait_ranks + trait_ranks ] pretty_print(dicts_for_pretty_print) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 3e53ce0..0f15c0e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -481,36 +481,26 @@ def get_poison_counts(self, attacks_per_second, current_stats): mh_hits_per_second = self.get_mh_procs_per_second(poison, attacks_per_second, None) oh_hits_per_second = self.get_oh_procs_per_second(poison, attacks_per_second, None) total_hits_per_second = mh_hits_per_second + oh_hits_per_second - if poison: - poison_base_proc_rate = .3 - else: + if not poison: return - proc_multiplier = 1 - if self.spec == 'outlaw': - if self.settings.cycle.blade_flurry: - ms_value = 1 + min(2 * (self.stats.get_multistrike_chance_from_rating(rating=current_stats['multistrike']) + self.buffs.multistrike_bonus()), 2) - proc_multiplier += min(self.settings.num_boss_adds, [4, 999][self.level==100]) * ms_value - - if self.settings.is_assassination_rogue(): - poison_base_proc_rate += .2 - poison_envenom_proc_rate = poison_base_proc_rate + .3 - aps_envenom = attacks_per_second['envenom'] - if self.talents.death_from_above: - aps_envenom = map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom']) - envenom_uptime = min(sum([(1 + cps + self.stats.gear_buffs.rogue_t15_2pc_bonus_cp()) * aps_envenom[cps] for cps in xrange(1, 6)]), 1) - avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime + + if self.talents.agonizing_poison: + poison_base_proc_rate = 0.2 else: - avg_poison_proc_rate = poison_base_proc_rate + poison_base_proc_rate = 0.5 + poison_envenom_proc_rate = poison_base_proc_rate + 0.3 + aps_envenom = attacks_per_second['envenom'] + if self.talents.death_from_above: + aps_envenom = map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom']) + envenom_uptime = min(sum([(1 + cps) * aps_envenom[cps] for cps in xrange(1, 6)]), 1) + avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime - if self.settings.dmg_poison == 'sp': - poison_procs = avg_poison_proc_rate * total_hits_per_second * proc_multiplier - 1 / self.settings.duration - attacks_per_second['swift_poison'] = poison_procs - elif self.settings.dmg_poison == 'dp': - poison_procs = avg_poison_proc_rate * total_hits_per_second * proc_multiplier - 1 / self.settings.duration + if self.talents.agonizing_poison: + attacks_per_second['agonizing_poison'] = total_hits_per_second * avg_poison_proc_rate + else: + poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = 1. / 3 * proc_multiplier - elif self.settings.dmg_poison == 'wp': - attacks_per_second['wound_poison'] = total_hits_per_second * avg_poison_proc_rate + attacks_per_second['deadly_poison'] = 1. / 3 def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 @@ -711,20 +701,14 @@ def compute_damage(self, attack_counts_function): #Legion TODO: #Talents: - #T1:Master Poisoner - #T1:Elaborate Planning #T2:Nightstalker #T2:Subter #T2:SF - #T5:Agonizing Poison #Artifact: # 'poison_knives', - # 'surge_of_toxins', # 'bag_of_tricks', - # 'master_alchemist', # 'from_the_shadows', - # 'blood_of_the_assassinated', #Items: #Class hall set bonus @@ -768,6 +752,46 @@ def assassination_dps_breakdown(self): stats, aps, crits, procs, additional_info = self.determine_stats(self.assassination_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + agonizing_poison_mod = 1 + if self.talents.agonizing_poison: + agonizing_poison_mod = 0.04 + if self.traits.surge_of_toxins: + agonizing_poison_mod += 0.01 * self.surge_of_toxins_multiplier + agonizing_poison_mod += 1 + agonizing_poison_mod *= 1 + (0.01 * self.traits.master_alchemist) + agonizing_poison_mod *= 1 + (0.01 * self.traits.poison_knives) + if self.talents.master_poisoner: + agonizing_poison_mod *= 1.2 + + agonizing_poison_mod *= 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])/2) + + elaborate_planning_mod = 1 + if self.talents.elaborate_planning: + elaborate_planning_mod = 1 + (0.15 * self.elaborate_planning_multiplier) + + hemo_mod = 1 + 0.25 * self.talents.hemorrhage + + surge_mod = 1 + if self.traits.surge_of_toxins: + surge_mod = 1 + self.surge_of_toxins_multiplier + + bota_mod = 1 + if self.traits.blood_of_the_assassinated: + bota_mod = 1 + self.bota_multiplier + + for ability in damage_breakdown: + damage_breakdown[ability] *= agonizing_poison_mod + damage_breakdown[ability] *= elaborate_planning_mod + if ability in ['rupture_ticks', 'garrote_ticks']: + damage_breakdown[ability] *= hemo_mod + if ability in ['deadly_poison_ticks', 'deadly_instant_poison', + 'kingsbane', 'kingsbane_ticks']: + damage_breakdown[ability] *= surge_mod + if ability == 'rupture_ticks': + damage_breakdown[ability] *= bota_mod + + + return damage_breakdown @@ -954,8 +978,37 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - #print attacks_per_second + attacks_per_second['mh_autoattacks'] = (self.haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed + attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] + + #poison computations, use old function for now + self.get_poison_counts(attacks_per_second, current_stats) + if self.talents.agonizing_poison: + stack_time = 5./attacks_per_second['agonizing_poison'] + max_time = self.settings.duration - stack_time + self.agonizing_poison_stacks = (max_time/self.settings.duration) * 5 + (stack_time/self.settings.duration) * 2.5 + + if self.talents.elaborate_planning: + finisher_aps = 0.0 + for ability in attacks_per_second: + if ability in self.finisher_damage_sources and 'ticks' not in ability: + finisher_aps += sum(attacks_per_second[ability]) + self.elaborate_planning_multiplier = min(1, 5 * finisher_aps) + if self.traits.surge_of_toxins: + finisher_aps = 0.0 + for ability in attacks_per_second: + if ability in self.finisher_damage_sources and 'ticks' not in ability: + finisher_aps += sum([0.02 * cp * 5for cp in attacks_per_second[ability]]) + self.surge_of_toxins_multiplier = finisher_aps + + if self.traits.blood_of_the_assassinated: + self.bota_multiplier = 0.35 * sum(attacks_per_second['rupture']) * 10 + self.bota_multiplier *= 2 + + + + #print attacks_per_second return attacks_per_second, crit_rates, additional_info ########################################################################### From 61b52e871a844ded39cde587111d362b5310672d Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 16 Sep 2016 15:41:31 -0400 Subject: [PATCH 080/265] Fix Ghostly Strike and DfA Implementation -Model results still aren't fully verified (DfA looks wrong) but no exceptions --- scripts/outlaw.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 42 ++++++++++---------- shadowcraft/calcs/rogue/__init__.py | 2 +- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 2407269..210907d 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -54,7 +54,7 @@ versatility=1515,) # Initialize talents.. -test_talents = talents.Talents('0000001', test_spec, test_class, level=test_level) +test_talents = talents.Talents('1000000', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0f15c0e..956ea4b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -485,7 +485,7 @@ def get_poison_counts(self, attacks_per_second, current_stats): return if self.talents.agonizing_poison: - poison_base_proc_rate = 0.2 + poison_base_proc_rate = 0.2 else: poison_base_proc_rate = 0.5 poison_envenom_proc_rate = poison_base_proc_rate + 0.3 @@ -500,7 +500,7 @@ def get_poison_counts(self, attacks_per_second, current_stats): else: poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = 1. / 3 + attacks_per_second['deadly_poison'] = 1. / 3 def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 @@ -764,7 +764,7 @@ def assassination_dps_breakdown(self): agonizing_poison_mod *= 1.2 agonizing_poison_mod *= 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])/2) - + elaborate_planning_mod = 1 if self.talents.elaborate_planning: elaborate_planning_mod = 1 + (0.15 * self.elaborate_planning_multiplier) @@ -775,7 +775,7 @@ def assassination_dps_breakdown(self): if self.traits.surge_of_toxins: surge_mod = 1 + self.surge_of_toxins_multiplier - bota_mod = 1 + bota_mod = 1 if self.traits.blood_of_the_assassinated: bota_mod = 1 + self.bota_multiplier @@ -828,10 +828,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): finisher_list = [0, 0, 0, 0, 0, 0, 0] if self.settings.finisher_threshold == 4: paths = [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4,)] - elif self.settings.finisher_threshold == 5: + elif self.settings.finisher_threshold == 5: paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] elif self.settings.finisher_threshold == 6: - paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 2), (2, 3, 3), (2, 3, 4), (2, 4), + paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 2), (2, 3, 3), (2, 3, 4), (2, 4), (3, 2, 2), (3, 2, 3), (3, 2, 4), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] else: raise InputNotModeledException(_('Finisher thresholds less than 4 unimplemented')) @@ -963,7 +963,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - + total_minicycles = float(energy_budget) / mini_cycle_energy attacks_per_second[self.cp_builder] += float(total_minicycles * builders_per_finisher) / self.settings.duration finishers_per_second = total_minicycles / self.settings.duration @@ -978,7 +978,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - attacks_per_second['mh_autoattacks'] = (self.haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed + attacks_per_second['mh_autoattacks'] = (self.haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] #poison computations, use old function for now @@ -1186,7 +1186,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): keep_shark_uptime = 0.0 keep_gm_uptime = 0.0 maintainence_buff_duration = 6 * (1 + self.settings.finisher_threshold) - + if self.talents.slice_and_dice: aps_normal = self.outlaw_attack_counts_mincycle(current_stats, snd=True, duration=maintainence_buff_duration) aps_ar = self.outlaw_attack_counts_mincycle(current_stats, snd=True, ar=True, duration=self.ar_duration) @@ -1200,10 +1200,10 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): shark = 's' in phase chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] - aps = self.outlaw_attack_counts_mincycle(current_stats, jolly=jolly, + aps = self.outlaw_attack_counts_mincycle(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, duration=maintainence_buff_duration) - aps_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, + aps_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, duration=self.ar_duration) @@ -1243,9 +1243,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): shark = 's' in phase chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] - aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, + aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks) - aps_ar, reroll_time_ar = self.outlaw_attack_counts_reroll(current_stats, ar=True, jolly=jolly, + aps_ar, reroll_time_ar = self.outlaw_attack_counts_reroll(current_stats, ar=True, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks_ar) phases[phase] = (chance * reroll_time, aps) ar_phases[phase] = (chance * reroll_time_ar, aps_ar) @@ -1308,13 +1308,13 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #remerge the ars attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) - + if old_ar_cd - new_ar_cd < 0.1: break else: old_ar_cd = new_ar_cd ar_uptime = self.ar_duration / new_ar_cd - + #add in cannonball and killing spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1396,10 +1396,10 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, if self.talents.death_from_above and not ar: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - (10 * true_bearing) dfa_count = duration/dfa_cd - dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.mh.speed/attack_speed_multiplier) + dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed/attack_speed_multiplier) dfa_energy_lost = dfa_lost_swings * (self.main_gauche_proc_rate * self.combat_potency_from_mg + self.combat_potency_regen_per_oh) energy_budget -= dfa_energy_lost - + mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) * (1 - self.white_swing_downtime) energy_budget += mg_cp_energy @@ -1484,7 +1484,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) * (1 + int(ar)) new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) * (1 + int(ar)) - energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration + energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration #compute new CP/MG regen old_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * alacrity_stacks)) new_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * new_alacrity_stacks)) @@ -1539,7 +1539,7 @@ def merge_attacks_per_second(self, aps_dicts, total_time=1.0): if isinstance(attacks_per_second[ability], list): for cp in xrange(7): attacks_per_second[ability][cp] += uptime * aps[ability][cp] - else: + else: attacks_per_second[ability] += uptime * aps[ability] else: if isinstance(aps[ability], list): @@ -1668,7 +1668,7 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= 1 + (0.3 * self.settings.cycle.positional_uptime) #add AoE damage sources: - if self.settings.num_boss_adds: + if self.settings.num_boss_adds: for key in damage_breakdown: if key == 'shuriken_toss': damage_breakdown[key] *= 1 + self.settings.num_boss_adds @@ -1737,7 +1737,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['symbols_of_death'] = 0 attacks_per_second['shadow_dance'] = 0 attacks_per_second['vanish'] = 0 - + #Leaving space for opener handling for the first cast sod_timeline = range(0, self.settings.duration, sod_duration) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 17bcbab..279c536 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -96,7 +96,7 @@ class RogueDamageCalculator(DamageCalculator): 'ambush': (60., 'strike'), 'between_the_eyes': (35., 'strike'), 'blunderbuss': (40., 'strike'), - 'ghostly_stike': (30., 'strike'), + 'ghostly_strike': (30., 'strike'), 'pistol_shot': (40., 'strike'), 'roll_the_bones': (25., 'buff'), 'run_through': (35., 'strike'), From 521c317535bda1d8853c9d8e2f62914f4393b315 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 21 Sep 2016 15:32:09 -0400 Subject: [PATCH 081/265] Update Outlaw Model: -Fix alacrity convergence loop -Fix overwriting of aps_ar dict -Still somewhat bugged based on AR duration --- scripts/outlaw.py | 24 +++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 77 ++++++++++++-------- 2 files changed, 60 insertions(+), 41 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 210907d..bd91fe3 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -46,15 +46,15 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=20909, - stam=19566, - crit=4402, - haste=5150, - mastery=5999, - versatility=1515,) + agi=21122, + stam=28367, + crit=6306, + haste=3260, + mastery=3706, + versatility=3486,) # Initialize talents.. -test_talents = talents.Talents('1000000', test_spec, test_class, level=test_level) +test_talents = talents.Talents('1010022', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') @@ -66,10 +66,12 @@ shark_reroll=1, true_bearing_reroll=1, buried_treasure_reroll=1, - broadsides_reroll=1 + broadsides_reroll=1, + between_the_eyes_policy='never' ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, - adv_params="", is_demon=True, num_boss_adds=0) + adv_params="", is_demon=True, num_boss_adds=0, + finisher_threshold=5) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) @@ -84,7 +86,7 @@ #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = #calculator.get_weapon_ep(dps=True, enchants=True) -talent_ranks = calculator.get_talents_ranking() +#talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): @@ -121,4 +123,4 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") -pprint(talent_ranks) +#pprint(talent_ranks) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 956ea4b..57a956f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1182,9 +1182,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_phases = {} keep_chance = 0.0 - keep_tb_uptime = 0.0 - keep_shark_uptime = 0.0 - keep_gm_uptime = 0.0 + keep_tb_chance = 0.0 + keep_shark_chance = 0.0 + keep_gm_chance = 0.0 maintainence_buff_duration = 6 * (1 + self.settings.finisher_threshold) if self.talents.slice_and_dice: @@ -1206,23 +1206,25 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): aps_ar = self.outlaw_attack_counts_mincycle(current_stats, ar=True, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, duration=self.ar_duration) - phases[phase] = (chance, aps) ar_phases[phase] = (chance, aps_ar) keep_chance += chance if melee: - keep_gm_uptime += chance + keep_gm_chance += chance if true_bearing: - keep_tb_uptime += chance + keep_tb_chance += chance if shark: - keep_shark_uptime += chance + keep_shark_chance += chance + keep_gm_uptime = keep_gm_chance/keep_chance + keep_tb_uptime = keep_tb_chance/keep_chance + keep_shark_uptime = keep_shark_chance/keep_chance #merge ar and non-ar into single phases - aps_normal = self.merge_attacks_per_second(phases, total_time=keep_chance) - aps_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) + aps_keep = self.merge_attacks_per_second(phases, total_time=keep_chance) + aps_keep_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) #technically there is a convergence relationship here but ignoring it if self.talents.alacrity: - alacrity_stacks = self.get_average_alacrity(aps_normal) - alacrity_stacks_ar = self.get_average_alacrity(aps_ar) + alacrity_stacks = self.get_average_alacrity(aps_keep) + alacrity_stacks_ar = self.get_average_alacrity(aps_keep_ar) else: alacrity_stacks = 0 alacrity_stacks_ar = 0 @@ -1231,9 +1233,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_phases = {} net_reroll_time = 0.0 net_reroll_time_ar = 0.0 - reroll_tb_uptime = 0.0 - reroll_shark_uptime = 0.0 - reroll_gm_uptime = 0.0 + reroll_tb_time = 0.0 + reroll_shark_time = 0.0 + reroll_gm_time = 0.0 for phase in self.settings.cycle.reroll_list: jolly = 'jr' in phase melee = 'gm' in phase @@ -1252,17 +1254,17 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): net_reroll_time += chance * reroll_time net_reroll_time_ar += chance * reroll_time_ar if true_bearing: - reroll_tb_uptime += chance * reroll_time + reroll_tb_time += chance * reroll_time if shark: - reroll_shark_uptime += chance * reroll_time + reroll_shark_time += chance * reroll_time if melee: - reroll_gm_uptime += chance * reroll_time + reroll_gm_time += chance * reroll_time #check for reroll time, to protect from divide by zero if net_reroll_time: - reroll_tb_uptime *= 1./net_reroll_time - reroll_shark_uptime *= 1./net_reroll_time - reroll_gm_uptime *= 1./net_reroll_time + reroll_tb_uptime = reroll_tb_time/net_reroll_time + reroll_shark_uptime = reroll_shark_time/net_reroll_time + reroll_gm_uptime = reroll_gm_time/net_reroll_time else: reroll_tb_uptime = 0 reroll_shark_uptime = 0 @@ -1275,11 +1277,13 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #will pandemic into rtb based on keep_chance rtb_keep_duration *= 1 + (0.3 * keep_chance) reroll_duration = net_reroll_time * len(self.settings.cycle.reroll_list) - ar_reroll_duration = net_reroll_time_ar * len(self.settings.cycle.reroll_list) - phases = {'keep': (rtb_keep_duration, aps_normal), + + ar_reroll_duration = net_reroll_time_ar + + phases = {'keep': (rtb_keep_duration, aps_keep), 'reroll': (reroll_duration, aps_reroll)} aps_normal = self.merge_attacks_per_second(phases, rtb_keep_duration + reroll_duration) - phases = {'keep': (rtb_keep_duration, aps_ar), + phases = {'keep': (rtb_keep_duration, aps_keep_ar), 'reroll': (ar_reroll_duration, aps_reroll_ar)} aps_ar = self.merge_attacks_per_second(phases, rtb_keep_duration + ar_reroll_duration) @@ -1293,9 +1297,14 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) ar_uptime = self.ar_duration / self.ar_cd tb_seconds_per_second = 0 + + print aps_normal + print aps_ar + print attacks_per_second + raw_input() #if rtb loop on ar cooldown if not self.talents.slice_and_dice: - old_ar_cd = self.ar_duration + old_ar_cd = self.ar_cd loop_counter = 0 while (loop_counter < 20): cp_spend_per_second = 0 @@ -1305,16 +1314,24 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cp_spend_per_second += attacks_per_second[ability][cp] * cp tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime new_ar_cd = self.ar_cd/(1 + tb_seconds_per_second) - #remerge the ars + print attacks_per_second + print cp_spend_per_second, tb_seconds_per_second + #remerge the aps + #print new_ar_cd + #print attacks_per_second attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) - + print new_ar_cd + print "-------" if old_ar_cd - new_ar_cd < 0.1: break else: old_ar_cd = new_ar_cd + ar_uptime = self.ar_duration / new_ar_cd + print self.ar_duration, new_ar_cd + print ar_uptime #add in cannonball and killing spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1356,7 +1373,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - #print attacks_per_second + print attacks_per_second return attacks_per_second, crit_rates, additional_info def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, @@ -1461,6 +1478,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, alacrity_stacks = 0 loop_counter = 0 + attacks_per_second['run_through'] = [0, 0, 0, 0, 0, 0, 0] while energy_budget > 0.1 and gcd_budget > 0.1: if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) @@ -1469,7 +1487,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) attacks_per_second['saber_slash'] += float(minicycle_count * (ss_count + ps_count))/duration attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count)/duration - attacks_per_second['run_through'] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): attacks_per_second['run_through'][cp] += float(minicycle_count * finisher_list[cp])/duration @@ -1510,7 +1528,6 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, if ar: energy_regen *= 2.0 attack_speed_multiplier *= 1.2 - mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) total_regen = energy_regen + mg_cp_energy reroll_time = reroll_energy_cost / total_regen @@ -1519,7 +1536,7 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, attacks_per_second['pistol_shot'] = float(ps_count)/reroll_time attacks_per_second['roll_the_bones'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): - attacks_per_second['roll_the_bones'][cp] *= finisher_list[cp]/reroll_time + attacks_per_second['roll_the_bones'][cp] = finisher_list[cp]/reroll_time return attacks_per_second, reroll_time From b063c1905b6faeac374d52eb74ae005a79d47fb7 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Wed, 21 Sep 2016 15:38:45 -0700 Subject: [PATCH 082/265] Overhaul: Purge much of the old test suite, green what's left --- scripts/import_character.py | 94 ++++---- shadowcraft/calcs/__init__.py | 37 +-- shadowcraft/calcs/darkmantle/__init__.py | 126 ----------- .../calcs/darkmantle/generic_attack.py | 51 ----- shadowcraft/calcs/darkmantle/generic_event.py | 25 -- .../calcs/darkmantle/rogue/__init__.py | 162 ------------- .../calcs/darkmantle/rogue/eviscerate.py | 27 --- .../calcs/darkmantle/rogue/instant_poison.py | 25 -- .../calcs/darkmantle/rogue/mh_attack.py | 19 -- .../calcs/darkmantle/rogue/oh_attack.py | 20 -- .../calcs/darkmantle/rogue/sinister_strike.py | 28 --- shadowcraft/calcs/darkmantle/settings.py | 53 ----- shadowcraft/core/jsoninput.py | 103 ++++----- shadowcraft/objects/buffs.py | 2 - shadowcraft/objects/class_data.py | 4 +- shadowcraft/objects/proc_data.py | 10 +- shadowcraft/objects/procs.py | 14 +- shadowcraft/objects/race.py | 4 +- shadowcraft/objects/stats.py | 41 ++-- shadowcraft/objects/talents.py | 4 +- tests/calcs_tests/__init__.py | 105 ++------- tests/calcs_tests/armor_mitigation_tests.py | 43 ---- .../rogue_tests/Aldriana_tests/__init__.py | 35 --- tests/calcs_tests/rogue_tests/__init__.py | 213 ++++++------------ tests/objects_tests/buffs_tests.py | 147 +++++------- tests/objects_tests/procs_tests.py | 58 +++-- tests/objects_tests/race_tests.py | 32 +-- .../rogue_tests/rogue_glyphs_tests.py | 11 - .../rogue_tests/rogue_talents_tests.py | 48 ++-- tests/objects_tests/stats_tests.py | 119 ++-------- 30 files changed, 370 insertions(+), 1290 deletions(-) delete mode 100644 shadowcraft/calcs/darkmantle/__init__.py delete mode 100644 shadowcraft/calcs/darkmantle/generic_attack.py delete mode 100644 shadowcraft/calcs/darkmantle/generic_event.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/__init__.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/eviscerate.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/instant_poison.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/mh_attack.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/oh_attack.py delete mode 100644 shadowcraft/calcs/darkmantle/rogue/sinister_strike.py delete mode 100755 shadowcraft/calcs/darkmantle/settings.py delete mode 100644 tests/calcs_tests/armor_mitigation_tests.py delete mode 100644 tests/calcs_tests/rogue_tests/Aldriana_tests/__init__.py delete mode 100644 tests/objects_tests/rogue_tests/rogue_glyphs_tests.py diff --git a/scripts/import_character.py b/scripts/import_character.py index ce92449..8b70674 100755 --- a/scripts/import_character.py +++ b/scripts/import_character.py @@ -113,19 +113,19 @@ class CharacterData: 4427 : [{'stat':'hit', 'value':175}], 4908 : [{'stat':'agi', 'value':120}, {'stat':'crit', 'value':80}], # Tiger Claw Inscrption } - - trinkets = {87057 : 'heroic_bottle_of_infinite_stars', + + trinkets = {87057 : 'heroic_bottle_of_infinite_stars', 86132 : 'bottle_of_infinite_stars', 87167 : 'heroic_terror_in_the_mists', 79328 : 'relic_of_xuen', 86791 : 'lfr_bottle_of_infinite_stars', - 86332 : 'terror_in_the_mists', + 86332 : 'terror_in_the_mists', 87079 : 'heroic_jade_bandit_figurine', 75274 : 'zen_alchemist_stone', - 86890 : 'lfr_terror_in_the_mists', + 86890 : 'lfr_terror_in_the_mists', 89082 : 'hawkmasters_talon', 86043 : 'jade_bandit_figurine', - 81267 : 'searing_words', + 81267 : 'searing_words', 81265 : 'flashing_steel_talisman', 81125 : 'windswept_pages', 86772 : 'lfr_jade_bandit_figurine', @@ -139,44 +139,44 @@ class CharacterData: 'set_bonus' : {2 : 'rogue_t14_2pc', 4 : 'rogue_t14_4pc'}}} glyphs = {# Major - u'Glyph of Adrenaline Rush' : 'adrenaline_rush', - u'Glyph of Ambush' : 'ambush', - u'Glyph of Blade Flurry' : 'blade_flurry', - u'Glyph of Blind' : 'blind', - u'Glyph of Cheap Shot' : 'cheap_shot', - u'Glyph of Cloak of Shadows' : 'cloak_of_shadows', - u'Glyph of Crippling Poison' : 'crippling_poison', - u'Glyph of Deadly Momentum' : 'deadly_momentum', - u'Glyph of Debilitation' : 'debilitation', - u'Glyph of Evasion' : 'evasion', - u'Glyph of Expose Armor' : 'expose_armor', - u'Glyph of Feint' : 'feint', - u'Glyph of Garrote' : 'garrote', - u'Glyph of Gouge' : 'gouge', - u'Glyph of Kick' : 'kick', - u'Glyph of Recuperate' : 'recuperate', - u'Glyph of Sap' : 'sap', - u'Glyph of Shadow Walk' : 'shadow_walk', - u'Glyph of Shiv' : 'shiv', - u'Glyph of Smoke Bomb' : 'smoke_bomb', - u'Glyph of Sprint' : 'sprint', - u'Glyph of Stealth' : 'stealth', - u'Glyph of Vanish' : 'vanish', - u'Glyph of Vendetta' : 'vendetta', - # Minor - u'Glyph of Blurred Speed' : 'blurred_speed', - u'Glyph of Decoy' : 'decoy', - u'Glyph of Detection' : 'detection', - u'Glyph of Disguise' : 'disguise', - u'Glyph of Distract' : 'distract', - u'Glyph of Hemorrhage' : 'hemorrhage', - u'Glyph of Killing Spree' : 'killing_spree', - u'Glyph of Pick Lock' : 'pick_lock', - u'Glyph of Pick Pocket' : 'pick_pocket', - u'Glyph of Poisons' : 'poisons', - u'Glyph of Safe Fall' : 'safe_fall', + u'Glyph of Adrenaline Rush' : 'adrenaline_rush', + u'Glyph of Ambush' : 'ambush', + u'Glyph of Blade Flurry' : 'blade_flurry', + u'Glyph of Blind' : 'blind', + u'Glyph of Cheap Shot' : 'cheap_shot', + u'Glyph of Cloak of Shadows' : 'cloak_of_shadows', + u'Glyph of Crippling Poison' : 'crippling_poison', + u'Glyph of Deadly Momentum' : 'deadly_momentum', + u'Glyph of Debilitation' : 'debilitation', + u'Glyph of Evasion' : 'evasion', + u'Glyph of Expose Armor' : 'expose_armor', + u'Glyph of Feint' : 'feint', + u'Glyph of Garrote' : 'garrote', + u'Glyph of Gouge' : 'gouge', + u'Glyph of Kick' : 'kick', + u'Glyph of Recuperate' : 'recuperate', + u'Glyph of Sap' : 'sap', + u'Glyph of Shadow Walk' : 'shadow_walk', + u'Glyph of Shiv' : 'shiv', + u'Glyph of Smoke Bomb' : 'smoke_bomb', + u'Glyph of Sprint' : 'sprint', + u'Glyph of Stealth' : 'stealth', + u'Glyph of Vanish' : 'vanish', + u'Glyph of Vendetta' : 'vendetta', + # Minor + u'Glyph of Blurred Speed' : 'blurred_speed', + u'Glyph of Decoy' : 'decoy', + u'Glyph of Detection' : 'detection', + u'Glyph of Disguise' : 'disguise', + u'Glyph of Distract' : 'distract', + u'Glyph of Hemorrhage' : 'hemorrhage', + u'Glyph of Killing Spree' : 'killing_spree', + u'Glyph of Pick Lock' : 'pick_lock', + u'Glyph of Pick Pocket' : 'pick_pocket', + u'Glyph of Poisons' : 'poisons', + u'Glyph of Safe Fall' : 'safe_fall', u'Glyph of Tricks of the Trade' : 'tricks_of_the_trade'} - + reforgeMap = {113: ('spirit', 'dodge_rating'), 114: ('spirit','parry_rating'), 115: ('spirit','hit'), @@ -273,7 +273,7 @@ def get_oh(self): item_data = self.raw_data['data'][u'items'][u'offHand'] weapon_data = get_item_cached(self.region, item_data[u'id']) return self.get_weapon(weapon_data, item_data) - + def get_mh_type(self): return self.get_mh()[2] @@ -328,8 +328,8 @@ def get_stats(self): return [str, agi, ap - 2 * agi, crit, hit, exp, haste, mast] def get_gear_stats(self): - # - lst = {'agi': 0, 'str':0, 'int':0, 'spirit':0, 'stam':0, 'crit':0, 'hit':0, 'exp':0, 'haste':0, 'mastery':0, 'ap':0, 'pvp_power':0, 'pvp_resil':0} + # + lst = {'agi': 0, 'str':0, 'int':0, 'stam':0, 'crit':0, 'haste':0, 'mastery':0, 'versatility':0} reforge = ('none', 'none') reforgeID = None gemColorToSocketColors = {u'RED': (u'RED'), u'YELLOW':(u'YELLOW'), u'BLUE':(u'BLUE'), u'META':(u'META'), u'COGWHEEL':(u'COGWHEEL'), u'HYDRAULIC':(u'HYDRAULIC'), @@ -423,10 +423,10 @@ def get_gear_stats(self): pp.pprint(lst) return lst #return [lst['str'], lst['agi'], lst['int'], lst['spirit'], lst['stam'], lst['ap'], lst['crit'], lst['hit'], lst['exp'], lst['haste'], lst['mastery']] - + def has_chaotic_metagem(self): return self.chaotic_metagem - + def get_current_spec_data(self): specs_data = self.raw_data['data'][u'talents'] if u'selected' in specs_data[0]: diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 0ff2fee..d1404cb 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -533,35 +533,20 @@ def get_talents_ranking(self, list=None): allowed_talent_list = self.talents.get_allowed_talents_for_level() if list == None else list #cache for tier, level in zip(self.talents.class_talents, tier_levels): - tier_ranking = {} #reinitialized to clear dict for each new tier - for talent in tier: - if talent in allowed_talent_list: - setattr(self.talents, talent, not getattr(self.talents, talent)) # this is a rather arcane way to handle state :/ - try: - new_dps = self.get_dps() - if new_dps != baseline_dps: - tier_ranking[talent] = abs(new_dps - baseline_dps) - else: - tier_ranking[talent] = 'not implemented' #unique error: no dps delta for this talent - except: - tier_ranking[talent] = 'implementation error' #unique error: error attempting to calc dps with this talent - setattr(self.talents, talent, not getattr(self.talents, talent)) - talents_ranking[level] = tier_ranking #place each tier into the talent tree - return talents_ranking def get_trait_ranking(self, list=None): @@ -603,11 +588,6 @@ def get_dps(self): # this is what callers will (initially) be looking at. pass - #def get_all_activated_stat_boosts(self): - # racial_boosts = self.race.get_racial_stat_boosts() - # gear_boosts = self.stats.gear_buffs.get_all_activated_boosts() - # return racial_boosts + gear_boosts - def armor_mitigation_multiplier(self, armor=None): if not armor: armor = self.target_base_armor @@ -618,24 +598,23 @@ def armor_mitigate(self, damage, armor): # damage value. return damage * self.armor_mitigation_multiplier(armor) - def melee_hit_chance(self, base_miss_chance, dodgeable, parryable, weapon_type, blockable=False, bonus_hit=0): + def melee_hit_chance(self, base_miss_chance, dodgeable, parryable, weapon_type, blockable=False): miss_chance = base_miss_chance - # Expertise represented as the reduced chance to be dodged, not true "Expertise". - if dodgeable: dodge_chance = self.base_dodge_chance else: dodge_chance = 0 if parryable: - # Expertise will negate dodge and spell miss, *then* parry - parry_expertise = max(expertise - self.base_dodge_chance, 0) - parry_chance = max(self.base_parry_chance - parry_expertise, 0) + parry_chance = self.base_parry_chance else: parry_chance = 0 - block_chance = self.base_block_chance * blockable + if blockable: + block_chance = self.base_block_chance + else: + block_chance = 0 return (1 - (miss_chance + dodge_chance + parry_chance)) * (1 - block_chance) @@ -643,13 +622,13 @@ def melee_spells_hit_chance(self, bonus_hit=0): hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable=False, parryable=False, weapon_type=None) return hit_chance - def one_hand_melee_hit_chance(self, dodgeable=False, parryable=False, weapon=None, bonus_hit=0): + def one_hand_melee_hit_chance(self, dodgeable=False, parryable=False, weapon=None, blockable=False): # Most attacks by DPS aren't parryable due to positional negation. But # if you ever want to attacking from the front, you can just set that # to True. if weapon == None: weapon = self.stats.mh - hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable, weapon.type) + hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable, weapon.type, blockable) return hit_chance def off_hand_melee_hit_chance(self, dodgeable=False, parryable=False, weapon=None, bonus_hit=0): diff --git a/shadowcraft/calcs/darkmantle/__init__.py b/shadowcraft/calcs/darkmantle/__init__.py deleted file mode 100644 index 414de8a..0000000 --- a/shadowcraft/calcs/darkmantle/__init__.py +++ /dev/null @@ -1,126 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.core import exceptions -from shadowcraft.calcs import armor_mitigation -from shadowcraft.objects import class_data -from shadowcraft.objects.procs import InvalidProcException - -class InputNotModeledException(exceptions.InvalidInputException): - pass - -class DarkmantleCalculator(object): - - def __init__(self, stats, talents, glyphs, buffs, race, settings=None, level=100, target_level=103, char_class='rogue'): - #load stats, class, procs, etc to main content - self.tools = class_data.Util() - self.stats = stats - self.talents = talents - self.glyphs = glyphs - self.buffs = buffs - self.race = race - self.char_class = char_class - self.settings = settings - self.target_level = target_level - self.level = level - - self.buffs.level = self.level - self.stats.level = self.level - self.race.level = self.level - self.stats.gear_buffs.level = self.level - # calculate and cache the level-dependent armor mitigation parameter - self.armor_mitigation_parameter = armor_mitigation.parameter(self.level) - - #setup global variables, these get deep-copy and passed to new objects - self.state_values = {} - self.state_values['damage_multiplier'] = 1.0 - self.state_values['gcd_size'] = 1.0 - self.state_values['trinket_1'] = {'name':'null', 'last_proc_time': -600} - self.state_values['trinket_2'] = {'name':'null', 'last_proc_time': -600} - self.state_values['weapon_proc_1'] = {'name':'null', 'last_proc_time': -600} - self.state_values['weapon_proc_2'] = {'name':'null', 'last_proc_time': -600} - self.state_values['cooldown'] = {} - self.state_values['stat_multipliers'] = { - 'primary':self.stats.gear_buffs.gear_specialization_multiplier(), #armor specialization - 'ap':self.buffs.attack_power_multiplier(), - 'haste':1.0, - 'crit':1.0, - 'mastery':1.0, - 'versatility':1.0, - 'readiness':1.0, - 'multistrike':1.0, - } - self.state_values['current_stats'] = { - 'str': (self.stats.str), #useless for rogues now - 'agi': (self.stats.agi + self.race.racial_agi), #+ self.buffs.buff_agi() - 'int': (self.stats.int), #useless for rogues now - 'ap': (self.stats.ap), - 'crit': (self.stats.crit), - 'haste': (self.stats.haste), - 'mastery': (self.stats.mastery), # + self.buffs.buff_mast() - 'readiness': (self.stats.readiness), - 'multistrike': (self.stats.multistrike), - 'versatility': (self.stats.versatility), - } - self.calculate_effective_ap() - - self.state_values['auras'] = [] #handles permanent and temporary - for e in self.buffs.buffs_debuffs: - self.state_values['auras'].append((e, 'inf')) # ('name', time), time = 'inf' or number - - #change stats to match buffs - - #combat tables - self.base_one_hand_miss_rate = 0 - self.base_parry_chance = .03 - self.base_dodge_chance = 0 - self.base_spell_miss_rate = 0 - self.base_dw_miss_rate = .17 - self.base_block_chance = .075 - self.crit_reduction = .01 * (self.target_level - self.level) - - #load class module data - class_variables = self._get_values_for_class() - for key in class_variables: - self.state_values[key] = class_variables[key] - - def calculate_effective_ap(self): - self.state_values['effective_ap'] = (self.state_values['current_stats']['agi'] * self.state_values['stat_multipliers']['primary'] + self.stats.ap) - self.state_values['effective_ap'] *= self.state_values['stat_multipliers']['ap'] - - def end_calc_branch(self, current_time, total_damage_done): - if self.settings.style == 'time' and current_time >= self.settings.limit: - print 'Stopping calculations at: ', current_time, ' seconds' - print '--------' - return True - if self.settings.style == 'health' and total_damage_done >= self.settings.limit: - print 'Stopping calculations at: ', total_damage_done, ' damage' - print '--------' - return True - return False - - def shallow_copy_table(self, base): - #need a deep copy variant - table = {} - for key in base: - table[key] = base[key] - return table - - def shallow_copy_array(self, base): - lst = [] - for e in base: - lst.append(e) - return lst - - def _class_bonus_crit(self): - return 0 #should be overwritten by individual class modules if the crit rate needs to be shifted - - def calculate_crit_rate(self): - crit = self.stats.get_crit_from_rating(rating=self.state_values['current_stats']['crit']) - crit += self._class_bonus_crit() + self.buffs.buff_all_crit() - return crit - \ No newline at end of file diff --git a/shadowcraft/calcs/darkmantle/generic_attack.py b/shadowcraft/calcs/darkmantle/generic_attack.py deleted file mode 100644 index 659e33a..0000000 --- a/shadowcraft/calcs/darkmantle/generic_attack.py +++ /dev/null @@ -1,51 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.rogue import RogueDamageCalculator -from shadowcraft.calcs.darkmantle.generic_event import GenericEvent -from shadowcraft.core import exceptions -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data - -class GenericAttack(GenericEvent): - _name = 'generic_attack' - _hand = 'mh' - _cost = 0 - _cast_time = 0.0 - - def calculate_damage(self): - return 1 #to be overwritten by actual actions - - def secondary_effects(self): - return - - def calculate_breakdown(self): - if self.engine.end_calc_branch(self.time, self.total_damage): - return self.breakdown - normal_damage = self.calculate_damage() - a = self.secondary_effects() - self.state_values['current_power'] -= self._cost - crit_rate = 0 - crit_damage = 0 - if self.can_crit: - crit_rate = self.engine.calculate_crit_rate() - #deal with crits at a later time - if self._name in self.breakdown: - self.breakdown[self._name] += normal_damage - else: - self.breakdown[self._name] = normal_damage - #spawn child objects - self.timeline = self.timeline[1:] - self.setup_queues(self.timeline, self.state_values['auras']) - next_attack_constructor = self.engine.get_next_attack(self.timeline[0][1]) - # engine, breakdown, time, timeline, - # total_damage, state_values - next = next_attack_constructor(self.engine, self.breakdown, self.timeline[0][0], self.engine.shallow_copy_array(self.timeline), - self.total_damage, self.state_values) - next.calculate_breakdown() - average_breakdown = self.breakdown - return average_breakdown diff --git a/shadowcraft/calcs/darkmantle/generic_event.py b/shadowcraft/calcs/darkmantle/generic_event.py deleted file mode 100644 index 1f96e60..0000000 --- a/shadowcraft/calcs/darkmantle/generic_event.py +++ /dev/null @@ -1,25 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.core import exceptions -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data - -class GenericEvent(object): - - def __init__(self, engine, breakdown, time, timeline, total_damage, state_values): - self.engine = engine - self.breakdown = breakdown - self.time = time - self.timeline = timeline - self.total_damage = total_damage - self.state_values = state_values - - self.can_crit = True - - def setup_queues(self, timeline, buffs): - pass #to be overwritten by actual actions diff --git a/shadowcraft/calcs/darkmantle/rogue/__init__.py b/shadowcraft/calcs/darkmantle/rogue/__init__.py deleted file mode 100644 index 26b5df2..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/__init__.py +++ /dev/null @@ -1,162 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -import shadowcraft -from shadowcraft.calcs.darkmantle import DarkmantleCalculator -from shadowcraft.calcs.darkmantle.rogue import mh_attack -from shadowcraft.calcs.darkmantle.rogue import oh_attack -from shadowcraft.calcs.darkmantle.rogue import instant_poison -from shadowcraft.core import exceptions -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data - - -class InputNotModeledException(exceptions.InvalidInputException): - # I'll return these when inputs don't make sense to the model. - pass - -class RogueDarkmantleCalculator(DarkmantleCalculator): - abilities_list = { - 'mh_autoattack': mh_attack, - 'oh_autoattack': oh_attack, - 'instant_poison': instant_poison, - } - ability_constructors = { - 'mh_autoattack': mh_attack.MHAttack, - 'oh_autoattack': oh_attack.OHAttack, - 'instant_poison': instant_poison.InstantPoison, - } - - def get_next_attack(self, name): - #pulls the constructor, not the module - if name not in self.ability_constructors: - raise InputNotModeledException(_('Can\'t locate action: {action}').format(action=str(name))) - return self.ability_constructors[name] - - def can_cast_ability(self, name): - if abilities_list._cost < self.state_values['current_power'] and self.state_values['cooldown'][name] < self.time: - return True - return False - - def _get_values_for_class(self): - #override global states if necessary - if self.settings.is_combat_rogue(): - self.base_dw_miss_rate = 0 - - #initialize variables into a table that won't disappear throughout the calculations - #additionally, set up data structures (like combo points) - class_table = {} - class_table['current_second_power'] = 0 #combot points - class_table['max_second_power'] = 5 #can only get to 5 CP (for now?) - - class_table['max_power'] = 100 #energy - if self.settings.is_assassination_rogue(): - class_table['max_power'] += 20 - if self.glyphs.energy: - class_table['max_power'] += 20 - if self.talents.lemon_zest: - class_table['max_power'] += 15 - if self.stats.gear_buffs.rogue_pvp_4pc_extra_energy(): - class_table['max_power'] += 30 - class_table['current_power'] = class_table['max_power'] - class_table['base_power_regen'] = 10 - if self.settings.is_combat_rogue(): - class_table['base_power_regen'] *= 1.2 - - if self.talents.anticipation: - class_table['anticipation'] = 0 - class_table['anticipation_max'] = 5 - if self.settings.is_combat_rogue(): - class_table['bg_counter'] = 0 - - return class_table - - def _class_bonus_crit(self): - return .05 #rogues get a "free" 5% extra crit - - def get_dps(self): - if self.settings.is_assassination_rogue(): - return self.assassination_dps_estimate() - elif self.settings.is_combat_rogue(): - return self.combat_dps_estimate() - elif self.settings.is_subtlety_rogue(): - return self.subtlety_dps_estimate() - else: - raise InputNotModeledException(_('You must specify a spec.')) - - def get_dps_breakdown(self): - if self.settings.is_assassination_rogue(): - return self.assassination_dps_breakdown() - elif self.settings.is_combat_rogue(): - return self.combat_dps_breakdown() - elif self.settings.is_subtlety_rogue(): - return self.subtlety_dps_breakdown() - else: - raise InputNotModeledException(_('You must specify a spec.')) - - def assassination_dps_estimate(self): - return sum(self.assassination_dps_breakdown().values()) - def assassination_dps_breakdown(self): - #determine pre-fight sequence, establish baseline event_queue and auras - #read priority list, determine first action - #load event_state object with event_queue - return {'none':1.} - - def combat_dps_estimate(self): - return sum(self.combat_dps_breakdown().values()) - def combat_dps_breakdown(self): - print 'Calculating Combat Breakdown...' - breakdown = {} - event_queue = [] - #determine pre-fight sequence, establish baseline event_queue and auras - #read priority list, determine first action - #load event_state object with event_queue - event_queue = [(0.0, 'mh_autoattack'), (0.01, 'oh_autoattack')] #temporary for development purposes - #self.combat_priority_list() #should determine opener, as well as handle normal rotational decisions - root_event = mh_attack.MHAttack(self, breakdown, 0, event_queue, 0, self.state_values) #timer always starts at 0, prefight has no bearing - root_event.calculate_breakdown() - return breakdown - def combat_priority_list(self, cost): - action = 'wait' - if self.state_values['current_energy'] > cost and self.state_values['combo_points'] < self.state_values['max_second_power']: - action = 'sinister_strike' - if self.state_values['current_energy'] > cost and self.state_values['combo_points'] == self.state_values['max_second_power']: - action = 'eviscerate' - if self.state_values: - return - return action - - def subtlety_dps_estimate(self): - return sum(self.subtlety_dps_breakdown().values()) - def subtlety_dps_breakdown(self): - #determine pre-fight sequence, establish baseline event_queue and auras - #read priority list, determine first action - #load event_state object with event_queue - return {'none':1.} - - def reset_bandits_guile(self): - self.state_values['bg_counter'] = 0 - self.state_values['damage_multiplier'] *= 1.0 / 1.5 #BG30 is now 50% - - def set_bandits_guile_level(self): - c = self.state_values['bg_counter'] - level = math.min(c // 4, 3) #BG30/50 is highest level - if level == 3: - self.state_values['damage_multiplier'] *= 1.5 / 1.2 #would be 1.3/1.2 if under level 100 - else: - self.state_values['damage_multiplier'] *= (1 + .1 * level) / (1 + .1 * (level-1)) - - def restless_blades_impact(self, cp): - self.state_values['cooldown']['killing_spree'] -= 2 * cp - self.state_values['cooldown']['adrenaline_rush'] -= 2 * cp - - def set_sanguinary_veins(self, enabled=True): - if enabled: - self.state_values['damage_multiplier'] *= 1.2 - else: - self.state_values['damage_multiplier'] *= 1.0/1.2 - \ No newline at end of file diff --git a/shadowcraft/calcs/darkmantle/rogue/eviscerate.py b/shadowcraft/calcs/darkmantle/rogue/eviscerate.py deleted file mode 100644 index e69cab1..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/eviscerate.py +++ /dev/null @@ -1,27 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.darkmantle.generic_attack import GenericAttack - -class Eviscerate(GenericAttack): - _name = 'Eviscerate' - _cost = 35 - - def calculate_damage(self): - # non-normalized weapon strike => (mh_weapon_damage + ap / 3.5 * weapon_speed) * weapon_damage_percentage - print 'Eviscerate: ', self.state_values['current_second_power'] - return .3 * self.state_values['current_second_power'] * self.state_values['effective_ap'] - - def secondary_effects(self): - self.engine.restless_blades_impact(self.state_values['current_second_power']) - #shift combo points, clean up residuals - self.state_values['current_second_power'] = self.state_values['anticipation'] - self.state_values['anticipation'] = 0 - - def setup_queues(self, timeline, buffs): - #enable_autoattacks() - timeline.append((self.time + self.state_values['gcd_size'], 'priority_queue')) diff --git a/shadowcraft/calcs/darkmantle/rogue/instant_poison.py b/shadowcraft/calcs/darkmantle/rogue/instant_poison.py deleted file mode 100644 index d15ed85..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/instant_poison.py +++ /dev/null @@ -1,25 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.darkmantle.generic_attack import GenericAttack -from shadowcraft.core import exceptions -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data - -class InstantPoison(GenericAttack): - _name = 'instant_poison' - _cost = 0 - _cast_time = 0.0 - - def calculate_damage(self): - # non-normalized weapon strike => (mh_weapon_damage + ap / 3.5 * weapon_speed) * weapon_damage_percentage - print 'Instant Poison: ', self.state_values['effective_ap'] * .20 - return self.state_values['effective_ap'] * .20 #??? - - def setup_queues(self, timeline, buffs): - return #nothing else to trigger for now - \ No newline at end of file diff --git a/shadowcraft/calcs/darkmantle/rogue/mh_attack.py b/shadowcraft/calcs/darkmantle/rogue/mh_attack.py deleted file mode 100644 index ef6bc42..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/mh_attack.py +++ /dev/null @@ -1,19 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.darkmantle.generic_attack import GenericAttack - -class MHAttack(GenericAttack): - _name = 'mh_autoattack' - - def calculate_damage(self): - # non-normalized weapon strike => (mh_weapon_damage + ap / 3.5 * weapon_speed) * weapon_damage_percentage - print 'MH Attack: ', self.engine.stats.mh.speed * (self.engine.stats.mh.weapon_dps + self.state_values['effective_ap'] / 3.5) - return self.engine.stats.mh.speed * (self.engine.stats.mh.weapon_dps + self.state_values['effective_ap'] / 3.5) - - def setup_queues(self, timeline, buffs): - timeline.append((self.time + self.engine.stats.mh.speed, 'mh_autoattack')) diff --git a/shadowcraft/calcs/darkmantle/rogue/oh_attack.py b/shadowcraft/calcs/darkmantle/rogue/oh_attack.py deleted file mode 100644 index 02d76d8..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/oh_attack.py +++ /dev/null @@ -1,20 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.darkmantle.generic_attack import GenericAttack - -class OHAttack(GenericAttack): - _name = 'oh_autoattack' - - def calculate_damage(self): - # non-normalized weapon strike => (oh_weapon_damage + ap / 3.5 * weapon_speed) * weapon_damage_percentage - print 'OH Attack: ', self.engine.stats.oh.speed * (self.engine.stats.oh.weapon_dps + self.state_values['effective_ap'] / 3.5) * .5 - return self.engine.stats.oh.speed * (self.engine.stats.oh.weapon_dps + self.state_values['effective_ap'] / 3.5) * .5 - - def setup_queues(self, timeline, buffs): - timeline.append((self.time + self.engine.stats.oh.speed, 'oh_autoattack')) - \ No newline at end of file diff --git a/shadowcraft/calcs/darkmantle/rogue/sinister_strike.py b/shadowcraft/calcs/darkmantle/rogue/sinister_strike.py deleted file mode 100644 index e727706..0000000 --- a/shadowcraft/calcs/darkmantle/rogue/sinister_strike.py +++ /dev/null @@ -1,28 +0,0 @@ -import copy -import gettext -import __builtin__ -import math - -__builtin__._ = gettext.gettext - -from shadowcraft.calcs.darkmantle.generic_attack import GenericAttack - -class SinisterStrike(GenericAttack): - _name = 'Sinister Strike' - _cost = 50 - - def calculate_damage(self): - # non-normalized weapon strike => (mh_weapon_damage + ap / 3.5 * weapon_speed) * weapon_damage_percentage - print 'Sinister Strike: ', 1.2 * .85 * self.engine.stats.mh.speed * (self.engine.stats.mh.weapon_dps + self.state_values['effective_ap'] / 3.5) - return 1.2 * .85 * self.engine.stats.mh.speed * (self.engine.stats.mh.weapon_dps + self.state_values['effective_ap'] / 3.5) - - def secondary_effects(self): - # +1 CP - if self.state_values['current_second_power'] < self.state_values['max_second_power']: - self.state_values['current_second_power'] = math.min(self.state_values['current_second_power']+1, self.state_values['max_second_power']) - if self.engine.talents.anticipation and self.state_values['current_second_power'] == self.state_values['max_second_power']: - self.state_values['anticipation'] += math.min(self.state_values['anticipation']+1, self.state_values['anticipation_max']) - - def setup_queues(self, timeline, buffs): - #enable_autoattacks() - timeline.append((self.time + self.state_values['gcd_size'], 'priority_queue')) diff --git a/shadowcraft/calcs/darkmantle/settings.py b/shadowcraft/calcs/darkmantle/settings.py deleted file mode 100755 index c94663e..0000000 --- a/shadowcraft/calcs/darkmantle/settings.py +++ /dev/null @@ -1,53 +0,0 @@ -from shadowcraft.core import exceptions - -class Settings(object): - - def __init__(self, cycle, response_time=.5, latency=.03, merge_damage=True, style='time', limit=10): - self.cycle = cycle #for the spec - self.response_time = response_time #general player reaction time - self.latency = latency #used sparingly - self.merge_damage = merge_damage #combines mh and oh attacks to a single source - self.style = style #determines end conditions, limited by health or time - self.limit = limit #end condition for the style, if time then in seconds - - def get_spec(self): - return self.cycle._cycle_type - - def is_assassination_rogue(self): - return self.get_spec() == 'assassination' - - def is_combat_rogue(self): - return self.get_spec() == 'combat' - - def is_subtlety_rogue(self): - return self.get_spec() == 'subtlety' - -class Cycle(object): - # Base class for cycle objects. Can't think of anything that particularly - # needs to go here yet, but it seems worth keeping options open in that - # respect. - - # When subclassing, define _cycle_type to be one of 'assassination', - # 'combat', or 'subtlety' - this is how the damage calculator makes sure - # you have an appropriate cycle object to go with your talent trees, etc. - _cycle_type = '' - - -class AssassinationCycle(Cycle): - _cycle_type = 'assassination' - - def __init__(self): - return - - -class CombatCycle(Cycle): - _cycle_type = 'combat' - - def __init__(self): - return - -class SubtletyCycle(Cycle): - _cycle_type = 'subtlety' - - def __init__(self): - return diff --git a/shadowcraft/core/jsoninput.py b/shadowcraft/core/jsoninput.py index 5ad33cc..df147d3 100644 --- a/shadowcraft/core/jsoninput.py +++ b/shadowcraft/core/jsoninput.py @@ -14,10 +14,10 @@ class InvalidJSONException(exceptions.InvalidInputException): def from_json(json_string, character_class='rogue'): j = json.loads(json_string) - try: + try: race_object = race.Race(str(j['race']), character_class=character_class) level = int(j['level']) - + s = j['settings'] settings_type = s['type'] if settings_type == 'assassination': @@ -39,7 +39,7 @@ def from_json(json_string, character_class='rogue'): # Settings(cycle, time_in_execute_range=.35, tricks_on_cooldown=True, response_time=.5, mh_poison='ip', oh_poison='dp', duration=300): settings_object = settings.Settings(cycle, s.get('time_in_execute_range', .35), s.get('tricks_on_cooldown', True), s.get('response_time', .5), s.get('mh_poison', 'ip'), s.get('oh_poison', 'dp'), s.get('duration', 300)) - + stats_dict = j['stats'] # Weapon(damage, speed, weapon_type, enchant=None): mh_dict = stats_dict['mh'] @@ -53,8 +53,9 @@ def from_json(json_string, character_class='rogue'): # Stats(str, agi, ap, crit, hit, exp, haste, mastery, mh, oh, ranged, procs, gear_buffs, level=85): def s(stat): return int(stats_dict[stat]) - stats_object = stats.Stats(s('str'), s('agi'), s('ap'), s('crit'), s('hit'), s('exp'), s('haste'), s('mastery'), - mh, oh, ranged, procs_list, gear_buffs, level) + stats_object = stats.Stats( + mh, oh, procs_list, gear_buffs, + str=s('str'), agi=s('agi'), crit=s('crit'), haste=s('haste'), mastery=s('mastery')) glyphs = rogue_glyphs.RogueGlyphs(*j['glyphs']) talents = rogue_talents.RogueTalents(*j['talents']) buffs_object = buffs.Buffs(*j['buffs']) @@ -66,74 +67,74 @@ def s(stat): if __name__ == '__main__': json_string = """{ - "level": 85, + "level": 85, "stats": { "str": 20, - "agi": 4756, - "ap": 190, - "crit": 1022, - "hit": 1329, - "exp": 159, - "haste": 1291, - "mastery": 1713, + "agi": 4756, + "ap": 190, + "crit": 1022, + "hit": 1329, + "exp": 159, + "haste": 1291, + "mastery": 1713, "gear_buffs": [ - "rogue_t11_2pc", - "leather_specialization", - "potion_of_the_tolvir", + "rogue_t11_2pc", + "leather_specialization", + "potion_of_the_tolvir", "chaotic_metagem" - ], + ], "procs": [ - "heroic_prestors_talisman_of_machination", - "fluid_death", + "heroic_prestors_talisman_of_machination", + "fluid_death", "rogue_t11_4pc" - ], + ], "mh": { - "type": "dagger", - "speed": 1.8, - "damage": 939.5, + "type": "dagger", + "speed": 1.8, + "damage": 939.5, "enchant": "landslide" }, "oh": { - "type": "dagger", - "speed": 1.4, - "damage": 730.5, + "type": "dagger", + "speed": 1.4, + "damage": 730.5, "enchant": "landslide" - }, + }, "ranged": { - "type": "thrown", - "speed": 2.2, + "type": "thrown", + "speed": 2.2, "damage": 1371.5 } }, "buffs": [ - "short_term_haste_buff", - "stat_multiplier_buff", - "crit_chance_buff", - "all_damage_buff", - "melee_haste_buff", - "attack_power_buff", - "str_and_agi_buff", - "armor_debuff", - "physical_vulnerability_debuff", - "spell_damage_debuff", - "spell_crit_debuff", - "bleed_damage_debuff", - "agi_flask", + "short_term_haste_buff", + "stat_multiplier_buff", + "crit_chance_buff", + "all_damage_buff", + "melee_haste_buff", + "attack_power_buff", + "str_and_agi_buff", + "armor_debuff", + "physical_vulnerability_debuff", + "spell_damage_debuff", + "spell_crit_debuff", + "bleed_damage_debuff", + "agi_flask", "guild_feast" - ], + ], "settings": { - "type": "assassination", + "type": "assassination", "response_time": 1 - }, + }, "talents": [ - "0333230113022110321", - "0020000000000000000", + "0333230113022110321", + "0020000000000000000", "2030030000000000000" - ], - "race": "night_elf", + ], + "race": "night_elf", "glyphs": [ - "backstab", - "mutilate", + "backstab", + "mutilate", "rupture" ] }""" diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index 1a91930..a842a48 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -3,7 +3,6 @@ class InvalidBuffException(exceptions.InvalidInputException): pass - class Buffs(object): allowed_buffs = frozenset([ @@ -80,7 +79,6 @@ def __init__(self, *args, **kwargs): if buff not in self.allowed_buffs: raise InvalidBuffException(_('Invalid buff {buff}').format(buff=buff)) setattr(self, buff, True) - self.level = kwargs.get('level', 100) def __getattr__(self, name): # Any buff we haven't assigned a value to, we don't have. diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index 70ee42d..7602b07 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -1099,10 +1099,10 @@ def get_constant_scaling_point(self, level): def get_base_armor(self, level): if level < 100: - raise exceptions.InvalidInputException(_('There\'s no armor value for a target level {level}').format(level=str(e))) + raise exceptions.InvalidInputException(_('There\'s no armor value for a target level {level}').format(level=str(level))) return self.BASE_ARMOR[level] def get_k_value(self, level): if level < 100: - raise exceptions.InvalidInputException(_('There\'s no k value for a level {level} player').format(level=str(e))) + raise exceptions.InvalidInputException(_('There\'s no k value for a level {level} player').format(level=str(level))) return self.ATTACKER_K_VALUE[level] \ No newline at end of file diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 01347bf..32ffd27 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -160,7 +160,7 @@ 'trigger': 'all_attacks' }, #7.0 neck enchants - 'mark_of_the_hidden_satyr': { #191259 Deals 41626 to 48375 damage. + 'mark_of_the_hidden_satyr': { #191259 Deals 41626 to 48375 damage. 'stat':'spell_damage', 'value': 45000, #average 41626 to 48375 'duration': 0, @@ -277,7 +277,7 @@ 'proc_name': 'Dominion Deck', #this does some wierd shuffling crit values /wrists 'scaling': 0.750315, #only valid for the 1336 draw 'item_level': 815, - 'type': 'rppm', + 'type': 'rppm', 'source': 'trinket', 'proc_rate': 1, 'trigger': 'all_attacks' @@ -288,7 +288,7 @@ 'value': 996160, #124520 * 8 'duration': 0, 'proc_name': 'Fel-Crazed Rage', - 'scaling': 40, + 'scaling': 40, 'item_level': 875, 'source': 'trinket', 'type': 'icd', @@ -497,7 +497,7 @@ 'haste_scales': True, 'trigger': 'all_attacks', }, - + 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal 86952 Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. 'stat':'spell_damage', 'value': 695616, #multiple targets not modeled, assuming 4 hits out and 4 in @@ -560,7 +560,7 @@ 'can_crit': True, 'trigger': 'all_attacks' }, - + #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 90c40d3..122f662 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -36,10 +36,10 @@ def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True self.on_crit = on_crit self.on_procced_strikes = on_procced_strikes self.proc_rate_modifier = proc_rate_modifier - + #separate method just to keep the constructor clean self.update_proc_value() - + def update_proc_value(self): tools = class_data.Util() #http://forums.elitistjerks.com/topic/130561-shadowcraft-for-mists-of-pandaria/page-3 @@ -114,12 +114,12 @@ def procs_off_procced_strikes(self): return True else: return False - + def get_rppm_proc_rate(self, haste=1.): if self.is_real_ppm(): return haste * self.proc_rate * self.proc_rate_modifier raise InvalidProcException(_('Invalid proc handling for proc {proc}').format(proc=self.proc_name)) - + def get_proc_rate(self, speed=None, haste=1.0): if self.is_ppm(): if speed is None: @@ -138,7 +138,7 @@ def is_ppm(self): return False # probably should configure this somehow, but type check is probably enough raise InvalidProcException(_('Invalid data for proc {proc}').format(proc=self.proc_name)) - + def is_rppm(self): return self.is_real_ppm() def is_real_ppm(self): @@ -165,7 +165,7 @@ def __init__(self, *args): def set_proc(self, proc): setattr(self, proc, Proc(**self.allowed_procs[proc])) - + def del_proc(self, proc): setattr(self, proc, False) @@ -174,7 +174,7 @@ def __getattr__(self, proc): if proc in self.allowed_procs: return False object.__getattribute__(self, proc) - + def get_all_procs_for_stat(self, stat=None): procs = [] for proc_name in self.allowed_procs: diff --git a/shadowcraft/objects/race.py b/shadowcraft/objects/race.py index 9ddda20..f244a86 100755 --- a/shadowcraft/objects/race.py +++ b/shadowcraft/objects/race.py @@ -131,7 +131,7 @@ def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': self._set_constants_for_level() - + def _set_constants_for_level(self): try: self.stats = self.stat_set[self.level] @@ -148,7 +148,7 @@ def __getattr__(self, name): return False else: object.__getattribute__(self, name) - + def get_stats_from_race(self, level, secondaries=False): str = Race.rogue_base_stats[level][0] + Race.racial_stat_offset[self.race_name][0] agi = Race.rogue_base_stats[level][1] + Race.racial_stat_offset[self.race_name][1] diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 33edf08..70d37a8 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -14,7 +14,7 @@ class Stats(object): mastery_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:350.0} versatility_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:27.0, 100:130.0, 110:400.0} - def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, spirit=0, stam=0, ap=0, crit=0, haste=0, mastery=0, + def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, stam=0, ap=0, crit=0, haste=0, mastery=0, versatility=0, level=None): # This will need to be adjusted if at any point we want to support # other classes, but this is probably the easiest way to do it for @@ -22,7 +22,6 @@ def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, spirit=0, sta self.str = str self.agi = agi self.int = int - self.spirit = spirit self.stam = stam self.ap = ap self.crit = crit @@ -31,11 +30,11 @@ def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, spirit=0, sta self.versatility = versatility self.mh = mh self.oh = oh - self.procs = procs self.gear_buffs = gear_buffs self.level = level + self.procs = procs + def _set_constants_for_level(self): - self.procs.level = self.level try: self.crit_rating_conversion = self.crit_rating_conversion_values[self.level] self.haste_rating_conversion = self.haste_rating_conversion_values[self.level] @@ -48,7 +47,7 @@ def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level' and value is not None: self._set_constants_for_level() - + def get_mastery_from_rating(self, rating=None): if rating is None: rating = self.mastery @@ -63,7 +62,7 @@ def get_haste_multiplier_from_rating(self, rating=None): if rating is None: rating = self.haste return 1 + rating / (100. * self.haste_rating_conversion) - + def get_versatility_multiplier_from_rating(self, rating=None): if rating is None: rating = self.versatility @@ -90,7 +89,7 @@ def set_normalization_speed(self): #elif self.type in ['2h_sword', '2h_mace', '2h_axe', 'polearm']: # self._normalization_speed = 3.3 #elif - + # commented out for micro performance's sake # should be re-enabled if other classes ever make use of Shadowcraft if self.type == 'dagger': @@ -158,14 +157,14 @@ class GearBuffs(object): ] allowed_buffs = frozenset(other_gear_buffs) - + def __init__(self, *args): for arg in args: if not isinstance(arg, (list,tuple)): arg = (arg,0) if arg[0] in self.allowed_buffs: setattr(self, arg[0], True) - + def __getattr__(self, name): # Any gear buff we haven't assigned a value to, we don't have. @@ -181,12 +180,12 @@ def metagem_crit_multiplier(self): return 1.03 else: return 1 - + def rogue_pvp_4pc_extra_energy(self): if self.rogue_pvp_4pc: return 30 return 0 - + def rogue_t14_2pc_damage_bonus(self, spell): if self.rogue_t14_2pc: bonus = { @@ -198,43 +197,43 @@ def rogue_t14_2pc_damage_bonus(self, spell): if spell in spells: return 1 + bonus[spells] return 1 - + def rogue_t14_4pc_extra_time(self, is_combat=False): if is_combat: return self.rogue_t14_4pc * 6 return self.rogue_t14_4pc * 12 - + def rogue_t15_2pc_bonus_cp(self): if self.rogue_t15_2pc: return 1 return 0 - + def rogue_t15_4pc_reduced_cost(self, uptime= 12. / 180.): #This is for Mut calcs cost_reduction = .15 if self.rogue_t15_4pc: return 1. - (cost_reduction * uptime) return 1. - + def rogue_t15_4pc_modifier(self, is_sb=False): #This is for Combat/Sub calcs if self.rogue_t15_4pc and is_sb: return .85 # 1 - .15 return 1. - + def rogue_t16_2pc_bonus(self): if self.rogue_t16_2pc: return True return False - + def rogue_t16_4pc_bonus(self): if self.rogue_t16_4pc: return True return False - + def rogue_t17_2pc_bonus(self): if self.rogue_t17_2pc: return True return False - + def rogue_t17_4pc_bonus(self): if self.rogue_t17_4pc: return True @@ -244,12 +243,12 @@ def rogue_t18_2pc_bonus(self): if self.rogue_t18_2pc: return True return False - + def rogue_t18_4pc_bonus(self): if self.rogue_T18_4pc: return True return False - + def gear_specialization_multiplier(self): if self.gear_specialization: return 1.05 diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index 94201ce..cc4d474 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -76,11 +76,11 @@ def get_tier_for_talent(self, name): def set_talent(self, name): # Clears talents in the tier and sets the new one if name not in self.allowed_talents: - return False + raise InvalidTalentException("Invalid talent") for talent in self.class_talents[self.get_tier_for_talent(name)]: setattr(self, talent, False) setattr(self, name, True) - + def get_active_talents(self): active_talents = [] for row in self.class_talents: diff --git a/tests/calcs_tests/__init__.py b/tests/calcs_tests/__init__.py index 6d0190c..d83cae6 100644 --- a/tests/calcs_tests/__init__.py +++ b/tests/calcs_tests/__init__.py @@ -6,20 +6,29 @@ from shadowcraft.objects import stats from shadowcraft.objects import procs from shadowcraft.objects import glyphs +from shadowcraft.objects import talents +from shadowcraft.objects import artifact class TestDamageCalculator(unittest.TestCase): - def make_calculator(self, buffs_list=[], gear_buffs_list=[], race_name='night_elf'): + def make_calculator(self, buffs_list=[], gear_buffs_list=[], race_name='night_elf', test_spec='outlaw'): test_buffs = buffs.Buffs(*buffs_list) test_gear_buffs = stats.GearBuffs(*gear_buffs_list) test_procs = procs.ProcsList() - test_mh = stats.Weapon(737, 1.8, 'dagger', 'hurricane') - test_oh = stats.Weapon(573, 1.4, 'dagger', 'hurricane') + test_mh = stats.Weapon(737, 1.8, 'dagger') + test_oh = stats.Weapon(573, 1.4, 'dagger') test_ranged = stats.Weapon(1104, 2.0, 'thrown') - test_stats = stats.Stats(20, 3485, 190, 1517, 1086, 641, 899, 666, test_mh, test_oh, test_ranged, test_procs, test_gear_buffs) + test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, + agi=20909, + stam=19566, + crit=4402, + haste=5150, + mastery=5999, + versatility=1515) test_race = race.Race(race_name) - test_talents = None + test_talents = talents.Talents('1000000', test_spec, 'rogue', level=110) test_glyphs = glyphs.Glyphs() - return calcs.DamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race) + test_traits = artifact.Artifact(test_spec, 'rogue', '000000000000000000') + return calcs.DamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, 'outlaw') def setUp(self): self.calculator = self.make_calculator() @@ -29,84 +38,16 @@ def test_melee_hit_chance(self): def test_one_hand_melee_hit_chance(self): self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=False, parryable=False), - 1.0) + self.calculator.one_hand_melee_hit_chance(dodgeable=False, parryable=False), 1.0) self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=False), - 1.0 - (0.065 - (641 / (30.027200698852539 * 4)) * 0.01)) - self.calculator.stats.exp = 0 + self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=False), 1.0) self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=False), - 1.0 - 0.065) + self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=True), 1.0 - 0.03) self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=True), - 1.0 - 0.14 - 0.065) - self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=False, parryable=True), - 1.0 - 0.14) - self.calculator.stats.hit = 0 - self.assertAlmostEqual( - self.calculator.one_hand_melee_hit_chance(dodgeable=True, parryable=False), - 1.0 - 0.065 - 0.08) + self.calculator.one_hand_melee_hit_chance(dodgeable=False, parryable=True), 1.0 - 0.03) def test_dual_wield_mh_hit_chance(self): - self.assertAlmostEqual( - self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=False), - 1.0 - (0.27 - 0.01 * (1086 / 120.109001159667969))) - self.calculator.stats.hit = 0 - self.calculator.stats.exp = 0 - self.assertAlmostEqual( - self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=False), - 1.0 - 0.27) - self.assertAlmostEqual( - self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=False), - 1.0 - 0.27 - 0.065) - self.assertAlmostEqual( - self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=True), - 1.0 - 0.27 - 0.065 - 0.14) - self.assertAlmostEqual( - self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=True), - 1.0 - 0.27 - 0.14) - - def test_dual_wield_oh_hit_chance(self): - pass - - def test_spell_hit_chance(self): - self.assertAlmostEqual(self.calculator.spell_hit_chance(), - 1.0 - (0.17 - 0.01 * (1086 / 102.445999145507812))) - - def test_buff_melee_crit(self): - pass - - def test_buff_spell_crit(self): - pass - - def test_target_armor(self): - pass - - def test_raid_settings_modifiers(self): - self.assertRaises(exceptions.InvalidInputException, self.calculator.raid_settings_modifiers, '') - - def test_mixology_no_flask(self): - test_calculator = self.make_calculator(gear_buffs_list=['mixology']) - self.assertEqual(test_calculator.stats.agi, self.calculator.stats.agi) - - def test_mixology(self): - test_calculator = self.make_calculator(buffs_list=['agi_flask'], gear_buffs_list=['mixology']) - self.assertEqual(test_calculator.stats.agi, self.calculator.stats.agi + 80) - - def test_master_of_anatomy(self): - test_calculator = self.make_calculator(gear_buffs_list=['master_of_anatomy']) - self.assertEqual(test_calculator.stats.crit, self.calculator.stats.crit + 80) - - def test_get_all_activated_stat_boosts(self): - calculator = self.make_calculator(gear_buffs_list=['leather_specialization', 'potion_of_the_tolvir'], race_name='orc') - boosts = calculator.get_all_activated_stat_boosts() - self.assertEqual(len(boosts), 3) # blood fury sp, blood fury ap, potion of the tolvir - for boost in boosts: - if boost['stat'] == 'ap': - self.assertEqual(boost['value'], 1170) - elif boost['stat'] == 'sp': - self.assertEqual(boost['value'], 585) - elif boost['stat'] == 'agi': - self.assertEqual(boost['value'], 1200) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=False), 1.0 - 0.17) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=False), 1.0 - 0.17) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=True), 1.0 - 0.17 - 0.03) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=True), 1.0 - 0.17 - 0.03) \ No newline at end of file diff --git a/tests/calcs_tests/armor_mitigation_tests.py b/tests/calcs_tests/armor_mitigation_tests.py deleted file mode 100644 index 627d587..0000000 --- a/tests/calcs_tests/armor_mitigation_tests.py +++ /dev/null @@ -1,43 +0,0 @@ -from shadowcraft import calcs -import unittest -from shadowcraft.core import exceptions -from shadowcraft.calcs import armor_mitigation - -class TestArmorMitigation(unittest.TestCase): - def test_thresholds(self): - self.assertRaises(exceptions.InvalidLevelException, armor_mitigation.lookup_parameters, 0) - self.assertEqual(1, armor_mitigation.lookup_parameters(1)[0]) - self.assertEqual(1, armor_mitigation.lookup_parameters(59)[0]) - self.assertEqual(60, armor_mitigation.lookup_parameters(60)[0]) - self.assertEqual(60, armor_mitigation.lookup_parameters(80)[0]) - self.assertEqual(81, armor_mitigation.lookup_parameters(81)[0]) - - def test_parameter_spot_checks(self): - self.assertAlmostEqual( 5882.5, armor_mitigation.parameter(60)) - self.assertAlmostEqual(10557.5, armor_mitigation.parameter(70)) - self.assertAlmostEqual(15232.5, armor_mitigation.parameter(80)) - self.assertAlmostEqual(26070.0, armor_mitigation.parameter(85)) - - def test_mitigation_spot_checks(self): - self.assertAlmostEqual(0.4441, armor_mitigation.mitigation(4700, 60), 4) - self.assertAlmostEqual(0.4217, armor_mitigation.mitigation(7700, 70), 4) - self.assertAlmostEqual(0.4109, armor_mitigation.mitigation(10623, 80), 4) - self.assertAlmostEqual(0.3148, armor_mitigation.mitigation(11977, 85), 4) - - def test_mitigation_cached_parameter(self): - self.assertAlmostEqual(0.4441, armor_mitigation.mitigation(4700, 60, 5882.5), 4) - self.assertAlmostEqual(0.4217, armor_mitigation.mitigation(7700, 70, 10557.5), 4) - self.assertAlmostEqual(0.4109, armor_mitigation.mitigation(10623, 80, 15232.5), 4) - self.assertAlmostEqual(0.3148, armor_mitigation.mitigation(11977, 85, 26070.0), 4) - - def test_multiplier_spot_checks(self): - self.assertAlmostEqual(1 - 0.4441, armor_mitigation.multiplier(4700, 60), 4) - self.assertAlmostEqual(1 - 0.4217, armor_mitigation.multiplier(7700, 70), 4) - self.assertAlmostEqual(1 - 0.4109, armor_mitigation.multiplier(10623, 80), 4) - self.assertAlmostEqual(1 - 0.3148, armor_mitigation.multiplier(11977, 85), 4) - - def test_multiplier_cached_paramter(self): - self.assertAlmostEqual(1 - 0.4441, armor_mitigation.multiplier(4700, 60, 5882.5), 4) - self.assertAlmostEqual(1 - 0.4217, armor_mitigation.multiplier(7700, 70, 10557.5), 4) - self.assertAlmostEqual(1 - 0.4109, armor_mitigation.multiplier(10623, 80, 15232.5), 4) - self.assertAlmostEqual(1 - 0.3148, armor_mitigation.multiplier(11977, 85, 26070.0), 4) diff --git a/tests/calcs_tests/rogue_tests/Aldriana_tests/__init__.py b/tests/calcs_tests/rogue_tests/Aldriana_tests/__init__.py deleted file mode 100644 index 97a7cbf..0000000 --- a/tests/calcs_tests/rogue_tests/Aldriana_tests/__init__.py +++ /dev/null @@ -1,35 +0,0 @@ -import unittest -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects.rogue import rogue_talents -from shadowcraft.objects.rogue import rogue_glyphs - -class TestAldrianasRogueDamageCalculator(unittest.TestCase): - def test_get_ep(self): - test_buffs = buffs.Buffs() - test_mh = stats.Weapon(939.5, 1.8, 'dagger', 'landslide') - test_oh = stats.Weapon(730.5, 1.4, 'dagger', 'landslide') - test_ranged = stats.Weapon(1371.5, 2.2, 'thrown') - test_procs = procs.ProcsList('heroic_prestors_talisman_of_machination', 'fluid_death', 'rogue_t11_4pc') - test_gear_buffs = stats.GearBuffs('rogue_t11_2pc', 'leather_specialization', 'potion_of_the_tolvir', 'chaotic_metagem') - test_stats = stats.Stats(20, 4756, 190, 1022, 1329, 597, 1189, 1377, test_mh, test_oh, test_ranged, test_procs, test_gear_buffs) - test_talents = rogue_talents.RogueTalents('0333230113022110321', '0020000000000000000', '2030030000000000000') - glyph_list = ['backstab', 'mutilate', 'rupture'] - test_glyphs = rogue_glyphs.RogueGlyphs(*glyph_list) - test_race = race.Race('night_elf') - test_cycle = settings.AssassinationCycle() - test_settings = settings.Settings(test_cycle, response_time=1) - test_level = 85 - calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - ep_values = calculator.get_ep() - self.assertTrue(ep_values['agi'] < 4.0) - self.assertTrue(ep_values['agi'] > 2.0) - self.assertTrue(ep_values['yellow_hit'] < 4.0) - self.assertTrue(ep_values['yellow_hit'] > 1.0) - self.assertTrue(ep_values['crit'] < 2.0) - self.assertTrue(ep_values['crit'] > 0.0) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index ba44c8a..8af173b 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -1,165 +1,96 @@ import unittest -from shadowcraft.calcs.rogue import RogueDamageCalculator +from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.core import exceptions from shadowcraft.objects import buffs from shadowcraft.objects import race from shadowcraft.objects import stats from shadowcraft.objects import procs -from shadowcraft.objects.rogue import rogue_talents +from shadowcraft.objects import talents +from shadowcraft.objects import artifact +from shadowcraft.calcs.rogue.Aldriana import settings class TestRogueDamageCalculator(unittest.TestCase): def setUp(self): - test_buffs = buffs.Buffs( - 'stat_multiplier_buff', - 'crit_chance_buff', - 'melee_haste_buff', - 'attack_power_buff', - 'str_and_agi_buff', - 'armor_debuff', - 'spell_damage_debuff', - 'spell_crit_debuff' - ) - test_mh = stats.Weapon(737, 1.8, 'dagger', 'hurricane') - test_oh = stats.Weapon(573, 1.4, 'dagger', 'hurricane') - test_ranged = stats.Weapon(1104, 2.0, 'thrown') - test_procs = procs.ProcsList('darkmoon_card_hurricane') - test_gear_buffs = stats.GearBuffs('chaotic_metagem') - test_stats = stats.Stats(20, 3485, 190, 1517, 1086, 641, 899, 666, test_mh, test_oh, test_ranged, test_procs, test_gear_buffs) - test_race = race.Race('night_elf') - test_talents = rogue_talents.RogueTalents('0333230113022110321', '0020000000000000000', '0030030000000000000') - self.calculator = RogueDamageCalculator(test_stats, test_talents, None, test_buffs, test_race) - - def test_get_spell_hit_from_talents(self): - self.assertAlmostEqual(self.calculator.get_spell_hit_from_talents(), .04) - self.calculator.talents.precision = 0 - self.assertAlmostEqual(self.calculator.get_spell_hit_from_talents(), .0) - - def test_get_melee_hit_from_talents(self): - self.assertAlmostEqual(self.calculator.get_melee_hit_from_talents(), .04) - self.calculator.talents.precision = 3 - self.assertAlmostEqual(self.calculator.get_melee_hit_from_talents(), .06) + test_level = 110 + test_race = race.Race('pandaren') + test_class = 'rogue' + test_spec = 'outlaw' + + # Set up buffs. + test_buffs = buffs.Buffs('short_term_haste_buff', + 'flask_wod_agi', + 'food_wod_versatility') + + # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand + test_mh = stats.Weapon(4821.0, 2.6, 'sword', None) + test_oh = stats.Weapon(4821.0, 2.6, 'sword', None) + + # Set up procs. + #test_procs = procs.ProcsList(('assurance_of_consequence', 588), + #('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', + #'archmages_incandescence') #trinkets, other things (legendary procs) + test_procs = procs.ProcsList() + + # Set up gear buffs. + test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here + + # Set up a calcs object.. + test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, + agi=20909, + stam=19566, + crit=4402, + haste=5150, + mastery=5999, + versatility=1515,) + + # Initialize talents.. + test_talents = talents.Talents('1000000', test_spec, test_class, level=test_level) + + #initialize artifact traits.. + test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') + + # Set up settings. + test_cycle = settings.OutlawCycle(blade_flurry=False, + jolly_roger_reroll=1, + grand_melee_reroll=1, + shark_reroll=1, + true_bearing_reroll=1, + buried_treasure_reroll=1, + broadsides_reroll=1 + ) + test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, + adv_params="", is_demon=True, num_boss_adds=0) + + # Build a DPS object. + self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) + def test_oh_penalty(self): self.assertAlmostEqual(self.calculator.oh_penalty(), 0.5) - def test_talents_modifiers_assassins_resolve(self): - self.assertAlmostEqual(self.calculator.talents_modifiers([]), 1.0) - self.assertAlmostEqual(self.calculator.talents_modifiers(['assassins_resolve']), 1.2) - self.calculator.stats.mh.type = '1h_axe' - self.assertAlmostEqual(self.calculator.talents_modifiers(['assassins_resolve']), 1.0) - - def test_talents_modifiers(self): - self.assertAlmostEqual(self.calculator.talents_modifiers(['opportunity', 'assassins_resolve']), 1.2 * 1.3) - def test_crit_damage_modifiers(self): - self.assertAlmostEqual(self.calculator.crit_damage_modifiers(), 1 + (2 * 1.03 - 1) * 1) - self.assertAlmostEqual(self.calculator.crit_damage_modifiers(is_spell=True), 1 + (1.5 * 1.03 - 1) * 1) - self.assertAlmostEqual(self.calculator.crit_damage_modifiers(lethality=True), 1 + (2 * 1.03 - 1) * 1.3) - - # Just do some basic checks for the individual abilities, increasing AP - # should increase damage and similar for combo points. - # The optional armor argument isn't tested for now. - # Should probably compare damage and crit_damage individually so it can - # catch some error where crit_damage is lower even with higher AP. - - def test_mh_damage(self): - self.assertTrue(self.calculator.mh_damage(0) < self.calculator.mh_damage(1)) - - def test_oh_damage(self): - self.assertTrue(self.calculator.oh_damage(0) < self.calculator.oh_damage(1)) - - def test_backstab_damage(self): - self.assertTrue(self.calculator.backstab_damage(0) < self.calculator.backstab_damage(1)) - - def test_mh_mutilate_damage(self): - self.assertTrue(self.calculator.mh_mutilate_damage(0) < self.calculator.mh_mutilate_damage(1)) - not_poisoned = self.calculator.mh_mutilate_damage(1, is_poisoned=False) - poisoned = self.calculator.mh_mutilate_damage(1) - self.assertAlmostEqual(not_poisoned[0] * 1.2, poisoned[0]) - self.assertAlmostEqual(not_poisoned[1] * 1.2, poisoned[1]) - - def test_oh_mutilate_damage(self): - self.assertTrue(self.calculator.oh_mutilate_damage(0) < self.calculator.oh_mutilate_damage(1)) - not_poisoned = self.calculator.oh_mutilate_damage(1, is_poisoned=False) - poisoned = self.calculator.oh_mutilate_damage(1) - self.assertAlmostEqual(not_poisoned[0] * 1.2, poisoned[0]) - self.assertAlmostEqual(not_poisoned[1] * 1.2, poisoned[1]) - - def test_sinister_strike_damage(self): - self.assertTrue(self.calculator.sinister_strike_damage(0) < self.calculator.sinister_strike_damage(1)) - - def test_hemorrhage_damage(self): - self.assertTrue(self.calculator.hemorrhage_damage(0) < self.calculator.hemorrhage_damage(1)) - - def test_hemorrhage_tick_damage(self): - self.assertTrue(self.calculator.hemorrhage_tick_damage(0) < self.calculator.hemorrhage_tick_damage(1)) - self.assertTrue(self.calculator.hemorrhage_tick_damage(0, from_crit_hemo=False) < self.calculator.hemorrhage_tick_damage(0, from_crit_hemo=True)) - - def test_ambush_damage(self): - self.assertTrue(self.calculator.ambush_damage(0) < self.calculator.ambush_damage(1)) - - def test_revealing_strike_damage(self): - self.assertTrue(self.calculator.revealing_strike_damage(0) < self.calculator.revealing_strike_damage(1)) - - def test_venomous_wounds_damage(self): - self.assertTrue(self.calculator.venomous_wounds_damage(0) < self.calculator.venomous_wounds_damage(1)) - - def test_main_gauche_damage(self): - self.assertTrue(self.calculator.main_gauche_damage(0) < self.calculator.main_gauche_damage(1)) - - def test_mh_killing_spree_damage(self): - self.assertTrue(self.calculator.mh_killing_spree_damage(0) < self.calculator.mh_killing_spree_damage(1)) - - def test_oh_killing_spree_damage(self): - self.assertTrue(self.calculator.oh_killing_spree_damage(0) < self.calculator.oh_killing_spree_damage(1)) - - def test_instant_poison_damage(self): - self.assertTrue(self.calculator.instant_poison_damage(0) < self.calculator.instant_poison_damage(1)) - self.assertTrue(self.calculator.instant_poison_damage(0, mastery=0) < self.calculator.instant_poison_damage(0, mastery=1)) - - def test_deadly_poison_tick_damage(self): - # test mastery - self.assertTrue(self.calculator.deadly_poison_tick_damage(0) < self.calculator.deadly_poison_tick_damage(1)) - self.assertTrue(self.calculator.deadly_poison_tick_damage(0, dp_stacks=1) < self.calculator.deadly_poison_tick_damage(0, dp_stacks=2)) - - def test_wound_poison_damage(self): - self.assertTrue(self.calculator.wound_poison_damage(0) < self.calculator.wound_poison_damage(1)) - self.assertTrue(self.calculator.wound_poison_damage(0, mastery=0) < self.calculator.wound_poison_damage(0, mastery=1)) - - def test_garrote_tick_damage(self): - self.assertTrue(self.calculator.garrote_tick_damage(0) < self.calculator.garrote_tick_damage(1)) - - def test_rupture_tick_damage(self): - self.assertTrue(self.calculator.rupture_tick_damage(0, 1) < self.calculator.rupture_tick_damage(1, 1)) - self.assertTrue(self.calculator.rupture_tick_damage(0, 1) < self.calculator.rupture_tick_damage(0, 2)) - self.assertRaises(IndexError, self.calculator.rupture_tick_damage, 0, 6) - - def test_eviscerate_damage(self): - self.assertTrue(self.calculator.eviscerate_damage(0, 1) < self.calculator.eviscerate_damage(1, 1)) - self.assertTrue(self.calculator.eviscerate_damage(0, 1) < self.calculator.eviscerate_damage(0, 2)) - - def test_envenom_damage(self): - self.assertTrue(self.calculator.envenom_damage(0, 1) < self.calculator.envenom_damage(1, 1)) - self.assertTrue(self.calculator.envenom_damage(0, 1) < self.calculator.envenom_damage(0, 2)) + self.assertAlmostEqual(self.calculator.crit_damage_modifiers(), 1 + (2 * 1. - 1) * 1) - def test_melee_crit_rate(self): - agi_per_crit = self.calculator.level == 80 and 83.15 or 324.72 - crit_rating_per_crit = self.calculator.level == 80 and 45.906 or 179.279998779296875 - self.assertAlmostEqual(self.calculator.melee_crit_rate(agi=1000), - 0.01 * (1000 / agi_per_crit - 0.295) + 0.01 * (1517 / crit_rating_per_crit) + 0.05 - 0.048) - self.assertTrue(self.calculator.spell_crit_rate(0) < self.calculator.spell_crit_rate(1)) + def test_ep(self): + ep_values = self.calculator.get_ep() - def test_spell_crit_rate(self): - self.assertTrue(self.calculator.melee_crit_rate(0) < self.calculator.melee_crit_rate(1)) + self.assertTrue(ep_values['agi'] < 1.5) + self.assertTrue(ep_values['agi'] > 1.0) + self.assertTrue(ep_values['mastery'] < 1.0) + self.assertTrue(ep_values['mastery'] > 0.0) + self.assertTrue(ep_values['haste'] < 1.0) + self.assertTrue(ep_values['haste'] > 0.0) + self.assertTrue(ep_values['versatility'] < 1.0) + self.assertTrue(ep_values['versatility'] > 0.0) + self.assertTrue(ep_values['crit'] < 1.0) + self.assertTrue(ep_values['crit'] > 0.0) - def test_crit_cap(self): - pass class TestRogueDamageCalculatorLevels(TestRogueDamageCalculator): def setUp(self): super(TestRogueDamageCalculatorLevels, self).setUp() - self.calculator.level = 80 + self.calculator.level = 110 def test_set_constants_for_level(self): - self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 86) + self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) diff --git a/tests/objects_tests/buffs_tests.py b/tests/objects_tests/buffs_tests.py index c302af1..f69080e 100644 --- a/tests/objects_tests/buffs_tests.py +++ b/tests/objects_tests/buffs_tests.py @@ -1,109 +1,80 @@ import unittest from shadowcraft.core import exceptions from shadowcraft.objects import buffs - + class TestBuffsTrue(unittest.TestCase): def setUp(self): self.buffs = buffs.Buffs(*buffs.Buffs.allowed_buffs) - + def test_exception(self): self.assertRaises(buffs.InvalidBuffException, buffs.Buffs, 'fake_buff') - + def test__getattr__(self): self.assertRaises(AttributeError, self.buffs.__getattr__, 'fake_buff') - self.assertTrue(self.buffs.crit_chance_buff) - - def test_stat_multiplier(self): - self.assertEqual(self.buffs.stat_multiplier(), 1.05) - - def test_all_damage_multiplier(self): - self.assertEqual(self.buffs.all_damage_multiplier(), 1.03) - - def test_spell_damage_multiplier(self): - self.assertEqual(self.buffs.spell_damage_multiplier(), 1.08 * 1.03) - - def test_physical_damage_multiplier(self): - self.assertEqual(self.buffs.physical_damage_multiplier(), 1.04 * 1.03) - - def test_bleed_damage_multiploer(self): - self.assertEqual(self.buffs.bleed_damage_multiplier(), 1.30 * 1.03 * 1.04) - - def test_attack_power_multiplier(self): - self.assertEqual(self.buffs.attack_power_multiplier(), 1.1) - - def test_melee_haste_multiplier(self): - self.assertEqual(self.buffs.melee_haste_multiplier(), 1.1) - - def test_buff_str(self): - self.assertEqual(self.buffs.buff_str(), 549) + self.assertTrue(self.buffs.flask_wod_agi_200) def test_buff_agi(self): - self.assertEqual(self.buffs.buff_agi(), 549 + 90 + 300) - - def test_buff_all_crit(self): - self.assertEqual(self.buffs.buff_all_crit(), 0.05) - - def test_buff_spell_crit(self): - self.assertEqual(self.buffs.buff_spell_crit(), 0.05) - - def test_armor_reduction_multiplier(self): - self.assertEqual(self.buffs.armor_reduction_multiplier(), 0.88) + self.assertEqual(self.buffs.buff_agi(), 2100) class TestBuffsFalse(unittest.TestCase): def setUp(self): self.buffs = buffs.Buffs() - + def test__getattr__(self): self.assertRaises(AttributeError, self.buffs.__getattr__, 'fake_buff') - self.assertFalse(self.buffs.bleed_damage_debuff) - - def test_stat_multiplier(self): - self.assertEqual(self.buffs.stat_multiplier(), 1.0) - - def test_all_damage_multiplier(self): - self.assertEqual(self.buffs.all_damage_multiplier(), 1.0) - - def test_spell_damage_multiplier(self): - self.assertEqual(self.buffs.spell_damage_multiplier(), 1.0) - - def test_physical_damage_multiplier(self): - self.assertEqual(self.buffs.physical_damage_multiplier(), 1.0) - - def test_bleed_damage_multiploer(self): - self.assertEqual(self.buffs.bleed_damage_multiplier(), 1.0) - - def test_attack_power_multiplier(self): - self.assertEqual(self.buffs.attack_power_multiplier(), 1.0) - - def test_melee_haste_multiplier(self): - self.assertEqual(self.buffs.melee_haste_multiplier(), 1.0) - - def test_buff_str(self): - self.assertEqual(self.buffs.buff_str(), 0) + self.assertFalse(self.buffs.flask_legion_agi) - def test_buff_agi(self): - self.assertEqual(self.buffs.buff_agi(), 0) - - def test_buff_all_crit(self): - self.assertEqual(self.buffs.buff_all_crit(), 0.0) - - def test_buff_spell_crit(self): - self.assertEqual(self.buffs.buff_spell_crit(), 0.0) - - def test_armor_reduction_multiplier(self): - self.assertEqual(self.buffs.armor_reduction_multiplier(), 1.0) - -class TestBuffsLevel(unittest.TestCase): - def setUp(self): - self.buffs = buffs.Buffs('str_and_agi_buff') - - def test(self): - self.assertEqual(self.buffs.buff_agi(), 549) - self.assertEqual(self.buffs.buff_str(), 549) - self.buffs.level = 80 - self.assertEqual(self.buffs.buff_agi(), 155) - self.assertEqual(self.buffs.buff_str(), 155) + def test_flask_legion_agi(self): + self.assertEqual(self.buffs.flask_legion_agi, False) - def test_exception(self): - self.assertRaises(exceptions.InvalidLevelException, self.buffs.__setattr__, 'level', 86) + def test_food_legion_mastery_225(self): + self.assertEqual(self.buffs.food_legion_mastery_225, False) + + def test_food_legion_crit_225(self): + self.assertEqual(self.buffs.food_legion_crit_225, False) + + def test_food_legion_haste_225(self): + self.assertEqual(self.buffs.food_legion_haste_225, False) + + def test_food_legion_versatility_225(self): + self.assertEqual(self.buffs.food_legion_versatility_225, False) + + def test_food_legion_mastery_300(self): + self.assertEqual(self.buffs.food_legion_mastery_300, False) + + def test_food_legion_crit_300(self): + self.assertEqual(self.buffs.food_legion_crit_300, False) + + def test_food_legion_haste_300(self): + self.assertEqual(self.buffs.food_legion_haste_300, False) + + def test_food_legion_versatility_300(self): + self.assertEqual(self.buffs.food_legion_versatility_300, False) + + def test_food_legion_mastery_375(self): + self.assertEqual(self.buffs.food_legion_mastery_375, False) + + def test_food_legion_crit_375(self): + self.assertEqual(self.buffs.food_legion_crit_375, False) + + def test_food_legion_haste_375(self): + self.assertEqual(self.buffs.food_legion_haste_375, False) + + def test_food_legion_versatility_375(self): + self.assertEqual(self.buffs.food_legion_versatility_375, False) + + def test_food_legion_damage_1(self): + self.assertEqual(self.buffs.food_legion_damage_1, False) + + def test_food_legion_damage_2(self): + self.assertEqual(self.buffs.food_legion_damage_2, False) + + def test_food_legion_damage_3(self): + self.assertEqual(self.buffs.food_legion_damage_3, False) + + def test_food_legion_feast_150(self): + self.assertEqual(self.buffs.food_legion_feast_150, False) + + def test_food_legion_feast_200(self): + self.assertEqual(self.buffs.food_legion_feast_200, False) \ No newline at end of file diff --git a/tests/objects_tests/procs_tests.py b/tests/objects_tests/procs_tests.py index 4375ea8..e1b3a73 100644 --- a/tests/objects_tests/procs_tests.py +++ b/tests/objects_tests/procs_tests.py @@ -1,76 +1,72 @@ import unittest from shadowcraft.objects import procs - + class TestProcsList(unittest.TestCase): def setUp(self): - self.procsList = procs.ProcsList('darkmoon_card_hurricane','heroic_left_eye_of_rajh') - + self.procsList = procs.ProcsList('fury_of_xuen','legendary_capacitive_meta') + def test__init__(self): self.assertRaises(procs.InvalidProcException, procs.ProcsList, 'fake_proc') - self.procsList = procs.ProcsList('darkmoon_card_hurricane') + self.procsList = procs.ProcsList('fury_of_xuen') self.assertEqual(len(self.procsList.get_all_procs_for_stat(stat=None)), 1) - + def test__getattr__(self): self.assertRaises(AttributeError, self.procsList.__getattr__, 'fake_proc') - self.assertTrue(self.procsList.darkmoon_card_hurricane) - self.assertFalse(self.procsList.fluid_death) - + self.assertTrue(self.procsList.fury_of_xuen) + self.assertFalse(self.procsList.touch_of_the_grave) + def test_get_all_procs_for_stat(self): self.assertEqual(len(self.procsList.get_all_procs_for_stat(stat=None)), 2) self.procsList = procs.ProcsList() self.assertEqual(len(self.procsList.get_all_procs_for_stat(stat=None)), 0) - + def test_get_all_damage_procs(self): - self.assertEqual(len(self.procsList.get_all_damage_procs()), 1) + self.assertEqual(len(self.procsList.get_all_damage_procs()), 2) self.procsList = procs.ProcsList() self.assertEqual(len(self.procsList.get_all_damage_procs()), 0) class TestProc(unittest.TestCase): def setUp(self): - self.proc = procs.Proc(**procs.ProcsList.allowed_procs['prestors_talisman_of_machination']) - + self.proc = procs.Proc(**procs.ProcsList.allowed_procs['bloodthirsty_instinct']) + def test__init__(self): - self.assertEqual(self.proc.stat, 'haste') - self.assertEqual(self.proc.value, 1926) - self.assertEqual(self.proc.duration, 15) - self.assertEqual(self.proc.proc_chance, .1) + self.assertEqual(self.proc.stat, 'stats') + self.assertEqual(self.proc.value['haste'], 3399) + self.assertEqual(self.proc.duration, 10) + self.assertEqual(self.proc.proc_rate, 3) self.assertEqual(self.proc.trigger, 'all_attacks') - self.assertEqual(self.proc.icd, 75) + self.assertEqual(self.proc.icd, 0) self.assertEqual(self.proc.max_stacks, 1) self.assertEqual(self.proc.on_crit, False) - self.assertEqual(self.proc.proc_name, 'Nefarious Plot') - self.assertEqual(self.proc.ppm, False) - + self.assertEqual(self.proc.proc_name, 'Bloodthirsty Instinct') + def test_procs_off_auto_attacks(self): self.assertTrue(self.proc.procs_off_auto_attacks()) - + def test_procs_off_strikes(self): self.assertTrue(self.proc.procs_off_strikes()) - + def test_procs_off_harmful_spells(self): self.assertFalse(self.proc.procs_off_harmful_spells()) - + def test_procs_off_heals(self): self.assertFalse(self.proc.procs_off_heals()) - + def test_procs_off_periodic_spell_damage(self): self.assertFalse(self.proc.procs_off_periodic_spell_damage()) - + def test_procs_off_periodic_heals(self): self.assertFalse(self.proc.procs_off_periodic_heals()) - + def test_procs_off_apply_debuff(self): self.assertTrue(self.proc.procs_off_apply_debuff()) - + def test_procs_off_bleeds(self): self.assertFalse(self.proc.procs_off_bleeds()) - + def test_procs_off_crit_only(self): self.assertFalse(self.proc.procs_off_crit_only()) def test_is_ppm(self): self.assertFalse(self.proc.is_ppm()) - - def test_proc_rate(self): - self.assertEqual(self.proc.proc_rate(), self.proc.proc_chance) diff --git a/tests/objects_tests/race_tests.py b/tests/objects_tests/race_tests.py index 18fefc3..529fc28 100644 --- a/tests/objects_tests/race_tests.py +++ b/tests/objects_tests/race_tests.py @@ -1,54 +1,42 @@ import unittest from shadowcraft.objects import race - + class TestRace(unittest.TestCase): def setUp(self): self.race = race.Race('human') - + def test__init__(self): self.assertEqual(self.race.race_name, 'human') self.assertEqual(self.race.character_class, 'rogue') def test_set_racials(self): - self.assertTrue(self.race.sword_1h_specialization) + self.assertTrue(self.race.human_spirit) self.assertFalse(self.race.blood_fury_physical) def test_exceptions(self): - self.assertRaises(race.InvalidRaceException, self.race.__setattr__, 'level', 81) + self.assertRaises(race.InvalidRaceException, self.race.__setattr__, 'level', 111) self.assertRaises(race.InvalidRaceException, self.race.__init__, 'murloc') self.assertRaises(race.InvalidRaceException, self.race.__init__, 'undead', 'demon_hunter') - + def test__getattr__(self): - racial_stats = (122, 206, 114, 46, 73) + racial_stats = (288, 306, 212, 169, 127) for i, stat in enumerate(['racial_str', 'racial_agi', 'racial_sta', 'racial_int', 'racial_spi']): self.assertEqual(getattr(self.race, stat), racial_stats[i]) - racial_stats = (122 - 4, 206 + 4, 114, 46, 73) + racial_stats = (288 - 4, 306 + 4, 212, 169, 127) night_elf = race.Race('night_elf') for i, stat in enumerate(['racial_str', 'racial_agi', 'racial_sta', 'racial_int', 'racial_spi']): self.assertEqual(getattr(night_elf, stat), racial_stats[i]) - def test_get_racial_expertise(self): - self.assertTrue(abs(self.race.get_racial_expertise('1h_sword') - 0.0075) < 0.00000001) - self.assertEqual(self.race.get_racial_expertise('1h_axe'), 0) - def test_get_racial_crit(self): for weapon in ('thrown', 'gun', 'bow'): self.assertEqual(self.race.get_racial_crit(weapon), 0) - troll = race.Race('troll') - self.assertEqual(troll.get_racial_crit('thrown'), 0.01) - self.assertEqual(troll.get_racial_crit('bow'), 0.01) - self.assertEqual(troll.get_racial_crit('gun'), 0) + troll = race.Race('troll') self.assertEqual(troll.get_racial_crit(), 0) worgen = race.Race('worgen') self.assertEqual(worgen.get_racial_crit(), 0.01) self.assertEqual(worgen.get_racial_crit('gun'), 0.01) self.assertEqual(worgen.get_racial_crit('axe'), 0.01) - def test_get_racial_hit(self): - self.assertEqual(self.race.get_racial_hit(), 0) - draenei = race.Race('draenei') - self.assertEqual(draenei.get_racial_hit(), 0.01) - def test_get_racial_haste(self): self.assertEqual(self.race.get_racial_haste(), 0) goblin = race.Race('goblin') @@ -57,14 +45,14 @@ def test_get_racial_haste(self): def test_get_racial_stat_boosts(self): self.assertEqual(len(self.race.get_racial_stat_boosts()), 0) orc = race.Race('orc') - orc.level = 85; + orc.level = 110; abilities = orc.get_racial_stat_boosts() self.assertEqual(len(abilities), 2) self.assertEqual(abilities[0]['duration'], 15) self.assertTrue(abilities[1]['stat'] in ('ap', 'sp')) self.assertNotEqual(abilities[0]['stat'],abilities[1]['stat']) if (abilities[0]['stat'] == 'ap'): - self.assertEqual(abilities[0]['value'], 1170) + self.assertEqual(abilities[0]['value'], 2243) else: self.assertEqual(abilities[0]['value'], 585) diff --git a/tests/objects_tests/rogue_tests/rogue_glyphs_tests.py b/tests/objects_tests/rogue_tests/rogue_glyphs_tests.py deleted file mode 100644 index 7bb7235..0000000 --- a/tests/objects_tests/rogue_tests/rogue_glyphs_tests.py +++ /dev/null @@ -1,11 +0,0 @@ -import unittest -from shadowcraft.objects.rogue import rogue_glyphs - -class TestRogueGlyphs(unittest.TestCase): - def setUp(self): - self.glyphs = rogue_glyphs.RogueGlyphs('backstab', 'mutilate', 'rupture') - - def test__getattr__(self): - self.assertRaises(AttributeError, self.glyphs.__getattr__, 'fake_glyph') - self.assertTrue(self.glyphs.backstab) - self.assertFalse(self.glyphs.slice_and_dice) diff --git a/tests/objects_tests/rogue_tests/rogue_talents_tests.py b/tests/objects_tests/rogue_tests/rogue_talents_tests.py index b06be64..56c6db5 100644 --- a/tests/objects_tests/rogue_tests/rogue_talents_tests.py +++ b/tests/objects_tests/rogue_tests/rogue_talents_tests.py @@ -1,30 +1,21 @@ import unittest -from shadowcraft.objects import talents -from shadowcraft.objects.rogue import rogue_talents +from shadowcraft.objects.talents import Talents +from shadowcraft.objects.talents import InvalidTalentException class TestAssassinationTalents(unittest.TestCase): # Tests for the abstract class objects.talents.TalentTree def setUp(self): - self.talents = rogue_talents.Assassination('0333230113022110321') + self.talents = Talents('1231231', 'assassination', 'rogue') def test__getattr__(self): self.assertRaises(AttributeError, self.talents.__getattr__, 'fake_talent') - talents = rogue_talents.Assassination() - self.assertEqual(talents.vendetta, 0) - - def test__init__kwargs(self): - talents = rogue_talents.Assassination(vendetta=1) - self.assertEqual(talents.vendetta, 1) - self.assertEqual(talents.cold_blood, 0) - self.assertRaises(AttributeError, talents.__getattr__, 'fake_talent') - + self.assertEqual(self.talents.master_poisoner, True) + self.assertEqual(self.talents.nightstalker, False) + def test_set_talent(self): - self.assertRaises(talents.InvalidTalentException, self.talents.set_talent, 'fake_talent', 2) - self.assertRaises(talents.InvalidTalentException, self.talents.set_talent, 'vendetta', -1) - self.assertRaises(talents.InvalidTalentException, self.talents.set_talent, 'vendetta', -2) - - def test_exceptions(self): - self.assertRaises(talents.InvalidTalentException, rogue_talents.Assassination, '10333230113022110321') + self.assertRaises(InvalidTalentException, self.talents.set_talent, 'fake_talent') + self.assertRaises(InvalidTalentException, self.talents.set_talent, 'vendetta') + self.assertRaises(InvalidTalentException, self.talents.set_talent, 'vendetta') class TestCombatTalents(unittest.TestCase): pass @@ -36,20 +27,11 @@ class TestSubtletyTalents(unittest.TestCase): class TestRogueTalents(unittest.TestCase): def setUp(self): - self.talents = rogue_talents.RogueTalents('0333230113022110321', '0020000000000000000', '2030030000000000000') + self.talents = Talents('1231231', 'assassination', 'rogue') def test(self): - self.assertEqual(self.talents.vendetta, 1) - self.assertEqual(self.talents.cold_blood, 1) - self.assertEqual(self.talents.relentless_strikes, 3) - self.assertEqual(self.talents.precision, 2) - self.assertEqual(self.talents.killing_spree, 0) - - def test_is_assassination_rogue(self): - self.assertTrue(self.talents.is_assassination_rogue()) - - def test_is_combat_rogue(self): - self.assertFalse(self.talents.is_combat_rogue()) - - def test_is_subtlety_rogue(self): - self.assertFalse(self.talents.is_subtlety_rogue()) + self.assertEqual(self.talents.master_poisoner, 1) + self.assertEqual(self.talents.subterfuge, 1) + self.assertEqual(self.talents.vigor, 1) + self.assertEqual(self.talents.cheat_death, 0) + self.assertEqual(self.talents.thuggee, 0) \ No newline at end of file diff --git a/tests/objects_tests/stats_tests.py b/tests/objects_tests/stats_tests.py index 0e30e3e..274729d 100644 --- a/tests/objects_tests/stats_tests.py +++ b/tests/objects_tests/stats_tests.py @@ -5,129 +5,48 @@ class TestStats(unittest.TestCase): def setUp(self): - self.stats = stats.Stats(20, 3485, 190, 1517, 1086, 641, 899, 666, None, None, None, None, None) + mainhand = stats.Weapon(1234, 2.6, "sword") + offhand = stats.Weapon(777, 2.6, "sword") + self.stats = stats.Stats(mainhand, offhand, procs.ProcsList(), None, str=20, agi=3485, int=190, stam=1086, crit=899, haste=666, mastery=1234, versatility=1222, level=110) def test_stats(self): self.assertEqual(self.stats.agi, 3485) def test_set_constants_for_level(self): - self.assertRaises(exceptions.InvalidLevelException, self.stats.__setattr__, 'level', 86) + self.assertRaises(exceptions.InvalidLevelException, self.stats.__setattr__, 'level', 111) def test_get_mastery_from_rating(self): - self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 666 / 179.279998779296875) - self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 179.279998779296875) - self.stats.level = 80 - self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 666 / 45.906) - self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 45.906) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 1234 / 350.0) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 350.0) - def test_get_melee_hit_from_rating(self): - self.assertAlmostEqual(self.stats.get_melee_hit_from_rating(), .01 * 1086 / 120.109001159667969) - self.assertAlmostEqual(self.stats.get_melee_hit_from_rating(100), .01 * 100 / 120.109001159667969) - - def test_get_expertise_from_rating(self): - self.assertAlmostEqual(self.stats.get_expertise_from_rating(), .01 * 641 / (30.027200698852539 * 4)) - self.assertAlmostEqual(self.stats.get_expertise_from_rating(100), .01 * 100 / (30.027200698852539 * 4)) - - def test_get_spell_hit_from_rating(self): - self.assertAlmostEqual(self.stats.get_spell_hit_from_rating(), .01 * 1086 / 102.445999145507812) - self.assertAlmostEqual(self.stats.get_spell_hit_from_rating(100, 0), .01 * 100 / 102.445999145507812) + def test_get_versatility_multiplier_from_rating(self): + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + 1222 / 40000.0) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + 100 / 40000.0) def test_get_crit_from_rating(self): - self.assertAlmostEqual(self.stats.get_crit_from_rating(), .01 * 1517 / 179.279998779296875) - self.assertAlmostEqual(self.stats.get_crit_from_rating(100), .01 * 100 / 179.279998779296875) + self.assertAlmostEqual(self.stats.get_crit_from_rating(), 899 / 35000.0) + self.assertAlmostEqual(self.stats.get_crit_from_rating(100), 100 / 35000.0) def test_get_haste_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + .01 * 899 / 128.057006835937500) - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + .01 * 100 / 128.057006835937500) - - -class TestWeapon(unittest.TestCase): - def setUp(self): - self.mh = stats.Weapon(1000, 2.0, 'dagger', 'hurricane') - self.ranged = stats.Weapon(1104, 2.0, 'thrown') - - def test___init__(self): - self.assertAlmostEqual(self.mh._normalization_speed, 1.7) - self.assertAlmostEqual(self.mh.speed, 2.0) - self.assertAlmostEqual(self.mh.weapon_dps, 1000 / 2.0) - self.assertEqual(self.mh.type, 'dagger') - self.assertRaises(exceptions.InvalidInputException, stats.Weapon, 1000, 2.0, 'thrown', 'fake_enchant') - self.assertAlmostEqual(self.ranged._normalization_speed, 2.1) - mh = stats.Weapon(1000, 1.8, 'dagger', 'hurricane') - self.assertAlmostEqual(mh.hurricane.proc_rate(speed=1.8), 1 * 1.8 / 60) - oh = stats.Weapon(1000, 1.4, 'dagger', 'hurricane') - self.assertAlmostEqual(mh.hurricane.proc_rate(speed=1.8), 1 * 1.8 / 60) - self.assertAlmostEqual(oh.hurricane.proc_rate(speed=1.4), 1 * 1.4 / 60) - - def test__getattr__(self): - self.assertTrue(self.mh.hurricane) - self.assertFalse(self.mh.landslide) - self.assertRaises(AttributeError, self.mh.__getattr__, 'fake_enchant') - - def test_is_melee(self): - self.assertTrue(self.mh.is_melee()) - self.assertFalse(self.ranged.is_melee()) - - def test_damage(self): - self.assertAlmostEqual(self.mh.damage(1000), 2.0 * (1000 / 2.0 + 1000 / 14.0)) - - def test_normalized_damage(self): - self.assertAlmostEqual(self.mh.normalized_damage(1000), 1000 + (1.7 * 1000 / 14.0)) - - def test_weapon_enchant_proc_rate_exception(self): - self.assertRaises(procs.InvalidProcException, self.mh.hurricane.proc_rate) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + 666 / 32500.0) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + 100 / 32500.0) class TestGearBuffs(unittest.TestCase): def setUp(self): - self.gear = stats.GearBuffs('chaotic_metagem', 'leather_specialization', 'rogue_t11_2pc', 'potion_of_the_tolvir', 'engineer_glove_enchant', 'lifeblood') + self.gear = stats.GearBuffs('chaotic_metagem', 'gear_specialization', 'rogue_t11_2pc', 'potion_of_the_tolvir', 'engineer_glove_enchant', 'lifeblood') self.gear_none = stats.GearBuffs() def test__getattr__(self): self.assertTrue(self.gear.chaotic_metagem) - self.assertTrue(self.gear.leather_specialization) - self.assertFalse(self.gear.unsolvable_riddle) + self.assertTrue(self.gear.gear_specialization) + self.assertFalse(self.gear.rogue_t16_2pc) self.assertRaises(AttributeError, self.gear.__getattr__, 'fake_gear_buff') def test_metagem_crit_multiplier(self): self.assertAlmostEqual(self.gear.metagem_crit_multiplier(), 1.03) self.assertAlmostEqual(self.gear_none.metagem_crit_multiplier(), 1.0) - def test_rogue_t11_2pc_crit_bonus(self): - self.assertAlmostEqual(self.gear.rogue_t11_2pc_crit_bonus(), 0.05) - self.assertAlmostEqual(self.gear_none.rogue_t11_2pc_crit_bonus(), 0.0) - - def test_leather_specialization_multiplier(self): - self.assertAlmostEqual(self.gear.leather_specialization_multiplier(), 1.05) - self.assertAlmostEqual(self.gear_none.leather_specialization_multiplier(), 1.0) - - def test_get_all_activated_agi_boosts(self): - self.assertEqual(len(self.gear.get_all_activated_agi_boosts()), 1) - self.assertEqual(len(self.gear_none.get_all_activated_agi_boosts()), 0) - - def test_get_all_activated_boosts_for_stat(self): - self.assertEqual(len(self.gear.get_all_activated_boosts_for_stat('agi')), 1) - self.assertEqual(len(self.gear.get_all_activated_boosts_for_stat('haste')), 2) - self.assertEqual(len(self.gear.get_all_activated_boosts_for_stat('crit')), 0) - - def test_get_all_activated_haste_rating_boosts(self): - self.assertEqual(len(self.gear.get_all_activated_haste_rating_boosts()), 2) - self.assertEqual(len(self.gear_none.get_all_activated_haste_rating_boosts()), 0) - - def test_engineer_glove_enchant(self): - test_gear = stats.GearBuffs('engineer_glove_enchant') - haste_boost = test_gear.get_all_activated_haste_rating_boosts()[0] - self.assertEqual(haste_boost['value'], 340) - self.assertEqual(haste_boost['duration'], 12) - self.assertEqual(haste_boost['cooldown'], 60) - - def test_lifeblood(self): - test_gear = stats.GearBuffs('lifeblood') - haste_boost = test_gear.get_all_activated_haste_rating_boosts()[0] - self.assertEqual(haste_boost['value'], 480) - self.assertEqual(haste_boost['duration'], 20) - self.assertEqual(haste_boost['cooldown'], 120) - - def test_get_all_activated_boosts(self): - self.assertEqual(len(self.gear.get_all_activated_boosts()), 3) - self.assertEqual(len(self.gear_none.get_all_activated_boosts()), 0) + def test_gear_specialization_multiplier(self): + self.assertAlmostEqual(self.gear.gear_specialization_multiplier(), 1.05) + self.assertAlmostEqual(self.gear_none.gear_specialization_multiplier(), 1.0) From e4a1d9018fcdca1acf01eb74146c86af020e1a10 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Wed, 21 Sep 2016 16:11:41 -0700 Subject: [PATCH 083/265] Remove rawinput() to allow suite to run Invokes calculations for all 3 specs, with breakdowns of DPS, talents, and traits (should run most of the code we're interested in) --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 9 -- tests/calcs_tests/rogue_tests/__init__.py | 136 +++++++++++-------- 2 files changed, 83 insertions(+), 62 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 57a956f..8341439 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -271,7 +271,6 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ average_hit = proc_value * multiplier average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count - #print proc.proc_name, average_hit, multiplier if proc.stat == 'physical_dot': average_damage *= proc.uptime / proc_count @@ -423,7 +422,6 @@ def lost_swings_from_swing_delay(self, delay, swing_timer): t1 = max(min( num_sum - delay_remainder, .5 )/swing_timer, 0) t2 = max(min( num_sum - delay_remainder - .5, .5 )/swing_timer * .5, 0) - #print "total delay: ", t0, t1, t2, (t0+t1+t2) return (t0+t1+t2)/swing_timer def set_uptime_for_ramping_proc(self, proc, procs_per_second): @@ -959,7 +957,6 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = 0 while energy_budget > 0.1: - #print self.energy_budget if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 @@ -1006,9 +1003,6 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): self.bota_multiplier = 0.35 * sum(attacks_per_second['rupture']) * 10 self.bota_multiplier *= 2 - - - #print attacks_per_second return attacks_per_second, crit_rates, additional_info ########################################################################### @@ -1301,7 +1295,6 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): print aps_normal print aps_ar print attacks_per_second - raw_input() #if rtb loop on ar cooldown if not self.talents.slice_and_dice: old_ar_cd = self.ar_cd @@ -1923,7 +1916,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = 0 while self.energy_budget > 0.1: - #print self.energy_budget if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 @@ -2016,7 +2008,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] - #print attacks_per_second #add SoD auto crits if 'shadowstrike' in attacks_per_second: diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 8af173b..16619ab 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -1,68 +1,52 @@ import unittest from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.core import exceptions -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import talents -from shadowcraft.objects import artifact -from shadowcraft.calcs.rogue.Aldriana import settings - -class TestRogueDamageCalculator(unittest.TestCase): - def setUp(self): - test_level = 110 - test_race = race.Race('pandaren') +from shadowcraft.objects import buffs as _buffs +from shadowcraft.objects import race as _race +from shadowcraft.objects import stats as _stats +from shadowcraft.objects import procs as _procs +from shadowcraft.objects import talents as _talents +from shadowcraft.objects import artifact as _artifact +from shadowcraft.calcs.rogue.Aldriana import settings as _settings + +class RogueDamageCalculatorTestBase(object): + def setUp(self, spec, mh, oh, cycle, + talent_str='1000000', + buffs=_buffs.Buffs('short_term_haste_buff', 'flask_wod_agi', 'food_wod_versatility'), + procs=_procs.ProcsList(), + gear_buffs=_stats.GearBuffs('gear_specialization'), + traits='000000000000000000', + level=110, + race='pandaren', + agi=20909, + stam=19566, + crit=4402, + haste=5150, + mastery=5999, + versatility=1515, + response_time=0.5, + duration=360, + is_demon=False, + num_boss_adds=0): + + test_race = _race.Race(race) test_class = 'rogue' - test_spec = 'outlaw' - - # Set up buffs. - test_buffs = buffs.Buffs('short_term_haste_buff', - 'flask_wod_agi', - 'food_wod_versatility') - - # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand - test_mh = stats.Weapon(4821.0, 2.6, 'sword', None) - test_oh = stats.Weapon(4821.0, 2.6, 'sword', None) - - # Set up procs. - #test_procs = procs.ProcsList(('assurance_of_consequence', 588), - #('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', - #'archmages_incandescence') #trinkets, other things (legendary procs) - test_procs = procs.ProcsList() - - # Set up gear buffs. - test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here # Set up a calcs object.. - test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=20909, - stam=19566, - crit=4402, - haste=5150, - mastery=5999, - versatility=1515,) + test_stats = _stats.Stats(mh, oh, procs, gear_buffs, agi=agi, stam=stam, crit=crit, haste=haste, mastery=mastery, versatility=versatility) # Initialize talents.. - test_talents = talents.Talents('1000000', test_spec, test_class, level=test_level) + test_talents = _talents.Talents(talent_str, spec, test_class, level=level) #initialize artifact traits.. - test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') + test_traits = _artifact.Artifact(spec, test_class, traits) # Set up settings. - test_cycle = settings.OutlawCycle(blade_flurry=False, - jolly_roger_reroll=1, - grand_melee_reroll=1, - shark_reroll=1, - true_bearing_reroll=1, - buried_treasure_reroll=1, - broadsides_reroll=1 - ) - test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, - adv_params="", is_demon=True, num_boss_adds=0) + test_settings = _settings.Settings(cycle, response_time=response_time, duration=duration, + adv_params="", is_demon=is_demon, num_boss_adds=num_boss_adds) # Build a DPS object. - self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) + self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, buffs, test_race, spec, test_settings, level) def test_oh_penalty(self): @@ -71,6 +55,18 @@ def test_oh_penalty(self): def test_crit_damage_modifiers(self): self.assertAlmostEqual(self.calculator.crit_damage_modifiers(), 1 + (2 * 1. - 1) * 1) + def test_dps_breakdowns(self): + # TODO: Add assertions. This at least runs it though. + self.calculator.get_dps_breakdown() + + def test_get_talents_ranking(self): + # TODO: Add assertions. This at least runs it though. + self.calculator.get_talents_ranking() + + def test_get_trait_ranking(self): + # TODO: Add assertions. This at least runs it though. + self.calculator.get_trait_ranking() + def test_ep(self): ep_values = self.calculator.get_ep() @@ -86,10 +82,44 @@ def test_ep(self): self.assertTrue(ep_values['crit'] > 0.0) +class TestOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): + def setUp(self): + mh = _stats.Weapon(4821.0, 2.6, 'sword', None) + oh = _stats.Weapon(4821.0, 2.6, 'sword', None) + cycle = _settings.OutlawCycle(blade_flurry=False, + jolly_roger_reroll=1, + grand_melee_reroll=1, + shark_reroll=1, + true_bearing_reroll=1, + buried_treasure_reroll=1, + broadsides_reroll=1) + + super(TestOutlawRogueDamageCalculator, self).setUp('outlaw', mh, oh, cycle) + self.calculator.level = 110 + + def test_set_constants_for_level(self): + self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + -class TestRogueDamageCalculatorLevels(TestRogueDamageCalculator): +class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestRogueDamageCalculatorLevels, self).setUp() + mh = _stats.Weapon(4821.0, 1.8, 'dagger', None) + oh = _stats.Weapon(4821.0, 1.8, 'dagger', None) + cycle = _settings.AssassinationCycle() + super(TestAssassinationRogueDamageCalculator, self).setUp('assassination', mh, oh, cycle) + self.calculator.level = 110 + + def test_set_constants_for_level(self): + self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + + +class TestSubtletyRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): + def setUp(self): + mh = _stats.Weapon(4821.0, 2.6, 'sword', None) + oh = _stats.Weapon(4821.0, 2.6, 'sword', None) + cycle = _settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, positional_uptime=0.9) + + super(TestSubtletyRogueDamageCalculator, self).setUp('subtlety', mh, oh, cycle) self.calculator.level = 110 def test_set_constants_for_level(self): From ba65e0e97ecd5091805adfc3f27c4d3579df6de0 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Wed, 21 Sep 2016 16:48:17 -0700 Subject: [PATCH 084/265] Set up tests for single and multi-target for each spec --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 20 +-- tests/calcs_tests/__init__.py | 2 - tests/calcs_tests/rogue_tests/__init__.py | 166 ++++++++++++------- 3 files changed, 115 insertions(+), 73 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 8341439..342d294 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1292,9 +1292,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_uptime = self.ar_duration / self.ar_cd tb_seconds_per_second = 0 - print aps_normal - print aps_ar - print attacks_per_second + # print aps_normal + # print aps_ar + # print attacks_per_second #if rtb loop on ar cooldown if not self.talents.slice_and_dice: old_ar_cd = self.ar_cd @@ -1307,15 +1307,15 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cp_spend_per_second += attacks_per_second[ability][cp] * cp tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime new_ar_cd = self.ar_cd/(1 + tb_seconds_per_second) - print attacks_per_second - print cp_spend_per_second, tb_seconds_per_second + # print attacks_per_second + # print cp_spend_per_second, tb_seconds_per_second #remerge the aps #print new_ar_cd #print attacks_per_second attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) - print new_ar_cd - print "-------" + # print new_ar_cd + # print "-------" if old_ar_cd - new_ar_cd < 0.1: break else: @@ -1323,8 +1323,8 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_uptime = self.ar_duration / new_ar_cd - print self.ar_duration, new_ar_cd - print ar_uptime + # print self.ar_duration, new_ar_cd + # print ar_uptime #add in cannonball and killing spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1366,7 +1366,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - print attacks_per_second + # print attacks_per_second return attacks_per_second, crit_rates, additional_info def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, diff --git a/tests/calcs_tests/__init__.py b/tests/calcs_tests/__init__.py index d83cae6..cc2e62d 100644 --- a/tests/calcs_tests/__init__.py +++ b/tests/calcs_tests/__init__.py @@ -5,7 +5,6 @@ from shadowcraft.objects import race from shadowcraft.objects import stats from shadowcraft.objects import procs -from shadowcraft.objects import glyphs from shadowcraft.objects import talents from shadowcraft.objects import artifact @@ -26,7 +25,6 @@ def make_calculator(self, buffs_list=[], gear_buffs_list=[], race_name='night_el versatility=1515) test_race = race.Race(race_name) test_talents = talents.Talents('1000000', test_spec, 'rogue', level=110) - test_glyphs = glyphs.Glyphs() test_traits = artifact.Artifact(test_spec, 'rogue', '000000000000000000') return calcs.DamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, 'outlaw') diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 16619ab..da526dd 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -10,44 +10,76 @@ from shadowcraft.calcs.rogue.Aldriana import settings as _settings class RogueDamageCalculatorTestBase(object): - def setUp(self, spec, mh, oh, cycle, - talent_str='1000000', - buffs=_buffs.Buffs('short_term_haste_buff', 'flask_wod_agi', 'food_wod_versatility'), - procs=_procs.ProcsList(), - gear_buffs=_stats.GearBuffs('gear_specialization'), - traits='000000000000000000', - level=110, - race='pandaren', - agi=20909, - stam=19566, - crit=4402, - haste=5150, - mastery=5999, - versatility=1515, - response_time=0.5, - duration=360, - is_demon=False, - num_boss_adds=0): - - test_race = _race.Race(race) + def setUp(self): + self.talent_str = '1000000' + self.buffs = _buffs.Buffs('short_term_haste_buff', 'flask_wod_agi', 'food_wod_versatility') + self.procs = _procs.ProcsList() + self.gear_buffs = _stats.GearBuffs('gear_specialization') + self.traits = '000000000000000000' + self.level = 110 + self.race = 'pandaren' + self.agi = 20909 + self.stam = 19566 + self.crit = 4402 + self.haste = 5150 + self.mastery = 5999 + self.versatility = 1515 + self.response_time = 0.5 + self.duration = 360 + self.is_demon = False + self.num_boss_adds = 0 + self.adv_params = 0 + + def buildSpecDefaults(self, spec, weapon_dps=2100): + self.spec = spec + if spec == "outlaw": + self.mh = _stats.Weapon(weapon_dps * 2.6, 2.6, 'sword', None) + self.oh = _stats.Weapon(weapon_dps * 2.6, 2.6, 'sword', None) + self.cycle = _settings.OutlawCycle(blade_flurry=False, + jolly_roger_reroll=1, + grand_melee_reroll=1, + shark_reroll=1, + true_bearing_reroll=1, + buried_treasure_reroll=1, + broadsides_reroll=1) + elif spec == "assassination": + self.mh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) + self.oh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) + self.cycle = _settings.AssassinationCycle() + elif spec == "subtlety": + self.mh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) + self.oh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) + self.cycle = _settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, positional_uptime=0.9) + else: + raise "Invalid spec: %s" % spec + + + def buildCalculator(self): + test_race = _race.Race(self.race) test_class = 'rogue' # Set up a calcs object.. - test_stats = _stats.Stats(mh, oh, procs, gear_buffs, agi=agi, stam=stam, crit=crit, haste=haste, mastery=mastery, versatility=versatility) + test_stats = _stats.Stats(self.mh, self.oh, self.procs, + self.gear_buffs, + agi=self.agi, + stam=self.stam, + crit=self.crit, + haste=self.haste, + mastery=self.mastery, + versatility=self.versatility) # Initialize talents.. - test_talents = _talents.Talents(talent_str, spec, test_class, level=level) + test_talents = _talents.Talents(self.talent_str, self.spec, test_class, level=self.level) #initialize artifact traits.. - test_traits = _artifact.Artifact(spec, test_class, traits) + test_traits = _artifact.Artifact(self.spec, test_class, self.traits) # Set up settings. - test_settings = _settings.Settings(cycle, response_time=response_time, duration=duration, - adv_params="", is_demon=is_demon, num_boss_adds=num_boss_adds) - - # Build a DPS object. - self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, buffs, test_race, spec, test_settings, level) + test_settings = _settings.Settings(self.cycle, response_time=self.response_time, duration=self.duration, + adv_params=self.adv_params, is_demon=self.is_demon, num_boss_adds=self.num_boss_adds) + self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, self.buffs, test_race, self.spec, test_settings, self.level) + self.calculator.level = 110 def test_oh_penalty(self): self.assertAlmostEqual(self.calculator.oh_penalty(), 0.5) @@ -57,15 +89,18 @@ def test_crit_damage_modifiers(self): def test_dps_breakdowns(self): # TODO: Add assertions. This at least runs it though. - self.calculator.get_dps_breakdown() + print "DPS Breakdown" + print(self.calculator.get_dps_breakdown()) def test_get_talents_ranking(self): # TODO: Add assertions. This at least runs it though. - self.calculator.get_talents_ranking() + print "Talent ranking" + print(self.calculator.get_talents_ranking()) def test_get_trait_ranking(self): # TODO: Add assertions. This at least runs it though. - self.calculator.get_trait_ranking() + print "Trait ranking" + print(self.calculator.get_trait_ranking()) def test_ep(self): ep_values = self.calculator.get_ep() @@ -81,46 +116,55 @@ def test_ep(self): self.assertTrue(ep_values['crit'] < 1.0) self.assertTrue(ep_values['crit'] > 0.0) + def test_set_constants_for_level(self): + self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + +## Single target class TestOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - mh = _stats.Weapon(4821.0, 2.6, 'sword', None) - oh = _stats.Weapon(4821.0, 2.6, 'sword', None) - cycle = _settings.OutlawCycle(blade_flurry=False, - jolly_roger_reroll=1, - grand_melee_reroll=1, - shark_reroll=1, - true_bearing_reroll=1, - buried_treasure_reroll=1, - broadsides_reroll=1) - - super(TestOutlawRogueDamageCalculator, self).setUp('outlaw', mh, oh, cycle) - self.calculator.level = 110 - - def test_set_constants_for_level(self): - self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + super(TestOutlawRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('outlaw') + self.num_boss_adds = 0 + self.buildCalculator() class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - mh = _stats.Weapon(4821.0, 1.8, 'dagger', None) - oh = _stats.Weapon(4821.0, 1.8, 'dagger', None) - cycle = _settings.AssassinationCycle() - super(TestAssassinationRogueDamageCalculator, self).setUp('assassination', mh, oh, cycle) - self.calculator.level = 110 - - def test_set_constants_for_level(self): - self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + super(TestAssassinationRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('assassination') + self.num_boss_adds = 0 + self.buildCalculator() class TestSubtletyRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - mh = _stats.Weapon(4821.0, 2.6, 'sword', None) - oh = _stats.Weapon(4821.0, 2.6, 'sword', None) - cycle = _settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, positional_uptime=0.9) + super(TestSubtletyRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('subtlety') + self.num_boss_adds = 0 + self.buildCalculator() - super(TestSubtletyRogueDamageCalculator, self).setUp('subtlety', mh, oh, cycle) - self.calculator.level = 110 +## Multi-target - def test_set_constants_for_level(self): - self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) +class TestAOEOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): + def setUp(self): + super(TestAOEOutlawRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('outlaw') + self.num_boss_adds = 3 + self.buildCalculator() + + +class TestAOEAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): + def setUp(self): + super(TestAOEAssassinationRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('assassination') + self.num_boss_adds = 3 + self.buildCalculator() + + +class TestAOESubtletyRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): + def setUp(self): + super(TestAOESubtletyRogueDamageCalculator, self).setUp() + self.buildSpecDefaults('subtlety') + self.num_boss_adds = 3 + self.buildCalculator() \ No newline at end of file From cc026715cc599e4f3d77760081d22d939edd3e26 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Wed, 21 Sep 2016 17:22:01 -0700 Subject: [PATCH 085/265] Add test invocations to readme --- README | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README b/README index 4cbfdc2..754db24 100644 --- a/README +++ b/README @@ -1,27 +1,41 @@ -ShadowCraft - Engine --------------------- -This repository contains the calculations piece of ShadowCraft, a WoW +# ShadowCraft - Engine + +This repository contains the calculations piece of ShadowCraft, a WoW theorycraft project. Initially, this is focused on rogues (hence the name), but the framework is designed such that if other classes wish to make use of it in the future, they can do so in a sensible and reasonable way. All rogue specific functionality is currently contained in directories named "rogue" - -for instance, the objects/ directory contains objects of general use for +for instance, the objects/ directory contains objects of general use for theorycrafting calculations, while objects/rogue contains objects specifically for use in rogue theorycraft. If you would like to contribute to this project, either to add your own calculations module (for rogues or otherwise) or to improve what's already here -(bugfixes, new features, etc.) by all means do so; however, I will be +(bugfixes, new features, etc.) by all means do so; however, I will be maintaining reasonably tight control over the architecture and *extremely* -tight control over my calculations module (currently located in +tight control over my calculations module (currently located in calcs/rogue/Aldriana). This doesn't mean you can't contribute stuff; it just means that you should be aware that I may not accept your changes. If you have any questions/comments/suggestions, you can email me at aldriana at -elitistjerks dot com. Additionally, if your question is of a more general +elitistjerks dot com. Additionally, if your question is of a more general nature, there is a discussion thread for this project on the EJ forums. NOTE: Please read style.txt if you intend to submit code to this project. -- Aldriana Oct 28 2010 + +## Tests + +Running basic tests: + + nosetests --rednose tests + +With code coverage: + + nosetests --rednose --with-coverage --cover-html tests + +With profiling: + + nosetests --rednose -s --with-profile tests \ No newline at end of file From ebd46e889b65dacef0cdcacb0b1d0b61ba551362 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 11:25:13 -0700 Subject: [PATCH 086/265] Add simple tests to verify that Outlaw traits improve DPS --- shadowcraft/objects/artifact.py | 2 +- tests/calcs_tests/rogue_tests/__init__.py | 171 ++++++++++++++++------ 2 files changed, 127 insertions(+), 46 deletions(-) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 076f5cc..08984c2 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -33,7 +33,7 @@ def set_trait(self, trait, value): def initialize_traits(self, trait_string): if len(trait_string) != len(self.allowed_traits): - raise InvalidTraitException(_('Trait strings must be {traits} characters long').format(traits=len(allowed_traits))) + raise InvalidTraitException(_('Trait strings must be {traits} characters long').format(traits=len(self.allowed_traits))) self.traits = {} for trait in xrange(len(self.allowed_traits)): self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index da526dd..34d4d33 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -7,32 +7,38 @@ from shadowcraft.objects import procs as _procs from shadowcraft.objects import talents as _talents from shadowcraft.objects import artifact as _artifact +from shadowcraft.objects import artifact_data as artifact_data from shadowcraft.calcs.rogue.Aldriana import settings as _settings -class RogueDamageCalculatorTestBase(object): - def setUp(self): - self.talent_str = '1000000' +class RogueDamageCalculatorFactory: + def __init__(self, spec, **kwargs): + self.class_name = 'rogue' + self.talent_str = '0000000' self.buffs = _buffs.Buffs('short_term_haste_buff', 'flask_wod_agi', 'food_wod_versatility') self.procs = _procs.ProcsList() self.gear_buffs = _stats.GearBuffs('gear_specialization') self.traits = '000000000000000000' self.level = 110 self.race = 'pandaren' - self.agi = 20909 - self.stam = 19566 - self.crit = 4402 - self.haste = 5150 - self.mastery = 5999 - self.versatility = 1515 + self.agi = 21122 + self.stam = 28367 + self.crit = 6306 + self.haste = 3260 + self.mastery = 3706 + self.versatility = 3486 self.response_time = 0.5 self.duration = 360 self.is_demon = False self.num_boss_adds = 0 self.adv_params = 0 + self.finisher_threshold = 5 + self.buildSpecDefaults(spec) + self.__dict__.update(kwargs) def buildSpecDefaults(self, spec, weapon_dps=2100): self.spec = spec if spec == "outlaw": + self.talent_str = '1010022' self.mh = _stats.Weapon(weapon_dps * 2.6, 2.6, 'sword', None) self.oh = _stats.Weapon(weapon_dps * 2.6, 2.6, 'sword', None) self.cycle = _settings.OutlawCycle(blade_flurry=False, @@ -41,12 +47,15 @@ def buildSpecDefaults(self, spec, weapon_dps=2100): shark_reroll=1, true_bearing_reroll=1, buried_treasure_reroll=1, - broadsides_reroll=1) + broadsides_reroll=1, + between_the_eyes_policy='never') elif spec == "assassination": + self.talent_str = '2101220' self.mh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) self.oh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) self.cycle = _settings.AssassinationCycle() elif spec == "subtlety": + self.talent_str = '2100120' self.mh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) self.oh = _stats.Weapon(weapon_dps * 1.8, 1.8, 'dagger', None) self.cycle = _settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, positional_uptime=0.9) @@ -54,9 +63,10 @@ def buildSpecDefaults(self, spec, weapon_dps=2100): raise "Invalid spec: %s" % spec - def buildCalculator(self): + def build(self, **kwargs): + self.__dict__.update(kwargs) + test_race = _race.Race(self.race) - test_class = 'rogue' # Set up a calcs object.. test_stats = _stats.Stats(self.mh, self.oh, self.procs, @@ -69,10 +79,10 @@ def buildCalculator(self): versatility=self.versatility) # Initialize talents.. - test_talents = _talents.Talents(self.talent_str, self.spec, test_class, level=self.level) + test_talents = _talents.Talents(self.talent_str, self.spec, self.class_name, level=self.level) #initialize artifact traits.. - test_traits = _artifact.Artifact(self.spec, test_class, self.traits) + test_traits = _artifact.Artifact(self.spec, self.class_name, self.traits) # Set up settings. test_settings = _settings.Settings(self.cycle, response_time=self.response_time, duration=self.duration, @@ -80,6 +90,16 @@ def buildCalculator(self): self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, self.buffs, test_race, self.spec, test_settings, self.level) self.calculator.level = 110 + return self.calculator + +class RogueDamageCalculatorTestBase: + def compare(self, a, b, method=None): + calc_a = self.factory.build(**a) + calc_b = self.factory.build(**b) + if method is not None: + return (getattr(calc_a, method)(), getattr(calc_b, method)()) + else: + return (a, b) def test_oh_penalty(self): self.assertAlmostEqual(self.calculator.oh_penalty(), 0.5) @@ -89,18 +109,15 @@ def test_crit_damage_modifiers(self): def test_dps_breakdowns(self): # TODO: Add assertions. This at least runs it though. - print "DPS Breakdown" - print(self.calculator.get_dps_breakdown()) + self.calculator.get_dps_breakdown() def test_get_talents_ranking(self): # TODO: Add assertions. This at least runs it though. - print "Talent ranking" - print(self.calculator.get_talents_ranking()) + self.calculator.get_talents_ranking() def test_get_trait_ranking(self): # TODO: Add assertions. This at least runs it though. - print "Trait ranking" - print(self.calculator.get_trait_ranking()) + self.calculator.get_trait_ranking() def test_ep(self): ep_values = self.calculator.get_ep() @@ -123,48 +140,112 @@ def test_set_constants_for_level(self): class TestOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestOutlawRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('outlaw') - self.num_boss_adds = 0 - self.buildCalculator() + self.factory = RogueDamageCalculatorFactory('outlaw') + self.calculator = self.factory.build() + + # This is a dumb test but illustrates how we can test changes in a calculator + def test_mastery_helps_dps(self): + a, b = self.compare({"mastery": 3706}, {"mastery": 4706}, "get_dps_breakdown") + self.assertGreater(b["main_gauche"], a["main_gauche"]) + + def test_cursed_edges_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '010000000000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_fates_thirst_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '001000000000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_blade_dancer_single_target_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 0}, 'get_dps') + self.assertEqual(b, a) + + def test_blade_dancer_multi_target_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}, 'get_dps') + self.assertGreater(b, a) + + def test_fatebringer_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000010000000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_gunslinger_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000001000000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_hidden_blade_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000100000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_fortune_strikes_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000010000000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_ghostly_shell_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000001000000000'}, 'get_dps') + self.assertEqual(b, a) + def test_deception_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000100000000'}, 'get_dps') + self.assertEqual(b, a) + + def test_black_powder_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000010000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_greed_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000001000000'}, 'get_dps') + self.assertGreater(b, a) + + def test_blurred_time_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000100000'}, 'get_dps') + self.assertGreater(b, a) + + def test_fortunes_boon_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000010000'}, 'get_dps') + self.assertGreater(b, a) + + def test_fortunes_strike_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000001000'}, 'get_dps') + self.assertGreater(b, a) + + a, b2 = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000002000'}, 'get_dps') + self.assertGreater(b2, b) + + a, b3 = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000003000'}, 'get_dps') + self.assertGreater(b3, b2) + + def test_blademaster_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000100'}, 'get_dps') + self.assertEqual(b, a) + + def test_blunderbuss_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000010'}, 'get_dps') + self.assertGreater(b, a) + + def test_cursed_steel_dps(self): + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000001'}, 'get_dps') + self.assertGreater(b, a) class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestAssassinationRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('assassination') - self.num_boss_adds = 0 - self.buildCalculator() - + self.calculator = RogueDamageCalculatorFactory('assassination').build() class TestSubtletyRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestSubtletyRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('subtlety') - self.num_boss_adds = 0 - self.buildCalculator() + self.calculator = RogueDamageCalculatorFactory('subtlety').build() ## Multi-target class TestAOEOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestAOEOutlawRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('outlaw') - self.num_boss_adds = 3 - self.buildCalculator() + self.calculator = RogueDamageCalculatorFactory('outlaw').build(num_boss_adds=3) class TestAOEAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestAOEAssassinationRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('assassination') - self.num_boss_adds = 3 - self.buildCalculator() + self.calculator = RogueDamageCalculatorFactory('assassination').build(num_boss_adds=3) class TestAOESubtletyRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): - super(TestAOESubtletyRogueDamageCalculator, self).setUp() - self.buildSpecDefaults('subtlety') - self.num_boss_adds = 3 - self.buildCalculator() \ No newline at end of file + self.calculator = RogueDamageCalculatorFactory('subtlety').build(num_boss_adds=3) \ No newline at end of file From 1b78c5f22e664b97c81b3f86fc07ad2272c341f5 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 11:28:05 -0700 Subject: [PATCH 087/265] Include finisher_threshold setting --- tests/calcs_tests/rogue_tests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 34d4d33..c7e15d3 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -86,7 +86,7 @@ def build(self, **kwargs): # Set up settings. test_settings = _settings.Settings(self.cycle, response_time=self.response_time, duration=self.duration, - adv_params=self.adv_params, is_demon=self.is_demon, num_boss_adds=self.num_boss_adds) + adv_params=self.adv_params, is_demon=self.is_demon, num_boss_adds=self.num_boss_adds, finisher_threshold=self.finisher_threshold) self.calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, self.buffs, test_race, self.spec, test_settings, self.level) self.calculator.level = 110 From 994d087f5cc5e130cd822f8bfc1e4f331e018568 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 11:33:04 -0700 Subject: [PATCH 088/265] Fix black powder test, add tests for blade flurry/blade dancer behavior --- tests/calcs_tests/rogue_tests/__init__.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index c7e15d3..733c9a1 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -156,11 +156,18 @@ def test_fates_thirst_dps(self): a, b = self.compare({'traits': '000000000000000000'}, {'traits': '001000000000000000'}, 'get_dps') self.assertGreater(b, a) - def test_blade_dancer_single_target_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 0}, 'get_dps') - self.assertEqual(b, a) + def test_blade_flurry_hurts_single_target_dps(self): + cycle = _settings.OutlawCycle(blade_flurry=True) + a, b = self.compare({}, {'num_boss_adds': 0}, 'get_dps') + self.assertLess(b, a) + + def test_blade_dancer_improves_blade_flurry_penalty(self): + cycle = _settings.OutlawCycle(blade_flurry=True) + a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000'}, 'get_dps') + self.assertLess(a, b) def test_blade_dancer_multi_target_dps(self): + cycle = _settings.OutlawCycle(blade_flurry=True) a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}, 'get_dps') self.assertGreater(b, a) @@ -189,7 +196,8 @@ def test_deception_dps(self): self.assertEqual(b, a) def test_black_powder_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000010000000'}, 'get_dps') + cycle = _settings.OutlawCycle(between_the_eyes_policy='shark') + a, b = self.compare({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000000000010000000', 'cycle': cycle}, 'get_dps') self.assertGreater(b, a) def test_greed_dps(self): From 0951cb5f4e278ca4137252db70e719fc95d5e300 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 11:36:34 -0700 Subject: [PATCH 089/265] minor refactor: add compare_dps method --- tests/calcs_tests/rogue_tests/__init__.py | 49 ++++++++++++----------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 733c9a1..245bda0 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -93,13 +93,16 @@ def build(self, **kwargs): return self.calculator class RogueDamageCalculatorTestBase: + def compare_dps(self, a, b): + return self.compare(a, b, "get_dps") + def compare(self, a, b, method=None): calc_a = self.factory.build(**a) calc_b = self.factory.build(**b) if method is not None: return (getattr(calc_a, method)(), getattr(calc_b, method)()) else: - return (a, b) + return (calc_a, calc_b) def test_oh_penalty(self): self.assertAlmostEqual(self.calculator.oh_penalty(), 0.5) @@ -145,93 +148,93 @@ def setUp(self): # This is a dumb test but illustrates how we can test changes in a calculator def test_mastery_helps_dps(self): - a, b = self.compare({"mastery": 3706}, {"mastery": 4706}, "get_dps_breakdown") + a, b = self.compare({"mastery": 3706}, {"mastery": 4706}, 'get_dps_breakdown') self.assertGreater(b["main_gauche"], a["main_gauche"]) def test_cursed_edges_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '010000000000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '010000000000000000'}) self.assertGreater(b, a) def test_fates_thirst_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '001000000000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '001000000000000000'}) self.assertGreater(b, a) def test_blade_flurry_hurts_single_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare({}, {'num_boss_adds': 0}, 'get_dps') + a, b = self.compare_dps({}, {'num_boss_adds': 0}) self.assertLess(b, a) def test_blade_dancer_improves_blade_flurry_penalty(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000'}) self.assertLess(a, b) def test_blade_dancer_multi_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}) self.assertGreater(b, a) def test_fatebringer_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000010000000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000010000000000000'}) self.assertGreater(b, a) def test_gunslinger_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000001000000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000001000000000000'}) self.assertGreater(b, a) def test_hidden_blade_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000100000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000100000000000'}) self.assertGreater(b, a) def test_fortune_strikes_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000010000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000010000000000'}) self.assertGreater(b, a) def test_ghostly_shell_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000001000000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000001000000000'}) self.assertEqual(b, a) def test_deception_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000100000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000100000000'}) self.assertEqual(b, a) def test_black_powder_dps(self): cycle = _settings.OutlawCycle(between_the_eyes_policy='shark') - a, b = self.compare({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000000000010000000', 'cycle': cycle}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000000000010000000', 'cycle': cycle}) self.assertGreater(b, a) def test_greed_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000001000000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000001000000'}) self.assertGreater(b, a) def test_blurred_time_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000100000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000100000'}) self.assertGreater(b, a) def test_fortunes_boon_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000010000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000010000'}) self.assertGreater(b, a) def test_fortunes_strike_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000001000'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000001000'}) self.assertGreater(b, a) - a, b2 = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000002000'}, 'get_dps') + a, b2 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000002000'}) self.assertGreater(b2, b) - a, b3 = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000003000'}, 'get_dps') + a, b3 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000003000'}) self.assertGreater(b3, b2) def test_blademaster_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000100'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000100'}) self.assertEqual(b, a) def test_blunderbuss_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000010'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000010'}) self.assertGreater(b, a) def test_cursed_steel_dps(self): - a, b = self.compare({'traits': '000000000000000000'}, {'traits': '000000000000000001'}, 'get_dps') + a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000001'}) self.assertGreater(b, a) class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): From 8f75cd3eaf7f3e3093d3a8f09e29ca8a8a8f6f12 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 11:45:24 -0700 Subject: [PATCH 090/265] Test multiple ranks of artifact traits --- tests/calcs_tests/rogue_tests/__init__.py | 132 +++++++++++++++------- 1 file changed, 90 insertions(+), 42 deletions(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 245bda0..1398b31 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -146,96 +146,144 @@ def setUp(self): self.factory = RogueDamageCalculatorFactory('outlaw') self.calculator = self.factory.build() + # This is a dumb test but illustrates how we can test changes in a calculator def test_mastery_helps_dps(self): a, b = self.compare({"mastery": 3706}, {"mastery": 4706}, 'get_dps_breakdown') self.assertGreater(b["main_gauche"], a["main_gauche"]) + def test_cursed_edges_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '010000000000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '010000000000000000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='020000000000000000', num_boss_adds=3).get_dps() + rank3 = self.factory.build(traits='030000000000000000', num_boss_adds=3).get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_fates_thirst_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '001000000000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '001000000000000000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='002000000000000000', num_boss_adds=3).get_dps() + rank3 = self.factory.build(traits='003000000000000000', num_boss_adds=3).get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_blade_flurry_hurts_single_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare_dps({}, {'num_boss_adds': 0}) - self.assertLess(b, a) + base, rank1 = self.compare_dps({}, {'num_boss_adds': 0}) + self.assertLess(rank1, base) + def test_blade_dancer_improves_blade_flurry_penalty(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000'}) - self.assertLess(a, b) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='000200000000000000').get_dps() + rank3 = self.factory.build(traits='000300000000000000').get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_blade_dancer_multi_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='000200000000000000', num_boss_adds=3).get_dps() + rank3 = self.factory.build(traits='000300000000000000', num_boss_adds=3).get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_fatebringer_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000010000000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000010000000000000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='000020000000000000').get_dps() + rank3 = self.factory.build(traits='000030000000000000').get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_gunslinger_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000001000000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000001000000000000'}) + self.assertGreater(rank1, base) + def test_hidden_blade_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000100000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000100000000000'}) + self.assertGreater(rank1, base) + def test_fortune_strikes_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000010000000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000010000000000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='000000020000000000').get_dps() + rank3 = self.factory.build(traits='000000030000000000').get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_ghostly_shell_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000001000000000'}) - self.assertEqual(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000001000000000'}) + self.assertEqual(base, rank1) + def test_deception_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000100000000'}) - self.assertEqual(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000100000000'}) + self.assertEqual(base, rank1) + def test_black_powder_dps(self): cycle = _settings.OutlawCycle(between_the_eyes_policy='shark') - a, b = self.compare_dps({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000000000010000000', 'cycle': cycle}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000000000010000000', 'cycle': cycle}) + self.assertGreater(rank1, base) + def test_greed_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000001000000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000001000000'}) + self.assertGreater(rank1, base) + def test_blurred_time_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000100000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000100000'}) + self.assertGreater(rank1, base) + def test_fortunes_boon_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000010000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000010000'}) + self.assertGreater(rank1, base) + rank2 = self.factory.build(traits='000000000000020000').get_dps() + rank3 = self.factory.build(traits='000000000000030000').get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) + def test_fortunes_strike_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000001000'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000001000'}) + self.assertGreater(rank1, base) - a, b2 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000002000'}) - self.assertGreater(b2, b) + rank2 = self.factory.build(traits='000000000000002000').get_dps() + rank3 = self.factory.build(traits='000000000000003000').get_dps() + self.assertGreater(rank2, rank1) + self.assertGreater(rank3, rank2) - a, b3 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000003000'}) - self.assertGreater(b3, b2) def test_blademaster_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000100'}) - self.assertEqual(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000100'}) + self.assertEqual(base, rank1) + def test_blunderbuss_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000010'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000010'}) + self.assertGreater(rank1, base) + def test_cursed_steel_dps(self): - a, b = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000001'}) - self.assertGreater(b, a) + base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000000000000000001'}) + self.assertGreater(rank1, base) + class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): From 9d440cae7c80ac046e7a12aab7f557b9bb31e86b Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 22 Sep 2016 13:31:17 -0700 Subject: [PATCH 091/265] Add talent ranking tests for tiers 1, 3, 6, 7 --- tests/calcs_tests/rogue_tests/__init__.py | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 1398b31..27d53b0 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -285,6 +285,42 @@ def test_cursed_steel_dps(self): self.assertGreater(rank1, base) + # These are sanity checks; they are what is expected from current modeling, so they're included to help + # guard against regressions in the calculator. + def test_best_rank_1(self): + gstrike = self.factory.build(talent_str='0000000').get_dps() + swords = self.factory.build(talent_str='1000000').get_dps() + quick = self.factory.build(talent_str='2000000').get_dps() + self.assertGreater(gstrike, swords, 'Swordmaster %s > Ghostly Strike %s' % (swords, gstrike)) + self.assertGreater(gstrike, quick, 'Quick Draw %s > Ghostly Strike %s' % (quick, gstrike)) + + + def test_best_rank_3(self): + stratagem = self.factory.build(talent_str='0000000').get_dps() + anticipation = self.factory.build(talent_str='0010000').get_dps() + vigor = self.factory.build(talent_str='0020000').get_dps() + self.assertGreater(stratagem, anticipation, 'Anticipation %s > Stratagem %s' % (anticipation, stratagem)) + self.assertGreater(stratagem, vigor, 'Vigor %s > Stratagem %s' % (vigor, stratagem)) + + + def test_best_rank_6(self): + cannons = self.factory.build(talent_str='0000000').get_dps() + alacrity = self.factory.build(talent_str='0000010').get_dps() + kspree = self.factory.build(talent_str='0000020').get_dps() + self.assertGreater(alacrity, kspree, 'KSpree %s > Alacrity %s' % (kspree, alacrity)) + self.assertGreater(alacrity, cannons, 'Cannons %s > Alacrity %s' % (cannons, alacrity)) + + + def test_best_rank_7(self): + snd = self.factory.build(talent_str='0000000').get_dps() + mfd = self.factory.build(talent_str='0000001').get_dps() + dfa = self.factory.build(talent_str='0000002').get_dps() + self.assertGreater(mfd, snd, 'SnD %s > Marked for Death %s' % (snd, mfd)) + self.assertGreater(mfd, dfa, 'Death From Above %s > mfd %s' % (dfa, mfd)) + + + + class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): def setUp(self): self.calculator = RogueDamageCalculatorFactory('assassination').build() From f6a415e3583fe4a35496f78e355fe35847e7f899 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Fri, 23 Sep 2016 11:42:26 -0700 Subject: [PATCH 092/265] Refactoring * Eliminating duplicate code for energy regen calculation * Eliminating duplicate code for attack speed calculation * Added tests for energy regen and attack speed * Refactored several loops to use comprehensions or enumerations for better speed --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 149 +++++++++---------- tests/calcs_tests/rogue_tests/__init__.py | 19 +++ 2 files changed, 89 insertions(+), 79 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 342d294..0e33361 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -154,6 +154,40 @@ def get_crit_rates(self, stats): return crit_rates + + def get_haste_multiplier(self, current_stats): + return self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod + + + def get_energy_regen(self, current_stats, buried=False, ar=False, alacrity_stacks=0): + regen = 10. + if self.spec == "outlaw": + regen = 12. + if self.settings.cycle.blade_flurry: + regen *= .8 + (0.03333 * self.traits.blade_dancer) + if buried: + regen *= 1.25 + if ar: + regen *= 2.0 + else: + alacrity_stacks = 0 + if self.talents.vigor: + regen *= 1.1 + regen *= self.get_haste_multiplier(current_stats) + 0.01 * alacrity_stacks + return regen + + + def get_attack_speed_multiplier(self, current_stats, snd=False, melee=False, ar=False, alacrity_stacks=0): + attack_speed_multiplier = self.get_haste_multiplier(current_stats) + 0.01 * alacrity_stacks + if melee: + attack_speed_multiplier *= 1.5 + elif snd: + attack_speed_multiplier *= 1.9 + if ar: + attack_speed_multiplier *= 1.2 + return attack_speed_multiplier + + def set_constants(self): # General setup that we'll use in all 3 cycles. self.load_from_advanced_parameters() @@ -215,6 +249,7 @@ def set_constants(self): #hit chances self.dw_mh_hit_chance = self.dual_wield_mh_hit_chance() self.dw_oh_hit_chance = self.dual_wield_oh_hit_chance() + return self def load_from_advanced_parameters(self): self.true_haste_mod = self.get_adv_param('haste_buff', 1., min_bound=.1, max_bound=3.) @@ -851,10 +886,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): cp_builder_energy_per_finisher = builders_per_finisher * self.get_spell_cost(self.cp_builder) #set up our energy budget - self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - energy_regen = 10 * self.haste_multiplier - if self.talents.vigor: - energy_regen *= 1.1 + haste_multiplier = self.get_haste_multiplier(current_stats) + energy_regen = self.get_energy_regen(current_stats) #set up rupture attacks_per_second['rupture'] = [0, 0, 0, 0, 0, 0, 0] @@ -975,7 +1008,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - attacks_per_second['mh_autoattacks'] = (self.haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed + attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] #poison computations, use old function for now @@ -1138,18 +1171,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second = {} additional_info = {} - #Compute values that are true through all RtB variations - self.base_energy_regen = 12. - if self.talents.vigor: - self.base_energy_regen *= 1.1 - if self.settings.cycle.blade_flurry: - self.base_energy_regen *= .8 + (0.03333 * self.traits.blade_dancer) - if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - self.haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - combat_potency_proc_energy = 15 + (1 * self.traits.fortune_strikes) self.combat_potency_regen_per_oh = combat_potency_proc_energy * 0.3 * self.stats.oh.speed / 1.4 # the new "normalized" formula self.combat_potency_from_mg = combat_potency_proc_energy * 0.3 @@ -1336,7 +1360,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #figure swing timer and add mg - attack_speed_multiplier = self.haste_multiplier * (1 + (0.9 * self.talents.slice_and_dice)) + attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) @@ -1369,25 +1393,13 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): # print attacks_per_second return attacks_per_second, crit_rates, additional_info - def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, - jolly=False, melee=False, buried=False, broadsides=False, duration=30, - #probably don't actually need shark or tb here but simpler - shark=False, true_bearing=True): - maintainence_buff ='roll_the_bones' - attack_speed_multiplier = self.haste_multiplier - if melee: - attack_speed_multiplier *= 1.5 - if snd: - attack_speed_multiplier *= 1.9 - maintainence_buff = 'slice_and_dice' - - energy_regen = self.base_energy_regen * self.haste_multiplier - if buried: - energy_regen *= 1.25 + # probably don't actually need shark or tb here but simpler + def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, jolly=False, melee=False, + buried=False, broadsides=False, duration=30, shark=False, true_bearing=True): - if ar: - attack_speed_multiplier *= 1.2 - energy_regen *= 2.0 + maintainence_buff = 'slice_and_dice' if snd else 'roll_the_bones' + attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd, melee, ar) + energy_regen = self.get_energy_regen(current_stats, buried, ar) gcd_size = 1.0 + self.settings.latency if ar: @@ -1426,17 +1438,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, attacks_per_second['saber_slash'] = float(ss_count + ps_count)/duration attacks_per_second['pistol_shot'] = float(ps_count)/duration - attacks_per_second[maintainence_buff] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second[maintainence_buff][cp] += finisher_list[cp]/duration + attacks_per_second[maintainence_buff] = [v / duration for v in finisher_list] if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': - bte_count = duration/(20 + self.settings.response_time - (10 * true_bearing)) - attacks_per_second['between_the_eyes'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second['between_the_eyes'][cp] += float(finisher_list[cp] * bte_count)/duration - attacks_per_second['pistol_shot'] += float(bte_count * ps_count)/duration - attacks_per_second['saber_slash'] += float(bte_count * (ss_count + ps_count))/duration + bte_count = duration / (20 + self.settings.response_time - (10 * true_bearing)) + attacks_per_second['between_the_eyes'] = [float(v * bte_count) / duration for v in finisher_list] + attacks_per_second['pistol_shot'] += float(bte_count * ps_count) / duration + attacks_per_second['saber_slash'] += float(bte_count * (ss_count + ps_count)) / duration energy_budget -= (bte_count * ss_count) * self.saber_slash_energy_cost energy_budget -= bte_count * self.between_the_eyes_energy_cost gcd_budget -= bte_count * (ss_count + ps_count + 1) @@ -1445,13 +1453,10 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, if self.talents.death_from_above and not ar: energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost energy_budget -= dfa_count * self.death_from_above_energy_cost - attacks_per_second['saber_slash'] += float((ss_count + ps_count) * dfa_count)/duration - attacks_per_second['pistol_shot'] += float(ps_count * dfa_count)/duration - attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second['death_from_above_strike'][cp] *= float(finisher_list[cp] * dfa_count)/duration - attacks_per_second['death_from_above_pulse'][cp] *= float(finisher_list[cp] * dfa_count)/duration + attacks_per_second['saber_slash'] += float((ss_count + ps_count) * dfa_count) / duration + attacks_per_second['pistol_shot'] += float(ps_count * dfa_count) / duration + attacks_per_second['death_from_above_strike'] = [float(v * dfa_count) / duration for v in finisher_list] + attacks_per_second['death_from_above_pulse'] = [float(v * dfa_count) / duration for v in finisher_list] #DfA forces a 2 second GCD gcd_budget -= dfa_count * (ss_count + ps_count + 2) @@ -1463,7 +1468,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, #TODO: use these ghostly strike cps energy_budget -= gs_energy gcd_budget -= gs_count - attacks_per_second['ghostly_strike'] = float(gs_count)/duration + attacks_per_second['ghostly_strike'] = float(gs_count) / duration #Burn the rest of our energy until you run out of energy or gcds gcds_per_minicycle = ss_count + ps_count + 1 @@ -1471,18 +1476,17 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, alacrity_stacks = 0 loop_counter = 0 - attacks_per_second['run_through'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['run_through'] = [0] * 7 while energy_budget > 0.1 and gcd_budget > 0.1: if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - minicycle_count = min(gcd_budget/gcds_per_minicycle, energy_budget/energy_per_minicycle) - attacks_per_second['saber_slash'] += float(minicycle_count * (ss_count + ps_count))/duration - attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count)/duration - - for cp in xrange(7): - attacks_per_second['run_through'][cp] += float(minicycle_count * finisher_list[cp])/duration + minicycle_count = min(gcd_budget / gcds_per_minicycle, energy_budget / energy_per_minicycle) + attacks_per_second['saber_slash'] += float(minicycle_count * (ss_count + ps_count)) / duration + attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count) / duration + for i, v in enumerate(finisher_list): + attacks_per_second['run_through'][i] += float(minicycle_count * v) / duration #Don't need to converge if we don't have alacrity if not self.talents.alacrity: @@ -1512,37 +1516,27 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, bool(self.talents.swordmaster), broadsides, jolly) ss_count, ps_count, finisher_list = self.minicycle_table[minicycle_key] reroll_energy_cost = (ss_count * self.saber_slash_energy_cost) + self.roll_the_bones_cost - energy_regen = self.base_energy_regen * (self.haste_multiplier + 0.01 * alacrity_stacks) - if buried: - energy_regen *= 1.25 - attack_speed_multiplier = self.haste_multiplier + 0.01 * alacrity_stacks - if melee: - attack_speed_multiplier *= 1.5 - if ar: - energy_regen *= 2.0 - attack_speed_multiplier *= 1.2 + + energy_regen = self.get_energy_regen(current_stats, buried, ar, alacrity_stacks) + attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, False, melee, ar, alacrity_stacks) mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) + total_regen = energy_regen + mg_cp_energy reroll_time = reroll_energy_cost / total_regen attacks_per_second = {} - attacks_per_second['saber_slash'] = float(ss_count + ps_count)/reroll_time - attacks_per_second['pistol_shot'] = float(ps_count)/reroll_time - attacks_per_second['roll_the_bones'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second['roll_the_bones'][cp] = finisher_list[cp]/reroll_time + attacks_per_second['saber_slash'] = float(ss_count + ps_count) / reroll_time + attacks_per_second['pistol_shot'] = float(ps_count) / reroll_time + attacks_per_second['roll_the_bones'] = [v / reroll_time for v in finisher_list] return attacks_per_second, reroll_time #dict of (probability, aps) pairs def merge_attacks_per_second(self, aps_dicts, total_time=1.0): - #print "CALL" total = 0.0 attacks_per_second = {} for key in aps_dicts: proportion, aps = aps_dicts[key] uptime = float(proportion)/total_time - total+= uptime - #print uptime, total for ability in aps: if ability in attacks_per_second: @@ -1700,11 +1694,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): use_sod = True #Set up initial energy budget - base_energy_regen = 10. - haste_multiplier = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - self.energy_regen = base_energy_regen * haste_multiplier - if self.talents.vigor: - self.energy_regen *= 1.1 + haste_multiplier = self.get_haste_multiplier(current_stats) + self.energy_regen = self.get_energy_regen(current_stats) self.max_energy = 100. if self.talents.vigor: diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 27d53b0..7ad5c42 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -153,6 +153,25 @@ def test_mastery_helps_dps(self): self.assertGreater(b["main_gauche"], a["main_gauche"]) + def test_energy_regen(self): + self.calculator.set_constants() + # This is 12.4 base because the calculator currently averages Heroism out over the course of the fight. + # Should fix at some point. + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 0}), 12.4) + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 5000}), 14.3076923) + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 5000}, buried=True), 17.8846153846) + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 5000}, ar=True), 28.615384615384613) + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 5000}, buried=True, ar=True), 35.76923076923077) + self.assertAlmostEqual(self.calculator.get_energy_regen({'haste': 5000}, alacrity_stacks=10), 15.50769230769231) + cycle = _settings.OutlawCycle(blade_flurry=True) + self.assertEqual(self.factory.build(cycle=cycle).set_constants().get_energy_regen({'haste': 0}), 12.4 * 0.8) + + + def test_energy_regen_blade_dancer(self): + cycle = _settings.OutlawCycle(blade_flurry=True) + self.assertEqual(self.factory.build(cycle=cycle,traits='000200000000000000').set_constants().get_energy_regen({'haste': 0}), 12.4 * (0.8 + 0.03333 * 2)) + + def test_cursed_edges_dps(self): base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '010000000000000000'}) self.assertGreater(rank1, base) From d39b8b0af91b6fdaaa10e5c95dcbb9ff47458ed9 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Fri, 23 Sep 2016 11:50:42 -0700 Subject: [PATCH 093/265] Minor: Comment cleanup --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 42 +++++++------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0e33361..b6e2f98 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1181,7 +1181,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.main_gauche_proc_rate = self.outlaw_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg - #compute MG lumped ability costs + # Compute Main Gauche lumped ability costs self.run_through_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (4 * self.traits.fatebringer) - cost_reducer self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer @@ -1195,7 +1195,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') - #compute dps phases each non-rerolling rtb buff combo ar and not + # Compute dps phases each non-rerolling RtB buff combo AR and not phases = {} ar_phases = {} @@ -1236,17 +1236,17 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): keep_gm_uptime = keep_gm_chance/keep_chance keep_tb_uptime = keep_tb_chance/keep_chance keep_shark_uptime = keep_shark_chance/keep_chance - #merge ar and non-ar into single phases + # Merge AR and non-AR into single phases aps_keep = self.merge_attacks_per_second(phases, total_time=keep_chance) aps_keep_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) - #technically there is a convergence relationship here but ignoring it + # Technically there is a convergence relationship here but ignoring it if self.talents.alacrity: alacrity_stacks = self.get_average_alacrity(aps_keep) alacrity_stacks_ar = self.get_average_alacrity(aps_keep_ar) else: alacrity_stacks = 0 alacrity_stacks_ar = 0 - #now compute the average time for each reroll + # Now compute the average time for each reroll phases = {} ar_phases = {} net_reroll_time = 0.0 @@ -1278,7 +1278,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): if melee: reroll_gm_time += chance * reroll_time - #check for reroll time, to protect from divide by zero + # Check for reroll time, to protect from divide by zero if net_reroll_time: reroll_tb_uptime = reroll_tb_time/net_reroll_time reroll_shark_uptime = reroll_shark_time/net_reroll_time @@ -1290,9 +1290,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): aps_reroll = self.merge_attacks_per_second(phases, total_time=net_reroll_time) aps_reroll_ar = self.merge_attacks_per_second(phases, total_time=net_reroll_time_ar) - #now combine the reroll and keep dicts + # Now combine the reroll and keep dicts rtb_keep_duration = 6 * (1+ self.settings.finisher_threshold) - #will pandemic into rtb based on keep_chance + # Will pandemic into RtB based on keep_chance rtb_keep_duration *= 1 + (0.3 * keep_chance) reroll_duration = net_reroll_time * len(self.settings.cycle.reroll_list) @@ -1310,16 +1310,13 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime - #determine ar uptime and merge the two distributions + # Determine AR uptime and merge the two distributions attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) ar_uptime = self.ar_duration / self.ar_cd tb_seconds_per_second = 0 - # print aps_normal - # print aps_ar - # print attacks_per_second - #if rtb loop on ar cooldown + # If RtB loop on AR cooldown if not self.talents.slice_and_dice: old_ar_cd = self.ar_cd loop_counter = 0 @@ -1331,15 +1328,8 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cp_spend_per_second += attacks_per_second[ability][cp] * cp tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime new_ar_cd = self.ar_cd/(1 + tb_seconds_per_second) - # print attacks_per_second - # print cp_spend_per_second, tb_seconds_per_second - #remerge the aps - #print new_ar_cd - #print attacks_per_second attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) - # print new_ar_cd - # print "-------" if old_ar_cd - new_ar_cd < 0.1: break else: @@ -1347,9 +1337,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_uptime = self.ar_duration / new_ar_cd - # print self.ar_duration, new_ar_cd - # print ar_uptime - #add in cannonball and killing spree + # Add in Cannonball and Killing Spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) #ksp is 7 hits per hand @@ -1359,7 +1347,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['cannonball_barrage'] = 1./cannonball_barrage_cd - #figure swing timer and add mg + # Figure swing timer and add Main Gauche attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: @@ -1368,7 +1356,8 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['mh_autoattacks'] = 1./swing_timer attacks_per_second['oh_autoattacks'] = 1./swing_timer attacks_per_second['main_gauche'] = self.main_gauche_proc_rate * attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() - #add in mg + + # Add in Main Gauche for ability in attacks_per_second: if ability in ['ambush', 'ghostly_strike', 'killing_spree', 'saber_slash']: attacks_per_second['main_gauche'] += self.main_gauche_proc_rate * attacks_per_second[ability] @@ -1390,10 +1379,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['blunderbuss'] = 0.33 * attacks_per_second['pistol_shot'] attacks_per_second['pistol_shot'] -= attacks_per_second['blunderbuss'] - # print attacks_per_second return attacks_per_second, crit_rates, additional_info - # probably don't actually need shark or tb here but simpler + # Probably don't actually need Shark or True Bearing here but simpler def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, jolly=False, melee=False, buried=False, broadsides=False, duration=30, shark=False, true_bearing=True): From 298f1a1d66bb5fcee8dabd7bfe8ed5d9eb2be5e4 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Fri, 23 Sep 2016 12:01:09 -0700 Subject: [PATCH 094/265] Fix Blade Flurry tests --- tests/calcs_tests/rogue_tests/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 7ad5c42..7073eac 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -192,13 +192,13 @@ def test_fates_thirst_dps(self): def test_blade_flurry_hurts_single_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - base, rank1 = self.compare_dps({}, {'num_boss_adds': 0}) + base, rank1 = self.compare_dps({}, {'num_boss_adds': 0, 'cycle': cycle}) self.assertLess(rank1, base) def test_blade_dancer_improves_blade_flurry_penalty(self): cycle = _settings.OutlawCycle(blade_flurry=True) - base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000'}) + base, rank1 = self.compare_dps({'traits': '000000000000000000', 'cycle': cycle}, {'traits': '000100000000000000', 'cycle': cycle}) self.assertGreater(rank1, base) rank2 = self.factory.build(traits='000200000000000000').get_dps() rank3 = self.factory.build(traits='000300000000000000').get_dps() @@ -208,10 +208,11 @@ def test_blade_dancer_improves_blade_flurry_penalty(self): def test_blade_dancer_multi_target_dps(self): cycle = _settings.OutlawCycle(blade_flurry=True) - base, rank1 = self.compare_dps({'traits': '000000000000000000'}, {'traits': '000100000000000000', 'num_boss_adds': 3}) + base = self.factory.build(traits='000000000000000000', cycle=cycle).get_dps() + rank1 = self.factory.build(traits='000100000000000000', cycle=cycle, num_boss_adds=3).get_dps() + rank2 = self.factory.build(traits='000200000000000000', cycle=cycle, num_boss_adds=3).get_dps() + rank3 = self.factory.build(traits='000300000000000000', cycle=cycle, num_boss_adds=3).get_dps() self.assertGreater(rank1, base) - rank2 = self.factory.build(traits='000200000000000000', num_boss_adds=3).get_dps() - rank3 = self.factory.build(traits='000300000000000000', num_boss_adds=3).get_dps() self.assertGreater(rank2, rank1) self.assertGreater(rank3, rank2) From 939a2f8ee7f287f48a321001f3bb00fd6c5b4d77 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 29 Sep 2016 13:00:59 -0700 Subject: [PATCH 095/265] Fix talents ranking; reset talents to baseline before performing rankings --- shadowcraft/calcs/__init__.py | 10 ++++++--- shadowcraft/objects/talents.py | 25 +++++++++++++++++++---- tests/calcs_tests/rogue_tests/__init__.py | 12 +++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index d1404cb..82fb8e3 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -528,16 +528,20 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) def get_talents_ranking(self, list=None): talents_ranking = {} + existing_talents = self.talents.get_talent_string() + tier_levels = [15, 30, 45, 60, 75, 90, 100] #list of levels for our tiers, because it is not in the talent data - baseline_dps = self.get_dps() #cache allowed_talent_list = self.talents.get_allowed_talents_for_level() if list == None else list #cache for tier, level in zip(self.talents.class_talents, tier_levels): tier_ranking = {} #reinitialized to clear dict for each new tier + self.talents.initialize_talents(existing_talents) + self.talents.set_talent(tier[0], False) # Wipes the row + baseline_dps = self.get_dps() for talent in tier: if talent in allowed_talent_list: - setattr(self.talents, talent, not getattr(self.talents, talent)) # this is a rather arcane way to handle state :/ try: + self.talents.set_talent(talent, True) new_dps = self.get_dps() if new_dps != baseline_dps: tier_ranking[talent] = abs(new_dps - baseline_dps) @@ -545,8 +549,8 @@ def get_talents_ranking(self, list=None): tier_ranking[talent] = 'not implemented' #unique error: no dps delta for this talent except: tier_ranking[talent] = 'implementation error' #unique error: error attempting to calc dps with this talent - setattr(self.talents, talent, not getattr(self.talents, talent)) talents_ranking[level] = tier_ranking #place each tier into the talent tree + self.talents.initialize_talents(existing_talents) return talents_ranking def get_trait_ranking(self, list=None): diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index cc4d474..02f23cf 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -56,11 +56,25 @@ def initialize_talents(self, talent_string): if int(i) not in range(4): raise InvalidTalentException(_('Values in the talent string must be 0, 1, 2, 3, or sometimes 4')) if int(i) == 0 or i == '.': - pass + setattr(self, self.class_talents[j][int(i) - 1], False) else: setattr(self, self.class_talents[j][int(i) - 1], True) j += 1 + def get_talent_string(self): + talent_str = "" + for row in self.class_talents: + got_talent = False + for index, talent in enumerate(row): + if getattr(self, talent): + got_talent = True + talent_str += str(index + 1) + break + if not got_talent: + talent_str += "0" + return talent_str + + def reset_talents(self): for talent in self.allowed_talents: setattr(self, talent, False) @@ -73,13 +87,16 @@ def get_tier_for_talent(self, name): if name in self.class_talents[i]: return i - def set_talent(self, name): + def set_talent(self, name, value=True): # Clears talents in the tier and sets the new one if name not in self.allowed_talents: raise InvalidTalentException("Invalid talent") for talent in self.class_talents[self.get_tier_for_talent(name)]: setattr(self, talent, False) - setattr(self, name, True) + setattr(self, name, value) + + def get_talent(self, name): + return getattr(self, name) def get_active_talents(self): active_talents = [] @@ -87,4 +104,4 @@ def get_active_talents(self): for talent in row: if getattr(self, talent): active_talents.append(talent) - return active_talents \ No newline at end of file + return active_talents diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 7073eac..2c24c77 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -139,6 +139,11 @@ def test_ep(self): def test_set_constants_for_level(self): self.assertRaises(exceptions.InvalidLevelException, self.calculator.__setattr__, 'level', 111) + + def test_get_talents_ranking_does_not_change_talents(self): + active_talents = self.calculator.talents.get_active_talents() + self.assertEqual(self.calculator.talents.get_active_talents(), active_talents) + ## Single target class TestOutlawRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): @@ -338,7 +343,14 @@ def test_best_rank_7(self): self.assertGreater(mfd, snd, 'SnD %s > Marked for Death %s' % (snd, mfd)) self.assertGreater(mfd, dfa, 'Death From Above %s > mfd %s' % (dfa, mfd)) + def test_get_talents_ranking_does_not_persist_talents_in_same_row(self): + self.calculator.talents.initialize_talents("0000000") + ranking_without_existing = self.calculator.get_talents_ranking() + + self.calculator.talents.initialize_talents("1000000") + ranking_with_existing = self.calculator.get_talents_ranking() + self.assertEqual(ranking_without_existing[15], ranking_with_existing[15]) class TestAssassinationRogueDamageCalculator(RogueDamageCalculatorTestBase, unittest.TestCase): From 8afca5dbde2590b4ed61308984f0529d7b2fc776 Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Thu, 29 Sep 2016 17:56:05 -0700 Subject: [PATCH 096/265] Reset talents on initialization --- shadowcraft/objects/talents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index 02f23cf..b91f51f 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -52,11 +52,12 @@ def initialize_talents(self, talent_string): if len(talent_string) > self.max_rows: raise InvalidTalentException(_('Talent strings must be 7 or less characters long')) j = 0 + self.reset_talents() for i in talent_string: if int(i) not in range(4): raise InvalidTalentException(_('Values in the talent string must be 0, 1, 2, 3, or sometimes 4')) if int(i) == 0 or i == '.': - setattr(self, self.class_talents[j][int(i) - 1], False) + pass else: setattr(self, self.class_talents[j][int(i) - 1], True) j += 1 From 344cc03895a18fa3a5690f620f2f4f6aca7bbf46 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 3 Oct 2016 10:06:47 -0400 Subject: [PATCH 097/265] Fix Exsang Garotte Handling -Exsang-garotte using tick interval rather than ticks per second --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b6e2f98..9c9fa05 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -921,8 +921,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): exsang_downtime = garrote_cooldown - exsang_garrote_duration normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd - attacks_per_second['garrote_ticks'] = 1.5 * float(exsang_garrote_duration) / self.exsang_cd + \ - 3.0 * float(self.exsang_cd - exsang_garrote_duration) / self.exsang_cd + attacks_per_second['garrote_ticks'] = 2./3 * float(exsang_garrote_duration) / self.exsang_cd + \ + 1./3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd else: attacks_per_second['garrote'] = 1. / base_garrote_duration attacks_per_second['garrote_ticks'] = 1. / 3 From a007a748056c835c63c39e22ebe0c12f0fc3646d Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 3 Oct 2016 16:12:08 -0400 Subject: [PATCH 098/265] Initial Curse of the Dreadblades and MfD Handling --- scripts/outlaw.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 72 ++++++++++++++++---- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index bd91fe3..0c4013f 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -57,7 +57,7 @@ test_talents = talents.Talents('1010022', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '000000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') # Set up settings. test_cycle = settings.OutlawCycle(blade_flurry=False, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9c9fa05..e7edf21 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1046,10 +1046,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #Talents: #T3:Anticipation - #T6:Marked for Death #Artifact: - # 'curse_of_the_dreadblades', # 'hidden_blade', (ambush proc weirdness) # 'blurred_time', @@ -1075,6 +1073,7 @@ def outlaw_dps_breakdown(self): self.ar_duration = 15 self.ar_cd = self.get_spell_cd('adrenaline_rush') + self.cotd_cd = self.get_spell_cd('curse_of_the_dreadblades') self.set_constants() @@ -1316,26 +1315,29 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): ar_uptime = self.ar_duration / self.ar_cd tb_seconds_per_second = 0 + ar_cd_modifier = 1 # If RtB loop on AR cooldown if not self.talents.slice_and_dice: - old_ar_cd = self.ar_cd loop_counter = 0 while (loop_counter < 20): + loop_counter +=1 + ar_cd = self.ar_cd * ar_cd_modifier cp_spend_per_second = 0 for ability in attacks_per_second: if ability in self.finisher_damage_sources: for cp in xrange(7): cp_spend_per_second += attacks_per_second[ability][cp] * cp - tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime - new_ar_cd = self.ar_cd/(1 + tb_seconds_per_second) + #tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime + ar_cd_modifier = (1 - (2 * tb_uptime)/(1. / cp_spend_per_second + 2 * tb_uptime)) + new_ar_cd = self.ar_cd * ar_cd_modifier attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) - if old_ar_cd - new_ar_cd < 0.1: + if ar_cd - new_ar_cd < 0.1: break - else: - old_ar_cd = new_ar_cd + #else: + #old_ar_cd = new_ar_cd - ar_uptime = self.ar_duration / new_ar_cd + ar_uptime = self.ar_duration / ar_cd # Add in Cannonball and Killing Spree if self.talents.killing_spree: @@ -1346,6 +1348,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cannonball_barrage_cd = self.get_spell_cd('cannonball_barrage') / (1. + tb_seconds_per_second) attacks_per_second['cannonball_barrage'] = 1./cannonball_barrage_cd + print attacks_per_second # Figure swing timer and add Main Gauche attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) @@ -1393,6 +1396,10 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll if ar: gcd_size -= .2 + max_cps = 5 + if self.talents.deeper_strategem: + max_cps += 1 + #fetch minicycle value minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_strategem), bool(self.talents.quick_draw), bool(self.talents.swordmaster), broadsides, jolly) @@ -1427,6 +1434,8 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll attacks_per_second['pistol_shot'] = float(ps_count)/duration attacks_per_second[maintainence_buff] = [v / duration for v in finisher_list] + print gcd_budget + print energy_budget if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': bte_count = duration / (20 + self.settings.response_time - (10 * true_bearing)) @@ -1448,23 +1457,62 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #DfA forces a 2 second GCD gcd_budget -= dfa_count * (ss_count + ps_count + 2) + bonus_cps = 0 + attacks_per_second['run_through'] = [0] * 7 + #consider ghostly strike if self.talents.ghostly_strike: gs_count = duration/15. - gs_cps = gs_count * (1 + broadsides) + bonus_cps += gs_count * (1 + broadsides) gs_energy = self.ghostly_strike_cost * gs_count - #TODO: use these ghostly strike cps energy_budget -= gs_energy gcd_budget -= gs_count attacks_per_second['ghostly_strike'] = float(gs_count) / duration + #consider MfD + if self.talents.marked_for_death: + mfd_count = (1 + self.settings.marked_for_death_resets) / duration + bonus_cps += 5 * (1 + self.settings.marked_for_death_resets) * mfd_count + + #consider Curse of the Dreadblades + if self.traits.curse_of_the_dreadblades: + curse_cd_multiplier = duration / self.cotd_cd + print curse_cd_multiplier, "-----" + #curse lasts 12 seconds, half to RT, half to CP builders + curse_gcds = (12. / gcd_size) * curse_cd_multiplier + rt_count = curse_gcds / 2 + ps_per_ss = 0.35 + if self.talents.swordmaster: + ps_per_ss += 0.1 + if jolly: + ps_per_ss += 0.25 + + ss_count = (curse_gcds / 2) * (1 / (ps_per_ss + 1)) + ps_count = (curse_gcds / 2) * (ps_per_ss / (ps_per_ss + 1)) + + attacks_per_second['saber_slash'] += ss_count / self.cotd_cd + attacks_per_second['pistol_shot'] += ps_count / self.cotd_cd + attacks_per_second['run_through'][max_cps] += rt_count / self.cotd_cd + gcd_budget -= curse_gcds + energy_budget -= (ss_count * self.saber_slash_energy_cost) + (rt_count * self.run_through_energy_cost) + + #Curse gives 8 cps with anticipation so 3 left over + if self.talents.anticipation: + bonus_cps += 3 * curse_cd_multiplier + + #spend bonus cps for max cp RTs + + extra_rt = (bonus_cps / max_cps) / duration + gcd_budget -= extra_rt + energy_budget -= extra_rt * self.run_through_energy_cost + attacks_per_second['run_through'][max_cps] += extra_rt + #Burn the rest of our energy until you run out of energy or gcds gcds_per_minicycle = ss_count + ps_count + 1 energy_per_minicycle = ss_count * self.saber_slash_energy_cost + self.run_through_energy_cost alacrity_stacks = 0 loop_counter = 0 - attacks_per_second['run_through'] = [0] * 7 while energy_budget > 0.1 and gcd_budget > 0.1: if loop_counter > 20: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) From 5d8cbdf5123fd8f7d6bcd54279314e15d792f411 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 3 Oct 2016 16:16:26 -0400 Subject: [PATCH 099/265] Removed debug output --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e7edf21..9781a07 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1348,8 +1348,6 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cannonball_barrage_cd = self.get_spell_cd('cannonball_barrage') / (1. + tb_seconds_per_second) attacks_per_second['cannonball_barrage'] = 1./cannonball_barrage_cd - print attacks_per_second - # Figure swing timer and add Main Gauche attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) @@ -1434,8 +1432,6 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll attacks_per_second['pistol_shot'] = float(ps_count)/duration attacks_per_second[maintainence_buff] = [v / duration for v in finisher_list] - print gcd_budget - print energy_budget if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': bte_count = duration / (20 + self.settings.response_time - (10 * true_bearing)) @@ -1477,7 +1473,6 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #consider Curse of the Dreadblades if self.traits.curse_of_the_dreadblades: curse_cd_multiplier = duration / self.cotd_cd - print curse_cd_multiplier, "-----" #curse lasts 12 seconds, half to RT, half to CP builders curse_gcds = (12. / gcd_size) * curse_cd_multiplier rt_count = curse_gcds / 2 From 54cd8f8e7294c02d783cb6e6d46160e049dd596d Mon Sep 17 00:00:00 2001 From: Chris Heald Date: Mon, 3 Oct 2016 15:12:52 -0700 Subject: [PATCH 100/265] Fix talent tests; talents are 1-indexed, not 0-indexed --- tests/calcs_tests/rogue_tests/__init__.py | 48 +++++++++++++---------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index 2c24c77..d68931e 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -313,35 +313,43 @@ def test_cursed_steel_dps(self): # These are sanity checks; they are what is expected from current modeling, so they're included to help # guard against regressions in the calculator. def test_best_rank_1(self): - gstrike = self.factory.build(talent_str='0000000').get_dps() - swords = self.factory.build(talent_str='1000000').get_dps() - quick = self.factory.build(talent_str='2000000').get_dps() - self.assertGreater(gstrike, swords, 'Swordmaster %s > Ghostly Strike %s' % (swords, gstrike)) - self.assertGreater(gstrike, quick, 'Quick Draw %s > Ghostly Strike %s' % (quick, gstrike)) + base = self.factory.build(talent_str='0000000').get_dps() + gstrike = self.factory.build(talent_str='1000000').get_dps() + swords = self.factory.build(talent_str='2000000').get_dps() + quick = self.factory.build(talent_str='3000000').get_dps() + self.assertGreater(gstrike, base, 'No Talents %s >= Ghostly Strike %s' % (base, gstrike)) + self.assertGreater(gstrike, swords, 'Swordmaster %s >= Ghostly Strike %s' % (swords, gstrike)) + self.assertGreater(gstrike, quick, 'Quick Draw %s >= Ghostly Strike %s' % (quick, gstrike)) def test_best_rank_3(self): - stratagem = self.factory.build(talent_str='0000000').get_dps() - anticipation = self.factory.build(talent_str='0010000').get_dps() - vigor = self.factory.build(talent_str='0020000').get_dps() - self.assertGreater(stratagem, anticipation, 'Anticipation %s > Stratagem %s' % (anticipation, stratagem)) - self.assertGreater(stratagem, vigor, 'Vigor %s > Stratagem %s' % (vigor, stratagem)) + base = self.factory.build(talent_str='0010000').get_dps() + stratagem = self.factory.build(talent_str='0010000').get_dps() + anticipation = self.factory.build(talent_str='0020000').get_dps() + vigor = self.factory.build(talent_str='0030000').get_dps() + self.assertGreater(stratagem, base, 'No Talents %s >= Stratagem %s' % (base, stratagem)) + self.assertGreater(stratagem, anticipation, 'Anticipation %s >= Stratagem %s' % (anticipation, stratagem)) + self.assertGreater(stratagem, vigor, 'Vigor %s >= Stratagem %s' % (vigor, stratagem)) def test_best_rank_6(self): - cannons = self.factory.build(talent_str='0000000').get_dps() - alacrity = self.factory.build(talent_str='0000010').get_dps() - kspree = self.factory.build(talent_str='0000020').get_dps() - self.assertGreater(alacrity, kspree, 'KSpree %s > Alacrity %s' % (kspree, alacrity)) - self.assertGreater(alacrity, cannons, 'Cannons %s > Alacrity %s' % (cannons, alacrity)) + base = self.factory.build(talent_str='0000000').get_dps() + cannons = self.factory.build(talent_str='0000010').get_dps() + alacrity = self.factory.build(talent_str='0000020').get_dps() + kspree = self.factory.build(talent_str='0000030').get_dps() + self.assertGreater(alacrity, base, 'No Talents %s >= Alacrity %s' % (base, alacrity)) + self.assertGreater(alacrity, kspree, 'KSpree %s >= Alacrity %s' % (kspree, alacrity)) + self.assertGreater(alacrity, cannons, 'Cannons %s >= Alacrity %s' % (cannons, alacrity)) def test_best_rank_7(self): - snd = self.factory.build(talent_str='0000000').get_dps() - mfd = self.factory.build(talent_str='0000001').get_dps() - dfa = self.factory.build(talent_str='0000002').get_dps() - self.assertGreater(mfd, snd, 'SnD %s > Marked for Death %s' % (snd, mfd)) - self.assertGreater(mfd, dfa, 'Death From Above %s > mfd %s' % (dfa, mfd)) + base = self.factory.build(talent_str='0000000').get_dps() + snd = self.factory.build(talent_str='0000001').get_dps() + mfd = self.factory.build(talent_str='0000002').get_dps() + dfa = self.factory.build(talent_str='0000003').get_dps() + self.assertGreater(mfd, base, 'No Talents %s >= Marked for Death %s' % (base, mfd)) + self.assertGreater(mfd, snd, 'SnD %s >= Marked for Death %s' % (snd, mfd)) + self.assertGreater(mfd, dfa, 'Death From Above %s >= mfd %s' % (dfa, mfd)) def test_get_talents_ranking_does_not_persist_talents_in_same_row(self): self.calculator.talents.initialize_talents("0000000") From fdebd95d64ad6b2cd4efab11ba8cc9735a575c28 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 3 Oct 2016 19:25:01 -0400 Subject: [PATCH 101/265] Outlaw merge_aps no doesn't modify underlying distribution --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9781a07..54d0aac 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1338,7 +1338,6 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #old_ar_cd = new_ar_cd ar_uptime = self.ar_duration / ar_cd - # Add in Cannonball and Killing Spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1560,15 +1559,12 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, attacks_per_second['roll_the_bones'] = [v / reroll_time for v in finisher_list] return attacks_per_second, reroll_time - #dict of (probability, aps) pairs def merge_attacks_per_second(self, aps_dicts, total_time=1.0): - total = 0.0 attacks_per_second = {} for key in aps_dicts: proportion, aps = aps_dicts[key] uptime = float(proportion)/total_time - for ability in aps: if ability in attacks_per_second: if isinstance(attacks_per_second[ability], list): @@ -1578,7 +1574,7 @@ def merge_attacks_per_second(self, aps_dicts, total_time=1.0): attacks_per_second[ability] += uptime * aps[ability] else: if isinstance(aps[ability], list): - attacks_per_second[ability] = aps[ability] + attacks_per_second[ability] = copy(aps[ability]) for cp in xrange(7): attacks_per_second[ability][cp] *= uptime else: From ad31947a55d8541d3e514c366fd815d09b3c922b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 4 Oct 2016 11:36:05 -0400 Subject: [PATCH 102/265] Update Finality Mechanic --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 59 ++++++-------------- shadowcraft/calcs/rogue/__init__.py | 23 ++------ 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 54d0aac..ab4484a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1663,11 +1663,9 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= ns_full_multiplier elif key == 'shuriken_storm': damage_breakdown[key] *= 1 + (0.12 * self.stealth_shuriken_uptime) - elif key == 'finality_nightblade_ticks': - damage_breakdown[key] *= 1 + (0.12 * self.dance_finality_nb_uptime) elif key == 'nightblade_ticks': damage_breakdown[key] *= 1 + (0.12 * self.dance_nb_uptime) - elif key in ('eviscerate', 'finality:eviscerate'): + elif key in ('eviscerate'): damage_breakdown[key] *= 1 + (0.12 * self.stealth_evis_uptime) #master of subtlety @@ -1680,11 +1678,17 @@ def subtlety_dps_breakdown(self): damage_breakdown[key] *= mos_full_multiplier elif key == 'shuriken_storm': damage_breakdown[key] *= 1 + (0.1 * self.stealth_shuriken_uptime) - elif key in ('eviscerate', 'finality:eviscerate'): + elif key in ('eviscerate'): damage_breakdown[key] *= 1 + (0.1 * self.stealth_evis_uptime) else: damage_breakdown[key] *= mos_uptime_multipler + if self.traits.finality: + #4% increase per cp applied every to every other + finality_damage_boost = 1 + 0.02 * self.settings.finisher_threshold + damage_breakdown['eviscerate'] *= finality_damage_boost + damage_breakdown['nightblade_ticks'] *= finality_damage_boost + ds_multiplier = 1.0 if self.talents.deeper_strategem: ds_multiplier = 1.1 @@ -1758,7 +1762,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #setup timelines sod_duration = 35 nightblade_duration = 6 + (2 * self.settings.finisher_threshold) - finality_nightblade_duration = 6 + (2 * self.settings.finisher_threshold) #Add attacks that could occur during first pass to aps attacks_per_second[self.dance_cp_builder] = 0 @@ -1769,27 +1772,15 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Leaving space for opener handling for the first cast sod_timeline = range(0, self.settings.duration, sod_duration) - if self.traits.finality: - finality_nb_timeline = range(0, self.settings.duration, finality_nightblade_duration + nightblade_duration) - nightblade_timeline = range(nightblade_duration, self.settings.duration, finality_nightblade_duration + nightblade_duration) - else: - finality_nb_timeline = [] - nightblade_timeline = range(nightblade_duration, self.settings.duration, nightblade_duration) + nightblade_timeline = range(nightblade_duration, self.settings.duration, nightblade_duration) - dance_finality_nb_uptime = 0.0 dance_nb_uptime = 0.0 - for finisher in ['finality:nightblade', 'nightblade', 'eviscerate']: + for finisher in ['nightblade', 'eviscerate']: attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] #Timeline match of ruptures, fill in rest with eviscerate if self.settings.cycle.dance_finishers_allowed: dance_count = 0 - if finisher == 'finality:nightblade' and self.traits.finality: - #Allow SoDs to be used on pandemic for match purposes - joint, sod_timeline, finality_nb_timeline = self.timeline_overlap(sod_timeline, finality_nb_timeline, -0.3 * sod_duration) - #if there is overlap compute a dance rotation for this combo - dance_count = len(joint) - dance_finality_nb_uptime = dance_count/len(finality_nb_timeline) - elif finisher == 'nightblade': + if finisher == 'nightblade': joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) dance_count = len(joint) dance_nb_uptime = dance_count/len(nightblade_timeline) @@ -1818,12 +1809,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('nightblade')) * nightblade_count self.dance_budget += (3. * self.settings.finisher_threshold * nightblade_count)/60. - finality_nightblade_count = len(finality_nb_timeline) - attacks_per_second['finality:nightblade'][self.settings.finisher_threshold] += float(finality_nightblade_count)/self.settings.duration - self.cp_budget -= self.settings.finisher_threshold * finality_nightblade_count - self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('finality:nightblade')) * finality_nightblade_count - self.dance_budget += (3. * self.settings.finisher_threshold * finality_nightblade_count)/60. - #Add in various cooldown abilities #This could be made better with timelining but for now simple time average will do if self.traits.goremaws_bite: @@ -1966,13 +1951,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks - for ability in ('finality:nightblade', 'nightblade'): - if ability in attacks_per_second: - tick_name = ability + '_ticks' - attacks_per_second[tick_name] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second[tick_name][cp] = (3 + cp) * attacks_per_second[ability][cp] - del attacks_per_second[ability] + if 'nightblade' in attacks_per_second: + print 'NB' + attacks_per_second['nightblade_ticks'] = [0, 0, 0, 0, 0, 0, 0] + for cp in xrange(7): + attacks_per_second['nightblade_ticks'][cp] = (3 + cp) * attacks_per_second['nightblade'][cp] + del attacks_per_second['nightblade'] + print attacks_per_second #convert some white swings into shadowblades #since weapon speeds are now fixed just handle a single shadowblades @@ -1999,7 +1984,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.mos_time = float(stealth_time)/self.settings.duration if self.talents.nightstalker: - self.dance_finality_nb_uptime = dance_finality_nb_uptime self.dance_nb_uptime = dance_nb_uptime for ability in attacks_per_second.keys(): @@ -2017,13 +2001,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_evis = 0 self.stealth_evis_uptime = stealth_evis/sum(attacks_per_second['eviscerate']) - #convert half of evis to finality - if self.traits.finality: - attacks_per_second['finality:eviscerate'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): - attacks_per_second['finality:eviscerate'][cp] = attacks_per_second['eviscerate'][cp] * 0.5 - attacks_per_second['eviscerate'][cp] *= 0.5 - if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 279c536..50fb831 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -27,11 +27,10 @@ class RogueDamageCalculator(DamageCalculator): 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', - 'backstab', 'eviscerate', 'finality:eviscerate', 'gloomblade', - 'goremaws_bite', 'nightblade', 'finality:nightblade', 'shadowstrike', + 'backstab', 'eviscerate', 'gloomblade', + 'goremaws_bite', 'nightblade', 'shadowstrike', 'shadow_blades', 'shuriken_storm', 'shuriken_toss', - 'nightblade_ticks', 'finality:nightblade_ticks', - 'soul_rip', 'shadow_nova'] + 'nightblade_ticks', 'soul_rip', 'shadow_nova'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', @@ -41,17 +40,15 @@ class RogueDamageCalculator(DamageCalculator): 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] #All damage sources the scale with mastery (assn or sub) mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', - 'eviscerate', 'finality:eviscerate', 'nightblade_ticks', - 'finality:nightblade_ticks'] + 'eviscerate', 'nightblade_ticks'] #All damage sources that deal damage with both hands dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', 'goremaws_bite', 'shadow_blades'] #All damage sources that scale with cps finisher_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'envenom', 'rupture_ticks', 'between_the_eyes', - 'run_through', 'eviscerate', 'finality:eviscerate', - 'nightblade', 'finality:nightblade', - 'nightblade_ticks', 'finality:nightblade_ticks', + 'run_through', 'eviscerate', + 'nightblade', 'nightblade_ticks', 'roll_the_bones', 'slice_and_dice'] #All damage source that are replicated by Blade Flurry blade_flurry_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', @@ -105,10 +102,8 @@ class RogueDamageCalculator(DamageCalculator): #subtlety 'backstab': (35., 'strike'), 'eviscerate': (35., 'strike'), - 'finality:eviscerate': (35., 'strike'), 'gloomblade': (35., 'strike'), 'nightblade': (25., 'strike'), - 'finality:nightblade': (25., 'strike'), 'shadowstrike': (40., 'strike'), 'shuriken_storm': (35., 'strike'), 'shuriken_toss': (40., 'strike'), @@ -421,8 +416,6 @@ def backstab_damage(self, ap): def eviscerate_damage(self, ap, cp): return 1.28 * cp * ap - def finality_eviscerate_damage(self, ap, cp): - return 1.5 * cp * ap def gloomblade_damage(self, ap): return 4.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) @@ -436,8 +429,6 @@ def oh_goremaws_bite_damage(self, ap): #Nightblade doesn't actually scale with cps but passing cps for simplicity def nightblade_tick_damage(self, ap, cp): return 1.2 * ap * (1 + (0.05 * self.traits.demons_kiss)) - def finality_nightblade_tick_damage(self, ap, cp): - return 1.4 * ap * (1 + (0.05 * self.traits.demons_kiss)) def shadowstrike_damage(self, ap): return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) @@ -500,12 +491,10 @@ def get_formula(self, name): #subtlety 'backstab': self.backstab_damage, 'eviscerate': self.eviscerate_damage, - 'finality:eviscerate': self.finality_eviscerate_damage, 'gloomblade': self.gloomblade_damage, 'mh_goremaws_bite': self.mh_goremaws_bite_damage, 'oh_goremaws_bite': self.oh_goremaws_bite_damage, 'nightblade_ticks': self.nightblade_tick_damage, - 'finality:nightblade_ticks': self.finality_nightblade_tick_damage, 'shadowstrike': self.shadowstrike_damage, 'mh_shadow_blades': self.mh_shadow_blades_damage, 'oh_shadow_blades': self.oh_shadow_blades_damage, From 0ea80363ba10a53b56d5ad1a688ae29030ae4165 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 5 Oct 2016 15:35:33 -0400 Subject: [PATCH 103/265] Damage hotfixes 9/23 -Gear spec now also handled by the UI --- scripts/subtlety.py | 48 ++++++++++++++------ shadowcraft/calcs/rogue/Aldriana/__init__.py | 22 +++++---- shadowcraft/calcs/rogue/__init__.py | 19 ++++---- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 6b65186..d459147 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -48,27 +48,45 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=20909, + agi=21122, stam=19566, - crit=4402, - haste=5150, - #haste=0, - mastery=5999, - versatility=1515,) + crit=4188, + haste=3630, + mastery=4373, + versatility=4153,) # Initialize talents.. -test_talents = talents.Talents('2200002', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2210011', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '110000000000100000') +test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ + 'goremaws_bite': 1, + 'shadow_fangs': 1, + 'gutripper': 3, + 'fortunes_bite': 0, + 'catlike_reflexes': 3, + 'embrace_of_darkness': 0, + 'ghost_armor': 3, + 'precision_strike': 0, + 'energetic_stabbing': 3+3, + 'flickering_shadows': 1, + 'second_shuriken': 0, + 'demons_kiss': 0, + 'finality': 1, + 'the_quiet_knife': 3, + 'akarris_soul': 1, + 'soul_shadows': 0, + 'shadow_nova': 0, + 'legionblade': 0, +}) # Set up settings. -test_cycle = settings.SubtletyCycle(cp_builder='backstab', +test_cycle = settings.SubtletyCycle(cp_builder='backstab', dance_finishers_allowed=True, - positional_uptime=0.9 + positional_uptime=1. ) test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, - adv_params="", is_demon=True, num_boss_adds=0, marked_for_death_resets=2.0) + adv_params="", is_demon=True, num_boss_adds=0, marked_for_death_resets=0.0) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) @@ -82,8 +100,8 @@ #ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) -talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#talent_ranks = calculator.get_talents_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -112,9 +130,9 @@ def pretty_print(dict_list): #tier_ep_values, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") -pprint(talent_ranks) \ No newline at end of file +#pprint(talent_ranks) \ No newline at end of file diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ab4484a..e2ad687 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -213,7 +213,8 @@ def set_constants(self): } self.stat_multipliers = { 'str': 1., - 'agi': self.stats.gear_buffs.gear_specialization_multiplier(), + #UI handling gear specialization for now + 'agi': 1, 'ap': 1, 'crit': 1. + (0.02 * self.race.human_spirit), 'haste': 1. + (0.02 * self.race.human_spirit), @@ -1181,15 +1182,15 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg # Compute Main Gauche lumped ability costs - self.run_through_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer - self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (4 * self.traits.fatebringer) - cost_reducer - self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (4 * self.traits.fatebringer) - cost_reducer + self.run_through_energy_cost = self.get_spell_cost('run_through') - (3 * self.traits.fatebringer) - cost_reducer + self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (3 * self.traits.fatebringer) - cost_reducer + self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (3 * self.traits.fatebringer) - cost_reducer self.saber_slash_energy_cost = self.get_spell_cost('saber_slash') - cost_reducer - self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (4 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) + self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (3 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) if self.talents.slice_and_dice: - self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (4 * self.traits.fatebringer) + self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (3 * self.traits.fatebringer) else: - self.roll_the_bones_cost = self.get_spell_cost('roll_the_bones') - (4 * self.traits.fatebringer) + self.roll_the_bones_cost = self.get_spell_cost('roll_the_bones') - (3 * self.traits.fatebringer) if self.talents.ghostly_strike: self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer @@ -1952,12 +1953,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks if 'nightblade' in attacks_per_second: - print 'NB' attacks_per_second['nightblade_ticks'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): attacks_per_second['nightblade_ticks'][cp] = (3 + cp) * attacks_per_second['nightblade'][cp] del attacks_per_second['nightblade'] - print attacks_per_second #convert some white swings into shadowblades #since weapon speeds are now fixed just handle a single shadowblades @@ -2017,6 +2016,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): else: attacks_per_second[ability] *=1.06 + #for a in attacks_per_second: + # if isinstance(attacks_per_second[a], list): + # print a, 1./sum(attacks_per_second[a]) + # else: + # print a, 1./attacks_per_second[a] return attacks_per_second, crit_rates, additional_info #Computes the net energy and combo points from a shadow dance rotation diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 50fb831..14763cb 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -295,6 +295,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da both_hands = ability in self.dual_wield_damage_sources cps = max_cps if ability in self.finisher_damage_sources else 0 + if ability in self.physical_damage_sources: modifier *= armor_modifier #assume for now that all non-physical damage sources are shadow @@ -326,14 +327,14 @@ def oh_damage(self, ap): #general abilities def death_from_above_pulse_damage(self, ap, cp): - return 0.3666 * cp * ap + return 0.732 * cp * ap #assassination def deadly_poison_tick_damage(self, ap): - return .275 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) + return 0.3575 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) def deadly_instant_poison_damage(self, ap): - return .142 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) + return 0.221 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) #Maybe add better handling for 'rule of three' for artifact traits def envenom_damage(self, ap, cp): @@ -371,7 +372,7 @@ def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) def between_the_eyes_damage(self, ap, cp): - return .75 * cp * ap * (1 + (0.08 * self.traits.black_powder)) + return .75 * cp * ap * (1 + (0.06 * self.traits.black_powder)) #7*55% AP def blunderbuss_damage(self, ap): @@ -404,7 +405,7 @@ def pistol_shot_damage(self, ap): return 1.5 * ap def run_through_damage(self, ap, cp): - return 1.5 * ap * cp * (1 + (0.08 * self.traits.fates_thirst)) + return 1.5 * ap * cp * (1 + (0.06 * self.traits.fates_thirst)) def saber_slash_damage(self, ap): return 2.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) @@ -415,7 +416,7 @@ def backstab_damage(self, ap): return 3.7 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) def eviscerate_damage(self, ap, cp): - return 1.28 * cp * ap + return 1.472 * cp * ap def gloomblade_damage(self, ap): return 4.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) @@ -428,7 +429,7 @@ def oh_goremaws_bite_damage(self, ap): #Nightblade doesn't actually scale with cps but passing cps for simplicity def nightblade_tick_damage(self, ap, cp): - return 1.2 * ap * (1 + (0.05 * self.traits.demons_kiss)) + return 1.38 * ap * (1 + (0.05 * self.traits.demons_kiss)) def shadowstrike_damage(self, ap): return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) @@ -440,10 +441,10 @@ def oh_shadow_blades_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) def second_shuriken_damage(self, ap): - return 0.264 * ap + return 0.338 * ap def shuriken_storm_damage(self, ap): - return 0.5544 * ap + return 0.7215 * ap def shuriken_toss_damage(self, ap): return 1.2 * ap From fc7b495e4cec3d578c0c7ccbbd8b0f61413f840f Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 5 Oct 2016 21:01:11 -0400 Subject: [PATCH 104/265] Hackish fix for subtlety haste-spike bug. -Likely better solution that can reduce convergence iterations required --- scripts/subtlety.py | 6 +++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index d459147..6c3cded 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -63,7 +63,7 @@ 'goremaws_bite': 1, 'shadow_fangs': 1, 'gutripper': 3, - 'fortunes_bite': 0, + 'fortunes_bite': 1, 'catlike_reflexes': 3, 'embrace_of_darkness': 0, 'ghost_armor': 3, @@ -97,7 +97,7 @@ # Compute EP values. #ep_values = calculator.get_ep(baseline_dps=total_dps) -#ep_values = calculator.get_ep() +ep_values = calculator.get_ep() #tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) #talent_ranks = calculator.get_talents_ranking() @@ -126,7 +126,7 @@ def pretty_print(dict_list): print '-' * (max_len + 15) dicts_for_pretty_print = [ - #ep_values, + ep_values, #tier_ep_values, #trinkets_ep_value, dps_breakdown, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e2ad687..4e229b2 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1929,9 +1929,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #add in dance energy mini_cycle_energy += net_energy if cps_to_generate: - mini_cycle_count = 0.9*float(self.energy_budget) / abs(mini_cycle_energy) + mini_cycle_count = float(self.energy_budget) / abs(mini_cycle_energy) else: mini_cycle_count = 1 + + mini_cycle_count = min(mini_cycle_count, 1) + #print loop_counter, mini_cycle_count #mini_cycle_count = 1 #build the minicycle attack_counts if self.cp_builder == 'shuriken_storm': From dd4d63eb595f624534e33c5426f7af5cabb45e54 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Thu, 13 Oct 2016 14:10:17 -0400 Subject: [PATCH 105/265] Update Agonizing Poison Mechanics --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 21 ++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4e229b2..9e3fd3b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -788,16 +788,17 @@ def assassination_dps_breakdown(self): agonizing_poison_mod = 1 if self.talents.agonizing_poison: - agonizing_poison_mod = 0.04 - if self.traits.surge_of_toxins: - agonizing_poison_mod += 0.01 * self.surge_of_toxins_multiplier - agonizing_poison_mod += 1 - agonizing_poison_mod *= 1 + (0.01 * self.traits.master_alchemist) - agonizing_poison_mod *= 1 + (0.01 * self.traits.poison_knives) + agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives + #if self.traits.surge_of_toxins: + # agonizing_poison_mod += 0.01 * self.surge_of_toxins_multiplier + agonizing_poison_mod_per_stack= 0.04 * (1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])/2) + agonizing_poison_adder) / 100 if self.talents.master_poisoner: - agonizing_poison_mod *= 1.2 + agonizing_poison_mod_per_stack *= 1.2 + + if self.traits.surge_of_toxins: + agonizing_poison_mod_per_stack *= 1 + self.surge_of_toxins_multiplier - agonizing_poison_mod *= 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])/2) + agonizing_poison_mod *= 1 + agonizing_poison_mod_per_stack * self.agonizing_poison_stacks elaborate_planning_mod = 1 if self.talents.elaborate_planning: @@ -1030,7 +1031,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): finisher_aps = 0.0 for ability in attacks_per_second: if ability in self.finisher_damage_sources and 'ticks' not in ability: - finisher_aps += sum([0.02 * cp * 5for cp in attacks_per_second[ability]]) + finisher_aps += sum([0.02 * cp * 5 for cp in attacks_per_second[ability]]) self.surge_of_toxins_multiplier = finisher_aps if self.traits.blood_of_the_assassinated: @@ -1920,7 +1921,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = 0 while self.energy_budget > 0.1: - if loop_counter > 20: + if loop_counter > 50: raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 cps_to_generate = max(cps_per_dance - self.cp_budget, 0) From 7081f930c0a0f9ba9a3de1cc09aa84675e50bc32 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Thu, 20 Oct 2016 22:56:06 -0400 Subject: [PATCH 106/265] New Subtlety Setting and Modified Assn Script --- scripts/assassination.py | 44 ++++++++++++++------ shadowcraft/calcs/rogue/Aldriana/__init__.py | 9 ++-- shadowcraft/calcs/rogue/Aldriana/settings.py | 8 ++-- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index bf5618a..2f0e2f8 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -47,17 +47,37 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=20909, - stam=19566, - crit=4402, - haste=5150, - mastery=5999, - versatility=1515,) + agi=22294, + stam=28368, + crit=10431, + haste=1518, + mastery=1001, + versatility=3809,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +#test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ + 'kingsbane': 1, + 'assassins_blades': 1, + 'toxic_blades': 3, + 'poison_knives': 3, + 'urge_to_kill': 1, + 'balanced_blades ': 0, + 'surge_of_toxins': 0, + 'shadow_walker': 0, + 'master_assassin': 3, + 'shadow_swiftness': 0, + 'serrated_edge': 0, + 'bag_of_tricks': 1, + 'master_alchemist': 3, + 'gushing_wounds': 3+3, + 'fade_into_shadows': 0, + 'from_the_shadows': 0, + 'blood_of_the_assassinated': 1, + 'slayers_precision': 0, +}) # Set up settings. test_cycle = settings.AssassinationCycle() @@ -76,8 +96,8 @@ #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) -talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#talent_ranks = calculator.get_talents_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -108,11 +128,11 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) #pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) print ' ' * (max_length([dps_breakdown]) + 1), total_dps, ("total damage per second.") -pprint(talent_ranks) +#pprint(talent_ranks) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9e3fd3b..c003df9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -825,9 +825,6 @@ def assassination_dps_breakdown(self): if ability == 'rupture_ticks': damage_breakdown[ability] *= bota_mod - - - return damage_breakdown def assassination_attack_counts(self, current_stats, crit_rates=None): @@ -1038,6 +1035,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): self.bota_multiplier = 0.35 * sum(attacks_per_second['rupture']) * 10 self.bota_multiplier *= 2 + #for a in attacks_per_second: + # if isinstance(attacks_per_second[a], list): + # print a, 1./sum(attacks_per_second[a]) + # else: + # print a, 1./attacks_per_second[a] + return attacks_per_second, crit_rates, additional_info ########################################################################### diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 63d9cc4..d40abaf 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -91,7 +91,7 @@ class OutlawCycle(Cycle): #2 buffs ('jr', 'gm'), ('jr', 's'), ('jr', 'tb'), ('jr', 'bt'), ('jr', 'b'), ('gm', 's'), - ('gm', 'tb'), ('gm', 'bt'), ('gm', 'b'), + ('gm', 'tb'), ('gm', 'bt'), ('gm', 'b'), ('s', 'tb'), ('s', 'bt'), ('s', 'b'), ('tb', 'bt'), ('tb', 'b'), ('bt', 'b'), #single buffs @@ -143,11 +143,13 @@ class SubtletyCycle(Cycle): _cycle_type = 'subtlety' def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy='just', - dance_finishers_allowed=True): + dance_finishers_allowed=True, compute_cp_waste=False): self.cp_builder = cp_builder #Allowed values: 'shuriken_storm', 'backstab' (implies gloomblade if selected and ssk during dance) self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) #'just' - Only use SoD when needed to refresh #Allow finishers to be scheduled during dance - self.dance_finishers_allowed= dance_finishers_allowed \ No newline at end of file + self.dance_finishers_allowed= dance_finishers_allowed + #EXPERIMENTAL CP Waste Test + self.compute_cp_waste = compute_cp_waste \ No newline at end of file From 96553486c9d6f9a0b829fd1abb3ebdbb7aa9e7fa Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 21 Oct 2016 10:09:59 -0400 Subject: [PATCH 107/265] Bag of Tricks implemented --- scripts/assassination.py | 44 ++++++++++++++------ shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 +++++ shadowcraft/calcs/rogue/Aldriana/settings.py | 8 ++-- shadowcraft/calcs/rogue/__init__.py | 9 +++- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index bf5618a..2f0e2f8 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -47,17 +47,37 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=20909, - stam=19566, - crit=4402, - haste=5150, - mastery=5999, - versatility=1515,) + agi=22294, + stam=28368, + crit=10431, + haste=1518, + mastery=1001, + versatility=3809,) # Initialize talents.. -test_talents = talents.Talents('0000000', test_spec, test_class, level=test_level) +#test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ + 'kingsbane': 1, + 'assassins_blades': 1, + 'toxic_blades': 3, + 'poison_knives': 3, + 'urge_to_kill': 1, + 'balanced_blades ': 0, + 'surge_of_toxins': 0, + 'shadow_walker': 0, + 'master_assassin': 3, + 'shadow_swiftness': 0, + 'serrated_edge': 0, + 'bag_of_tricks': 1, + 'master_alchemist': 3, + 'gushing_wounds': 3+3, + 'fade_into_shadows': 0, + 'from_the_shadows': 0, + 'blood_of_the_assassinated': 1, + 'slayers_precision': 0, +}) # Set up settings. test_cycle = settings.AssassinationCycle() @@ -76,8 +96,8 @@ #tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) -talent_ranks = calculator.get_talents_ranking() -trait_ranks = calculator.get_trait_ranking() +#talent_ranks = calculator.get_talents_ranking() +#trait_ranks = calculator.get_trait_ranking() def max_length(dict_list): max_len = 0 @@ -108,11 +128,11 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): #talent_ranks, #trinkets_ep_value, dps_breakdown, - trait_ranks + #trait_ranks ] pretty_print(dicts_for_pretty_print) #pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) print ' ' * (max_length([dps_breakdown]) + 1), total_dps, ("total damage per second.") -pprint(talent_ranks) +#pprint(talent_ranks) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9e3fd3b..c61ddc3 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1013,6 +1013,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] + if self.traits.bag_of_tricks: + bag_of_tricks_proc_chance = (haste_multiplier * (1 + (alacrity_stacks))) * (1./sum(attacks_per_second['envenom'])) / 60 + attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) + #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) if self.talents.agonizing_poison: @@ -1038,6 +1042,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): self.bota_multiplier = 0.35 * sum(attacks_per_second['rupture']) * 10 self.bota_multiplier *= 2 + # for a in attacks_per_second: + # if isinstance(attacks_per_second[a], list): + # print a, 1./sum(attacks_per_second[a]) + # else: + # print a, 1./attacks_per_second[a] + return attacks_per_second, crit_rates, additional_info ########################################################################### diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 63d9cc4..d40abaf 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -91,7 +91,7 @@ class OutlawCycle(Cycle): #2 buffs ('jr', 'gm'), ('jr', 's'), ('jr', 'tb'), ('jr', 'bt'), ('jr', 'b'), ('gm', 's'), - ('gm', 'tb'), ('gm', 'bt'), ('gm', 'b'), + ('gm', 'tb'), ('gm', 'bt'), ('gm', 'b'), ('s', 'tb'), ('s', 'bt'), ('s', 'b'), ('tb', 'bt'), ('tb', 'b'), ('bt', 'b'), #single buffs @@ -143,11 +143,13 @@ class SubtletyCycle(Cycle): _cycle_type = 'subtlety' def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy='just', - dance_finishers_allowed=True): + dance_finishers_allowed=True, compute_cp_waste=False): self.cp_builder = cp_builder #Allowed values: 'shuriken_storm', 'backstab' (implies gloomblade if selected and ssk during dance) self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target self.symbols_policy = symbols_policy #Allowed values: #'always' - use SoD every dance (macro) #'just' - Only use SoD when needed to refresh #Allow finishers to be scheduled during dance - self.dance_finishers_allowed= dance_finishers_allowed \ No newline at end of file + self.dance_finishers_allowed= dance_finishers_allowed + #EXPERIMENTAL CP Waste Test + self.compute_cp_waste = compute_cp_waste \ No newline at end of file diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 14763cb..ade2cbf 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -21,7 +21,7 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'rupture_ticks'] + 'poisoned_knife', 'poison_bomb', 'rupture_ticks'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -40,7 +40,7 @@ class RogueDamageCalculator(DamageCalculator): 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] #All damage sources the scale with mastery (assn or sub) mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', - 'eviscerate', 'nightblade_ticks'] + 'eviscerate', 'nightblade_ticks', 'poison_bomb'] #All damage sources that deal damage with both hands dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', 'goremaws_bite', 'shadow_blades'] @@ -364,6 +364,10 @@ def oh_mutilate_damage(self, ap): def poisoned_knife_damage(self, ap): return 0.6 * ap + #Lumping 6 ticks together for simplicity + def poison_bomb_damage(self, ap): + return 6 * 1.2 * ap + def rupture_tick_damage(self, ap, cp): return .3 * cp * ap * (1 + (0.0333 * self.traits.gushing_wounds)) @@ -474,6 +478,7 @@ def get_formula(self, name): 'mh_mutilate': self.mh_mutilate_damage, 'oh_mutilate': self.oh_mutilate_damage, 'poisoned_knife': self.poisoned_knife_damage, + 'poison_bomb': self.poison_bomb_damage, 'rupture_ticks': self.rupture_tick_damage, #outlaw 'ambush': self.ambush_damage, From 8a9c35b7f61a75faac879166317461dfa7177065 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 21 Oct 2016 10:28:21 -0400 Subject: [PATCH 108/265] From The Shadows implemented --- scripts/assassination.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 +++ shadowcraft/calcs/rogue/__init__.py | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 2f0e2f8..4d97b99 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -74,7 +74,7 @@ 'master_alchemist': 3, 'gushing_wounds': 3+3, 'fade_into_shadows': 0, - 'from_the_shadows': 0, + 'from_the_shadows': 1, 'blood_of_the_assassinated': 1, 'slayers_precision': 0, }) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 95fe6cf..6f4d883 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1014,6 +1014,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): bag_of_tricks_proc_chance = (haste_multiplier * (1 + (alacrity_stacks))) * (1./sum(attacks_per_second['envenom'])) / 60 attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) + if self.traits.from_the_shadows: + attacks_per_second['from_the_shadows'] = 1. / self.vendetta_cd + #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) if self.talents.agonizing_poison: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index ade2cbf..8f4d21e 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -21,7 +21,7 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'poison_bomb', 'rupture_ticks'] + 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -343,6 +343,10 @@ def envenom_damage(self, ap, cp): def fan_of_knives_damage(self, ap): return .8316 * ap + #Lumping 40 ticks together for simplicity + def from_the_shadows_damage(self, ap): + return 40 * 0.35 * ap + def garrote_tick_damage(self, ap): return .9 * ap @@ -470,6 +474,7 @@ def get_formula(self, name): 'deadly_instant_poison': self.deadly_instant_poison_damage, 'envenom': self.envenom_damage, 'fan_of_knives_damage': self.fan_of_knives_damage, + 'from_the_shadows': self.from_the_shadows_damage, 'garrote_ticks': self.garrote_tick_damage, 'hemorrhage': self.hemorrhage_damage, 'mh_kingsbane': self.mh_kingsbane_damage, From 2ee683c7b237cfe58137465ae794fe933808a09b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 21 Oct 2016 11:41:39 -0400 Subject: [PATCH 109/265] Shadow Satyr's Walk implemented --- scripts/subtlety.py | 2 +- shadowcraft/calcs/rogue/__init__.py | 3 +++ shadowcraft/objects/stats.py | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 6c3cded..d28b529 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -44,7 +44,7 @@ test_procs = procs.ProcsList() # Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here +test_gear_buffs = stats.GearBuffs('gear_specialization',) #tier buffs located here # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8f4d21e..4cf3514 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -521,6 +521,9 @@ def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod if ability == 'shadowstrike': cost -= 0.25 * (5 * self.traits.energetic_stabbing) + #Assume 5 yards away so 10 + 5/2 + if self.stats.gear_buffs.shadow_satyrs_walk: + cost -= 12 return cost def get_spell_cd(self, ability): diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 70d37a8..a6f0765 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -153,7 +153,12 @@ class GearBuffs(object): 'rogue_t17_4pc_lfr', # 1.1 RPPM, 30% energy generation for 6s 'rogue_t18_2pc', # Dispatch deals 25% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 30% for 10 sec 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP - 'rogue_t18_4pc_lfr' # Energy increased by 20, 5% increase in energy regen + 'rogue_t18_4pc_lfr', # Energy increased by 20, 5% increase in energy regen + #Legendaries + 'shadow_satyrs_walk', + + + ] allowed_buffs = frozenset(other_gear_buffs) From 59b0a02973fbcfbc91bea26348b8744f00796cad Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 22 Oct 2016 14:34:03 -0400 Subject: [PATCH 110/265] Fix Agonizing Poison Formula --- scripts/assassination.py | 6 +++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 ++++++---- shadowcraft/calcs/rogue/__init__.py | 2 +- shadowcraft/objects/proc_data.py | 17 +---------------- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 4d97b99..b7b8d1f 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -21,7 +21,7 @@ i18n.set_language(test_language) # Set up level/class/race -test_level = 100 +test_level = 110 test_race = race.Race('none') test_class = 'rogue' test_spec = 'assassination' @@ -65,7 +65,7 @@ 'poison_knives': 3, 'urge_to_kill': 1, 'balanced_blades ': 0, - 'surge_of_toxins': 0, + 'surge_of_toxins': 1, 'shadow_walker': 0, 'master_assassin': 3, 'shadow_swiftness': 0, @@ -74,7 +74,7 @@ 'master_alchemist': 3, 'gushing_wounds': 3+3, 'fade_into_shadows': 0, - 'from_the_shadows': 1, + 'from_the_shadows': 0, 'blood_of_the_assassinated': 1, 'slayers_precision': 0, }) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 6f4d883..3e1b3ee 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -124,7 +124,6 @@ def get_crit_rates(self, stats): 'mh_autoattacks': min(base_melee_crit_rate, self.dw_mh_hit_chance), 'oh_autoattacks': min(base_melee_crit_rate, self.dw_oh_hit_chance), } - for attack in ('rupture_ticks', 'shuriken_toss'): crit_rates[attack] = base_melee_crit_rate @@ -789,16 +788,17 @@ def assassination_dps_breakdown(self): agonizing_poison_mod = 1 if self.talents.agonizing_poison: agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives + agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 #if self.traits.surge_of_toxins: # agonizing_poison_mod += 0.01 * self.surge_of_toxins_multiplier - agonizing_poison_mod_per_stack= 0.04 * (1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])/2) + agonizing_poison_adder) / 100 + agonizing_poison_mod_per_stack= 0.04 * agonizing_poison_adder if self.talents.master_poisoner: agonizing_poison_mod_per_stack *= 1.2 if self.traits.surge_of_toxins: agonizing_poison_mod_per_stack *= 1 + self.surge_of_toxins_multiplier - agonizing_poison_mod *= 1 + agonizing_poison_mod_per_stack * self.agonizing_poison_stacks + agonizing_poison_mod = 1 + (agonizing_poison_mod_per_stack * self.agonizing_poison_stacks) elaborate_planning_mod = 1 if self.talents.elaborate_planning: @@ -872,7 +872,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): max_cps = 6 builders_per_finisher = 0.0 avg_finisher_size = 0.0 - finisher_list = [0, 0, 0, 0, 0, 0, 0] + finisher_list = [0., 0., 0., 0., 0., 0., 0.] + for path in paths: chance = 1.0 for step in path: @@ -1047,6 +1048,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): # print a, 1./sum(attacks_per_second[a]) # else: # print a, 1./attacks_per_second[a] + # print "--------" return attacks_per_second, crit_rates, additional_info diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 4cf3514..dc4ee59 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -70,7 +70,7 @@ class RogueDamageCalculator(DamageCalculator): 6: 1, } - assassination_mastery_conversion = .035 + assassination_mastery_conversion = .04 outlaw_mastery_conversion = .022 subtlety_mastery_conversion = .0276 diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 32ffd27..f58a7a1 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -64,21 +64,6 @@ 'proc_rate': 1.0, 'trigger': 'all_attacks' }, - #weapon enchant support - 'mark_of_the_shattered_hand_dot': { - 'stat': 'physical_dot', - 'value': 750, - 'duration': 6, - 'proc_name': 'Mark of the Shattered Hand DOT', - 'type': 'rppm', - 'source': 'weapon', - 'item_level': 100, - 'icd': 0, - 'proc_rate': 2.5, - 'can_crit': False, - 'haste_scales': True, - 'trigger': 'all_attacks', - }, #racials 'touch_of_the_grave': { 'stat': 'spell_damage', @@ -192,7 +177,7 @@ 'value': {'haste': 550, 'crit': 550}, 'duration': 6, 'proc_name': 'Mark of the Claw', - 'source': 'trinket', + 'source': 'neck', 'type': 'rppm', 'proc_rate': 2.5, 'trigger': 'all_attacks', From 43a6ad72af90525d03d34aa8b672621bcb7029cd Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 22 Oct 2016 14:48:37 -0400 Subject: [PATCH 111/265] Version bump --- shadowcraft/calcs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 82fb8e3..355ea66 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -28,7 +28,7 @@ class DamageCalculator(object): def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '7.0.0' # should reflect the game patch being targetted - self.SHADOWCRAFT_BUILD = '0.01' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch + self.SHADOWCRAFT_BUILD = '0.02' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats self.talents = talents From c955e8a65f76701d402014b0f5ec5f127fd17e7c Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 22 Oct 2016 14:56:15 -0400 Subject: [PATCH 112/265] Removed Mark of Shattered Hand DOT --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 3e1b3ee..4153369 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -573,20 +573,6 @@ def determine_stats(self, attack_counts_function): if self.buffs.felmouth_food(): self.stats.procs.set_proc('felmouth_frenzy') - shatt_hand = 0 - for hand in ('mh', 'oh'): - if getattr(getattr(self.stats, hand), 'mark_of_the_shattered_hand'): - self.stats.procs.set_proc('mark_of_the_shattered_hand_dot') #this enables the proc if it's not active, doesn't duplicate - shatt_hand += 1 - if shatt_hand > 0: - if shatt_hand > 1: - getattr(self.stats.procs, 'mark_of_the_shattered_hand_dot').proc_rate = 5 - else: - getattr(self.stats.procs, 'mark_of_the_shattered_hand_dot').proc_rate = 2.5 - self.set_rppm_uptime(getattr(self.stats.procs, 'mark_of_the_shattered_hand_dot')) - if not shatt_hand: - self.stats.procs.del_proc('mark_of_the_shattered_hand_dot') - #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): if (proc.stat == 'stats'): From a7a66ee62140901137280f5eed618e92f28885b1 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Sat, 22 Oct 2016 20:47:44 -0400 Subject: [PATCH 113/265] Fix bag of tricks with alacrity --- scripts/assassination.py | 6 +++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index b7b8d1f..e0ff8ae 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -51,11 +51,11 @@ stam=28368, crit=10431, haste=1518, - mastery=1001, + mastery=9335, versatility=3809,) # Initialize talents.. #test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) -test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) +test_talents = talents.Talents('1110021', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ @@ -65,7 +65,7 @@ 'poison_knives': 3, 'urge_to_kill': 1, 'balanced_blades ': 0, - 'surge_of_toxins': 1, + 'surge_of_toxins': 0, 'shadow_walker': 0, 'master_assassin': 3, 'shadow_swiftness': 0, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4153369..ec5acf5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -783,8 +783,10 @@ def assassination_dps_breakdown(self): if self.traits.surge_of_toxins: agonizing_poison_mod_per_stack *= 1 + self.surge_of_toxins_multiplier + print agonizing_poison_mod_per_stack agonizing_poison_mod = 1 + (agonizing_poison_mod_per_stack * self.agonizing_poison_stacks) + print agonizing_poison_mod elaborate_planning_mod = 1 if self.talents.elaborate_planning: @@ -998,7 +1000,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] if self.traits.bag_of_tricks: - bag_of_tricks_proc_chance = (haste_multiplier * (1 + (alacrity_stacks))) * (1./sum(attacks_per_second['envenom'])) / 60 + bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (1./sum(attacks_per_second['envenom'])) / 60 attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) if self.traits.from_the_shadows: From 68b353081f9273b5fd9e925be2cb530844c642ed Mon Sep 17 00:00:00 2001 From: Tamen Date: Mon, 31 Oct 2016 06:24:00 +0000 Subject: [PATCH 114/265] Add support for Jacin's Ruse set bonus --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 ++ shadowcraft/objects/proc_data.py | 14 ++++++++++++++ shadowcraft/objects/stats.py | 8 ++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ec5acf5..4d809b5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -572,6 +572,8 @@ def determine_stats(self, attack_counts_function): if self.buffs.felmouth_food(): self.stats.procs.set_proc('felmouth_frenzy') + if self.stats.gear_buffs.jacins_ruse_2pc_bonus(): + self.stats.procs.set_proc('jacins_ruse_2pc') #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index f58a7a1..955c4b2 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -546,6 +546,20 @@ 'trigger': 'all_attacks' }, + 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. + 'stat':'stats', + 'value':{'mastery':3000}, + 'duration':15, + 'proc_name': "Jacin's Ruse", + 'item_level': 820, + 'type': 'rppm', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1, + 'can_crit': False, + 'trigger': 'all_attacks' + }, + #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index a6f0765..0d5b0ee 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -154,11 +154,10 @@ class GearBuffs(object): 'rogue_t18_2pc', # Dispatch deals 25% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 30% for 10 sec 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP 'rogue_t18_4pc_lfr', # Energy increased by 20, 5% increase in energy regen + 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm #Legendaries 'shadow_satyrs_walk', - - ] allowed_buffs = frozenset(other_gear_buffs) @@ -254,6 +253,11 @@ def rogue_t18_4pc_bonus(self): return True return False + def jacins_ruse_2pc_bonus(self): + if self.jacins_ruse_2pc: + return True + return False + def gear_specialization_multiplier(self): if self.gear_specialization: return 1.05 From 32758787fbf9d640b4100de07948f5e1ef6a1394 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 4 Nov 2016 10:40:22 -0400 Subject: [PATCH 115/265] March of the Legion 2pc --- scripts/assassination.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 ++ shadowcraft/objects/proc_data.py | 37 ++++++++++++++------ shadowcraft/objects/stats.py | 1 + 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index e0ff8ae..de3cf8f 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -43,7 +43,7 @@ test_procs = procs.ProcsList() # Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization') +test_gear_buffs = stats.GearBuffs('gear_specialization', ) # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4d809b5..63d6205 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -574,6 +574,8 @@ def determine_stats(self, attack_counts_function): self.stats.procs.set_proc('felmouth_frenzy') if self.stats.gear_buffs.jacins_ruse_2pc_bonus(): self.stats.procs.set_proc('jacins_ruse_2pc') + if self.stats.gear_buffs.march_of_the_legion_2pc and self.settings.is_demon: + self.stats.procs.set_proc('march_of_the_legion_2pc') #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 955c4b2..6a94f13 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -547,17 +547,32 @@ }, 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. - 'stat':'stats', - 'value':{'mastery':3000}, - 'duration':15, - 'proc_name': "Jacin's Ruse", - 'item_level': 820, - 'type': 'rppm', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1, - 'can_crit': False, - 'trigger': 'all_attacks' + 'stat':'stats', + 'value':{'mastery':3000}, + 'duration':15, + 'proc_name': "Jacin's Ruse", + 'item_level': 820, + 'type': 'rppm', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1, + 'can_crit': False, + 'trigger': 'all_attacks' + }, + + 'march_of_the_legion_2pc': { #Equip: Your spells and attacks against Demons have a chance to deal an additional 27200 to 36800 Fire damage. + 'stat':'spell_damage', + 'value': 35000, + 'duration':0, + 'proc_name': "March of the Legion", + 'item_level': 820, + 'type': 'rppm', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 6, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' }, #6.2.3 procs diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 0d5b0ee..7599bcc 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -155,6 +155,7 @@ class GearBuffs(object): 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP 'rogue_t18_4pc_lfr', # Energy increased by 20, 5% increase in energy regen 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm + 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM #Legendaries 'shadow_satyrs_walk', From 653f6b1ecd4fa4e2d5844e45b98e95780eef20e4 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 4 Nov 2016 11:03:36 -0400 Subject: [PATCH 116/265] Dreadlord's Deceit Added --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 22 ++++++++++++++++++++ shadowcraft/calcs/rogue/__init__.py | 2 +- shadowcraft/objects/stats.py | 1 + 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 63d6205..9066c7d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -806,6 +806,10 @@ def assassination_dps_breakdown(self): if self.traits.blood_of_the_assassinated: bota_mod = 1 + self.bota_multiplier + if self.stats.gear_buffs.the_dreadlords_deceit: + avg_dreadlord_stacks = 0.5/aps['fan_of_knives'] + damage_breakdown['fan_of_knives'] *= (1 + (0.35 * avg_dreadlord_stacks)) + for ability in damage_breakdown: damage_breakdown[ability] *= agonizing_poison_mod damage_breakdown[ability] *= elaborate_planning_mod @@ -932,6 +936,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5. * (1. + self.settings.marked_for_death_resets)) cp_budget = mfd_cps + if self.stats.gear_buffs.the_dreadlords_deceit: + fok_interval = 1./60 + attacks_per_second['fan_of_knives'] = fok_interval + cp_budget += self.settings.duration * fok_interval + net_energy_per_second -= fok_interval * 35 + if self.traits.kingsbane: attacks_per_second['kingsbane'] = 1./self.kingsbane_cd attacks_per_second['kingsbane_ticks'] = 7. / self.kingsbane_cd @@ -1699,6 +1709,11 @@ def subtlety_dps_breakdown(self): if self.talents.deeper_strategem: ds_multiplier = 1.1 + + if self.stats.gear_buffs.the_dreadlords_deceit: + avg_dreadlord_stacks = 0.5/aps['shuriken_storm'] + damage_breakdown['shuriken_storm'] *= (1 + (0.35 * avg_dreadlord_stacks)) + for key in damage_breakdown: damage_breakdown[key] *= infallible_trinket_mod if key == 'shuriken_storm': @@ -1977,6 +1992,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.shadow_nova: attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] + #FIXME: Kinda hackish, better approach would be to compute a seperate dance rotation + if self.stats.gear_buffs.the_dreadlords_deceit and self.cp_builder =='backstab': + shuriken_interval = 1./60 + attacks_per_second['shadowstrike'] -= shuriken_interval + attacks_per_second['shuriken_storm'] = shuriken_interval + self.stealth_shuriken_uptime = 1. + self.stealth_shuriken_uptime = 0. if self.cp_builder == 'shuriken_storm': self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index dc4ee59..c9f356f 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -473,7 +473,7 @@ def get_formula(self, name): 'deadly_poison': self.deadly_poison_tick_damage, 'deadly_instant_poison': self.deadly_instant_poison_damage, 'envenom': self.envenom_damage, - 'fan_of_knives_damage': self.fan_of_knives_damage, + 'fan_of_knives': self.fan_of_knives_damage, 'from_the_shadows': self.from_the_shadows_damage, 'garrote_ticks': self.garrote_tick_damage, 'hemorrhage': self.hemorrhage_damage, diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 7599bcc..9043e11 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -158,6 +158,7 @@ class GearBuffs(object): 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM #Legendaries 'shadow_satyrs_walk', + 'the_dreadlords_deceit' ] From f8cab33e7087c95c0a08da462aafb6517dd8f24b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 29 Nov 2016 15:38:00 -0500 Subject: [PATCH 117/265] Added Modifier Object for Damage Modifier Handling --- shadowcraft/objects/modifiers.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 shadowcraft/objects/modifiers.py diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py new file mode 100644 index 0000000..a6b6e51 --- /dev/null +++ b/shadowcraft/objects/modifiers.py @@ -0,0 +1,38 @@ +from shadowcraft.core import exceptions + +class ModifierList(object): + def __init__(self, sources): + self.sources = sources + self.modifiers = {} + + def register_modifier(self, modifier): + self.modifiers[modifier.name] = modifier + for ability in modifier.ability_list: + if ability not in self.sources: + raise exceptions.InvalidInputException(_('Unknown source {source} in damage modifier {mod}').format(source=ability, mod=modifier.name)) + + def update_modifier_value(self, modifier_name, value): + self.modifiers[modifier_name].value = value + + def compile__modifier_dict(self): + lumped_modifier = {s:1 for s in self.sources} + for mod in self.modifiers.values(): + if mod.whitelist: + for ability in mod.ability_list: + lumped_modifier[ability] *= mod.value + if mod.blacklist: + for ability in self.sources: + if not ability in mod.ability_list: + lumped_modifier[ability] *= mod.value + return lumped_modifier + +class DamageModifier(object): + def __init__(self, name, value, ability_list, whitelist=True, blacklist=False): + if not (whitelist ^ blacklist): + raise exceptions.InvalidInputException(_('Damage Modifier {mod} must be either a blacklist or whitelist').format(mod=name)) + self.name = name + self.value = value + self.ability_list = ability_list + self.blacklist = blacklist + self.whitelist = whitelist + From 89f8aca86770b24f6a15fef46099b063d1b9f193 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 30 Nov 2016 11:46:47 -0500 Subject: [PATCH 118/265] Modifiers Whitelist by Default, also Documentation --- shadowcraft/objects/modifiers.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index a6b6e51..7a4515e 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -1,5 +1,10 @@ from shadowcraft.core import exceptions +#ModifierList contains all modifiers needed for dps computation. +#ModifierList is used to compile all modifiers into a single lumped modifier per damage source. +#Typical use case registers all modifiers needed at the beginning of the computation with a +#None value and then updates later. +#Before final damage computation a dict is compiled and used for damage computation class ModifierList(object): def __init__(self, sources): self.sources = sources @@ -14,25 +19,26 @@ def register_modifier(self, modifier): def update_modifier_value(self, modifier_name, value): self.modifiers[modifier_name].value = value - def compile__modifier_dict(self): + def compile_modifier_dict(self): lumped_modifier = {s:1 for s in self.sources} for mod in self.modifiers.values(): - if mod.whitelist: + if not mod.blacklist: for ability in mod.ability_list: lumped_modifier[ability] *= mod.value - if mod.blacklist: + else: for ability in self.sources: if not ability in mod.ability_list: lumped_modifier[ability] *= mod.value return lumped_modifier +#DamageModifier specifies any type of modifier applied to ability damage. +#Each modifier is specified as a value applied to either a whitelist or blacklist of abilities. +#Whitelist is default since it is more compact for most modifiers +#but all damage modifiers can be represented either way class DamageModifier(object): - def __init__(self, name, value, ability_list, whitelist=True, blacklist=False): - if not (whitelist ^ blacklist): - raise exceptions.InvalidInputException(_('Damage Modifier {mod} must be either a blacklist or whitelist').format(mod=name)) + def __init__(self, name, value, ability_list, blacklist=False): self.name = name self.value = value self.ability_list = ability_list self.blacklist = blacklist - self.whitelist = whitelist From c25123ddfaad132f5eeea2fca1e683e4ae17be58 Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Sat, 10 Dec 2016 19:19:30 -0700 Subject: [PATCH 119/265] Fix calculation of shadow_nova breakdown with boss_adds > 0 --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9066c7d..76d52ce 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1731,7 +1731,7 @@ def subtlety_dps_breakdown(self): elif key == 'second_shuriken': damage_breakdown[key] *= 1 + self.settings.num_boss_adds elif key == 'shadow_nova': - damage_breakdown *= 1 + self.settings.num_boss_adds + damage_breakdown[shadow_nova] *= 1 + self.settings.num_boss_adds return damage_breakdown From 762bb100f4c5a1f0ded526a613e20b942f673981 Mon Sep 17 00:00:00 2001 From: Tamen Date: Fri, 6 Jan 2017 17:57:41 +0000 Subject: [PATCH 120/265] Fix shadow_nova again --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 76d52ce..a1d3c17 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1731,7 +1731,7 @@ def subtlety_dps_breakdown(self): elif key == 'second_shuriken': damage_breakdown[key] *= 1 + self.settings.num_boss_adds elif key == 'shadow_nova': - damage_breakdown[shadow_nova] *= 1 + self.settings.num_boss_adds + damage_breakdown[key] *= 1 + self.settings.num_boss_adds return damage_breakdown From f3f0318ff67c54e9d9bd6042e673183210258f1a Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 13 Jan 2017 12:54:12 -0500 Subject: [PATCH 121/265] New Modifier System Added for Sub and Assn -Assn fully implemented -Sub should be modified to move uptime computations out of attack_counts -Outlaw needs integration with modifier system -Need to integrate procs for all specs --- scripts/assassination.py | 24 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 244 +++++++++++-------- shadowcraft/calcs/rogue/__init__.py | 44 +--- shadowcraft/objects/modifiers.py | 2 + 4 files changed, 169 insertions(+), 145 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index de3cf8f..0e50b08 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -47,15 +47,15 @@ # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=22294, + agi=25583, stam=28368, - crit=10431, - haste=1518, - mastery=9335, - versatility=3809,) + crit=11133, + haste=1666, + mastery=2469, + versatility=3085,) # Initialize talents.. #test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) -test_talents = talents.Talents('1110021', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ @@ -64,17 +64,17 @@ 'toxic_blades': 3, 'poison_knives': 3, 'urge_to_kill': 1, - 'balanced_blades ': 0, - 'surge_of_toxins': 0, + 'balanced_blades ': 2, + 'surge_of_toxins': 1, 'shadow_walker': 0, - 'master_assassin': 3, + 'master_assassin': 3+3, 'shadow_swiftness': 0, - 'serrated_edge': 0, + 'serrated_edge': 3, 'bag_of_tricks': 1, 'master_alchemist': 3, - 'gushing_wounds': 3+3, + 'gushing_wounds': 3, 'fade_into_shadows': 0, - 'from_the_shadows': 0, + 'from_the_shadows': 1, 'blood_of_the_assassinated': 1, 'slayers_precision': 0, }) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9066c7d..2bb25ed 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -9,6 +9,7 @@ from shadowcraft.calcs.rogue import RogueDamageCalculator from shadowcraft.core import exceptions +from shadowcraft.objects import modifiers from shadowcraft.objects import procs from shadowcraft.objects import proc_data @@ -748,15 +749,50 @@ def assassination_dps_breakdown(self): if not self.spec == 'assassination': raise InputNotModeledException(_('You must specify a assassination cycle to match your assassination spec.')) - #outlaw specific constants + #assassination specific constants + #set up damage modifier list and all relevant modifiers, use None for placeholder values + self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', + 'fan_of_knives', 'hemorrhage', 'mutilate', 'autoattacks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', + 'deadly_instant_poison', 'envenom', 'poison_bomb',])) + + #time averaged vendetta modifier used for most things + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True)) + + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) + + #talent specific modifiers + if self.talents.elaborate_planning: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], blacklist=True)) + if self.talents.hemorrhage: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks'])) + if self.talents.agonizing_poison: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True)) + + #trait specific modifiers + if self.traits.blood_of_the_assassinated: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('blood_of_the_assassinated', None, ['rupture_ticks'])) + if self.traits.surge_of_toxins: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('surge_of_toxins', None, ['deadly_poison', + 'deadly_instant_poison', 'envenom', 'poison_bomb',])) + + if self.traits.slayers_precision: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', + 1.05 + (0.005 * self.traits.slayers_precision - 1), [], blacklist=True)) - self.damage_modifier_cache = 1 + (0.005 * self.traits.slayers_precision) + #gear specific modifiers + if self.stats.gear_buffs.the_dreadlords_deceit: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) self.set_constants() self.vendetta_cd = self.get_spell_cd('vendetta') - #cp stacking handlers + self.vendetta_multiplier = 0.3 * (20 / self.vendetta_cd) + #cd stacking handlers if self.settings.cycle.kingsbane_with_vendetta == 'only': self.kingsbane_cd = min(self.vendetta_cd, self.get_spell_cd('kingsbane')) kb_venn_uptime = 1.0 @@ -771,55 +807,64 @@ def assassination_dps_breakdown(self): self.exsang_cd = self.get_spell_cd('exsanguinate') exsang_venn_uptime = self.exsang_cd/self.vendetta_cd + self.damage_modifiers.update_modifier_value('vendetta_time_average', 1 + self.vendetta_multiplier) + self.damage_modifiers.update_modifier_value('vendetta_exsang', 1 + (self.vendetta_multiplier * exsang_venn_uptime)) + self.damage_modifiers.update_modifier_value('vendetta_kb', 1 + (self.vendetta_multiplier * kb_venn_uptime)) stats, aps, crits, procs, additional_info = self.determine_stats(self.assassination_attack_counts) - damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) - agonizing_poison_mod = 1 + self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) + self.damage_modifiers.update_modifier_value('potent_poisons', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) + + if self.traits.blood_of_the_assassinated: + bota_uptime = 0.35 * sum(aps['rupture']) * 10 # procs/ability * ability/second * seconds/proc gives unit-less uptime + bota_multiplier = 2 * bota_uptime + self.damage_modifiers.update_modifier_value('blood_of_the_assassinated', bota_multiplier) + + finisher_aps = 0.0 + for ability in aps: + if ability in self.finisher_damage_sources and 'ticks' not in ability: + finisher_aps += sum(aps[ability]) + + #actually 2% per cp up to max of 5 + surge_of_toxins_multiplier = 1. + if self.traits.surge_of_toxins: + finisher_cpps = 0.0 #finisher cps per second + for ability in aps: + if ability in self.finisher_damage_sources and 'ticks' not in ability: + finisher_cpps += sum([min(cp, 5) * aps[ability][cp] for cp in xrange(len(aps[ability]))]) + surge_uptime = finisher_aps * 5 #attacks/second * seconds/attack + surge_of_toxins_multiplier = 1. + ((0.02 * finisher_cpps) * surge_uptime) + self.damage_modifiers.update_modifier_value('surge_of_toxins', surge_of_toxins_multiplier) + + if self.talents.elaborate_planning: + ep_uptime = finisher_aps * 5 #attacks/second * seconds/attack + self.damage_modifiers.update_modifier_value('elaborate_planning', 1 + (0.15 * ep_uptime)) + + if self.talents.agonizing_poison: + stack_time = 5./aps['agonizing_poison'] + max_time = self.settings.duration - stack_time + agonizing_poison_stacks = (max_time/self.settings.duration) * 5 + (stack_time/self.settings.duration) * 2.5 + agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 - #if self.traits.surge_of_toxins: - # agonizing_poison_mod += 0.01 * self.surge_of_toxins_multiplier + agonizing_poison_mod_per_stack= 0.04 * agonizing_poison_adder if self.talents.master_poisoner: agonizing_poison_mod_per_stack *= 1.2 if self.traits.surge_of_toxins: - agonizing_poison_mod_per_stack *= 1 + self.surge_of_toxins_multiplier - print agonizing_poison_mod_per_stack + agonizing_poison_mod_per_stack *= surge_of_toxins_multiplier - agonizing_poison_mod = 1 + (agonizing_poison_mod_per_stack * self.agonizing_poison_stacks) - print agonizing_poison_mod - - elaborate_planning_mod = 1 - if self.talents.elaborate_planning: - elaborate_planning_mod = 1 + (0.15 * self.elaborate_planning_multiplier) - - hemo_mod = 1 + 0.25 * self.talents.hemorrhage - - surge_mod = 1 - if self.traits.surge_of_toxins: - surge_mod = 1 + self.surge_of_toxins_multiplier - - bota_mod = 1 - if self.traits.blood_of_the_assassinated: - bota_mod = 1 + self.bota_multiplier + agonizing_poison_mod = 1 + (agonizing_poison_mod_per_stack * agonizing_poison_stacks) + self.damage_modifiers.update_modifier_value('agonizing_poison', agonizing_poison_mod) if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5/aps['fan_of_knives'] - damage_breakdown['fan_of_knives'] *= (1 + (0.35 * avg_dreadlord_stacks)) + self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) - for ability in damage_breakdown: - damage_breakdown[ability] *= agonizing_poison_mod - damage_breakdown[ability] *= elaborate_planning_mod - if ability in ['rupture_ticks', 'garrote_ticks']: - damage_breakdown[ability] *= hemo_mod - if ability in ['deadly_poison_ticks', 'deadly_instant_poison', - 'kingsbane', 'kingsbane_ticks']: - damage_breakdown[ability] *= surge_mod - if ability == 'rupture_ticks': - damage_breakdown[ability] *= bota_mod + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) return damage_breakdown @@ -1022,35 +1067,13 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) - if self.talents.agonizing_poison: - stack_time = 5./attacks_per_second['agonizing_poison'] - max_time = self.settings.duration - stack_time - self.agonizing_poison_stacks = (max_time/self.settings.duration) * 5 + (stack_time/self.settings.duration) * 2.5 - if self.talents.elaborate_planning: - finisher_aps = 0.0 - for ability in attacks_per_second: - if ability in self.finisher_damage_sources and 'ticks' not in ability: - finisher_aps += sum(attacks_per_second[ability]) - self.elaborate_planning_multiplier = min(1, 5 * finisher_aps) - - if self.traits.surge_of_toxins: - finisher_aps = 0.0 - for ability in attacks_per_second: - if ability in self.finisher_damage_sources and 'ticks' not in ability: - finisher_aps += sum([0.02 * cp * 5 for cp in attacks_per_second[ability]]) - self.surge_of_toxins_multiplier = finisher_aps - - if self.traits.blood_of_the_assassinated: - self.bota_multiplier = 0.35 * sum(attacks_per_second['rupture']) * 10 - self.bota_multiplier *= 2 - - # for a in attacks_per_second: - # if isinstance(attacks_per_second[a], list): - # print a, 1./sum(attacks_per_second[a]) - # else: - # print a, 1./attacks_per_second[a] - # print "--------" + for a in attacks_per_second: + if isinstance(attacks_per_second[a], list): + print a, 1./sum(attacks_per_second[a]) + else: + print a, 1./attacks_per_second[a] + print "--------" return attacks_per_second, crit_rates, additional_info @@ -1658,11 +1681,53 @@ def subtlety_dps_breakdown(self): self.set_constants() - #symbols of death - self.damage_modifier_cache = 1.2 * (1 +(0.005 * self.traits.legionblade)) + #set up damage modifier list and all relevant modifiers, use None for placeholder values + self.damage_modifiers = modifiers.ModifierList(self.subtlety_damage_sources + ['autoattacks']) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', + 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'autoattacks',])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) + + #talent specific modifiers + + if self.traits.nightstalker: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_ssk', None, ['shadowstrike'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_shuriken_storm', None, ['shuriken_storm'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_nightblade', None, ['nightblade_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_evis', None, ['eviscerate'])) + + if self.talents.master_of_subtlety: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_ssk', None, ['shadowstrike'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_shuriken_storm', None, ['shuriken_storm'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate'], blacklist=True)) + + + if self.talents.deeper_strategem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.1, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) + + + #trait specific modifiers + if self.traits.finality: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('finality', None, ['nightblade_ticks', 'eviscerate'])) + + if self.traits.legionblade: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', + 1.05 + (0.005 * self.traits.legionblade - 1), [], blacklist=True)) + + #gear specific modifiers + if self.stats.gear_buffs.the_dreadlords_deceit: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) - damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + + self.damage_modifiers.update_modifier_value('executioner', (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) + self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) + self.damage_modifiers.update_modifier_value('stealth_shuriken_storm', 1 + self.stealth_shuriken_uptime * 3) infallible_trinket_mod = 1.0 if self.settings.is_demon: @@ -1674,54 +1739,33 @@ def subtlety_dps_breakdown(self): #nightstalker if self.talents.nightstalker: ns_full_multiplier = 0.12 - for key in damage_breakdown: - if key == 'shadowstrike': - damage_breakdown[key] *= ns_full_multiplier - elif key == 'shuriken_storm': - damage_breakdown[key] *= 1 + (0.12 * self.stealth_shuriken_uptime) - elif key == 'nightblade_ticks': - damage_breakdown[key] *= 1 + (0.12 * self.dance_nb_uptime) - elif key in ('eviscerate'): - damage_breakdown[key] *= 1 + (0.12 * self.stealth_evis_uptime) + self.damage_modifiers.update_modifier_value('nightstalker_ssk', 1 + ns_full_multiplier) + self.damage_modifiers.update_modifier_value('nightstalker_shuriken_storm', 1 + (0.12 * self.stealth_shuriken_uptime)) + self.damage_modifiers.update_modifier_value('nightstalker_nightblade', 1 + (0.12 * self.dance_nb_uptime)) + self.damage_modifiers.update_modifier_value('nightstalker_evis', 1 + (0.12 * self.stealth_evis_uptime)) #master of subtlety if self.talents.master_of_subtlety: mos_full_multiplier = 1.1 mos_uptime_multipler = 1. + (0.1 * self.mos_time) - - for key in damage_breakdown: - if key == 'shadowstrike': - damage_breakdown[key] *= mos_full_multiplier - elif key == 'shuriken_storm': - damage_breakdown[key] *= 1 + (0.1 * self.stealth_shuriken_uptime) - elif key in ('eviscerate'): - damage_breakdown[key] *= 1 + (0.1 * self.stealth_evis_uptime) - else: - damage_breakdown[key] *= mos_uptime_multipler + self.damage_modifiers.update_modifier_value('mos_ssk', mos_full_multiplier) + self.damage_modifiers.update_modifier_value('mos_shuriken_storm', 1 + (0.1 * self.stealth_shuriken_uptime)) + self.damage_modifiers.update_modifier_value('mos_evis', 1 + (0.1 * self.stealth_evis_uptime)) + self.damage_modifiers.update_modifier_value('mos_other', mos_uptime_multipler) if self.traits.finality: #4% increase per cp applied every to every other finality_damage_boost = 1 + 0.02 * self.settings.finisher_threshold - damage_breakdown['eviscerate'] *= finality_damage_boost - damage_breakdown['nightblade_ticks'] *= finality_damage_boost - - ds_multiplier = 1.0 - if self.talents.deeper_strategem: - ds_multiplier = 1.1 - + self.damage_modifiers.update_modifier_value('finality', finality_damage_boost) if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5/aps['shuriken_storm'] - damage_breakdown['shuriken_storm'] *= (1 + (0.35 * avg_dreadlord_stacks)) + self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) + + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) for key in damage_breakdown: damage_breakdown[key] *= infallible_trinket_mod - if key == 'shuriken_storm': - damage_breakdown[key] *= (1 + self.stealth_shuriken_uptime * 3) - if key in self.finisher_damage_sources: - damage_breakdown[key] *= ds_multiplier - if key == 'backstab': - damage_breakdown[key] *= 1 + (0.3 * self.settings.cycle.positional_uptime) #add AoE damage sources: if self.settings.num_boss_adds: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index c9f356f..0aa5798 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -30,7 +30,7 @@ class RogueDamageCalculator(DamageCalculator): 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', 'nightblade', 'shadowstrike', 'shadow_blades', 'shuriken_storm', 'shuriken_toss', - 'nightblade_ticks', 'soul_rip', 'shadow_nova'] + 'nightblade_ticks', 'soul_rip', 'shadow_nova', 'second_shuriken'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', @@ -196,8 +196,9 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da damage_breakdown = {} crit_damage_modifier = self.crit_damage_modifiers() - base_modifier = self.get_base_modifier(current_stats) - armor_modifier = self.armor_mitigation_multiplier() + + modifier_dict = self.damage_modifiers.compile_modifier_dict() + print modifier_dict # this removes keys with empty values, prevents errors from: # attacks_per_second['sinister_strike'] = None @@ -209,12 +210,12 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da # Assumes mh and oh attacks are both active at the same time. As # they should always be. # Friends don't let friends raid without gear. - mh_base_damage = self.mh_damage(average_ap) * armor_modifier * base_modifier + mh_base_damage = self.mh_damage(average_ap) * modifier_dict['autoattacks'] mh_hit_rate = self.dw_mh_hit_chance - crit_rates['mh_autoattacks'] average_mh_hit = mh_hit_rate * mh_base_damage + crit_rates['mh_autoattacks'] * mh_base_damage * crit_damage_modifier mh_dps_tuple = average_mh_hit * attacks_per_second['mh_autoattacks'] - oh_base_damage = self.oh_damage(average_ap) * armor_modifier * base_modifier + oh_base_damage = self.oh_damage(average_ap) * modifier_dict['autoattacks'] oh_hit_rate = self.dw_oh_hit_chance - crit_rates['oh_autoattacks'] average_oh_hit = oh_hit_rate * oh_base_damage + crit_rates['oh_autoattacks'] * oh_base_damage * crit_damage_modifier oh_dps_tuple = average_oh_hit * attacks_per_second['oh_autoattacks'] @@ -228,7 +229,6 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da #compute damage breakdown for each spec if self.spec == 'assassination': - potent_poisons_mod = (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery'])) * base_modifier for ability in self.assassination_damage_sources: if ability not in attacks_per_second: @@ -236,22 +236,17 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier - modifier = base_modifier + modifier = modifier_dict[ability] both_hands = ability in self.dual_wield_damage_sources cps = max_cps if ability in self.finisher_damage_sources else 0 - if ability in self.physical_damage_sources: - modifier *= armor_modifier - if ability in self.mastery_scaling_damage_sources: - modifier *= potent_poisons_mod - #override for "weird" abilities #death from above strike is actually an envenom with 1.5 #modifier #manually add in base modifier because DfA strike is in #physical sources if ability == 'death_from_above_strike': - modifier = base_modifier * 1.5 * potent_poisons_mod + modifier *= 1.5 ability = 'envenom' damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) @@ -262,13 +257,10 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier - modifier = base_modifier + modifier = modifier_dict[ability] both_hands = ability in self.dual_wield_damage_sources cps = max_cps if ability in self.finisher_damage_sources else 0 - if ability in self.physical_damage_sources: - modifier *= armor_modifier - #override for "weird" abilities #death from above strike is actually an evis with 1.5 modifier if ability == 'death_from_above_strike': @@ -282,8 +274,6 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) if self.spec == 'subtlety': - executioner_mod = executioner_mod = 1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(current_stats['mastery']) - shadow_fangs_mod = (1 + (0.04 * self.traits.shadow_fangs)) for ability in self.subtlety_damage_sources: if ability not in attacks_per_second: @@ -291,28 +281,16 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da aps = attacks_per_second[ability] crits = crit_rates[ability] crit_mod = crit_damage_modifier - modifier = base_modifier + modifier = modifier_dict[ability] both_hands = ability in self.dual_wield_damage_sources cps = max_cps if ability in self.finisher_damage_sources else 0 - - if ability in self.physical_damage_sources: - modifier *= armor_modifier - #assume for now that all non-physical damage sources are shadow - #damage - else: - modifier *= shadow_fangs_mod - if ability in self.mastery_scaling_damage_sources: - modifier *= executioner_mod - #override for "weird" abilities #death from above strike is actually an evis with 1.5 modifier #and dfa pulse needs mastery if ability == 'death_from_above_strike': - modifier *= 1.5 * executioner_mod + modifier *= 1.5 ability = 'eviscerate' - if ability == 'death_from_above_pulse': - modifier *= executioner_mod damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index 7a4515e..b988a43 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -22,6 +22,8 @@ def update_modifier_value(self, modifier_name, value): def compile_modifier_dict(self): lumped_modifier = {s:1 for s in self.sources} for mod in self.modifiers.values(): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) if not mod.blacklist: for ability in mod.ability_list: lumped_modifier[ability] *= mod.value From 775a812e183c47868d86e2a7fada5896a2ea777b Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 16 Jan 2017 11:09:51 -0500 Subject: [PATCH 122/265] Documented 7.1.5 Changes for Assn and Sub --Undocumented Changes like trait scaling not included --Proc changes not included --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 44 +++++++++++--------- shadowcraft/calcs/rogue/__init__.py | 18 ++++---- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2bb25ed..0de60a0 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -543,13 +543,13 @@ def get_average_alacrity(self, attacks_per_second): if finisher in attacks_per_second and finisher != 'death_from_above_pulse': for cp in xrange(7): stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] - stack_time = 20/stacks_per_second + stack_time = 10/stacks_per_second if stack_time > self.settings.duration: max_stacks = self.settings.duration * stacks_per_second return max_stacks/2 else: max_time = self.settings.duration - stack_time - return (max_time/self.settings.duration) * 20 + (stack_time/self.settings.duration) * 10 + return (max_time/self.settings.duration) * 10 + (stack_time/self.settings.duration) * 5 def determine_stats(self, attack_counts_function): current_stats = { @@ -757,6 +757,8 @@ def assassination_dps_breakdown(self): 'fan_of_knives', 'hemorrhage', 'mutilate', 'autoattacks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'envenom', 'poison_bomb',])) + #Generic tuning aura + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.07, [], blacklist=True)) #time averaged vendetta modifier used for most things self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True)) @@ -771,6 +773,8 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks'])) if self.talents.agonizing_poison: self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True)) + if self.talents.deeper_strategem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) #trait specific modifiers if self.traits.blood_of_the_assassinated: @@ -839,7 +843,7 @@ def assassination_dps_breakdown(self): if self.talents.elaborate_planning: ep_uptime = finisher_aps * 5 #attacks/second * seconds/attack - self.damage_modifiers.update_modifier_value('elaborate_planning', 1 + (0.15 * ep_uptime)) + self.damage_modifiers.update_modifier_value('elaborate_planning', 1 + (0.12 * ep_uptime)) if self.talents.agonizing_poison: @@ -978,7 +982,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): net_energy_per_second -= rupture_cost_per_second - garrote_cost_per_second #compute cooldowned talents: - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5. * (1. + self.settings.marked_for_death_resets)) + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) cp_budget = mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: @@ -1049,9 +1053,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget -= total_minicycles * mini_cycle_energy if self.talents.alacrity: - old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.02)) new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) - new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.02)) energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks @@ -1565,13 +1569,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll gcd_budget -= minicycle_count * gcds_per_minicycle #ar doubles the effect of alacrity while up - old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.01)) * (1 + int(ar)) + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.02)) * (1 + int(ar)) new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) - new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.01)) * (1 + int(ar)) + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.02)) * (1 + int(ar)) energy_budget += (new_alacrity_regen - old_alacrity_regen) * duration #compute new CP/MG regen - old_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * alacrity_stacks)) - new_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.01 * new_alacrity_stacks)) + old_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.02 * alacrity_stacks)) + new_cp_mg = self.get_mg_cp_regen_from_haste(attack_speed_multiplier * 1 + (0.02 * new_alacrity_stacks)) energy_budget += new_cp_mg - old_cp_mg alacrity_stacks = new_alacrity_stacks @@ -1691,6 +1695,8 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) + #Generic tuning aura + self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.09, [], blacklist=True)) #talent specific modifiers @@ -1708,7 +1714,7 @@ def subtlety_dps_breakdown(self): if self.talents.deeper_strategem: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.1, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) #trait specific modifiers @@ -1724,7 +1730,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) - + self.damage_modifiers.update_modifier_value('executioner', (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) self.damage_modifiers.update_modifier_value('stealth_shuriken_storm', 1 + self.stealth_shuriken_uptime * 3) @@ -1813,7 +1819,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) #Set up initial combo point budget - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * 5. * (1. + self.settings.marked_for_death_resets)) + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) self.cp_budget = mfd_cps @@ -2011,9 +2017,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget += net_cps - 20 + cps_to_generate #Update energy budget with alacrity and haste procs if self.talents.alacrity: - old_alacrity_regen = self.energy_regen * (1 + (alacrity_stacks *0.01)) + old_alacrity_regen = self.energy_regen * (1 + (alacrity_stacks *0.02)) new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) - new_alacrity_regen = self.energy_regen * (1 + (new_alacrity_stacks *0.01)) + new_alacrity_regen = self.energy_regen * (1 + (new_alacrity_stacks *0.02)) self.energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks @@ -2051,9 +2057,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Full additive assumption for now if self.talents.master_of_subtlety: - stealth_time = 9. * attacks_per_second['shadow_dance'] + 6 * attacks_per_second['vanish'] + stealth_time = 8. * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] if self.talents.subterfuge: - stealth_time = 11. * attacks_per_second['shadow_dance'] + 9 * attacks_per_second['vanish'] + stealth_time = 10. * attacks_per_second['shadow_dance'] + 8 * attacks_per_second['vanish'] self.mos_time = float(stealth_time)/self.settings.duration if self.talents.nightstalker: @@ -2107,11 +2113,11 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): attack_counts = {} if self.talents.master_of_shadows: - net_energy += 30 + net_energy += 25 cost_mod = 1.0 if self.talents.shadow_focus: - cost_mod = 0.7 + cost_mod = 0.8 if use_sod: net_energy -= self.get_spell_cost('symbols_of_death', cost_mod=cost_mod) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 0aa5798..f6e1a6b 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -78,7 +78,7 @@ class RogueDamageCalculator(DamageCalculator): #general 'crimson_vial': (30., 'buff'), 'death_from_above': (25., 'strike'), - 'feint': (20., 'buff'), + 'feint': (35., 'buff'), 'kick': (15., 'strike'), #assassination 'envenom': (35., 'strike'), @@ -289,7 +289,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da #death from above strike is actually an evis with 1.5 modifier #and dfa pulse needs mastery if ability == 'death_from_above_strike': - modifier *= 1.5 + modifier *= 1.5 ability = 'eviscerate' damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) @@ -305,7 +305,7 @@ def oh_damage(self, ap): #general abilities def death_from_above_pulse_damage(self, ap, cp): - return 0.732 * cp * ap + return 0.8784 * cp * ap #20% buff in 7.1.5 #assassination def deadly_poison_tick_damage(self, ap): @@ -351,7 +351,7 @@ def poison_bomb_damage(self, ap): return 6 * 1.2 * ap def rupture_tick_damage(self, ap, cp): - return .3 * cp * ap * (1 + (0.0333 * self.traits.gushing_wounds)) + return 1.5 * ap * (1 + (0.0333 * self.traits.gushing_wounds)) #outlaw def ambush_damage(self, ap): @@ -405,13 +405,13 @@ def eviscerate_damage(self, ap, cp): return 1.472 * cp * ap def gloomblade_damage(self, ap): - return 4.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) + return 5.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) def mh_goremaws_bite_damage(self, ap): - return 5 * self.get_weapon_damage('mh', ap) + return 10 * self.get_weapon_damage('mh', ap) def oh_goremaws_bite_damage(self, ap): - return 5 * self.oh_penalty() * self.get_weapon_damage('oh', ap) + return 10 * self.oh_penalty() * self.get_weapon_damage('oh', ap) #Nightblade doesn't actually scale with cps but passing cps for simplicity def nightblade_tick_damage(self, ap, cp): @@ -499,9 +499,9 @@ def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod if ability == 'shadowstrike': cost -= 0.25 * (5 * self.traits.energetic_stabbing) - #Assume 5 yards away so 10 + 5/2 + #Assume 5 yards away so 3 + 5/3 if self.stats.gear_buffs.shadow_satyrs_walk: - cost -= 12 + cost -= 4.67 return cost def get_spell_cd(self, ability): From 7c827762a69d08cbf726e49016a9c48d5806b2cb Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 16 Jan 2017 13:21:44 -0500 Subject: [PATCH 123/265] Sub has Shuriken Storm not FoK --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b3076a2..6f95b6e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1727,7 +1727,7 @@ def subtlety_dps_breakdown(self): #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['shuriken_storm'])) stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) From 74c5d6f8c7ba949368befd7c9015a0d76345984d Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Mon, 16 Jan 2017 13:42:10 -0500 Subject: [PATCH 124/265] Assorted Sub Fixes --Gloomblade works with dreadlords --Nightstalker is a talent not a trait --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 6f95b6e..b7999f8 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1700,7 +1700,7 @@ def subtlety_dps_breakdown(self): #talent specific modifiers - if self.traits.nightstalker: + if self.talents.nightstalker: self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_shuriken_storm', None, ['shuriken_storm'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_nightblade', None, ['nightblade_ticks'])) @@ -2043,7 +2043,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] #FIXME: Kinda hackish, better approach would be to compute a seperate dance rotation - if self.stats.gear_buffs.the_dreadlords_deceit and self.cp_builder =='backstab': + if self.stats.gear_buffs.the_dreadlords_deceit and (self.cp_builder =='backstab' or self.cp_builder == 'gloomblade'): shuriken_interval = 1./60 attacks_per_second['shadowstrike'] -= shuriken_interval attacks_per_second['shuriken_storm'] = shuriken_interval From 542d8a8757252edf48dd1f1d6194e0cb61a61020 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 17 Jan 2017 09:55:28 -0500 Subject: [PATCH 125/265] BotA isn't a dps loss --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b7999f8..be069e1 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -822,7 +822,7 @@ def assassination_dps_breakdown(self): if self.traits.blood_of_the_assassinated: bota_uptime = 0.35 * sum(aps['rupture']) * 10 # procs/ability * ability/second * seconds/proc gives unit-less uptime - bota_multiplier = 2 * bota_uptime + bota_multiplier = 1 + 2 * bota_uptime self.damage_modifiers.update_modifier_value('blood_of_the_assassinated', bota_multiplier) finisher_aps = 0.0 From f16a0bf545eb7313a2c5f2aad16fd19a63fecc25 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 20 Jan 2017 21:01:04 -0500 Subject: [PATCH 126/265] Assassination and Subtlety Set Bonuses --Assassination 2pc is a bit hackish but it works --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 23 ++++++++++++++++---- shadowcraft/calcs/rogue/__init__.py | 9 ++++++-- shadowcraft/objects/stats.py | 2 ++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index be069e1..fb5d3e6 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -754,7 +754,7 @@ def assassination_dps_breakdown(self): self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'fan_of_knives', 'hemorrhage', 'mutilate', 'autoattacks'])) + 'fan_of_knives', 'hemorrhage', 'mutilate', 'autoattacks', 't19_2pc'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'envenom', 'poison_bomb',])) #Generic tuning aura @@ -770,7 +770,7 @@ def assassination_dps_breakdown(self): if self.talents.elaborate_planning: self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], blacklist=True)) if self.talents.hemorrhage: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks', 't19_2pc'])) if self.talents.agonizing_poison: self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True)) if self.talents.deeper_strategem: @@ -790,6 +790,8 @@ def assassination_dps_breakdown(self): #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) + if self.stats.gear_buffs.rogue_t19_4pc: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) self.set_constants() @@ -854,7 +856,8 @@ def assassination_dps_breakdown(self): agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 - agonizing_poison_mod_per_stack= 0.04 * agonizing_poison_adder + #12% reduction from 4% per stack + agonizing_poison_mod_per_stack= 0.0352 * agonizing_poison_adder if self.talents.master_poisoner: agonizing_poison_mod_per_stack *= 1.2 @@ -868,6 +871,11 @@ def assassination_dps_breakdown(self): avg_dreadlord_stacks = 0.5/aps['fan_of_knives'] self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) + if self.stats.gear_buffs.rogue_t19_4pc: + if aps['mutilate'] < 0.125: + t19_4pc_multiplier = 0.1 * (aps['mutilate'] / 0.125) + self.damage_modifiers.update_modifier_value('t19_4pc', 1.2 + t19_4pc_multiplier) + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) return damage_breakdown @@ -1071,6 +1079,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) + if self.stats.gear_buffs.rogue_t19_2pc: + attacks_per_second['t19_2pc'] = attacks_per_second['mutilate'] for a in attacks_per_second: if isinstance(attacks_per_second[a], list): @@ -1649,7 +1659,6 @@ def get_max_energy(self): #Items: #Class hall set bonus - #Tier bonus #Trinkets #Legendaries @@ -1833,6 +1842,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #setup timelines sod_duration = 35 nightblade_duration = 6 + (2 * self.settings.finisher_threshold) + if self.stats.gear_buffs.rogue_t19_2pc: + nightblade_duration = 6 + (4 * self.settings.finisher_threshold) #Add attacks that could occur during first pass to aps attacks_per_second[self.dance_cp_builder] = 0 @@ -2029,6 +2040,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['nightblade_ticks'] = [0, 0, 0, 0, 0, 0, 0] for cp in xrange(7): attacks_per_second['nightblade_ticks'][cp] = (3 + cp) * attacks_per_second['nightblade'][cp] + if self.stats.gear_buffs.rogue_t19_2pc: + attacks_per_second['nightblade_ticks'][cp] = (3 + (2 * cp)) * attacks_per_second['nightblade'][cp] del attacks_per_second['nightblade'] #convert some white swings into shadowblades @@ -2155,6 +2168,8 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): net_energy -= attack_counts[cp_builder] * cp_builder_cost if cp_builder == 'shadowstrike': net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation) + self.shadow_blades_uptime + if self.stats.gear_buffs.rogue_t19_4pc: + net_cps += attack_counts['shadowstrike'] * 0.3 elif cp_builder == 'shuriken_storm': net_cps += min(1 + self.settings.num_boss_adds, self.max_store_cps) + self.shadow_blades_uptime diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index f6e1a6b..f2429a7 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -21,7 +21,8 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows'] + 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', + 't19_2pc'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -198,7 +199,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da crit_damage_modifier = self.crit_damage_modifiers() modifier_dict = self.damage_modifiers.compile_modifier_dict() - print modifier_dict + #print modifier_dict # this removes keys with empty values, prevents errors from: # attacks_per_second['sinister_strike'] = None @@ -353,6 +354,9 @@ def poison_bomb_damage(self, ap): def rupture_tick_damage(self, ap, cp): return 1.5 * ap * (1 + (0.0333 * self.traits.gushing_wounds)) + def assn_t19_2pc_damage(self, ap): + return 0.3 * (self.mh_mutilate_damage(ap) + self.oh_mutilate_damage(ap)) + #outlaw def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) @@ -463,6 +467,7 @@ def get_formula(self, name): 'poisoned_knife': self.poisoned_knife_damage, 'poison_bomb': self.poison_bomb_damage, 'rupture_ticks': self.rupture_tick_damage, + 't19_2pc': self.assn_t19_2pc_damage, #outlaw 'ambush': self.ambush_damage, 'between_the_eyes': self.between_the_eyes_damage, diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 9043e11..3d0858e 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -154,6 +154,8 @@ class GearBuffs(object): 'rogue_t18_2pc', # Dispatch deals 25% additional damage as Nature damage, SnD internal ticks have 8% change to proc ARfor 4 sec, Vanish awards 5cps and increases all damage done by 30% for 10 sec 'rogue_t18_4pc', # Dispatch generates +2cps, AR increased damage by 15%, Evis and Rupture reduce the CD of vanish by 1 seconds per CP 'rogue_t18_4pc_lfr', # Energy increased by 20, 5% increase in energy regen + 'rogue_t19_2pc', # Mutilate causes 30% bleed over 8 seconds, Nightblades lasts additional 2 seconds per CP + 'rogue_t19_4pc', # 10% envenom damage per bleed, 30% SSk generates additional CP if nightblade up 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM #Legendaries From dd05bd29fdf03996af6776350a07ea55f7608091 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Fri, 20 Jan 2017 21:27:17 -0500 Subject: [PATCH 127/265] Legendary Infrstructure and Zoldyks's Implemented --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++++ shadowcraft/objects/stats.py | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index fb5d3e6..d646549 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -790,6 +790,10 @@ def assassination_dps_breakdown(self): #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) + if self.stats.gear_buffs.zoldyck_family_training_shakles: + #Assume spend 30% of the time sub 30% health, imperfect but good enough + self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shakles', 1.09, ['deadly_poison', + 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'])) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 3d0858e..c877639 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -159,8 +159,16 @@ class GearBuffs(object): 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM #Legendaries - 'shadow_satyrs_walk', - 'the_dreadlords_deceit' + 'the_dreadlords_deceit', #fok/ssk damage increased by 35% per 2 seconds up to 1 minute + 'duskwalker_footpads', #Vendetta CD reduced by 1 second for each 50 energy spent + 'thraxis_tricksy_treads', # + 'shadow_satyrs_walk', #3+1/3yd energy refund on ssk + 'insignia_of_ravenholdt', #15% damage as shadow on cp generators + 'zoldyck_family_training_shakles', #Poisons and Bleeds deal 30% additional damage below 30% health + 'greenskins_waterlogged_wristcuffs', # + 'denial_of_the_half-giants', # Finishers extend ShB by 0.3 seconds per cp spent + 'shivarran_symmetry', # + 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after ] From ca60f6ab906312c4eaeba8ba41db5fe1f6404fa4 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 24 Jan 2017 14:14:54 -0500 Subject: [PATCH 128/265] Fix Missing Parens in Final Trait --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d646549..edb9fcd 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -785,14 +785,14 @@ def assassination_dps_breakdown(self): if self.traits.slayers_precision: self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', - 1.05 + (0.005 * self.traits.slayers_precision - 1), [], blacklist=True)) + 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], blacklist=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) if self.stats.gear_buffs.zoldyck_family_training_shakles: #Assume spend 30% of the time sub 30% health, imperfect but good enough - self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shakles', 1.09, ['deadly_poison', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shakles', 1.09, ['deadly_poison', 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'])) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) @@ -1086,12 +1086,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.stats.gear_buffs.rogue_t19_2pc: attacks_per_second['t19_2pc'] = attacks_per_second['mutilate'] - for a in attacks_per_second: - if isinstance(attacks_per_second[a], list): - print a, 1./sum(attacks_per_second[a]) - else: - print a, 1./attacks_per_second[a] - print "--------" + # for a in attacks_per_second: + # if isinstance(attacks_per_second[a], list): + # print a, 1./sum(attacks_per_second[a]) + # else: + # print a, 1./attacks_per_second[a] + # print "--------" return attacks_per_second, crit_rates, additional_info @@ -1736,7 +1736,7 @@ def subtlety_dps_breakdown(self): if self.traits.legionblade: self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', - 1.05 + (0.005 * self.traits.legionblade - 1), [], blacklist=True)) + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: From 44e1fcb473e37f06d250a6fdbd53eb31ef0a99ba Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 24 Jan 2017 16:24:21 -0500 Subject: [PATCH 129/265] Fix Zoldyk's I think --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 +++-- shadowcraft/calcs/rogue/__init__.py | 3 +-- shadowcraft/objects/modifiers.py | 4 ++++ shadowcraft/objects/stats.py | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index edb9fcd..8cabe96 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -790,9 +790,10 @@ def assassination_dps_breakdown(self): #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) - if self.stats.gear_buffs.zoldyck_family_training_shakles: + + if self.stats.gear_buffs.zoldyck_family_training_shackles: #Assume spend 30% of the time sub 30% health, imperfect but good enough - self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shakles', 1.09, ['deadly_poison', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'])) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index f2429a7..e9df9b5 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -21,7 +21,7 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', + 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', 't19_2pc'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', @@ -199,7 +199,6 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da crit_damage_modifier = self.crit_damage_modifiers() modifier_dict = self.damage_modifiers.compile_modifier_dict() - #print modifier_dict # this removes keys with empty values, prevents errors from: # attacks_per_second['sinister_strike'] = None diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index b988a43..09c8e98 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -22,6 +22,10 @@ def update_modifier_value(self, modifier_name, value): def compile_modifier_dict(self): lumped_modifier = {s:1 for s in self.sources} for mod in self.modifiers.values(): + # for key in lumped_modifier: + # print key, lumped_modifier[key] + # print "-----" + # print mod.name, mod.value, mod.ability_list, mod.blacklist if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) if not mod.blacklist: diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index c877639..5f09ac2 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -164,9 +164,9 @@ class GearBuffs(object): 'thraxis_tricksy_treads', # 'shadow_satyrs_walk', #3+1/3yd energy refund on ssk 'insignia_of_ravenholdt', #15% damage as shadow on cp generators - 'zoldyck_family_training_shakles', #Poisons and Bleeds deal 30% additional damage below 30% health + 'zoldyck_family_training_shackles', #Poisons and Bleeds deal 30% additional damage below 30% health 'greenskins_waterlogged_wristcuffs', # - 'denial_of_the_half-giants', # Finishers extend ShB by 0.3 seconds per cp spent + 'denial_of_the_half_giants', # Finishers extend ShB by 0.3 seconds per cp spent 'shivarran_symmetry', # 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after From 41272d84ba1baaa9e8d15f0ddad933dead713565 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Tue, 24 Jan 2017 19:42:26 -0500 Subject: [PATCH 130/265] Update Assn Script --- scripts/assassination.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 0e50b08..e355dc4 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -43,19 +43,25 @@ test_procs = procs.ProcsList() # Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization', ) +test_gear_buffs = stats.GearBuffs('gear_specialization', +#'the_dreadlords_deceit', +#'rogue_t19_2pc', +#'rogue_t19_4pc', +#'zoldyck_family_training_shackles', +#'cinidaria_the_symbiote', +) # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, agi=25583, stam=28368, - crit=11133, - haste=1666, - mastery=2469, + crit=7804, + haste=1521, + mastery=8230, versatility=3085,) # Initialize talents.. #test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) -test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3110011', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ @@ -76,7 +82,7 @@ 'fade_into_shadows': 0, 'from_the_shadows': 1, 'blood_of_the_assassinated': 1, - 'slayers_precision': 0, + 'slayers_precision': 8, }) # Set up settings. @@ -93,7 +99,7 @@ # Compute EP values. #ep_values = calculator.get_ep(baseline_dps=total_dps) -#tier_ep_values = calculator.get_other_ep(['rogue_t14_4pc', 'rogue_t14_2pc', 'rogue_t15_4pc', 'rogue_t15_2pc', 'rogue_t16_2pc', 'rogue_t16_4pc']) +tier_ep_values = calculator.get_other_ep(['the_dreadlords_deceit', 'zoldyck_family_training_shackles']) #talent_ranks = calculator.get_talents_ranking() @@ -124,7 +130,7 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): dicts_for_pretty_print = [ #ep_values, - #tier_ep_values, + tier_ep_values, #talent_ranks, #trinkets_ep_value, dps_breakdown, From aecce30b95556f091640f377081f9d30e25b50f6 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 25 Jan 2017 12:06:25 -0500 Subject: [PATCH 131/265] Final Trait Can be 2 Digits --- shadowcraft/objects/artifact.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 08984c2..4338891 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -32,11 +32,15 @@ def set_trait(self, trait, value): self.traits[trait] = value def initialize_traits(self, trait_string): - if len(trait_string) != len(self.allowed_traits): - raise InvalidTraitException(_('Trait strings must be {traits} characters long').format(traits=len(self.allowed_traits))) + if len(trait_string) != len(self.allowed_traits) and len(trait_string) != len(self.allowed_traits) + 1: + raise InvalidTraitException(_('Trait strings must be {traits} (or {traits} + 1) characters long').format(traits=len(self.allowed_traits))) self.traits = {} for trait in xrange(len(self.allowed_traits)): - self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) + #grab all charcters for final trait + if trait == len(self.allowed_traits) - 1: + self.set_trait(self.allowed_traits[trait], int(trait_string[trait:])) + else: + self.set_trait(self.allowed_traits[trait], int(trait_string[trait])) def get_trait_list(self): return list(self.allowed_traits) From f8c48c14736e51f15db23965528c9d187f6f6ba2 Mon Sep 17 00:00:00 2001 From: Fierydemise Date: Wed, 25 Jan 2017 12:23:07 -0500 Subject: [PATCH 132/265] Updated Subtlety Test Script --- scripts/subtlety.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index d28b529..5a91386 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -44,7 +44,10 @@ test_procs = procs.ProcsList() # Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization',) #tier buffs located here +test_gear_buffs = stats.GearBuffs('gear_specialization', +'the_dreadlords_deceit', +#'rogue_t19_2pc', +) #tier buffs located here # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, @@ -56,7 +59,7 @@ versatility=4153,) # Initialize talents.. -test_talents = talents.Talents('2210011', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ @@ -97,8 +100,8 @@ # Compute EP values. #ep_values = calculator.get_ep(baseline_dps=total_dps) -ep_values = calculator.get_ep() -#tier_ep_values = calculator.get_other_ep(['rogue_t17_2pc', 'rogue_t17_4pc', 'rogue_t17_4pc_lfr']) +#ep_values = calculator.get_ep() +tier_ep_values = calculator.get_other_ep(['rogue_t19_2pc', 'rogue_t19_4pc', 'the_dreadlords_deceit']) #talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() @@ -126,8 +129,8 @@ def pretty_print(dict_list): print '-' * (max_len + 15) dicts_for_pretty_print = [ - ep_values, - #tier_ep_values, + #ep_values, + tier_ep_values, #trinkets_ep_value, dps_breakdown, #trait_ranks From 508a57c0ee449f158ea7f7fc88fce1c915aee345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 17 Feb 2017 19:15:33 +0100 Subject: [PATCH 133/265] 7.1.5 stat scaling --- shadowcraft/calcs/rogue/__init__.py | 4 ++-- shadowcraft/objects/stats.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index e9df9b5..52eea06 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -517,8 +517,8 @@ def get_spell_cd(self, ability): return cd def crit_rate(self, crit=None): - # all rogues get 10% bonus crit, .05 of base crit for everyone + # all rogues have 10% base crit # should be coded better? - base_crit = .15 + base_crit = .10 base_crit += self.stats.get_crit_from_rating(crit) return base_crit + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 5f09ac2..ee91ae7 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -9,10 +9,10 @@ class Stats(object): # rows 1-9 from my WotLK spreadsheets to see how these are typically # defined, though the numbers will need to updated for level 85. - crit_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:350.0} - haste_rating_conversion_values = {60:9.00, 70:10.0, 80:12.0, 85:14.0, 90:20.0, 100:100.0, 110:325.0} - mastery_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:350.0} - versatility_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:27.0, 100:130.0, 110:400.0} + crit_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:400.0} + haste_rating_conversion_values = {60:9.00, 70:10.0, 80:12.0, 85:14.0, 90:20.0, 100:100.0, 110:375.0} + mastery_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:400.0} + versatility_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:27.0, 100:130.0, 110:475.0} def __init__(self, mh, oh, procs, gear_buffs, str=0, agi=0, int=0, stam=0, ap=0, crit=0, haste=0, mastery=0, versatility=0, level=None): From e8fb8f7ce9cd9883c576f5f7f10c241114758a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 17 Feb 2017 19:16:17 +0100 Subject: [PATCH 134/265] Fortune's Boon and Master Assassin CDR --- shadowcraft/calcs/rogue/__init__.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 52eea06..d094f80 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -134,6 +134,28 @@ class RogueDamageCalculator(DamageCalculator): 'shadow_blades': 180., } + # Vendetta CDR for number of points in trait + master_assassin_cdr = { + 0: 0, + 1: 10, + 2: 20, + 3: 30, + 4: 38, + 5: 44, + 6: 48, + } + + # Vendetta CDR for number of points in trait + fortunes_boon_cdr = { + 0: 0, + 1: 10, + 2: 18, + 3: 25, + 4: 31, + 5: 37, + 6: 42, + } + def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': @@ -511,9 +533,9 @@ def get_spell_cost(self, ability, cost_mod=1.0): def get_spell_cd(self, ability): cd = self.ability_cds[ability] if ability == 'adrenaline_rush': - cd -= 10 * self.traits.fortunes_boon + cd -= self.fortunes_boon_cdr[self.traits.fortunes_boon] elif ability == 'vendetta': - cd -= 10 * self.traits.master_assassin + cd -= self.master_assassin_cdr[self.traits.master_assassin] return cd def crit_rate(self, crit=None): From df69d8e9c8192907c03291c325fa168db34e6488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 17 Feb 2017 22:24:48 +0100 Subject: [PATCH 135/265] Aiming for 7.1.5 by now --- shadowcraft/calcs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 355ea66..d0a8efd 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -27,7 +27,7 @@ class DamageCalculator(object): normalize_ep_stat = None def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '7.0.0' # should reflect the game patch being targetted + self.WOW_BUILD_TARGET = '7.1.5' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = '0.02' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch self.tools = class_data.Util() self.stats = stats From 6696f31ee87833d153063ca244d0ab8122d5d38e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 19 Feb 2017 16:24:48 +0100 Subject: [PATCH 136/265] Insignia of Ravenholdt --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 35 +++++++++++++++++--- shadowcraft/calcs/rogue/__init__.py | 10 +++--- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 8cabe96..fe8275d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -718,6 +718,26 @@ def compute_damage(self, attack_counts_function): #damage_breakdown, additional_info = self.get_damage_breakdown(self.determine_stats(attack_counts_function)) return damage_breakdown, additional_info + def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second, crit_rates): + # Insignia of Ravenholdt, 30% (Assassination) / 15% base generator damage with crit chance + ap = stats['ap'] + stats['agi'] * self.stat_multipliers['ap'] + insignia_base_dmg = 0 + insignia_dmg_factor = 0.3 if self.spec == 'assassination' else 0.15 + for ability in attacks_per_second: + if ability in ['mutilate', 'hemorrhage', + 'ambush', 'blunderbuss', 'pistol_shot', 'saber_slash', + 'backstab', 'gloomblade', 'shadowstrike']: + both_hands = ability in self.dual_wield_damage_sources + insignia_base_dmg += insignia_dmg_factor * self.get_ability_dps(ap, ability, attacks_per_second[ability], 0, 1, 1, both_hands) # base dps wihout modifiers + crit_rate = crit_rates['insignia_of_ravenholdt'] + crit_mod = self.crit_damage_modifiers() + insignia_dmg = insignia_base_dmg * (1 - crit_rate) + insignia_base_dmg * crit_rate * crit_mod + + # Also hits adds within 15yd in front + if self.settings.num_boss_adds: + insignia_dmg *= 1 + self.settings.num_boss_adds + return insignia_dmg + ########################################################################### # Assassination DPS functions ########################################################################### @@ -883,6 +903,9 @@ def assassination_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + if self.stats.gear_buffs.insignia_of_ravenholdt: + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + return damage_breakdown def assassination_attack_counts(self, current_stats, crit_rates=None): @@ -1205,6 +1228,9 @@ def outlaw_dps_breakdown(self): stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + if self.stats.gear_buffs.insignia_of_ravenholdt: + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + bf_mod = .35 if self.settings.cycle.blade_flurry: damage_breakdown['blade_flurry'] = 0 @@ -1784,17 +1810,16 @@ def subtlety_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + if self.stats.gear_buffs.insignia_of_ravenholdt: + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + for key in damage_breakdown: damage_breakdown[key] *= infallible_trinket_mod #add AoE damage sources: if self.settings.num_boss_adds: for key in damage_breakdown: - if key == 'shuriken_toss': - damage_breakdown[key] *= 1 + self.settings.num_boss_adds - elif key == 'second_shuriken': - damage_breakdown[key] *= 1 + self.settings.num_boss_adds - elif key == 'shadow_nova': + if key in ['shuriken_toss', 'second_shuriken', 'shadow_nova']: damage_breakdown[key] *= 1 + self.settings.num_boss_adds return damage_breakdown diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index d094f80..d16fabf 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -22,16 +22,18 @@ class RogueDamageCalculator(DamageCalculator): 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', - 't19_2pc'] + 't19_2pc', 'insignia_of_ravenholdt'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', - 'pistol_shot', 'run_through', 'saber_slash'] + 'pistol_shot', 'run_through', 'saber_slash', + 'insignia_of_ravenholdt'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', 'nightblade', 'shadowstrike', 'shadow_blades', 'shuriken_storm', 'shuriken_toss', - 'nightblade_ticks', 'soul_rip', 'shadow_nova', 'second_shuriken'] + 'nightblade_ticks', 'soul_rip', 'shadow_nova', 'second_shuriken', + 'insignia_of_ravenholdt'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', @@ -145,7 +147,7 @@ class RogueDamageCalculator(DamageCalculator): 6: 48, } - # Vendetta CDR for number of points in trait + # Adrenaline Rush CDR for number of points in trait fortunes_boon_cdr = { 0: 0, 1: 10, From 3e6e3c76f2b280c926cf810f324d935d968bb5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 20 Feb 2017 16:56:39 +0100 Subject: [PATCH 137/265] Cinidaria the Symbiote --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 14 ++++++++++++++ shadowcraft/objects/stats.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index fe8275d..cf2860f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -738,6 +738,11 @@ def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second, crit_ insignia_dmg *= 1 + self.settings.num_boss_adds return insignia_dmg + def compute_symbiote_strike_damage(self, damage_breakdown): + # Cinidaria's Symbiote Strike is plain 30% of all damage we actually do + # Assume it's up for 10% of the fight + return sum(damage_breakdown.values()) * 0.03 + ########################################################################### # Assassination DPS functions ########################################################################### @@ -906,6 +911,9 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.insignia_of_ravenholdt: damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + if self.stats.gear_buffs.cinidaria_the_symbiote: + damage_breakdown['symbiote_strike'] = self.compute_symbiote_strike_damage(damage_breakdown) + return damage_breakdown def assassination_attack_counts(self, current_stats, crit_rates=None): @@ -1248,6 +1256,9 @@ def outlaw_dps_breakdown(self): for ability in damage_breakdown: damage_breakdown[ability] *= infallible_trinket_mod + if self.stats.gear_buffs.cinidaria_the_symbiote: + damage_breakdown['symbiote_strike'] = self.compute_symbiote_strike_damage(damage_breakdown) + return damage_breakdown def outlaw_attack_counts(self, current_stats, crit_rates=None): @@ -1822,6 +1833,9 @@ def subtlety_dps_breakdown(self): if key in ['shuriken_toss', 'second_shuriken', 'shadow_nova']: damage_breakdown[key] *= 1 + self.settings.num_boss_adds + if self.stats.gear_buffs.cinidaria_the_symbiote: + damage_breakdown['symbiote_strike'] = self.compute_symbiote_strike_damage(damage_breakdown) + return damage_breakdown def subtlety_attack_counts(self, current_stats, crit_rates=None): diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index ee91ae7..da7d660 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -169,7 +169,7 @@ class GearBuffs(object): 'denial_of_the_half_giants', # Finishers extend ShB by 0.3 seconds per cp spent 'shivarran_symmetry', # 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after - + 'cinidaria_the_symbiote', #30% additional damage to enemies above 90% health ] allowed_buffs = frozenset(other_gear_buffs) From f051071618cab1e2675145e0fb4ed04e27d608ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 20 Feb 2017 21:01:26 +0100 Subject: [PATCH 138/265] Add a few basic damage modifiers for Outlaw Includes Assassination Aura update --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 35 ++++++++++++++++---- shadowcraft/calcs/rogue/__init__.py | 2 +- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index cf2860f..4ce7906 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -779,11 +779,11 @@ def assassination_dps_breakdown(self): self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'fan_of_knives', 'hemorrhage', 'mutilate', 'autoattacks', 't19_2pc'])) + 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'envenom', 'poison_bomb',])) #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.07, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, [], blacklist=True)) #time averaged vendetta modifier used for most things self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True)) @@ -1158,8 +1158,6 @@ def outlaw_dps_breakdown(self): #outlaw specific constants self.outlaw_cd_delay = 0 #this is for DFA convergence, mostly - self.damage_modifier_cache = 1 + (0.005 * self.traits.cursed_steel) - self.ar_duration = 15 self.ar_cd = self.get_spell_cd('adrenaline_rush') self.cotd_cd = self.get_spell_cd('curse_of_the_dreadblades') @@ -1233,7 +1231,29 @@ def outlaw_dps_breakdown(self): (6, True, False, False, False, False) : (2.9230175, 1.0230561, [0, 0, 0, 0, 0, 0, 1.0]) , } + self.damage_modifiers = modifiers.ModifierList(self.outlaw_damage_sources + ['autoattacks']) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', + 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', + 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', + 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'])) + + # Generic tuning aura + self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.16, [], blacklist=True)) + + # Talent specific modifiers + if self.talents.deeper_strategem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['between_the_eyes', 'run_through', 'death_from_above_pulse', 'death_from_above_strike'])) + + # Trait specific modifiers + if self.traits.cursed_steel: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('cursed_steel', + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True)) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) + + self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: @@ -1740,17 +1760,15 @@ def subtlety_dps_breakdown(self): self.damage_modifiers = modifiers.ModifierList(self.subtlety_damage_sources + ['autoattacks']) self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'autoattacks',])) + 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], blacklist=True)) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.09, [], blacklist=True)) #talent specific modifiers - if self.talents.nightstalker: self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_shuriken_storm', None, ['shuriken_storm'])) @@ -1769,6 +1787,9 @@ def subtlety_dps_breakdown(self): #trait specific modifiers + if self.traits.shadow_fangs: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True)) + if self.traits.finality: self.damage_modifiers.register_modifier(modifiers.DamageModifier('finality', None, ['nightblade_ticks', 'eviscerate'])) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index d16fabf..88d4932 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -37,7 +37,7 @@ class RogueDamageCalculator(DamageCalculator): #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', - 'ambush, between_the_eyes', 'blunderbuss', 'cannonball_barrage', + 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash', 'backstab', 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] From 848f9fcc894fe750f500209597b5d72f2014425b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 22 Feb 2017 17:43:07 +0100 Subject: [PATCH 139/265] Fix Shadow Nova APS and Anticipation is 10 CP now --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4ce7906..d0c4559 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1605,9 +1605,9 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll gcd_budget -= curse_gcds energy_budget -= (ss_count * self.saber_slash_energy_cost) + (rt_count * self.run_through_energy_cost) - #Curse gives 8 cps with anticipation so 3 left over + #Curse gives 10 cps with anticipation so 5 left over if self.talents.anticipation: - bonus_cps += 3 * curse_cd_multiplier + bonus_cps += 5 * curse_cd_multiplier #spend bonus cps for max cp RTs @@ -1752,7 +1752,7 @@ def subtlety_dps_breakdown(self): self.max_spend_cps += 1 self.max_store_cps = self.max_spend_cps if self.talents.anticipation: - self.max_store_cps += 3 + self.max_store_cps += 5 self.set_constants() @@ -2118,7 +2118,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.akarris_soul: attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] if self.traits.shadow_nova: - attacks_per_second['shadow_nova'] = attacks_per_second['symbols_of_death'] + attacks_per_second['vanish'] + attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], 1./5.) #FIXME: Kinda hackish, better approach would be to compute a seperate dance rotation if self.stats.gear_buffs.the_dreadlords_deceit and (self.cp_builder =='backstab' or self.cp_builder == 'gloomblade'): From 05fad8d4cddaa5a7fb2f1baa76b4ae83f7afc6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Feb 2017 08:56:00 +0100 Subject: [PATCH 140/265] Some updated values from changes since Legion release Symbols of Death costs 35 Energy Master Poisoner's damage increase is now 30% Garrote generates a combo point Fan of Knives can proc Seal Fate Kingsbane damage nerfed but it generates a combo point Fix Hemo CP generation --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 15 +++++++++++---- shadowcraft/calcs/rogue/__init__.py | 13 +++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d0c4559..84cfcd5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1004,7 +1004,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #set up garrote: base_garrote_duration = 18. - garrote_cooldown = 15. + garrote_cooldown = self.get_spell_cd('garrote') if self.talents.exsanguinate: exsang_garrote_duration = base_garrote_duration / 2 exsang_downtime = garrote_cooldown - exsang_garrote_duration @@ -1016,6 +1016,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['garrote'] = 1. / base_garrote_duration attacks_per_second['garrote_ticks'] = 1. / 3 + cp_budget = attacks_per_second['garrote'] * self.settings.duration garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] #Now that ticks are done, we can compute VW regen @@ -1027,23 +1028,29 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #compute cooldowned talents: mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) - cp_budget = mfd_cps + cp_budget += mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: fok_interval = 1./60 attacks_per_second['fan_of_knives'] = fok_interval - cp_budget += self.settings.duration * fok_interval + cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) net_energy_per_second -= fok_interval * 35 if self.traits.kingsbane: attacks_per_second['kingsbane'] = 1./self.kingsbane_cd attacks_per_second['kingsbane_ticks'] = 7. / self.kingsbane_cd + kb_crit = crit_rates['kingsbane'] + cpg_cps = {1: (1 - kb_crit) ** 2, + 2: 2 * (1 - kb_crit) * kb_crit, + 3: kb_crit ** 2} + avg_cp_per_kb = sum([cp * cpg_cps[cp] for cp in cpg_cps]) + cp_budget += avg_cp_per_kb * attacks_per_second['kingsbane'] * self.settings.duration net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] if self.talents.hemorrhage: hemos_per_second = 1./20 attacks_per_second['hemorrhage'] = hemos_per_second - hemo_cps = (1 + crit_rates['hemorrhage']) * (20. / self.settings.duration) + hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) cp_budget += hemo_cps net_energy_per_second -= self.get_spell_cost('hemorrhage') * hemos_per_second diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 88d4932..d35a3b7 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -110,7 +110,7 @@ class RogueDamageCalculator(DamageCalculator): 'shadowstrike': (40., 'strike'), 'shuriken_storm': (35., 'strike'), 'shuriken_toss': (40., 'strike'), - 'symbols_of_death': (20., 'buff'), + 'symbols_of_death': (35., 'buff'), } ability_cds = { #general @@ -123,6 +123,7 @@ class RogueDamageCalculator(DamageCalculator): 'vanish': 120., #assassination 'exsanguinate': 45., + 'garrote': 15., 'kingsbane': 45., 'vendetta': 120., #outlaw @@ -333,10 +334,10 @@ def death_from_above_pulse_damage(self, ap, cp): #assassination def deadly_poison_tick_damage(self, ap): - return 0.3575 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) + return 0.3575 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.3 * self.talents.master_poisoner)) def deadly_instant_poison_damage(self, ap): - return 0.221 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.4 * self.talents.master_poisoner)) + return 0.221 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.3 * self.talents.master_poisoner)) #Maybe add better handling for 'rule of three' for artifact traits def envenom_damage(self, ap, cp): @@ -356,11 +357,11 @@ def hemorrhage_damage(self, ap): return 1 * self.get_weapon_damage('mh', ap) def mh_kingsbane_damage(self, ap): - return 3 * self.get_weapon_damage('mh', ap) * (1 + (0.4 * self.talents.master_poisoner)) + return 2.4 * self.get_weapon_damage('mh', ap) * (1 + (0.3 * self.talents.master_poisoner)) def oh_kingsbane_damage(self, ap): - return 3 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.4 * self.talents.master_poisoner)) + return 2.4 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.3 * self.talents.master_poisoner)) def kingsbane_tick_damage(self, ap): - return 0.45 * ap * (1 + (0.4 * self.talents.master_poisoner)) + return 0.36 * ap * (1 + (0.3 * self.talents.master_poisoner)) def mh_mutilate_damage(self, ap): return 3.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.assassins_blades)) From a3a57b5367cb74f2a1a0017d1a724f091c780c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Feb 2017 19:24:14 +0100 Subject: [PATCH 141/265] Modifier changes Introduce all_damage and dmg_school modifiers for procs Introduce ap_coefficient for procs Remove Insignia from spec sources and use crit from stat Update Legion neck enchants --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 75 ++++++++++---------- shadowcraft/calcs/rogue/__init__.py | 13 ++-- shadowcraft/objects/modifiers.py | 24 ++++++- shadowcraft/objects/proc_data.py | 20 +++--- shadowcraft/objects/procs.py | 8 ++- 5 files changed, 84 insertions(+), 56 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 84cfcd5..4a30f43 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -261,22 +261,16 @@ def load_from_advanced_parameters(self): self.settings.is_day = self.get_adv_param('is_day', self.settings.is_day, ignore_bounds=True) self.get_version_number = self.get_adv_param('print_version', False, ignore_bounds=True) - def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ap, damage_breakdown): + def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ap, modifier_dict): crit_multiplier = self.crit_damage_modifiers() crit_rate = self.crit_rate(crit=current_stats['crit']) - #TODO Re-add multipliers here - multiplier = 1 - '''if proc.stat == 'spell_damage': - multiplier = self.get_modifiers(current_stats, damage_type='spell') - elif proc.stat == 'physical_damage': - multiplier = self.get_modifiers(current_stats, damage_type='physical') - elif proc.stat == 'physical_dot': - multiplier = self.get_modifiers(current_stats, damage_type='bleed') - elif proc.stat == 'bleed_damage': - multiplier = self.get_modifiers(current_stats, damage_type='bleed') + if proc.proc_name in modifier_dict: + multiplier = modifier_dict[proc.proc_name] + elif proc.dmg_school is not None: + multiplier = self.damage_modifiers.get_damage_school_modifier(proc.dmg_school) else: - return 0''' + multiplier = self.damage_modifiers.get_all_damage_modifier() if proc.can_crit == False: crit_rate = 0 @@ -305,8 +299,8 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc is getattr(self.stats.procs, 'felmouth_frenzy'): proc_value = average_ap * 0.424 * 5 - average_hit = proc_value * multiplier - average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count + average_hit = proc_value + proc.ap_coefficient * average_ap + average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count * multiplier if proc.stat == 'physical_dot': average_damage *= proc.uptime / proc_count @@ -718,7 +712,7 @@ def compute_damage(self, attack_counts_function): #damage_breakdown, additional_info = self.get_damage_breakdown(self.determine_stats(attack_counts_function)) return damage_breakdown, additional_info - def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second, crit_rates): + def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second): # Insignia of Ravenholdt, 30% (Assassination) / 15% base generator damage with crit chance ap = stats['ap'] + stats['agi'] * self.stat_multipliers['ap'] insignia_base_dmg = 0 @@ -729,7 +723,7 @@ def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second, crit_ 'backstab', 'gloomblade', 'shadowstrike']: both_hands = ability in self.dual_wield_damage_sources insignia_base_dmg += insignia_dmg_factor * self.get_ability_dps(ap, ability, attacks_per_second[ability], 0, 1, 1, both_hands) # base dps wihout modifiers - crit_rate = crit_rates['insignia_of_ravenholdt'] + crit_rate = self.crit_rate(crit=stats['crit']) crit_mod = self.crit_damage_modifiers() insignia_dmg = insignia_base_dmg * (1 - crit_rate) + insignia_base_dmg * crit_rate * crit_mod @@ -777,27 +771,30 @@ def assassination_dps_breakdown(self): #assassination specific constants #set up damage modifier list and all relevant modifiers, use None for placeholder values self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', - 'deadly_instant_poison', 'envenom', 'poison_bomb',])) + 'deadly_instant_poison', 'envenom', 'poison_bomb'])) + #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, ['death_from_above_pulse', 'death_from_above_strike', + 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', + 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'])) #time averaged vendetta modifier used for most things - self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) #talent specific modifiers if self.talents.elaborate_planning: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], blacklist=True, all_damage=True)) if self.talents.hemorrhage: self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks', 't19_2pc'])) if self.talents.agonizing_poison: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True, all_damage=True)) if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) @@ -806,11 +803,11 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('blood_of_the_assassinated', None, ['rupture_ticks'])) if self.traits.surge_of_toxins: self.damage_modifiers.register_modifier(modifiers.DamageModifier('surge_of_toxins', None, ['deadly_poison', - 'deadly_instant_poison', 'envenom', 'poison_bomb',])) + 'deadly_instant_poison', 'envenom', 'poison_bomb'], dmg_schools=['poison'])) if self.traits.slayers_precision: self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', - 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], blacklist=True)) + 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], blacklist=True, all_damage=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: @@ -819,7 +816,7 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.zoldyck_family_training_shackles: #Assume spend 30% of the time sub 30% health, imperfect but good enough self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', - 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'])) + 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'], dmg_schools=['poison', 'bleed'])) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) @@ -909,7 +906,7 @@ def assassination_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) if self.stats.gear_buffs.cinidaria_the_symbiote: damage_breakdown['symbiote_strike'] = self.compute_symbiote_strike_damage(damage_breakdown) @@ -1239,14 +1236,16 @@ def outlaw_dps_breakdown(self): } self.damage_modifiers = modifiers.ModifierList(self.outlaw_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'])) # Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.16, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.16, ['death_from_above_pulse', 'death_from_above_strike', + 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'killing_spree', + 'pistol_shot', 'run_through', 'saber_slash'])) # Talent specific modifiers if self.talents.deeper_strategem: @@ -1255,7 +1254,7 @@ def outlaw_dps_breakdown(self): # Trait specific modifiers if self.traits.cursed_steel: self.damage_modifiers.register_modifier(modifiers.DamageModifier('cursed_steel', - 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True)) + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True, all_damage=True)) stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) @@ -1264,7 +1263,7 @@ def outlaw_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) bf_mod = .35 if self.settings.cycle.blade_flurry: @@ -1765,15 +1764,17 @@ def subtlety_dps_breakdown(self): #set up damage modifier list and all relevant modifiers, use None for placeholder values self.damage_modifiers = modifiers.ModifierList(self.subtlety_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) + #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.09, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.09, ['death_from_above_pulse', 'death_from_above_strike', + 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks'])) #talent specific modifiers if self.talents.nightstalker: @@ -1786,7 +1787,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_shuriken_storm', None, ['shuriken_storm'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate'], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate'], blacklist=True, all_damage=True)) if self.talents.deeper_strategem: @@ -1795,14 +1796,14 @@ def subtlety_dps_breakdown(self): #trait specific modifiers if self.traits.shadow_fangs: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True, dmg_schools=['physical', 'shadow'])) if self.traits.finality: self.damage_modifiers.register_modifier(modifiers.DamageModifier('finality', None, ['nightblade_ticks', 'eviscerate'])) if self.traits.legionblade: self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', - 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True)) + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True, all_damage=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: @@ -1850,7 +1851,7 @@ def subtlety_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps, crits) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) for key in damage_breakdown: damage_breakdown[key] *= infallible_trinket_mod diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index d35a3b7..1483266 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -22,18 +22,16 @@ class RogueDamageCalculator(DamageCalculator): 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', - 't19_2pc', 'insignia_of_ravenholdt'] + 't19_2pc'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', - 'pistol_shot', 'run_through', 'saber_slash', - 'insignia_of_ravenholdt'] + 'pistol_shot', 'run_through', 'saber_slash'] subtlety_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'goremaws_bite', 'nightblade', 'shadowstrike', 'shadow_blades', 'shuriken_storm', 'shuriken_toss', - 'nightblade_ticks', 'soul_rip', 'shadow_nova', 'second_shuriken', - 'insignia_of_ravenholdt'] + 'nightblade_ticks', 'soul_rip', 'shadow_nova', 'second_shuriken'] #All damage sources mitigated by armor physical_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', @@ -41,9 +39,6 @@ class RogueDamageCalculator(DamageCalculator): 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', 'pistol_shot', 'run_through', 'saber_slash', 'backstab', 'eviscerate', 'shadowstrike', 'shuriken_storm', 'shuriken_toss'] - #All damage sources the scale with mastery (assn or sub) - mastery_scaling_damage_sources = ['deadly_poison', 'deadly_instant_poison', 'evenom', - 'eviscerate', 'nightblade_ticks', 'poison_bomb'] #All damage sources that deal damage with both hands dual_wield_damage_sources = ['kingsbane', 'mutilate', 'greed', 'killing_spree', 'goremaws_bite', 'shadow_blades'] @@ -250,7 +245,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if proc.proc_name not in damage_breakdown: # Toss multiple damage procs with the same name (Avalanche): # attacks_per_second is already being updated with that key. - damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, damage_breakdown) + damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, modifier_dict) #compute damage breakdown for each spec if self.spec == 'assassination': diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index 09c8e98..21e2993 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -37,14 +37,36 @@ def compile_modifier_dict(self): lumped_modifier[ability] *= mod.value return lumped_modifier + # modifiers marked as all_damage count for EVERYTHING including stuff not in the ability_list + def get_all_damage_modifier(self): + all_damage_mod = 1 + for mod in self.modifiers.values(): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) + if mod.all_damage: + all_damage_mod *= mod.value + return all_damage_mod + + # returns the total modifier for the specified damage school + def get_damage_school_modifier(self, school): + school_mod = 1 + for mod in self.modifiers.values(): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) + if mod.all_damage or (mod.dmg_schools is not None and school in mod.dmg_schools): + school_mod *= mod.value + return school_mod + #DamageModifier specifies any type of modifier applied to ability damage. #Each modifier is specified as a value applied to either a whitelist or blacklist of abilities. #Whitelist is default since it is more compact for most modifiers #but all damage modifiers can be represented either way class DamageModifier(object): - def __init__(self, name, value, ability_list, blacklist=False): + def __init__(self, name, value, ability_list, blacklist=False, all_damage=False, dmg_schools=None): self.name = name self.value = value self.ability_list = ability_list self.blacklist = blacklist + self.all_damage = all_damage + self.dmg_schools = dmg_schools diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 6a94f13..3e09310 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -144,10 +144,13 @@ 'proc_rate': 0.92, 'trigger': 'all_attacks' }, + #7.0 neck enchants - 'mark_of_the_hidden_satyr': { #191259 Deals 41626 to 48375 damage. + 'mark_of_the_hidden_satyr': { 'stat':'spell_damage', - 'value': 45000, #average 41626 to 48375 + 'value': 0, # AP based + 'ap_coefficient': 2.5, # server-side, not in dbc + 'dmg_school': 'fire', 'duration': 0, 'proc_name': 'Mark of the Hidden Satyr', 'type': 'rppm', @@ -158,10 +161,11 @@ 'trigger': 'all_attacks' }, - #aoe proc? ranged only? - 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 41628 to 48375 damage over 1.5 sec. + #TODO: aoe proc + 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 3 ticks of damage over 1.5 sec. 'stat':'physical_damage', - 'value': 45000, #average 41626 to 48375 + 'value': 0, # AP based + 'ap_coefficient': 2.5, # server-side, not in dbc, per tick is 2.5 / 3 'duration': 0, 'proc_name': 'Mark of the Distant Army', 'type': 'rppm', @@ -172,14 +176,14 @@ 'trigger': 'all_attacks' }, - 'mark_of_the_claw': { #Permanently enchants a necklace to sometimes increase critical strike and haste by 550 for 6 sec. + 'mark_of_the_claw': { #Permanently enchants a necklace to sometimes increase critical strike and haste by 1000 for 6 sec. 'stat':'stats', - 'value': {'haste': 550, 'crit': 550}, + 'value': {'haste': 1000, 'crit': 1000}, 'duration': 6, 'proc_name': 'Mark of the Claw', 'source': 'neck', 'type': 'rppm', - 'proc_rate': 2.5, + 'proc_rate': 3, 'trigger': 'all_attacks', }, #7.0 trinket procs diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 122f662..9b6799c 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -11,7 +11,8 @@ class InvalidProcException(exceptions.InvalidInputException): class Proc(object): def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True, stats=None, upgradable=False, scaling=None, buffs=None, base_value=0, type='rppm', icd=0, proc_rate=1.0, trigger='all_attacks', haste_scales=False, item_level=1, - on_crit=False, on_procced_strikes=True, proc_rate_modifier=1., source='generic', att_spd_scales=False,): + on_crit=False, on_procced_strikes=True, proc_rate_modifier=1., source='generic', att_spd_scales=False, + ap_coefficient=0., dmg_school=None): self.stat = stat if stats is not None: self.stats = set(stats) @@ -36,6 +37,11 @@ def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True self.on_crit = on_crit self.on_procced_strikes = on_procced_strikes self.proc_rate_modifier = proc_rate_modifier + self.ap_coefficient = ap_coefficient + self.dmg_school = dmg_school + + if self.dmg_school is None and stat in ['physical_damage', 'physical_dot']: + self.dmg_school = 'physical' #separate method just to keep the constructor clean self.update_proc_value() From 4446e7193828285085d3564aef90bcb2106e8de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Feb 2017 20:10:46 +0100 Subject: [PATCH 142/265] Update random prop points --- shadowcraft/objects/class_data.py | 2006 ++++++++++++++--------------- 1 file changed, 1003 insertions(+), 1003 deletions(-) diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index 7602b07..e942d93 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -71,1010 +71,1010 @@ class Util(object): 113: 8164., } - #SimC keeps their enchant scale data here: https://code.google.com/p/simulationcraft/source/browse/engine/dbc/sc_scale_data.inc#342 - #these, however, are the trinket scale data + #SimC keeps random prop points in engine/dbc/generated/sc_item_data2.inc + #Trinket random property points for item levels 1-1000, wow build 23360, updated 2017-02-24 RANDOM_PROP_POINTS = [ - [0,0], - [1,0], - [2,0], - [3,0], - [4,0], - [5,0], - [6,0], - [7,0], - [8,0], - [9,0], - [10,9], - [11,10], - [12,10], - [13,11], - [14,11], - [15,12], - [16,12], - [17,13], - [18,13], - [19,14], - [20,14], - [21,15], - [22,15], - [23,16], - [24,16], - [25,17], - [26,17], - [27,18], - [28,18], - [29,19], - [30,19], - [31,20], - [32,20], - [33,21], - [34,21], - [35,22], - [36,22], - [37,23], - [38,23], - [39,24], - [40,24], - [41,25], - [42,25], - [43,26], - [44,26], - [45,27], - [46,27], - [47,28], - [48,28], - [49,29], - [50,29], - [51,30], - [52,30], - [53,31], - [54,31], - [55,32], - [56,32], - [57,33], - [58,33], - [59,34], - [60,34], - [61,35], - [62,35], - [63,36], - [64,36], - [65,37], - [66,37], - [67,37], - [68,37], - [69,37], - [70,37], - [71,37], - [72,37], - [73,37], - [74,37], - [75,37], - [76,37], - [77,37], - [78,37], - [79,37], - [80,37], - [81,37], - [82,37], - [83,37], - [84,37], - [85,37], - [86,37], - [87,37], - [88,37], - [89,37], - [90,37], - [91,37], - [92,37], - [93,37], - [94,38], - [95,38], - [96,39], - [97,39], - [98,40], - [99,40], - [100,40], - [101,41], - [102,41], - [103,42], - [104,42], - [105,43], - [106,43], - [107,44], - [108,44], - [109,44], - [110,45], - [111,45], - [112,46], - [113,46], - [114,47], - [115,47], - [116,47], - [117,47], - [118,47], - [119,47], - [120,47], - [121,47], - [122,47], - [123,47], - [124,47], - [125,47], - [126,47], - [127,47], - [128,47], - [129,47], - [130,47], - [131,47], - [132,47], - [133,47], - [134,47], - [135,47], - [136,47], - [137,47], - [138,47], - [139,47], - [140,47], - [141,47], - [142,47], - [143,47], - [144,47], - [145,47], - [146,47], - [147,47], - [148,47], - [149,47], - [150,47], - [151,47], - [152,47], - [153,47], - [154,47], - [155,47], - [156,47], - [157,47], - [158,47], - [159,47], - [160,47], - [161,47], - [162,47], - [163,47], - [164,47], - [165,47], - [166,48], - [167,48], - [168,48], - [169,48], - [170,48], - [171,48], - [172,48], - [173,49], - [174,49], - [175,49], - [176,49], - [177,49], - [178,49], - [179,49], - [180,50], - [181,50], - [182,50], - [183,50], - [184,50], - [185,50], - [186,50], - [187,51], - [188,51], - [189,51], - [190,51], - [191,51], - [192,51], - [193,51], - [194,52], - [195,52], - [196,52], - [197,52], - [198,52], - [199,52], - [200,52], - [201,53], - [202,53], - [203,53], - [204,53], - [205,53], - [206,53], - [207,54], - [208,54], - [209,54], - [210,54], - [211,54], - [212,54], - [213,55], - [214,55], - [215,55], - [216,55], - [217,55], - [218,55], - [219,55], - [220,55], - [221,55], - [222,55], - [223,55], - [224,55], - [225,55], - [226,55], - [227,55], - [228,55], - [229,55], - [230,55], - [231,55], - [232,55], - [233,55], - [234,55], - [235,55], - [236,55], - [237,55], - [238,55], - [239,55], - [240,55], - [241,55], - [242,55], - [243,55], - [244,55], - [245,55], - [246,55], - [247,55], - [248,55], - [249,55], - [250,55], - [251,55], - [252,55], - [253,55], - [254,55], - [255,55], - [256,55], - [257,55], - [258,55], - [259,55], - [260,55], - [261,55], - [262,55], - [263,55], - [264,55], - [265,55], - [266,55], - [267,55], - [268,55], - [269,55], - [270,55], - [271,55], - [272,55], - [273,55], - [274,55], - [275,55], - [276,55], - [277,55], - [278,55], - [279,55], - [280,55], - [281,55], - [282,55], - [283,55], - [284,55], - [285,55], - [286,55], - [287,55], - [288,55], - [289,55], - [290,55], - [291,56], - [292,56], - [293,56], - [294,56], - [295,56], - [296,56], - [297,56], - [298,56], - [299,56], - [300,56], - [301,56], - [302,56], - [303,56], - [304,56], - [305,57], - [306,57], - [307,57], - [308,57], - [309,57], - [310,57], - [311,57], - [312,57], - [313,57], - [314,57], - [315,57], - [316,57], - [317,57], - [318,57], - [319,58], - [320,58], - [321,58], - [322,58], - [323,58], - [324,58], - [325,58], - [326,58], - [327,58], - [328,58], - [329,58], - [330,58], - [331,58], - [332,59], - [333,59], - [334,59], - [335,59], - [336,59], - [337,59], - [338,59], - [339,59], - [340,59], - [341,59], - [342,59], - [343,59], - [344,59], - [345,59], - [346,60], - [347,60], - [348,60], - [349,60], - [350,60], - [351,60], - [352,60], - [353,60], - [354,60], - [355,60], - [356,60], - [357,60], - [358,60], - [359,61], - [360,61], - [361,61], - [362,61], - [363,61], - [364,61], - [365,61], - [366,61], - [367,61], - [368,61], - [369,61], - [370,61], - [371,61], - [372,61], - [373,61], - [374,61], - [375,61], - [376,61], - [377,61], - [378,61], - [379,61], - [380,61], - [381,61], - [382,61], - [383,61], - [384,61], - [385,61], - [386,61], - [387,61], - [388,61], - [389,61], - [390,61], - [391,61], - [392,61], - [393,61], - [394,61], - [395,61], - [396,61], - [397,61], - [398,61], - [399,61], - [400,61], - [401,61], - [402,61], - [403,61], - [404,61], - [405,61], - [406,61], - [407,61], - [408,61], - [409,61], - [410,61], - [411,61], - [412,61], - [413,61], - [414,61], - [415,61], - [416,61], - [417,61], - [418,62], - [419,62], - [420,62], - [421,62], - [422,62], - [423,62], - [424,62], - [425,62], - [426,62], - [427,62], - [428,62], - [429,62], - [430,62], - [431,62], - [432,62], - [433,62], - [434,62], - [435,63], - [436,63], - [437,63], - [438,63], - [439,63], - [440,63], - [441,63], - [442,63], - [443,63], - [444,64], - [445,64], - [446,64], - [447,64], - [448,64], - [449,64], - [450,65], - [451,65], - [452,65], - [453,65], - [454,65], - [455,65], - [456,65], - [457,66], - [458,66], - [459,66], - [460,66], - [461,66], - [462,66], - [463,67], - [464,68], - [465,68], - [466,69], - [467,70], - [468,70], - [469,71], - [470,72], - [471,72], - [472,73], - [473,74], - [474,74], - [475,75], - [476,76], - [477,76], - [478,77], - [479,78], - [480,78], - [481,79], - [482,80], - [483,81], - [484,81], - [485,82], - [486,83], - [487,84], - [488,85], - [489,85], - [490,86], - [491,87], - [492,88], - [493,89], - [494,89], - [495,90], - [496,91], - [497,92], - [498,93], - [499,94], - [500,95], - [501,95], - [502,96], - [503,97], - [504,98], - [505,99], - [506,100], - [507,101], - [508,102], - [509,103], - [510,104], - [511,105], - [512,106], - [513,107], - [514,108], - [515,109], - [516,110], - [517,111], - [518,112], - [519,113], - [520,114], - [521,115], - [522,116], - [523,117], - [524,118], - [525,119], - [526,121], - [527,122], - [528,123], - [529,124], - [530,125], - [531,126], - [532,127], - [533,129], - [534,130], - [535,131], - [536,132], - [537,134], - [538,135], - [539,136], - [540,137], - [541,139], - [542,140], - [543,141], - [544,143], - [545,144], - [546,145], - [547,147], - [548,148], - [549,149], - [550,151], - [551,152], - [552,154], - [553,155], - [554,156], - [555,158], - [556,159], - [557,161], - [558,162], - [559,164], - [560,165], - [561,167], - [562,169], - [563,170], - [564,172], - [565,173], - [566,175], - [567,177], - [568,178], - [569,180], - [570,182], - [571,183], - [572,185], - [573,187], - [574,188], - [575,190], - [576,192], - [577,194], - [578,196], - [579,197], - [580,199], - [581,201], - [582,203], - [583,205], - [584,207], - [585,209], - [586,211], - [587,213], - [588,215], - [589,217], - [590,219], - [591,221], - [592,223], - [593,225], - [594,227], - [595,229], - [596,231], - [597,234], - [598,236], - [599,238], - [600,240], - [601,242], - [602,245], - [603,247], - [604,249], - [605,252], - [606,254], - [607,256], - [608,259], - [609,261], - [610,264], - [611,266], - [612,269], - [613,271], - [614,274], - [615,276], - [616,279], - [617,281], - [618,284], - [619,287], - [620,289], - [621,292], - [622,295], - [623,298], - [624,300], - [625,303], - [626,306], - [627,309], - [628,312], - [629,315], - [630,318], - [631,321], - [632,324], - [633,327], - [634,330], - [635,333], - [636,336], - [637,339], - [638,342], - [639,345], - [640,349], - [641,352], - [642,355], - [643,358], - [644,362], - [645,365], - [646,369], - [647,372], - [648,376], - [649,379], - [650,383], - [651,386], - [652,390], - [653,393], - [654,397], - [655,401], - [656,405], - [657,408], - [658,412], - [659,416], - [660,420], - [661,424], - [662,428], - [663,432], - [664,436], - [665,440], - [666,444], - [667,448], - [668,452], - [669,457], - [670,461], - [671,465], - [672,470], - [673,474], - [674,479], - [675,483], - [676,488], - [677,492], - [678,497], - [679,501], - [680,506], - [681,511], - [682,516], - [683,520], - [684,525], - [685,530], - [686,535], - [687,540], - [688,545], - [689,550], - [690,555], - [691,561], - [692,566], - [693,571], - [694,577], - [695,582], - [696,587], - [697,593], - [698,598], - [699,604], - [700,610], - [701,615], - [702,621], - [703,627], - [704,633], - [705,639], - [706,645], - [707,651], - [708,657], - [709,663], - [710,669], - [711,675], - [712,682], - [713,688], - [714,695], - [715,701], - [716,708], - [717,714], - [718,721], - [719,728], - [720,735], - [721,741], - [722,748], - [723,755], - [724,762], - [725,770], - [726,777], - [727,784], - [728,791], - [729,799], - [730,806], - [731,814], - [732,821], - [733,829], - [734,837], - [735,845], - [736,853], - [737,861], - [738,869], - [739,877], - [740,885], - [741,893], - [742,902], - [743,910], - [744,919], - [745,927], - [746,936], - [747,945], - [748,954], - [749,962], - [750,971], - [751,981], - [752,990], - [753,999], - [754,1008], - [755,1018], - [756,1027], - [757,1037], - [758,1047], - [759,1056], - [760,1066], - [761,1076], - [762,1086], - [763,1097], - [764,1107], - [765,1117], - [766,1128], - [767,1138], - [768,1149], - [769,1160], - [770,1170], - [771,1181], - [772,1192], - [773,1204], - [774,1215], - [775,1226], - [776,1238], - [777,1249], - [778,1261], - [779,1273], - [780,1285], - [781,1297], - [782,1309], - [783,1321], - [784,1334], - [785,1346], - [786,1359], - [787,1371], - [788,1384], - [789,1397], - [790,1410], - [791,1423], - [792,1437], - [793,1450], - [794,1464], - [795,1477], - [796,1491], - [797,1505], - [798,1519], - [799,1534], - [800,1548], - [801,1562], - [802,1577], - [803,1592], - [804,1607], - [805,1622], - [806,1637], - [807,1652], - [808,1668], - [809,1683], - [810,1699], - [811,1715], - [812,1731], - [813,1747], - [814,1764], - [815,1780], - [816,1797], - [817,1814], - [818,1831], - [819,1848], - [820,1865], - [821,1882], - [822,1900], - [823,1918], - [824,1936], - [825,1954], - [826,1972], - [827,1991], - [828,2009], - [829,2028], - [830,2047], - [831,2066], - [832,2086], - [833,2105], - [834,2125], - [835,2145], - [836,2165], - [837,2185], - [838,2206], - [839,2226], - [840,2247], - [841,2268], - [842,2289], - [843,2311], - [844,2332], - [845,2354], - [846,2376], - [847,2398], - [848,2421], - [849,2444], - [850,2466], - [851,2490], - [852,2513], - [853,2536], - [854,2560], - [855,2584], - [856,2608], - [857,2633], - [858,2657], - [859,2682], - [860,2707], - [861,2733], - [862,2758], - [863,2784], - [864,2810], - [865,2836], - [866,2863], - [867,2890], - [868,2917], - [869,2944], - [870,2972], - [871,3000], - [872,3028], - [873,3056], - [874,3085], - [875,3113], - [876,3143], - [877,3172], - [878,3202], - [879,3232], - [880,3262], - [881,3292], - [882,3323], - [883,3354], - [884,3386], - [885,3417], - [886,3449], - [887,3482], - [888,3514], - [889,3547], - [890,3580], - [891,3614], - [892,3648], - [893,3682], - [894,3716], - [895,3751], - [896,3786], - [897,3822], - [898,3858], - [899,3894], - [900,3930], - [901,3967], - [902,4004], - [903,4042], - [904,4079], - [905,4118], - [906,4156], - [907,4195], - [908,4234], - [909,4274], - [910,4314], - [911,4354], - [912,4395], - [913,4436], - [914,4478], - [915,4520], - [916,4562], - [917,4605], - [918,4648], - [919,4691], - [920,4735], - [921,4779], - [922,4824], - [923,4869], - [924,4915], - [925,4961], - [926,5007], - [927,5054], - [928,5102], - [929,5149], - [930,5198], - [931,5246], - [932,5295], - [933,5345], - [934,5395], - [935,5445], - [936,5496], - [937,5548], - [938,5600], - [939,5652], - [940,5705], - [941,5759], - [942,5812], - [943,5867], - [944,5922], - [945,5977], - [946,6033], - [947,6090], - [948,6147], - [949,6204], - [950,6262], - [951,6321], - [952,6380], - [953,6440], - [954,6500], - [955,6561], - [956,6622], - [957,6684], - [958,6747], - [959,6810], - [960,6874], - [961,6938], - [962,7003], - [963,7069], - [964,7135], - [965,7202], - [966,7269], - [967,7337], - [968,7406], - [969,7475], - [970,7545], - [971,7616], - [972,7687], - [973,7759], - [974,7832], - [975,7905], - [976,7979], - [977,8054], - [978,8129], - [979,8205], - [980,8282], - [981,8359], - [982,8438], - [983,8517], - [984,8596], - [985,8677], - [986,8758], - [987,8840], - [988,8923], - [989,9006], - [990,9091], - [991,9176], - [992,9262], - [993,9348], - [994,9436], - [995,9524], - [996,9613], - [997,9703], - [998,9794], - [999,9886], - [1000,9978], + [0,0], + [1,0], + [2,0], + [3,0], + [4,0], + [5,0], + [6,0], + [7,0], + [8,0], + [9,0], + [10,9], + [11,10], + [12,10], + [13,11], + [14,11], + [15,12], + [16,12], + [17,13], + [18,13], + [19,14], + [20,14], + [21,15], + [22,15], + [23,16], + [24,16], + [25,17], + [26,17], + [27,18], + [28,18], + [29,19], + [30,19], + [31,20], + [32,20], + [33,21], + [34,21], + [35,22], + [36,22], + [37,23], + [38,23], + [39,24], + [40,24], + [41,25], + [42,25], + [43,26], + [44,26], + [45,27], + [46,27], + [47,28], + [48,28], + [49,29], + [50,29], + [51,30], + [52,30], + [53,31], + [54,31], + [55,32], + [56,32], + [57,33], + [58,33], + [59,34], + [60,34], + [61,35], + [62,35], + [63,36], + [64,36], + [65,37], + [66,37], + [67,37], + [68,37], + [69,37], + [70,37], + [71,37], + [72,37], + [73,37], + [74,37], + [75,37], + [76,37], + [77,37], + [78,37], + [79,37], + [80,37], + [81,37], + [82,37], + [83,37], + [84,37], + [85,37], + [86,37], + [87,37], + [88,37], + [89,37], + [90,37], + [91,37], + [92,37], + [93,37], + [94,38], + [95,38], + [96,39], + [97,39], + [98,40], + [99,40], + [100,40], + [101,41], + [102,41], + [103,42], + [104,42], + [105,43], + [106,43], + [107,44], + [108,44], + [109,44], + [110,45], + [111,45], + [112,46], + [113,46], + [114,47], + [115,47], + [116,47], + [117,47], + [118,47], + [119,47], + [120,47], + [121,47], + [122,47], + [123,47], + [124,47], + [125,47], + [126,47], + [127,47], + [128,47], + [129,47], + [130,47], + [131,47], + [132,47], + [133,47], + [134,47], + [135,47], + [136,47], + [137,47], + [138,47], + [139,47], + [140,47], + [141,47], + [142,47], + [143,47], + [144,47], + [145,47], + [146,47], + [147,47], + [148,47], + [149,47], + [150,47], + [151,47], + [152,47], + [153,47], + [154,47], + [155,47], + [156,47], + [157,47], + [158,47], + [159,47], + [160,47], + [161,47], + [162,47], + [163,47], + [164,47], + [165,47], + [166,48], + [167,48], + [168,48], + [169,48], + [170,48], + [171,48], + [172,48], + [173,49], + [174,49], + [175,49], + [176,49], + [177,49], + [178,49], + [179,49], + [180,50], + [181,50], + [182,50], + [183,50], + [184,50], + [185,50], + [186,50], + [187,51], + [188,51], + [189,51], + [190,51], + [191,51], + [192,51], + [193,51], + [194,52], + [195,52], + [196,52], + [197,52], + [198,52], + [199,52], + [200,52], + [201,53], + [202,53], + [203,53], + [204,53], + [205,53], + [206,53], + [207,54], + [208,54], + [209,54], + [210,54], + [211,54], + [212,54], + [213,55], + [214,55], + [215,55], + [216,55], + [217,55], + [218,55], + [219,55], + [220,55], + [221,55], + [222,55], + [223,55], + [224,55], + [225,55], + [226,55], + [227,55], + [228,55], + [229,55], + [230,55], + [231,55], + [232,55], + [233,55], + [234,55], + [235,55], + [236,55], + [237,55], + [238,55], + [239,55], + [240,55], + [241,55], + [242,55], + [243,55], + [244,55], + [245,55], + [246,55], + [247,55], + [248,55], + [249,55], + [250,55], + [251,55], + [252,55], + [253,55], + [254,55], + [255,55], + [256,55], + [257,55], + [258,55], + [259,55], + [260,55], + [261,55], + [262,55], + [263,55], + [264,55], + [265,55], + [266,55], + [267,55], + [268,55], + [269,55], + [270,55], + [271,55], + [272,55], + [273,55], + [274,55], + [275,55], + [276,55], + [277,55], + [278,55], + [279,55], + [280,55], + [281,55], + [282,55], + [283,55], + [284,55], + [285,55], + [286,55], + [287,55], + [288,55], + [289,55], + [290,55], + [291,56], + [292,56], + [293,56], + [294,56], + [295,56], + [296,56], + [297,56], + [298,56], + [299,56], + [300,56], + [301,56], + [302,56], + [303,56], + [304,56], + [305,57], + [306,57], + [307,57], + [308,57], + [309,57], + [310,57], + [311,57], + [312,57], + [313,57], + [314,57], + [315,57], + [316,57], + [317,57], + [318,57], + [319,58], + [320,58], + [321,58], + [322,58], + [323,58], + [324,58], + [325,58], + [326,58], + [327,58], + [328,58], + [329,58], + [330,58], + [331,58], + [332,59], + [333,59], + [334,59], + [335,59], + [336,59], + [337,59], + [338,59], + [339,59], + [340,59], + [341,59], + [342,59], + [343,59], + [344,59], + [345,59], + [346,60], + [347,60], + [348,60], + [349,60], + [350,60], + [351,60], + [352,60], + [353,60], + [354,60], + [355,60], + [356,60], + [357,60], + [358,60], + [359,61], + [360,61], + [361,61], + [362,61], + [363,61], + [364,61], + [365,61], + [366,61], + [367,61], + [368,61], + [369,61], + [370,61], + [371,61], + [372,61], + [373,61], + [374,61], + [375,61], + [376,61], + [377,61], + [378,61], + [379,61], + [380,61], + [381,61], + [382,61], + [383,61], + [384,61], + [385,61], + [386,61], + [387,61], + [388,61], + [389,61], + [390,61], + [391,61], + [392,61], + [393,61], + [394,61], + [395,61], + [396,61], + [397,61], + [398,61], + [399,61], + [400,61], + [401,61], + [402,61], + [403,61], + [404,61], + [405,61], + [406,61], + [407,61], + [408,61], + [409,61], + [410,61], + [411,61], + [412,61], + [413,61], + [414,61], + [415,61], + [416,61], + [417,61], + [418,62], + [419,62], + [420,62], + [421,62], + [422,62], + [423,62], + [424,62], + [425,62], + [426,62], + [427,62], + [428,62], + [429,62], + [430,62], + [431,62], + [432,62], + [433,62], + [434,62], + [435,63], + [436,63], + [437,63], + [438,63], + [439,63], + [440,63], + [441,63], + [442,63], + [443,63], + [444,64], + [445,64], + [446,64], + [447,64], + [448,64], + [449,64], + [450,65], + [451,65], + [452,65], + [453,65], + [454,65], + [455,65], + [456,65], + [457,66], + [458,66], + [459,66], + [460,66], + [461,66], + [462,66], + [463,67], + [464,68], + [465,68], + [466,69], + [467,70], + [468,70], + [469,71], + [470,72], + [471,72], + [472,73], + [473,74], + [474,74], + [475,75], + [476,76], + [477,76], + [478,77], + [479,78], + [480,78], + [481,79], + [482,80], + [483,81], + [484,81], + [485,82], + [486,83], + [487,84], + [488,85], + [489,85], + [490,86], + [491,87], + [492,88], + [493,89], + [494,89], + [495,90], + [496,91], + [497,92], + [498,93], + [499,94], + [500,95], + [501,95], + [502,96], + [503,97], + [504,98], + [505,99], + [506,100], + [507,101], + [508,102], + [509,103], + [510,104], + [511,105], + [512,106], + [513,107], + [514,108], + [515,109], + [516,110], + [517,111], + [518,112], + [519,113], + [520,114], + [521,115], + [522,116], + [523,117], + [524,118], + [525,119], + [526,121], + [527,122], + [528,123], + [529,124], + [530,125], + [531,126], + [532,127], + [533,129], + [534,130], + [535,131], + [536,132], + [537,134], + [538,135], + [539,136], + [540,137], + [541,139], + [542,140], + [543,141], + [544,143], + [545,144], + [546,145], + [547,147], + [548,148], + [549,149], + [550,151], + [551,152], + [552,154], + [553,155], + [554,156], + [555,158], + [556,159], + [557,161], + [558,162], + [559,164], + [560,165], + [561,167], + [562,169], + [563,170], + [564,172], + [565,173], + [566,175], + [567,177], + [568,178], + [569,180], + [570,182], + [571,183], + [572,185], + [573,187], + [574,188], + [575,190], + [576,192], + [577,194], + [578,196], + [579,197], + [580,199], + [581,201], + [582,203], + [583,205], + [584,207], + [585,209], + [586,211], + [587,213], + [588,215], + [589,217], + [590,219], + [591,221], + [592,223], + [593,225], + [594,227], + [595,229], + [596,231], + [597,234], + [598,236], + [599,238], + [600,240], + [601,242], + [602,245], + [603,247], + [604,249], + [605,252], + [606,254], + [607,256], + [608,259], + [609,261], + [610,264], + [611,266], + [612,269], + [613,271], + [614,274], + [615,276], + [616,279], + [617,281], + [618,284], + [619,287], + [620,289], + [621,292], + [622,295], + [623,298], + [624,300], + [625,303], + [626,306], + [627,309], + [628,312], + [629,315], + [630,318], + [631,321], + [632,324], + [633,327], + [634,330], + [635,333], + [636,336], + [637,339], + [638,342], + [639,345], + [640,349], + [641,352], + [642,355], + [643,358], + [644,362], + [645,365], + [646,369], + [647,372], + [648,376], + [649,379], + [650,383], + [651,386], + [652,390], + [653,393], + [654,397], + [655,401], + [656,405], + [657,408], + [658,412], + [659,416], + [660,420], + [661,424], + [662,428], + [663,432], + [664,436], + [665,440], + [666,444], + [667,448], + [668,452], + [669,457], + [670,461], + [671,465], + [672,470], + [673,474], + [674,479], + [675,483], + [676,488], + [677,492], + [678,497], + [679,501], + [680,506], + [681,511], + [682,516], + [683,520], + [684,525], + [685,530], + [686,535], + [687,540], + [688,545], + [689,550], + [690,555], + [691,561], + [692,566], + [693,571], + [694,577], + [695,582], + [696,587], + [697,593], + [698,598], + [699,604], + [700,610], + [701,615], + [702,621], + [703,627], + [704,633], + [705,639], + [706,645], + [707,651], + [708,657], + [709,663], + [710,669], + [711,675], + [712,682], + [713,688], + [714,695], + [715,701], + [716,708], + [717,714], + [718,721], + [719,728], + [720,735], + [721,741], + [722,748], + [723,755], + [724,762], + [725,770], + [726,777], + [727,784], + [728,791], + [729,799], + [730,806], + [731,814], + [732,821], + [733,829], + [734,837], + [735,845], + [736,853], + [737,861], + [738,869], + [739,877], + [740,885], + [741,893], + [742,902], + [743,910], + [744,919], + [745,927], + [746,936], + [747,945], + [748,954], + [749,962], + [750,971], + [751,981], + [752,990], + [753,999], + [754,1008], + [755,1018], + [756,1027], + [757,1037], + [758,1047], + [759,1056], + [760,1066], + [761,1076], + [762,1086], + [763,1097], + [764,1107], + [765,1117], + [766,1128], + [767,1138], + [768,1149], + [769,1160], + [770,1170], + [771,1181], + [772,1192], + [773,1204], + [774,1215], + [775,1226], + [776,1238], + [777,1249], + [778,1261], + [779,1273], + [780,1285], + [781,1297], + [782,1309], + [783,1321], + [784,1334], + [785,1346], + [786,1359], + [787,1371], + [788,1384], + [789,1397], + [790,1410], + [791,1423], + [792,1437], + [793,1450], + [794,1464], + [795,1477], + [796,1491], + [797,1505], + [798,1519], + [799,1534], + [800,1551], + [801,1568], + [802,1586], + [803,1604], + [804,1623], + [805,1641], + [806,1659], + [807,1678], + [808,1698], + [809,1716], + [810,1736], + [811,1756], + [812,1776], + [813,1795], + [814,1816], + [815,1836], + [816,1858], + [817,1879], + [818,1900], + [819,1921], + [820,1943], + [821,1964], + [822,1987], + [823,2010], + [824,2032], + [825,2051], + [826,2070], + [827,2090], + [828,2109], + [829,2129], + [830,2149], + [831,2169], + [832,2190], + [833,2210], + [834,2231], + [835,2252], + [836,2273], + [837,2294], + [838,2316], + [839,2337], + [840,2359], + [841,2381], + [842,2403], + [843,2426], + [844,2448], + [845,2471], + [846,2494], + [847,2517], + [848,2542], + [849,2566], + [850,2589], + [851,2614], + [852,2638], + [853,2662], + [854,2688], + [855,2713], + [856,2738], + [857,2764], + [858,2789], + [859,2816], + [860,2842], + [861,2869], + [862,2895], + [863,2923], + [864,2950], + [865,2977], + [866,3006], + [867,3034], + [868,3062], + [869,3091], + [870,3120], + [871,3150], + [872,3179], + [873,3208], + [874,3239], + [875,3268], + [876,3300], + [877,3330], + [878,3362], + [879,3393], + [880,3425], + [881,3456], + [882,3489], + [883,3521], + [884,3555], + [885,3587], + [886,3621], + [887,3656], + [888,3689], + [889,3724], + [890,3759], + [891,3794], + [892,3830], + [893,3866], + [894,3901], + [895,3938], + [896,3975], + [897,4013], + [898,4050], + [899,4088], + [900,4126], + [901,4165], + [902,4204], + [903,4244], + [904,4282], + [905,4323], + [906,4363], + [907,4404], + [908,4445], + [909,4487], + [910,4529], + [911,4571], + [912,4614], + [913,4657], + [914,4701], + [915,4746], + [916,4790], + [917,4835], + [918,4880], + [919,4925], + [920,4971], + [921,5017], + [922,5065], + [923,5112], + [924,5160], + [925,5209], + [926,5257], + [927,5306], + [928,5357], + [929,5406], + [930,5457], + [931,5508], + [932,5559], + [933,5612], + [934,5664], + [935,5717], + [936,5770], + [937,5825], + [938,5880], + [939,5934], + [940,5990], + [941,6046], + [942,6102], + [943,6160], + [944,6218], + [945,6275], + [946,6334], + [947,6394], + [948,6454], + [949,6514], + [950,6575], + [951,6637], + [952,6699], + [953,6762], + [954,6825], + [955,6889], + [956,6953], + [957,7018], + [958,7084], + [959,7150], + [960,7217], + [961,7284], + [962,7353], + [963,7422], + [964,7491], + [965,7562], + [966,7632], + [967,7703], + [968,7776], + [969,7848], + [970,7922], + [971,7996], + [972,8071], + [973,8146], + [974,8223], + [975,8300], + [976,8377], + [977,8456], + [978,8535], + [979,8615], + [980,8696], + [981,8776], + [982,8859], + [983,8942], + [984,9025], + [985,9110], + [986,9195], + [987,9282], + [988,9369], + [989,9456], + [990,9545], + [991,9634], + [992,9725], + [993,9815], + [994,9907], + [995,10000], + [996,10093], + [997,10188], + [998,10283], + [999,10380], + [1000,10476], ] def get_class_number(self, game_class): From 2cb5f8ac099c3deb50a954d4affb94c6e719e562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 25 Feb 2017 12:14:08 +0100 Subject: [PATCH 143/265] Compile school and all damage modifiers into dict --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 6 +-- shadowcraft/objects/modifiers.py | 51 +++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4a30f43..c5a7b94 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -267,10 +267,10 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc.proc_name in modifier_dict: multiplier = modifier_dict[proc.proc_name] - elif proc.dmg_school is not None: - multiplier = self.damage_modifiers.get_damage_school_modifier(proc.dmg_school) + elif proc.dmg_school is not None and 'school_' + proc.dmg_school in modifier_dict: + multiplier = modifier_dict['school_' + proc.dmg_school] else: - multiplier = self.damage_modifiers.get_all_damage_modifier() + multiplier = modifier_dict['all_damage'] if proc.can_crit == False: crit_rate = 0 diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index 21e2993..0f0ed79 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -21,41 +21,41 @@ def update_modifier_value(self, modifier_name, value): def compile_modifier_dict(self): lumped_modifier = {s:1 for s in self.sources} + + # mods for all damage + lumped_modifier['all_damage'] = 1 for mod in self.modifiers.values(): - # for key in lumped_modifier: - # print key, lumped_modifier[key] - # print "-----" - # print mod.name, mod.value, mod.ability_list, mod.blacklist if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - if not mod.blacklist: - for ability in mod.ability_list: - lumped_modifier[ability] *= mod.value - else: - for ability in self.sources: - if not ability in mod.ability_list: - lumped_modifier[ability] *= mod.value - return lumped_modifier + if mod.all_damage: + lumped_modifier['all_damage'] *= mod.value - # modifiers marked as all_damage count for EVERYTHING including stuff not in the ability_list - def get_all_damage_modifier(self): - all_damage_mod = 1 + # mods for damage schools for mod in self.modifiers.values(): if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - if mod.all_damage: - all_damage_mod *= mod.value - return all_damage_mod + if mod.dmg_schools: + for school in mod.dmg_schools: + modname = 'school_' + school + if modname in lumped_modifier: + lumped_modifier[modname] *= mod.value + else: + lumped_modifier[modname] = lumped_modifier['all_damage'] * mod.value - # returns the total modifier for the specified damage school - def get_damage_school_modifier(self, school): - school_mod = 1 + # mods for source abilities for mod in self.modifiers.values(): if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - if mod.all_damage or (mod.dmg_schools is not None and school in mod.dmg_schools): - school_mod *= mod.value - return school_mod + for ability in self.sources: + if mod.blacklist: + if ability in mod.ability_list: + continue + else: + lumped_modifier[ability] *= mod.value + elif mod.all_damage or ability in mod.ability_list: + lumped_modifier[ability] *= mod.value + + return lumped_modifier #DamageModifier specifies any type of modifier applied to ability damage. #Each modifier is specified as a value applied to either a whitelist or blacklist of abilities. @@ -70,3 +70,6 @@ def __init__(self, name, value, ability_list, blacklist=False, all_damage=False, self.all_damage = all_damage self.dmg_schools = dmg_schools + if self.all_damage and self.dmg_schools is not None: + raise exceptions.InvalidInputException(_('Modifier {mod} should only specify either all_damage or dmg_schools').format(mod=mod.name)) + From 2e7983ee6378eaf1d3f93836218dd736ba18114b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 25 Feb 2017 13:29:14 +0100 Subject: [PATCH 144/265] Allow per spec proc_rates --- shadowcraft/calcs/__init__.py | 6 ++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 ++++---- shadowcraft/objects/procs.py | 27 +++++++++++++++----- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index d0a8efd..c37ee4e 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -116,14 +116,14 @@ def set_rppm_uptime(self, proc): #The 1.1307 is a value that increases the proc rate due to bad luck prevention. It /should/ be constant among all rppm proc styles if not proc.icd: if proc.max_stacks <= 1: - proc.uptime = 1.1307 * (1 - math.e ** (-1 * haste * proc.get_rppm_proc_rate() * proc.duration / 60)) + proc.uptime = 1.1307 * (1 - math.e ** (-1 * haste * proc.get_rppm_proc_rate(spec=self.spec) * proc.duration / 60)) else: - lambd = haste * proc.get_rppm_proc_rate() * proc.duration / 60 + lambd = haste * proc.get_rppm_proc_rate(spec=self.spec) * proc.duration / 60 e_lambda = math.e ** lambd e_minus_lambda = math.e ** (-1 * lambd) proc.uptime = 1.1307 * (e_lambda - 1) * (1 - ((1 - e_minus_lambda) ** proc.max_stacks)) else: - mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate()) + proc.icd - min(proc.icd, 10) + mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) proc.uptime = 1.1307 * proc.duration / mean_proc_time def set_uptime(self, proc, attacks_per_second, crit_rates): diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c5a7b94..b4a57f9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -369,7 +369,7 @@ def get_mh_procs_per_second(self, proc, attacks_per_second, crit_rates): triggers_per_second += attacks_per_second['garrote'] if 'hemorrhage_ticks' in attacks_per_second: triggers_per_second += attacks_per_second['hemorrhage'] - return triggers_per_second * proc.get_proc_rate(self.stats.mh.speed) + return triggers_per_second * proc.get_proc_rate(self.stats.mh.speed, spec=self.spec) def get_oh_procs_per_second(self, proc, attacks_per_second, crit_rates): triggers_per_second = 0 @@ -387,7 +387,7 @@ def get_oh_procs_per_second(self, proc, attacks_per_second, crit_rates): triggers_per_second += attacks_per_second[ability] * crit_rates[ability] else: triggers_per_second += attacks_per_second[ability] - return triggers_per_second * proc.get_proc_rate(self.stats.oh.speed) + return triggers_per_second * proc.get_proc_rate(self.stats.oh.speed, spec=self.spec) def get_other_procs_per_second(self, proc, attacks_per_second, crit_rates): triggers_per_second = 0 @@ -423,7 +423,7 @@ def get_other_procs_per_second(self, proc, attacks_per_second, crit_rates): else: raise InputNotModeledException(_('PPMs that also proc off spells are not yet modeled.')) else: - return triggers_per_second * proc.get_proc_rate() + return triggers_per_second * proc.get_proc_rate(spec=self.spec) def get_procs_per_second(self, proc, attacks_per_second, crit_rates): # TODO: Include damaging proc hits in figuring out how often everything else procs. @@ -474,9 +474,9 @@ def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): haste *= 1.4 #The 1.1307 is a value that increases the proc rate due to bad luck prevention. It /should/ be constant among all rppm proc styles if not proc.icd: - frequency = haste * 1.1307 * proc.get_rppm_proc_rate() / 60 + frequency = haste * 1.1307 * proc.get_rppm_proc_rate(spec=self.spec) / 60 else: - mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate()) + proc.icd - min(proc.icd, 10) + mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) if proc.max_stacks > 1: # just correct if you only do damage on max_stacks, e.g. legendary_capacitive_meta mean_proc_time *= proc.max_stacks frequency = 1.1307 / mean_proc_time diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 9b6799c..94d6f5e 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -121,21 +121,36 @@ def procs_off_procced_strikes(self): else: return False - def get_rppm_proc_rate(self, haste=1.): + def get_base_proc_rate_for_spec(self, spec): + proc_rate = self.proc_rate + if hasattr(self.proc_rate,'__iter__'): # list of proc rates by spec + if not spec: + raise InvalidProcException(_('Spec expected for the proc rate of {proc}').format(proc=self.proc_name)) + if not spec in self.proc_rate: + if 'other' in self.proc_rate: + spec = 'other' + else: + raise InvalidProcException(_('Proc rate of {proc} not found for current spec').format(proc=self.proc_name)) + proc_rate = self.proc_rate[spec] + return proc_rate + + def get_rppm_proc_rate(self, haste=1., spec=None): if self.is_real_ppm(): - return haste * self.proc_rate * self.proc_rate_modifier + proc_rate = self.get_base_proc_rate_for_spec(spec) + return haste * proc_rate * self.proc_rate_modifier raise InvalidProcException(_('Invalid proc handling for proc {proc}').format(proc=self.proc_name)) - def get_proc_rate(self, speed=None, haste=1.0): + def get_proc_rate(self, speed=None, haste=1.0, spec=None): + proc_rate = self.get_base_proc_rate_for_spec(spec) if self.is_ppm(): if speed is None: raise InvalidProcException(_('Weapon speed needed to calculate the proc rate of {proc}').format(proc=self.proc_name)) else: - return self.proc_rate * speed / 60. + return proc_rate * speed / 60. elif self.is_real_ppm(): - return haste * self.proc_rate / 60. + return haste * proc_rate / 60. else: - return self.proc_rate + return proc_rate def is_ppm(self): if self.type == 'ppm': From 413690eda3128c71f19c2f00a288c0aa30ad633b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 25 Feb 2017 22:37:46 +0100 Subject: [PATCH 145/265] Add combat rating multipliers that some trinkets scale with --- shadowcraft/objects/class_data.py | 270 ++++++++++++++++++++++++++++++ shadowcraft/objects/procs.py | 18 +- 2 files changed, 280 insertions(+), 8 deletions(-) diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index e942d93..0830c5e 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -1077,6 +1077,271 @@ class Util(object): [1000,10476], ] + #SimC keeps combat rating multipliers in engine/dbc/generated/sc_scale_data.inc + #Trinket combat rating multipliers for item level 1 - 1000, wow build 23420, updated 2017-02-25 + COMBAT_RATING_MULTIPLIERS = [ + 1, 1, 1, 1, 1, # 5 + 1, 1, 1, 1, 1, # 10 + 1, 1, 1, 1, 1, # 15 + 1, 1, 1, 1, 1, # 20 + 1, 1, 1, 1, 1, # 25 + 1, 1, 1, 1, 1, # 30 + 1, 1, 1, 1, 1, # 35 + 1, 1, 1, 1, 1, # 40 + 1, 1, 1, 1, 1, # 45 + 1, 1, 1, 1, 1, # 50 + 1, 1, 1, 1, 1, # 55 + 1, 1, 1, 1, 1, # 60 + 1, 1, 1, 1, 1, # 65 + 1, 1, 1, 1, 1, # 70 + 1, 1, 1, 1, 1, # 75 + 1, 1, 1, 1, 1, # 80 + 1, 1, 1, 1, 1, # 85 + 1, 1, 1, 1, 1, # 90 + 1, 1, 1, 1, 1, # 95 + 1, 1, 1, 1, 1, # 100 + 1, 1, 1, 1, 1, # 105 + 1, 1, 1, 1, 1, # 110 + 1, 1, 1, 1, 1, # 115 + 1, 1, 1, 1, 1, # 120 + 1, 1, 1, 1, 1, # 125 + 1, 1, 1, 1, 1, # 130 + 1, 1, 1, 1, 1, # 135 + 1, 1, 1, 1, 1, # 140 + 1, 1, 1, 1, 1, # 145 + 1, 1, 1, 1, 1, # 150 + 1, 1, 1, 1, 1, # 155 + 1, 1, 1, 1, 1, # 160 + 1, 1, 1, 1, 1, # 165 + 1, 1, 1, 1, 1, # 170 + 1, 1, 1, 1, 1, # 175 + 1, 1, 1, 1, 1, # 180 + 1, 1, 1, 1, 1, # 185 + 1, 1, 1, 1, 1, # 190 + 1, 1, 1, 1, 1, # 195 + 1, 1, 1, 1, 1, # 200 + 1, 1, 1, 1, 1, # 205 + 1, 1, 1, 1, 1, # 210 + 1, 1, 1, 1, 1, # 215 + 1, 1, 1, 1, 1, # 220 + 1, 1, 1, 1, 1, # 225 + 1, 1, 1, 1, 1, # 230 + 1, 1, 1, 1, 1, # 235 + 1, 1, 1, 1, 1, # 240 + 1, 1, 1, 1, 1, # 245 + 1, 1, 1, 1, 1, # 250 + 1, 1, 1, 1, 1, # 255 + 1, 1, 1, 1, 1, # 260 + 1, 1, 1, 1, 1, # 265 + 1, 1, 1, 1, 1, # 270 + 1, 1, 1, 1, 1, # 275 + 1, 1, 1, 1, 1, # 280 + 1, 1, 1, 1, 1, # 285 + 1, 1, 1, 1, 1, # 290 + 1, 1, 1, 1, 1, # 295 + 1, 1, 1, 1, 1, # 300 + 1, 1, 1, 1, 1, # 305 + 1, 1, 1, 1, 1, # 310 + 1, 1, 1, 1, 1, # 315 + 1, 1, 1, 1, 1, # 320 + 1, 1, 1, 1, 1, # 325 + 1, 1, 1, 1, 1, # 330 + 1, 1, 1, 1, 1, # 335 + 1, 1, 1, 1, 1, # 340 + 1, 1, 1, 1, 1, # 345 + 1, 1, 1, 1, 1, # 350 + 1, 1, 1, 1, 1, # 355 + 1, 1, 1, 1, 1, # 360 + 1, 1, 1, 1, 1, # 365 + 1, 1, 1, 1, 1, # 370 + 1, 1, 1, 1, 1, # 375 + 1, 1, 1, 1, 1, # 380 + 1, 1, 1, 1, 1, # 385 + 1, 1, 1, 1, 1, # 390 + 1, 1, 1, 1, 1, # 395 + 1, 1, 1, 1, 1, # 400 + 1, 1, 1, 1, 1, # 405 + 1, 1, 1, 1, 1, # 410 + 1, 1, 1, 1, 1, # 415 + 1, 1, 1, 1, 1, # 420 + 1, 1, 1, 1, 1, # 425 + 1, 1, 1, 1, 1, # 430 + 1, 1, 1, 1, 1, # 435 + 1, 1, 1, 1, 1, # 440 + 1, 1, 1, 1, 1, # 445 + 1, 1, 1, 1, 1, # 450 + 1, 1, 1, 1, 1, # 455 + 1, 1, 1, 1, 1, # 460 + 1, 1, 1, 1, 1, # 465 + 1, 1, 1, 1, 1, # 470 + 1, 1, 1, 1, 1, # 475 + 1, 1, 1, 1, 1, # 480 + 1, 1, 1, 1, 1, # 485 + 1, 1, 1, 1, 1, # 490 + 1, 1, 1, 1, 1, # 495 + 1, 1, 1, 1, 1, # 500 + 1, 1, 1, 1, 1, # 505 + 1, 1, 1, 1, 1, # 510 + 1, 1, 1, 1, 1, # 515 + 1, 1, 1, 1, 1, # 520 + 1, 1, 1, 1, 1, # 525 + 1, 1, 1, 1, 1, # 530 + 1, 1, 1, 1, 1, # 535 + 1, 1, 1, 1, 1, # 540 + 1, 1, 1, 1, 1, # 545 + 1, 1, 1, 1, 1, # 550 + 1, 1, 1, 1, 1, # 555 + 1, 1, 1, 1, 1, # 560 + 1, 1, 1, 1, 1, # 565 + 1, 1, 1, 1, 1, # 570 + 1, 1, 1, 1, 1, # 575 + 1, 1, 1, 1, 1, # 580 + 1, 1, 1, 1, 1, # 585 + 1, 1, 1, 1, 1, # 590 + 1, 1, 1, 1, 1, # 595 + 1, 1, 1, 1, 1, # 600 + 1, 1, 1, 1, 1, # 605 + 1, 1, 1, 1, 1, # 610 + 1, 1, 1, 1, 1, # 615 + 1, 1, 1, 1, 1, # 620 + 1, 1, 1, 1, 1, # 625 + 1, 1, 1, 1, 1, # 630 + 1, 1, 1, 1, 1, # 635 + 1, 1, 1, 1, 1, # 640 + 1, 1, 1, 1, 1, # 645 + 1, 1, 1, 1, 1, # 650 + 1, 1, 1, 1, 1, # 655 + 1, 1, 1, 1, 1, # 660 + 1, 1, 1, 1, 1, # 665 + 1, 1, 1, 1, 1, # 670 + 1, 1, 1, 1, 1, # 675 + 1, 1, 1, 1, 1, # 680 + 1, 1, 1, 1, 1, # 685 + 1, 1, 1, 1, 1, # 690 + 1, 1, 1, 1, 1, # 695 + 1, 1, 1, 1, 1, # 700 + 1, 1, 1, 1, 1, # 705 + 1, 1, 1, 1, 1, # 710 + 1, 1, 1, 1, 1, # 715 + 1, 1, 1, 1, 1, # 720 + 1, 1, 1, 1, 1, # 725 + 1, 1, 1, 1, 1, # 730 + 1, 1, 1, 1, 1, # 735 + 1, 1, 1, 1, 1, # 740 + 1, 1, 1, 1, 1, # 745 + 1, 1, 1, 1, 1, # 750 + 1, 1, 1, 1, 1, # 755 + 1, 1, 1, 1, 1, # 760 + 1, 1, 1, 1, 1, # 765 + 1, 1, 1, 1, 1, # 770 + 1, 1, 1, 1, 1, # 775 + 1, 1, 1, 1, 1, # 780 + 1, 1, 1, 1, 1, # 785 + 1, 1, 1, 1, 1, # 790 + 1, 1, 1, 1, 1, # 795 + 1, 1, 1, 1, 1, # 800 + 0.994435486, 0.988901936, 0.983399178, 0.977927039, 0.972485351, # 805 + 0.967073942, 0.961692646, 0.956341294, 0.95101972, 0.945727757, # 810 + 0.940465242, 0.93523201, 0.930027899, 0.924852745, 0.91970639, # 815 + 0.914588671, 0.909499429, 0.904438507, 0.899405746, 0.894400991, # 820 + 0.889424084, 0.884474871, 0.879553199, 0.874658913, 0.869791861, # 825 + 0.864951892, 0.860138855, 0.855352601, 0.850592979, 0.845859843, # 830 + 0.841153044, 0.836472436, 0.831817874, 0.827189212, 0.822586306, # 835 + 0.818009013, 0.813457191, 0.808930697, 0.804429391, 0.799953132, # 840 + 0.795501782, 0.791075201, 0.786673252, 0.782295798, 0.777942702, # 845 + 0.773613829, 0.769309044, 0.765028214, 0.760771204, 0.756537882, # 850 + 0.752328116, 0.748141776, 0.743978731, 0.739838851, 0.735722007, # 855 + 0.731628072, 0.727556917, 0.723508417, 0.719482444, 0.715478874, # 860 + 0.711497582, 0.707538444, 0.703601336, 0.699686137, 0.695792724, # 865 + 0.691920975, 0.688070772, 0.684241992, 0.680434518, 0.676648231, # 870 + 0.672883012, 0.669138746, 0.665415314, 0.661712601, 0.658030492, # 875 + 0.654368872, 0.650727628, 0.647106645, 0.643505811, 0.639925014, # 880 + 0.636364142, 0.632823085, 0.629301732, 0.625799974, 0.622317701, # 885 + 0.618854806, 0.61541118, 0.611986716, 0.608581307, 0.605194848, # 890 + 0.601827233, 0.598478357, 0.595148116, 0.591836406, 0.588543124, # 895 + 0.585268168, 0.582011435, 0.578772824, 0.575552235, 0.572349566, # 900 + 0.569164719, 0.565997594, 0.562848093, 0.559716117, 0.556601569, # 905 + 0.553504352, 0.550424369, 0.547361525, 0.544315724, 0.541286872, # 910 + 0.538274873, 0.535279635, 0.532301064, 0.529339068, 0.526393553, # 915 + 0.523464429, 0.520551604, 0.517654987, 0.514774489, 0.511910019, # 920 + 0.509061489, 0.506228809, 0.503411892, 0.500610649, 0.497824994, # 925 + 0.49505484, 0.492300101, 0.48956069, 0.486836523, 0.484127514, # 930 + 0.48143358, 0.478754636, 0.476090599, 0.473441387, 0.470806916, # 935 + 0.468187104, 0.46558187, 0.462991133, 0.460414813, 0.457852828, # 940 + 0.4553051, 0.452771548, 0.450252095, 0.447746661, 0.445255168, # 945 + 0.44277754, 0.440313698, 0.437863566, 0.435427068, 0.433004128, # 950 + 0.430594671, 0.428198621, 0.425815904, 0.423446445, 0.421090172, # 955 + 0.41874701, 0.416416886, 0.414099729, 0.411795465, 0.409504023, # 960 + 0.407225332, 0.404959321, 0.40270592, 0.400465057, 0.398236664, # 965 + 0.39602067, 0.393817008, 0.391625607, 0.389446401, 0.387279321, # 970 + 0.3851243, 0.382981271, 0.380850166, 0.37873092, 0.376623467, # 975 + 0.37452774, 0.372443675, 0.370371207, 0.368310272, 0.366260804, # 980 + 0.364222741, 0.362196018, 0.360180574, 0.358176344, 0.356183267, # 985 + 0.35420128, 0.352230322, 0.350270331, 0.348321247, 0.346383009, # 990 + 0.344455556, 0.342538828, 0.340632766, 0.33873731, 0.336852402, # 995 + 0.334977982, 0.333113992, 0.331260375, 0.329417072, 0.327584026, # 1000 + 0.32576118, 0.323948478, 0.322145862, 0.320353277, 0.318570666, # 1005 + 0.316797976, 0.315035149, 0.313282131, 0.311538869, 0.309805306, # 1010 + 0.30808139, 0.306367067, 0.304662283, 0.302966986, 0.301281122, # 1015 + 0.299604639, 0.297937485, 0.296279607, 0.294630955, 0.292991477, # 1020 + 0.291361122, 0.289739839, 0.288127578, 0.286524288, 0.28492992, # 1025 + 0.283344423, 0.281767749, 0.280199849, 0.278640673, 0.277090173, # 1030 + 0.275548301, 0.274015008, 0.272490248, 0.270973972, 0.269466134, # 1035 + 0.267966686, 0.266475582, 0.264992774, 0.263518218, 0.262051868, # 1040 + 0.260593676, 0.259143599, 0.257701591, 0.256267607, 0.254841602, # 1045 + 0.253423533, 0.252013354, 0.250611022, 0.249216494, 0.247829725, # 1050 + 0.246450673, 0.245079295, 0.243715548, 0.242359389, 0.241010777, # 1055 + 0.239669669, 0.238336024, 0.2370098, 0.235690956, 0.23437945, # 1060 + 0.233075242, 0.231778292, 0.230488558, 0.229206002, 0.227930582, # 1065 + 0.226662259, 0.225400994, 0.224146747, 0.222899479, 0.221659152, # 1070 + 0.220425726, 0.219199164, 0.217979427, 0.216766478, 0.215560278, # 1075 + 0.21436079, 0.213167976, 0.2119818, 0.210802224, 0.209629212, # 1080 + 0.208462728, 0.207302734, 0.206149195, 0.205002075, 0.203861338, # 1085 + 0.202726949, 0.201598872, 0.200477072, 0.199361515, 0.198252165, # 1090 + 0.197148988, 0.19605195, 0.194961016, 0.193876153, 0.192797326, # 1095 + 0.191724503, 0.190657649, 0.189596732, 0.188541718, 0.187492575, # 1100 + 0.18644927, 0.185411771, 0.184380044, 0.183354059, 0.182333783, # 1105 + 0.181319184, 0.180310231, 0.179306892, 0.178309136, 0.177316933, # 1110 + 0.17633025, 0.175349058, 0.174373326, 0.173403023, 0.172438119, # 1115 + 0.171478585, 0.17052439, 0.169575505, 0.1686319, 0.167693545, # 1120 + 0.166760412, 0.165832471, 0.164909694, 0.163992052, 0.163079516, # 1125 + 0.162172058, 0.161269649, 0.160372262, 0.159479868, 0.15859244, # 1130 + 0.15770995, 0.156832371, 0.155959675, 0.155091836, 0.154228825, # 1135 + 0.153370616, 0.152517184, 0.1516685, 0.150824538, 0.149985273, # 1140 + 0.149150678, 0.148320727, 0.147495394, 0.146674654, 0.145858481, # 1145 + 0.145046849, 0.144239734, 0.14343711, 0.142638952, 0.141845236, # 1150 + 0.141055936, 0.140271028, 0.139490488, 0.138714291, 0.137942414, # 1155 + 0.137174831, 0.13641152, 0.135652456, 0.134897616, 0.134146977, # 1160 + 0.133400514, 0.132658205, 0.131920026, 0.131185956, 0.13045597, # 1165 + 0.129730046, 0.129008161, 0.128290293, 0.12757642, 0.126866519, # 1170 + 0.126160569, 0.125458547, 0.124760431, 0.1240662, 0.123375832, # 1175 + 0.122689305, 0.122006599, 0.121327691, 0.120652562, 0.119981189, # 1180 + 0.119313552, 0.11864963, 0.117989402, 0.117332849, 0.116679948, # 1185 + 0.116030681, 0.115385027, 0.114742965, 0.114104477, 0.113469541, # 1190 + 0.112838138, 0.112210248, 0.111585853, 0.110964932, 0.110347466, # 1195 + 0.109733436, 0.109122823, 0.108515607, 0.107911771, 0.107311294, # 1200 + 0.106714159, 0.106120347, 0.105529838, 0.104942616, 0.104358662, # 1205 + 0.103777956, 0.103200482, 0.102626222, 0.102055157, 0.10148727, # 1210 + 0.100922542, 0.100360957, 0.099802497, 0.099247145, 0.098694883, # 1215 + 0.098145694, 0.097599561, 0.097056467, 0.096516395, 0.095979328, # 1220 + 0.095445249, 0.094914143, 0.094385992, 0.09386078, 0.09333849, # 1225 + 0.092819107, 0.092302614, 0.091788995, 0.091278233, 0.090770314, # 1230 + 0.090265222, 0.08976294, 0.089263453, 0.088766745, 0.088272801, # 1235 + 0.087781606, 0.087293144, 0.0868074, 0.086324359, 0.085844006, # 1240 + 0.085366326, 0.084891304, 0.084418925, 0.083949174, 0.083482038, # 1245 + 0.083017501, 0.082555549, 0.082096168, 0.081639342, 0.081185059, # 1250 + 0.080733304, 0.080284062, 0.07983732, 0.079393065, 0.078951281, # 1255 + 0.078511955, 0.078075074, 0.077640625, 0.077208592, 0.076778964, # 1260 + 0.076351726, 0.075926866, 0.07550437, 0.075084225, 0.074666418, # 1265 + 0.074250935, 0.073837765, 0.073426894, 0.073018309, 0.072611997, # 1270 + 0.072207947, 0.071806145, 0.071406578, 0.071009236, 0.070614104, # 1275 + 0.070221171, 0.069830424, 0.069441851, 0.069055441, 0.068671181, # 1280 + 0.06828906, 0.067909064, 0.067531183, 0.067155405, 0.066781718, # 1285 + 0.06641011, 0.06604057, 0.065673086, 0.065307648, 0.064944242, # 1290 + 0.064582859, 0.064223487, 0.063866115, 0.063510731, 0.063157324, # 1295 + 0.062805884, 0.0624564, 0.062108861, 0.061763255, 0.061419573, # 1300 + ] + def get_class_number(self, game_class): for i in self.GAME_CLASS_NUMBER.keys(): if self.GAME_CLASS_NUMBER[i] == game_class: @@ -1094,6 +1359,11 @@ def get_random_prop_point(self, item_level): raise exceptions.InvalidInputException(_('item_level={item_level} need to be >= 1').format(item_level=item_level)) return self.RANDOM_PROP_POINTS[item_level][1] + def get_combat_rating_multiplier(self, item_level): + if item_level < 1: + raise exceptions.InvalidInputException(_('item_level={item_level} need to be >= 1').format(item_level=item_level)) + return self.COMBAT_RATING_MULTIPLIERS[item_level - 1] + def get_constant_scaling_point(self, level): return self.CONSTANT_SCALING[level-1] diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 94d6f5e..cf2892c 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -12,7 +12,7 @@ class Proc(object): def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True, stats=None, upgradable=False, scaling=None, buffs=None, base_value=0, type='rppm', icd=0, proc_rate=1.0, trigger='all_attacks', haste_scales=False, item_level=1, on_crit=False, on_procced_strikes=True, proc_rate_modifier=1., source='generic', att_spd_scales=False, - ap_coefficient=0., dmg_school=None): + ap_coefficient=0., dmg_school=None, crm_scales=False): self.stat = stat if stats is not None: self.stats = set(stats) @@ -39,6 +39,7 @@ def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True self.proc_rate_modifier = proc_rate_modifier self.ap_coefficient = ap_coefficient self.dmg_school = dmg_school + self.crm_scales = crm_scales if self.dmg_school is None and stat in ['physical_damage', 'physical_dot']: self.dmg_school = 'physical' @@ -53,13 +54,14 @@ def update_proc_value(self): #not sure if this is the correct way to handle damage procs. Most seem to have both disabled scaling and raw value or both enabled scaling and an {object:value}, #they should probably all use the same notation either way. If we want both, the scaling property should probably be set by value property instead of manually configured. #the other option is to always handle it deeper into the calc module, but that is coupling object responsibilities and not ideal. - if self.scaling: - if self.source in ('trinket',): - if hasattr(self.value,'__iter__'): #handle object value - for e in self.value: - self.value[e] = round(self.scaling * tools.get_random_prop_point(self.item_level)) - else: #handle raw value - self.value = round(self.scaling * tools.get_random_prop_point(self.item_level)) + if self.scaling and self.source in ('trinket',): + crm = tools.get_combat_rating_multiplier(self.item_level) if self.crm_scales else 1. # apply combat rating modifier + scaled_value = round(self.scaling * tools.get_random_prop_point(self.item_level) * crm) + if hasattr(self.value,'__iter__'): #handle object value + for e in self.value: + self.value[e] = scaled_value + else: #handle raw value + self.value = scaled_value def procs_off_auto_attacks(self): if self.trigger in ('all_attacks', 'auto_attacks', 'all_spells_and_attacks', 'all_melee_attacks'): From e4d2a1c9c37b54b5fd46f8b2134a0d404682f896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 00:08:46 +0100 Subject: [PATCH 146/265] Update proc_data for Legion trinkets Added trinkets that were missing - Six-Feather Fan - The Devilsaur's Bite - Toe Knee's Promis - Bloodstained Handkerchief - Eye of Command - Kil'jaedens Burning Wish --- shadowcraft/objects/proc_data.py | 328 ++++++++++++++++++++----------- 1 file changed, 214 insertions(+), 114 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 3e09310..8266733 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -186,29 +186,47 @@ 'proc_rate': 3, 'trigger': 'all_attacks', }, - #7.0 trinket procs - 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for 37356 Arcane damage. + + #Legion trinket procs + 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for X Arcane damage. 'stat':'spell_damage', - 'value': 37356, #multiple targets not modeled + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled 'duration': 0, 'proc_name': 'Arcane Swipe', - 'scaling': 12, - 'item_level': 875, + 'dmg_school': 'arcane', + 'scaling': 14.21082, #hotfixed value + 'item_level': 870, 'type': 'rppm', 'source': 'trinket', - 'proc_rate': 1, + 'proc_rate': 5, 'icd': 1, 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' }, - 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by 3399 for 10 sec. This effect occurs more often against targets at low health. + 'bloodstained_handkerchief': { #Use: Garrote your target from behind, causing them to bleed for X Physical damage every 3 sec until they die. (1 Min Cooldown) + 'stat':'physical_damage', + 'value': 0, #rpp-scaled, TODO: could be applied to adds as well, after CD + 'duration': 0, + 'proc_name': 'Cruel Garrote', + 'scaling': 3.474452, + 'item_level': 855, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 3, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by X for 10 sec. This effect occurs more often against targets at low health. 'stat':'stats', - 'value': {'haste': 3399}, + 'value': {'haste': 0}, #rpp-scaled 'duration': 10, 'proc_name': 'Bloodthirsty Instinct', - 'scaling': 1.378349, + 'scaling': 1.470561, + 'crm_scales': True, 'item_level': 850, 'source': 'trinket', 'type': 'rppm', @@ -216,32 +234,32 @@ 'trigger': 'all_attacks', }, - 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by 55, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. + 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by X, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. 'stat':'stats', - 'value': {'agi': 42}, - 'duration': 24, #decays 1/s after 4s + 'value': {'agi': 0}, #rpp-scaled + 'duration': 23, #decays by 1 stack after 4s without autoattacks (assume we can ignore decay) 'max_stacks': 20, 'proc_name': 'Chaotic Energy', 'scaling': 0.029595, - 'item_level': 790, + 'item_level': 805, 'source': 'trinket', 'type': 'icd', 'icd': 1, - 'proc_rate': 1000000, #idk how to force a stack every autoattack, and messing with icd gives wonky behavior - 'trigger': 'all_attacks', + 'proc_rate': 1, + 'trigger': 'auto_attacks', }, - 'chrono_shard': { #Equip: Your spells and abilities have a chance to grant you 5112 Haste and 15% movement speed for 10 sec. + 'chrono_shard': { #Equip: Your spells and abilities have a chance to grant you X Haste and 15% movement speed for 10 sec. 'stat':'stats', - 'value': {'haste': 5112}, + 'value': {'haste': 0}, #rpp-scaled, TODO: set bonus? 'duration': 10, 'proc_name': 'Acceleration', 'scaling': 2.741159, - 'item_level': 820, + 'crm_scales': True, + 'item_level': 805, 'source': 'trinket', 'type': 'rppm', 'proc_rate': 1, - 'haste_scales': True, 'trigger': 'all_attacks', }, @@ -254,58 +272,78 @@ 'item_level': 875, 'source': 'trinket', 'type': 'rppm', - 'proc_rate': 3, + 'proc_rate': {'assassination': 3.51, 'outlaw': 8.4, 'subtlety': 9}, 'trigger': 'all_attacks', }, #removed the ":" not sure which way it should be - 'darkmoon_deck_dominion': { #Equip: Increase critical strike by 668-1336. The amount of critical strike depends on the topmost card in the deck. Equip: Periodically shuffle the deck while in combat. + 'darkmoon_deck_dominion': { #Equip: Increase critical strike by X-Y. The amount of critical strike depends on the topmost card in the deck. Equip: Periodically shuffle the deck while in combat. 'stat': 'stats', - 'value': {'crit':1336}, #not accurate, it should be shuffled every proc, may also stack + 'value': {'crit':0}, #rpp-scaled, TODO: not accurate, it should be shuffled every 20s 'duration': 20, 'proc_name': 'Dominion Deck', #this does some wierd shuffling crit values /wrists - 'scaling': 0.750315, #only valid for the 1336 draw + 'scaling': 0.5627245, #use average for now, min 0.375134, max 0.750315 'item_level': 815, - 'type': 'rppm', + 'type': 'icd', + 'icd': 20, #slight loss? should change value instantly, not on attack trigger 'source': 'trinket', 'proc_rate': 1, 'trigger': 'all_attacks' }, - 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing 124520 damage to a random nearby enemy every second for 8 sec. You cannot use abilities during your rage, and your movement speed is slowed by 30%. (2 Min Cooldown) + 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing X damage to a random nearby enemy every 0.25sec for 3 sec. You cannot move or use abilities during your rage. (1 Min, 20 Sec Cooldown) 'stat':'spell_damage', - 'value': 996160, #124520 * 8 - 'duration': 0, + 'value': 0, #rpp-scaled + 'duration': 3, #TODO: 3sec ability downtime 'proc_name': 'Fel-Crazed Rage', - 'scaling': 40, - 'item_level': 875, + 'dmg_school': 'shadow', + 'scaling': 33. * 13., #13 hits total + 'item_level': 880, 'source': 'trinket', 'type': 'icd', - 'icd': 120, + 'icd': 80, 'proc_rate': 1, + 'can_crit': True, 'trigger': 'all_attacks', }, - 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant Fiery, Frost, or Arcane enchants for 8 sec. + 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant you a Fiery, Frost, or Arcane enchants for 20 sec. 'stat':'stats', - 'value': {'haste': 0}, #needs special modeling - 'duration': 8, + 'value': {'haste': 0, 'crit': 0, 'mastery': 0}, #TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time + 'duration': 20, 'proc_name': 'Triumvirate', - 'scaling': 1.5, + 'scaling': 2.069368 / 3., #for now, assume we get all 3 for 1/3 each + 'crm_scales': True, 'item_level': 875, 'source': 'trinket', 'type': 'rppm', - 'proc_rate': 1, + 'proc_rate': 0.7, 'trigger': 'all_attacks', }, - 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause 54933 additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) - 'stat':'spell_damage', - 'value': 0, - 'duration': 15, - 'proc_name': 'Sheathed in Frost', #need special handling - 'scaling': 29.454582, - 'item_level': 820, + 'eye_of_command': { #Equip: Your melee auto attacks increase your Critical Strike by 148 for 10 sec, stacking up to 10 times. This effect is reset if you auto attack a different target. + 'stat':'stats', + 'value': {'crit': 0}, #rpp-scaled + 'duration': 10, #decays when autoattacking different target, assume we can ignore + 'max_stacks': 10, + 'proc_name': "Legion's Gaze", + 'scaling': 0.072857, + 'crm_scales': True, + 'item_level': 860, + 'source': 'trinket', + 'type': 'icd', + 'icd': 1, + 'proc_rate': 1, + 'trigger': 'auto_attacks', + }, + + 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause X additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) + 'stat':'ability_modifier', + 'value': 0, #rpp-scaled + 'duration': 30, + 'proc_name': 'Sheathed in Frost', #TODO: need special handling, rppm during uptime is 20 and scales with haste + 'scaling': 17.47413, + 'item_level': 805, 'type': 'icd', 'source': 'trinket', 'proc_rate': 1, @@ -314,13 +352,15 @@ 'trigger': 'all_attacks' }, - 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to 233125 damage for 8 sec. When the bubble is consumed or expires, it explodes and deals 111900 Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) + 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to X damage for 8 sec. When the bubble is consumed or expires, it explodes and deals Y Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) 'stat':'spell_damage', - 'value': 111900, #multiple targets not modeled + 'dmg_school': 'frost', + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled 'duration': 0, 'proc_name': 'Gaseous Bubble', - 'scaling': 60, - 'item_level': 820, + 'dmg_school': 'frost', + 'scaling': 55.83131, + 'item_level': 805, 'type': 'icd', 'source': 'trinket', 'icd': 60, @@ -329,13 +369,13 @@ 'trigger': 'all_attacks', }, - 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by 2798 for 30 sec. (2 Min Cooldown) + 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by X for 30 sec. (2 Min Cooldown) 'stat':'stats', - 'value': {'agi': 2798}, + 'value': {'agi': 0}, #rpp-scaled 'duration': 30, 'proc_name': "Valarjar's Path", 'scaling': 1.2, - 'item_level': 820, + 'item_level': 805, 'type': 'icd', 'source': 'trinket', 'icd': 120, @@ -343,9 +383,9 @@ 'trigger': 'all_attacks', }, - 'infernal_alchemist_stone': { #Equip: When you heal or deal damage you have a chance to increase your Strength, Agility, or Intellect by 3275 for 15 sec. Your highest stat is always chosen. + 'infernal_alchemist_stone': { #Equip: When you heal or deal damage you have a chance to increase your Strength, Agility, or Intellect by X for 15 sec. Your highest stat is always chosen. 'stat': 'stats', - 'value': {'agi':3275}, + 'value': {'agi': 0}, #rpp-scaled 'duration': 15, 'proc_name': 'Infernal Alchemist Stone', 'scaling': 1.839772, @@ -356,17 +396,32 @@ 'trigger': 'all_attacks' }, - 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing 43597 Physical damage to all enemies directly in front of you. + 'kiljaedens_burning_wish': { #Use: Launch a vortex of destruction that seeks your current enemy. When it reaches the target, it explodes, dealing a critical strike to all enemies within 10 yds for X Fire damage. (1 Min, 15 Sec Cooldown) 'stat':'spell_damage', - 'value': 43597, #multiple targets not modeled + 'dmg_school': 'fire', + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'duration': 0, + 'proc_name': "Kil'jaeden's Burning Wish", + 'scaling': 70 * 2, #always crits, hotfixed value + 'item_level': 910, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 75, + 'trigger': 'all_attacks' + }, + + 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing X Physical damage to all enemies directly in front of you. + 'stat':'physical_damage', + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled 'duration': 0, 'proc_name': 'Landslide', - 'scaling': 23.376637, - 'item_level': 820, + 'scaling': 21.66943, + 'item_level': 805, 'type': 'rppm', 'source': 'trinket', 'proc_rate': 4, - 'icd': 1, + 'icd': 2, 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' @@ -374,23 +429,25 @@ 'memento_of_angerboda': { #Equip: Your melee attacks have a chance to activate Screams of the Dead, granting you a random combat enhancement for 8 sec. 'stat':'stats', - 'value': {'mastery': 1189}, # actually 1-3 stat buffs each time, only modeled one + 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #TODO: actually 1-3 stat buffs each time 'duration': 8, 'proc_name': 'Memento of Angerboda', - 'scaling': 0.637533, #only one data point 1189stat@ilvl820 - 'item_level': 820, + 'scaling': 2.297781, #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'crm_scales': True, + 'item_level': 805, 'source': 'trinket', 'type': 'rppm', 'proc_rate': 1.5, 'trigger': 'all_attacks', }, - 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 8 sec. + 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 10 sec. 'stat':'stats', - 'value': {'haste': 0}, #needs special modeling - 'duration': 8, + 'value': {'haste': 0}, #rpp-scaled, TODO: check other possible effects and if this needs special modeling + 'duration': 10, 'proc_name': 'Allies of Nature', - 'scaling': 1.98186, + 'scaling': 1.378778, + 'crm_scales': True, #TODO: also verify this and to which effects the mod applies 'item_level': 850, 'source': 'trinket', 'type': 'rppm', @@ -398,38 +455,41 @@ 'trigger': 'all_attacks', }, - 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec,causing your auto attacks to deal an additional 1557 damage and increase the intensity of Recursive Strikes. - 'stat':'physical_damage', - 'value': 1557, #needs special modeling + 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional X damage and increase the intensity of Recursive Strikes. + 'stat':'ability_modifier', + 'value': 0, #rpp-scaled, TODO: needs special modeling 'duration': 15, 'proc_name': 'Recursive Strikes', - 'scaling': 0.5001606, + 'scaling': 2.12, 'item_level': 875, 'source': 'trinket', 'type': 'rppm', 'proc_rate': 1, + 'can_crit': True, 'trigger': 'all_attacks', }, - 'nightmare_egg_shell': { #Equip: Your melee attacks have a chance to grant you 184 Haste every 1 sec for 20 sec. + 'nightmare_egg_shell': { #Equip: Your melee attacks have a chance to grant you X Haste every 1 sec for 20 sec. 'stat':'stats', - 'value': {'haste': 184}, - 'duration': 8, + 'value': {'haste': 0}, #rpp-scaled + 'duration': 20, 'proc_name': 'Down Draft', - 'scaling': 0.098396, - 'item_level': 820, + 'scaling': 0.187677 * 10.5, # avg should be 10.5 stacks for 20 sec, melee attacks only needed to proc, not for stacks + 'item_level': 805, 'source': 'trinket', 'type': 'rppm', + 'icd': 20, 'proc_rate': .7, 'trigger': 'all_attacks', }, - 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing 18798 Shadow damage to enemies in the area each second. While you remain in this area, you gain 1540 Leech. (1 Min Cooldown) + 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing X Shadow damage to enemies in the area each second. While you remain in this area, you gain Y Leech. (1 Min Cooldown) 'stat':'spell_damage', - 'value': 18798, #multiple targets not modeled + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'dmg_school': 'shadow', 'duration': 10, 'proc_name': 'Infested Ground', - 'scaling': 7.622871, + 'scaling': 6.624573, 'item_level': 850, 'type': 'icd', 'icd': 60, @@ -437,80 +497,103 @@ 'proc_rate': 1, }, - 'spiked_counterweight': { #Your melee attacks have a chance to deal 90047 Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to 271840 extra damage dealt. - 'stat':'physical_damage', - 'value': 103562, #initial hit portion + 'six_feather_fan': { #Equip: Your attacks have a chance to launch a volley of 6 Wind Bolts, each dealing X Nature damage and slowing your target by 30% for 6 sec. + 'stat':'spell_damage', + 'dmg_school': 'nature', + 'value': 0, #rpp-scaled 'duration': 0, - 'proc_name': 'Brutal Haymaker', - 'scaling': 53, - 'item_level': 825, + 'proc_name': 'Wind Bolt', + 'scaling': 19.01865 * 6., #6 bolts, one every second + 'item_level': 810, 'type': 'rppm', 'source': 'trinket', - 'proc_rate': .92, + 'proc_rate': 1, + 'haste_scales': True, + 'can_crit': True }, - 'spiked_counterweight': { #Your melee attacks have a chance to deal 90047 Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to 271840 extra damage dealt. + 'spiked_counterweight': { #Your melee attacks have a chance to deal X Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to Y extra damage dealt. 'stat':'physical_damage', - 'value': 312640, #modeled as all direct damage since bonus has a cap + 'value': 0, #rpp-scaled 'duration': 0, 'proc_name': 'Brutal Haymaker', - 'scaling': 160, - 'item_level': 825, + 'scaling': 49.4631 + 185.487, #scaling for initial + extra damage. can we just add full extra dmg? what about crits? + 'item_level': 805, 'type': 'rppm', 'source': 'trinket', 'proc_rate': .92, }, - 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for 15132 Physical damage every 0.75 sec. + 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for X Physical damage every 0.75 sec. 'stat':'physical_damage', - 'value': 0, #needs special handling - 'duration': 12, - 'proc_name': 'Horrific Appendages', - 'scaling': 6.136254, + 'value': 0, #rpp-scaled, TODO: aoe damage + 'duration': 0, #accumulate all dmg + 'proc_name': 'Horrific Slam', #not the proc name but the dmg + 'can_crit': True, + 'scaling': 10.1246 * 16., # 16 hits overall, hotfixed value 'item_level': 850, 'type': 'rppm', 'source': 'trinket', 'proc_rate': .7, + 'haste_scales': True, }, 'tempered_egg_of_serpentrix': { #Equip: Your attacks have a chance to summon a Spawn of Serpentrix to assist you. - 'stat':'physical_damage', - 'value': 0, #unmodeled - 'duration': 0, - 'proc_name': 'Spawn of Serpentrix', - 'scaling': 0, - 'item_level': 820, + 'stat':'spell_damage', + 'dmg_school': 'fire', + 'value': 0, #rpp-scaled + 'duration': 15, + 'proc_name': 'Magma Spit', #not the proc name but the dmg of the add + 'scaling': 8.235604 * 8., # pet might be scaling with haste, but most logs have 8 magma spits, assume that for now + 'item_level': 805, 'source': 'trinket', 'type': 'rppm', 'proc_rate': 1, + 'can_crit': True, 'haste_scales': True, 'trigger': 'all_attacks', }, - 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal 86952 Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. + 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal X Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. 'stat':'spell_damage', - 'value': 695616, #multiple targets not modeled, assuming 4 hits out and 4 in + 'dmg_school': 'shadow', + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled, assuming 4 hits out and 4 in 'duration': 0, 'proc_name': 'Shadow Wave', - 'scaling': 372.986208, #46.623276 * 8 - 'item_level': 820, + 'scaling': 46.22871 * 8., #assume 8 hits + 'item_level': 805, 'type': 'rppm', 'source': 'trinket', 'proc_rate': 1, - 'icd': 1, + 'icd': 10, 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' }, - 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting [6228 * (1 + $versadmg)] Nature damage per Goo consumed. (20 Sec Cooldown) + 'the_devilsaurs_bite': { #Equip: Your attacks have a chance to inflict X Physical damage and stun the target for 1 sec. + 'stat':'physical_damage', + 'value': 0, #rpp-scaled + 'duration': 0, + 'proc_name': "Devilsaur's Bite", + 'scaling': 65., + 'item_level': 805, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 2, + 'haste_scales': True, + 'can_crit': True + }, + + 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting X Nature damage per Goo consumed. (20 Sec Cooldown) 'stat':'spell_damage', - 'value': 37368, #4709 per stack * 6 stacks + 'dmg_school': 'nature', + 'value': 0, #rpp-scaled, TODO: aoe 'duration': 0, #'max_stacks': 6, 'proc_name': 'Fetid Regurgitation', - 'scaling': 3.339412, - 'item_level': 620, + 'scaling': 17.123 * 6., #assume 6 stacks, scaling value not found in DBC, server-side/hotfixed? calculated by hand + 'item_level': 805, 'type': 'icd', #actually rppm 'source': 'trinket', #'proc_rate': 3, @@ -521,12 +604,12 @@ }, 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) - 'stat':'spell_damage', + 'stat':'ability_modifier', 'value': 0, 'duration': 15, - 'proc_name': 'Darkstrikes', #need special handling - 'scaling': 20.829683, - 'item_level': 820, + 'proc_name': 'Darkstrikes', #TODO: need special handling + 'scaling': 16.11315, + 'item_level': 805, 'type': 'icd', 'source': 'trinket', 'proc_rate': 1, @@ -535,13 +618,29 @@ 'trigger': 'all_attacks' }, - 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting (7 * 40619) Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) + 'toe_knees_promise': { #Use: Create a Flame Gale at an enemy's location, dealing X Fire damage over 8 sec. If Flame Gale strikes an enemy affected by Thunder Ritual, Flame Gale's damage is increased by 30%, and its radius by 50%. (1 Min Cooldown) 'stat':'spell_damage', - 'value': 284333, #multiple targets not modeled + 'dmg_school': 'fire', + 'value': 0, #rpp-scaled, TODO: only modeled base damage without Thunder Ritual + 'duration': 0, + 'proc_name': 'Flame Gale', + 'scaling': 9.768856 * 8., + 'item_level': 855, + 'source': 'trinket', + 'type': 'icd', + 'icd': 60, + 'proc_rate': 1, + 'can_crit': True, + 'trigger': 'all_attacks', + }, + + 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting X Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) + 'stat':'physical_damage', + 'value': 0, #rpp-scaled, TODO: multiple targets not modeled 'duration': 0, 'proc_name': 'Slicing Maelstrom', - 'scaling': 152.455464, - 'item_level': 790, + 'scaling': 19.93953 * 7., # 7 hits + 'item_level': 805, 'type': 'icd', 'source': 'trinket', 'icd': 120, @@ -550,6 +649,7 @@ 'trigger': 'all_attacks' }, + #Other Legion procs 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. 'stat':'stats', 'value':{'mastery':3000}, From 062271341c27a181e23f6c5e21ec5bd834aad093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 14:52:32 +0100 Subject: [PATCH 147/265] AoE damage trinkets --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 ++ shadowcraft/objects/proc_data.py | 31 +++++++++++++------- shadowcraft/objects/procs.py | 3 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index b4a57f9..62b24ba 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -305,6 +305,9 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc.stat == 'physical_dot': average_damage *= proc.uptime / proc_count + if proc.aoe: + average_damage *= 1 + self.settings.num_boss_adds + return average_damage def set_openers(self): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 8266733..8fb923b 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -161,10 +161,10 @@ 'trigger': 'all_attacks' }, - #TODO: aoe proc 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 3 ticks of damage over 1.5 sec. 'stat':'physical_damage', 'value': 0, # AP based + 'aoe': True, 'ap_coefficient': 2.5, # server-side, not in dbc, per tick is 2.5 / 3 'duration': 0, 'proc_name': 'Mark of the Distant Army', @@ -190,7 +190,8 @@ #Legion trinket procs 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for X Arcane damage. 'stat':'spell_damage', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': 'Arcane Swipe', 'dmg_school': 'arcane', @@ -355,7 +356,8 @@ 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to X damage for 8 sec. When the bubble is consumed or expires, it explodes and deals Y Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) 'stat':'spell_damage', 'dmg_school': 'frost', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': 'Gaseous Bubble', 'dmg_school': 'frost', @@ -399,7 +401,8 @@ 'kiljaedens_burning_wish': { #Use: Launch a vortex of destruction that seeks your current enemy. When it reaches the target, it explodes, dealing a critical strike to all enemies within 10 yds for X Fire damage. (1 Min, 15 Sec Cooldown) 'stat':'spell_damage', 'dmg_school': 'fire', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': "Kil'jaeden's Burning Wish", 'scaling': 70 * 2, #always crits, hotfixed value @@ -413,7 +416,8 @@ 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing X Physical damage to all enemies directly in front of you. 'stat':'physical_damage', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': 'Landslide', 'scaling': 21.66943, @@ -485,7 +489,8 @@ 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing X Shadow damage to enemies in the area each second. While you remain in this area, you gain Y Leech. (1 Min Cooldown) 'stat':'spell_damage', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'dmg_school': 'shadow', 'duration': 10, 'proc_name': 'Infested Ground', @@ -526,7 +531,8 @@ 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for X Physical damage every 0.75 sec. 'stat':'physical_damage', - 'value': 0, #rpp-scaled, TODO: aoe damage + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, #accumulate all dmg 'proc_name': 'Horrific Slam', #not the proc name but the dmg 'can_crit': True, @@ -557,10 +563,11 @@ 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal X Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. 'stat':'spell_damage', 'dmg_school': 'shadow', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled, assuming 4 hits out and 4 in + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': 'Shadow Wave', - 'scaling': 46.22871 * 8., #assume 8 hits + 'scaling': 46.22871 * 8., #assuming 4 hits out and 4 in 'item_level': 805, 'type': 'rppm', 'source': 'trinket', @@ -588,7 +595,8 @@ 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting X Nature damage per Goo consumed. (20 Sec Cooldown) 'stat':'spell_damage', 'dmg_school': 'nature', - 'value': 0, #rpp-scaled, TODO: aoe + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, #'max_stacks': 6, 'proc_name': 'Fetid Regurgitation', @@ -636,7 +644,8 @@ 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting X Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) 'stat':'physical_damage', - 'value': 0, #rpp-scaled, TODO: multiple targets not modeled + 'value': 0, #rpp-scaled + 'aoe': True, 'duration': 0, 'proc_name': 'Slicing Maelstrom', 'scaling': 19.93953 * 7., # 7 hits diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index cf2892c..0b89daf 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -12,7 +12,7 @@ class Proc(object): def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True, stats=None, upgradable=False, scaling=None, buffs=None, base_value=0, type='rppm', icd=0, proc_rate=1.0, trigger='all_attacks', haste_scales=False, item_level=1, on_crit=False, on_procced_strikes=True, proc_rate_modifier=1., source='generic', att_spd_scales=False, - ap_coefficient=0., dmg_school=None, crm_scales=False): + ap_coefficient=0., dmg_school=None, crm_scales=False, aoe=False): self.stat = stat if stats is not None: self.stats = set(stats) @@ -40,6 +40,7 @@ def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True self.ap_coefficient = ap_coefficient self.dmg_school = dmg_school self.crm_scales = crm_scales + self.aoe = aoe if self.dmg_school is None and stat in ['physical_damage', 'physical_dot']: self.dmg_school = 'physical' From ca4a929ab1bf4a7d03258e4f45103500a1f64acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 14:53:30 +0100 Subject: [PATCH 148/265] Fix Shuriken Storm builder --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 62b24ba..8cca0d1 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2126,7 +2126,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['mh_autoattacks'] -= attacks_per_second['shadow_blades'] attacks_per_second['oh_autoattacks'] -= attacks_per_second['shadow_blades'] - if self.traits.akarris_soul: + if self.traits.akarris_soul and 'shadowstrike' in attacks_per_second: attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] if self.traits.shadow_nova: attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], 1./5.) From 41ec4c6155d9a87b30056945a491d324d6b247b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 14:55:41 +0100 Subject: [PATCH 149/265] No need for empty blacklists in all_damage modifiers --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 8cca0d1..0280c3c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -774,7 +774,7 @@ def assassination_dps_breakdown(self): #assassination specific constants #set up damage modifier list and all relevant modifiers, use None for placeholder values self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', @@ -786,18 +786,18 @@ def assassination_dps_breakdown(self): 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'])) #time averaged vendetta modifier used for most things - self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) #talent specific modifiers if self.talents.elaborate_planning: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], all_damage=True)) if self.talents.hemorrhage: self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks', 't19_2pc'])) if self.talents.agonizing_poison: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], all_damage=True)) if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) @@ -810,7 +810,7 @@ def assassination_dps_breakdown(self): if self.traits.slayers_precision: self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', - 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], blacklist=True, all_damage=True)) + 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], all_damage=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: @@ -1239,7 +1239,7 @@ def outlaw_dps_breakdown(self): } self.damage_modifiers = modifiers.ModifierList(self.outlaw_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -1257,7 +1257,7 @@ def outlaw_dps_breakdown(self): # Trait specific modifiers if self.traits.cursed_steel: self.damage_modifiers.register_modifier(modifiers.DamageModifier('cursed_steel', - 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True, all_damage=True)) + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], all_damage=True)) stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) @@ -1767,11 +1767,11 @@ def subtlety_dps_breakdown(self): #set up damage modifier list and all relevant modifiers, use None for placeholder values self.damage_modifiers = modifiers.ModifierList(self.subtlety_damage_sources + ['autoattacks']) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'])) + 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) @@ -1806,7 +1806,7 @@ def subtlety_dps_breakdown(self): if self.traits.legionblade: self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', - 1.05 + (0.005 * (self.traits.legionblade - 1)), [], blacklist=True, all_damage=True)) + 1.05 + (0.005 * (self.traits.legionblade - 1)), [], all_damage=True)) #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: From 96808b3e6155ee083744455e44d82996fdac2dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 15:54:47 +0100 Subject: [PATCH 150/265] Convergence of Fates --- shadowcraft/calcs/rogue/__init__.py | 8 ++++++++ shadowcraft/objects/proc_data.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 1483266..4c2bde5 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -534,6 +534,14 @@ def get_spell_cd(self, ability): cd -= self.fortunes_boon_cdr[self.traits.fortunes_boon] elif ability == 'vendetta': cd -= self.master_assassin_cdr[self.traits.master_assassin] + + #Convergence of Fates Trinket + cof = self.stats.procs.convergence_of_fates + if cof and ability in ['vendetta', 'adrenaline_rush', 'shadow_blades']: + #We want time t in sec when CD is ready. CD goes down by 1 every sec plus 5 * proc_chance. + #That gives us: 0 = cd - t(1 + 5 * proc_chance) <=> t = cd / (1 + 5 * proc_chance) + cd /= 1 + 5 * cof.get_proc_rate(spec=self.spec) + return cd def crit_rate(self, crit=None): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 8fb923b..79d0fda 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -266,7 +266,7 @@ 'convergence_of_fates': { #Equip: Your attacks have a chance to reduce the remaining cooldown on one of your powerful abilities by 5 sec. 'stat':'ability_modifier', - 'value': 0, #needs special modeling + 'value': 0, 'duration': 0, 'proc_name': 'Prescience', # reduce cd of shadow blades, vendetta, adrenaline rush 'scaling': 0, #no values appear to scale From a887c22e9701750c59b259ffde833d8cda407237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Feb 2017 23:12:12 +0100 Subject: [PATCH 151/265] Procs off auto attacks when autoattack_hits is not set Also dual wield miss penalty seems to be 0.19 --- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index c37ee4e..c13185a 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -51,7 +51,7 @@ def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, lev self.base_parry_chance = .01 * self.level_difference self.base_dodge_chance = 0 - self.dw_miss_penalty = .17 + self.dw_miss_penalty = .19 self._set_constants_for_class() self.level = level diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0280c3c..fd42c84 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -352,6 +352,8 @@ def get_mh_procs_per_second(self, proc, attacks_per_second, crit_rates): else: if 'mh_autoattack_hits' in attacks_per_second: triggers_per_second += attacks_per_second['mh_autoattack_hits'] + elif 'mh_autoattacks' in attacks_per_second: + triggers_per_second += attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance if proc.procs_off_strikes(): for ability in ('mutilate', 'dispatch', 'backstab', 'pistol_shot', 'saber_slash', 'ambush', 'hemorrhage', 'mh_killing_spree', 'shuriken_toss'): if ability in attacks_per_second: @@ -383,6 +385,8 @@ def get_oh_procs_per_second(self, proc, attacks_per_second, crit_rates): else: if 'oh_autoattack_hits' in attacks_per_second: triggers_per_second += attacks_per_second['oh_autoattack_hits'] + elif 'oh_autoattacks' in attacks_per_second: + triggers_per_second += attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance if proc.procs_off_strikes(): for ability in ('mutilate', 'oh_killing_spree'): if ability in attacks_per_second: From f44da39f316251a45c2547387f3fee21884bc7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 27 Feb 2017 00:46:44 +0100 Subject: [PATCH 152/265] Nightblooming Frond --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- shadowcraft/calcs/rogue/__init__.py | 24 ++++++++++++++++++++ shadowcraft/objects/proc_data.py | 4 +++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index fd42c84..97a6c97 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -780,7 +780,7 @@ def assassination_dps_breakdown(self): self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'])) + 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'envenom', 'poison_bomb'])) @@ -1247,7 +1247,7 @@ def outlaw_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', - 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'])) + 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'], dmg_schools=['physical'])) # Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.16, ['death_from_above_pulse', 'death_from_above_strike', diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 4c2bde5..caa7e45 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -247,6 +247,8 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da # attacks_per_second is already being updated with that key. damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, modifier_dict) + self.add_special_procs_damage(current_stats, attacks_per_second, crit_rates, modifier_dict, damage_breakdown) + #compute damage breakdown for each spec if self.spec == 'assassination': @@ -550,3 +552,25 @@ def crit_rate(self, crit=None): base_crit = .10 base_crit += self.stats.get_crit_from_rating(crit) return base_crit + self.race.get_racial_crit(is_day=self.settings.is_day) - self.crit_reduction + + def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates, modifier_dict, damage_breakdown): + ap = current_stats['ap'] + current_stats['agi'] * self.stat_multipliers['ap'] + + # Nightblooming Frond + frond = self.stats.procs.nightblooming_frond + if frond: + autoattacks_per_second = attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() + autoattacks_per_second += attacks_per_second['oh_autoattacks'] * self.dual_wield_oh_hit_chance() + if 'shadow_blades' in attacks_per_second: + autoattacks_per_second += attacks_per_second['shadow_blades'] * 2 #both hands + + # calculate stacks for each second and accumulate bonus damage per proc + stack_list = [] + for second in range(1, frond.duration + 1): + stack_list.append(min(second * autoattacks_per_second, frond.max_stacks)) + stack_damage = self.get_proc_damage_contribution(frond, 1, current_stats, ap, modifier_dict) + proc_damage = 0 + for stack_count in stack_list: + proc_damage += stack_count * stack_damage * autoattacks_per_second + + damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 79d0fda..ea5cd65 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -461,8 +461,10 @@ 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional X damage and increase the intensity of Recursive Strikes. 'stat':'ability_modifier', - 'value': 0, #rpp-scaled, TODO: needs special modeling + 'value': 0, #rpp-scaled 'duration': 15, + 'max_stacks': 15, + 'dmg_school': 'physical', 'proc_name': 'Recursive Strikes', 'scaling': 2.12, 'item_level': 875, From 1d80be59e40504361c9a43c9128b4796e0aaa351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=83=3Fner?= Date: Mon, 27 Feb 2017 15:10:10 +0100 Subject: [PATCH 153/265] Draught of Souls ability downtime --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 18 ++++++++++++++++++ shadowcraft/calcs/rogue/__init__.py | 6 +++--- shadowcraft/objects/proc_data.py | 7 +++---- shadowcraft/objects/procs.py | 2 ++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 97a6c97..17995b5 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -641,6 +641,7 @@ def determine_stats(self, attack_counts_function): current_stats[k] += static_proc_stats[ k ] attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats) + self.add_special_aps_penalties(attacks_per_second) recalculate_crit = False #check need to converge @@ -680,6 +681,7 @@ def determine_stats(self, attack_counts_function): crit_rates = None recalculate_crit = False attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats, crit_rates=crit_rates) + self.add_special_aps_penalties(attacks_per_second) if self.are_close_enough(old_attacks_per_second, attacks_per_second): break @@ -696,6 +698,7 @@ def determine_stats(self, attack_counts_function): if recalculate_crit: crit_rates = None attacks_per_second, crit_rates, additional_info = attack_counts_function(current_stats, crit_rates=crit_rates) + self.add_special_aps_penalties(attacks_per_second) #some procs need specific prep, think RoRO/VoS self.setup_unique_procs(current_stats, current_stats['agi']+current_stats['ap']) @@ -707,6 +710,21 @@ def determine_stats(self, attack_counts_function): self.set_uptime(proc, attacks_per_second, crit_rates) return current_stats, attacks_per_second, crit_rates, damage_procs, additional_info + def add_special_aps_penalties(self, attacks_per_second): + #Draught of Souls Trinket, 3s ability downtime per use + dos = self.stats.procs.draught_of_souls + if dos: + lost_seconds = self.settings.duration * float(dos.duration) / float(dos.icd) + loss_ratio = (self.settings.duration - lost_seconds) / self.settings.duration + for attack in attacks_per_second: + if attack not in ['mh_autoattacks', 'oh_autoattacks', 'shadow_blades', 'nightblade_ticks', + 'rupture_ticks', 'from_the_shadows', 'kingsbane_ticks', 'garrote_ticks', 'deadly_poison']: + if isinstance(attacks_per_second[attack], list): + for i in range(len(attacks_per_second[attack])): + attacks_per_second[attack][i] *= loss_ratio + else: + attacks_per_second[attack] *= loss_ratio + def compute_damage_from_aps(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): # this method exists solely to let us use cached values you would get from determine stats # really only useful for outlaw calculations (restless blades calculations) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index caa7e45..22f1cd8 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -540,9 +540,9 @@ def get_spell_cd(self, ability): #Convergence of Fates Trinket cof = self.stats.procs.convergence_of_fates if cof and ability in ['vendetta', 'adrenaline_rush', 'shadow_blades']: - #We want time t in sec when CD is ready. CD goes down by 1 every sec plus 5 * proc_chance. - #That gives us: 0 = cd - t(1 + 5 * proc_chance) <=> t = cd / (1 + 5 * proc_chance) - cd /= 1 + 5 * cof.get_proc_rate(spec=self.spec) + #We want time t in sec when CD is ready. CD goes down by 1 every sec plus value * proc_chance. + #That gives us: 0 = cd - t(1 + value * proc_chance) <=> t = cd / (1 + value * proc_chance) + cd /= 1 + cof.value * cof.get_proc_rate(spec=self.spec) return cd diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index ea5cd65..40e672c 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -266,10 +266,9 @@ 'convergence_of_fates': { #Equip: Your attacks have a chance to reduce the remaining cooldown on one of your powerful abilities by 5 sec. 'stat':'ability_modifier', - 'value': 0, + 'value': 5, #5 sec decrease, modeled in get_spell_cd 'duration': 0, 'proc_name': 'Prescience', # reduce cd of shadow blades, vendetta, adrenaline rush - 'scaling': 0, #no values appear to scale 'item_level': 875, 'source': 'trinket', 'type': 'rppm', @@ -295,7 +294,7 @@ 'draught_of_souls': { #Use: Enter a fel-crazed rage, dealing X damage to a random nearby enemy every 0.25sec for 3 sec. You cannot move or use abilities during your rage. (1 Min, 20 Sec Cooldown) 'stat':'spell_damage', 'value': 0, #rpp-scaled - 'duration': 3, #TODO: 3sec ability downtime + 'duration': 3, #3sec ability downtime modeled in add_special_aps_penalties 'proc_name': 'Fel-Crazed Rage', 'dmg_school': 'shadow', 'scaling': 33. * 13., #13 hits total @@ -461,7 +460,7 @@ 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional X damage and increase the intensity of Recursive Strikes. 'stat':'ability_modifier', - 'value': 0, #rpp-scaled + 'value': 0, #rpp-scaled, modeled in add_special_procs_damage 'duration': 15, 'max_stacks': 15, 'dmg_school': 'physical', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 0b89daf..f9e5ddf 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -138,6 +138,8 @@ def get_base_proc_rate_for_spec(self, spec): return proc_rate def get_rppm_proc_rate(self, haste=1., spec=None): + if not self.haste_scales: #Failsafe to allow passing haste for non-scling procs + haste = 1. if self.is_real_ppm(): proc_rate = self.get_base_proc_rate_for_spec(spec) return haste * proc_rate * self.proc_rate_modifier From 9ee2d53efe950ec4c1b3b3f7cc0542f17475287f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 01:02:08 +0100 Subject: [PATCH 154/265] Let's also use gear_specialization when specified --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 ++- shadowcraft/objects/stats.py | 35 -------------------- 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 17995b5..f9bb533 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -213,8 +213,7 @@ def set_constants(self): } self.stat_multipliers = { 'str': 1., - #UI handling gear specialization for now - 'agi': 1, + 'agi': self.stats.gear_buffs.gear_specialization_multiplier(), 'ap': 1, 'crit': 1. + (0.02 * self.race.human_spirit), 'haste': 1. + (0.02 * self.race.human_spirit), @@ -574,7 +573,7 @@ def determine_stats(self, attack_counts_function): if self.buffs.felmouth_food(): self.stats.procs.set_proc('felmouth_frenzy') - if self.stats.gear_buffs.jacins_ruse_2pc_bonus(): + if self.stats.gear_buffs.jacins_ruse_2pc: self.stats.procs.set_proc('jacins_ruse_2pc') if self.stats.gear_buffs.march_of_the_legion_2pc and self.settings.is_demon: self.stats.procs.set_proc('march_of_the_legion_2pc') diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index da7d660..0284189 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -235,41 +235,6 @@ def rogue_t15_4pc_modifier(self, is_sb=False): #This is for Combat/Sub calcs return .85 # 1 - .15 return 1. - def rogue_t16_2pc_bonus(self): - if self.rogue_t16_2pc: - return True - return False - - def rogue_t16_4pc_bonus(self): - if self.rogue_t16_4pc: - return True - return False - - def rogue_t17_2pc_bonus(self): - if self.rogue_t17_2pc: - return True - return False - - def rogue_t17_4pc_bonus(self): - if self.rogue_t17_4pc: - return True - return False - - def rogue_t18_2pc_bonus(self): - if self.rogue_t18_2pc: - return True - return False - - def rogue_t18_4pc_bonus(self): - if self.rogue_T18_4pc: - return True - return False - - def jacins_ruse_2pc_bonus(self): - if self.jacins_ruse_2pc: - return True - return False - def gear_specialization_multiplier(self): if self.gear_specialization: return 1.05 From 60feedc440bb6298c966150d9ba360311d5e561e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 15:31:32 +0100 Subject: [PATCH 155/265] Adjust proc data for Nature's Call Still needs special modeling, added info as TODOs --- shadowcraft/objects/proc_data.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 40e672c..43e94b6 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -312,7 +312,7 @@ 'value': {'haste': 0, 'crit': 0, 'mastery': 0}, #TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 20, 'proc_name': 'Triumvirate', - 'scaling': 2.069368 / 3., #for now, assume we get all 3 for 1/3 each + 'scaling': 2.069368 / 3., #FIXME: for now using 1/3 for each stat / assume we get all 3 for 1/3 each 'crm_scales': True, 'item_level': 875, 'source': 'trinket', @@ -434,8 +434,8 @@ 'stat':'stats', 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #TODO: actually 1-3 stat buffs each time 'duration': 8, - 'proc_name': 'Memento of Angerboda', - 'scaling': 2.297781, #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'proc_name': 'Screams of the Dead', + 'scaling': 2.297781 / 3., #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci 'crm_scales': True, 'item_level': 805, 'source': 'trinket', @@ -446,15 +446,16 @@ 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 10 sec. 'stat':'stats', - 'value': {'haste': 0}, #rpp-scaled, TODO: check other possible effects and if this needs special modeling + 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #rpp-scaled, TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 10, 'proc_name': 'Allies of Nature', - 'scaling': 1.378778, - 'crm_scales': True, #TODO: also verify this and to which effects the mod applies + 'scaling': 1.378778 / 3., #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'crm_scales': True, 'item_level': 850, 'source': 'trinket', 'type': 'rppm', 'proc_rate': 2, + 'can_crit': True, #TODO: according to wowhead this can also proc Cleansed Drake's Breath for (scale factor 48.72993) damage 'trigger': 'all_attacks', }, From 627a97e63cd999576cf11b13cc117992b3947c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 16:40:36 +0100 Subject: [PATCH 156/265] Use uptime calculation for RPPM dot procs --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 8 ++++-- shadowcraft/calcs/rogue/__init__.py | 2 ++ shadowcraft/objects/proc_data.py | 29 ++++++++++++-------- shadowcraft/objects/procs.py | 6 ++-- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f9bb533..22e5a73 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -301,8 +301,10 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ average_hit = proc_value + proc.ap_coefficient * average_ap average_damage = average_hit * (1 + crit_rate * (crit_multiplier - 1)) * proc_count * multiplier - if proc.stat == 'physical_dot': - average_damage *= proc.uptime / proc_count + if proc.stat in ['physical_dot', 'spell_dot']: + initial_tick = 1. if proc.dot_initial_tick else 0. + ticks_per_second = float(proc.dot_ticks - initial_tick) / float(proc.duration) + average_damage *= initial_tick + ticks_per_second * proc.uptime / proc_count if proc.aoe: average_damage *= 1 + self.settings.num_boss_adds @@ -590,7 +592,7 @@ def determine_stats(self, attack_counts_function): active_procs_no_icd.append(proc) elif proc.stat == 'stats_modifier': active_procs_rppm_stat_mods.append(proc) - elif proc.stat in ('spell_damage', 'physical_damage', 'physical_dot'): + elif proc.stat in ('spell_damage', 'physical_damage', 'physical_dot', 'spell_dot'): damage_procs.append(proc) elif proc.stat == 'extra_weapon_damage': weapon_damage_procs.append(proc) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 22f1cd8..ef49885 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -245,6 +245,8 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if proc.proc_name not in damage_breakdown: # Toss multiple damage procs with the same name (Avalanche): # attacks_per_second is already being updated with that key. + if proc.stat in ['physical_dot', 'spell_dot']: + self.set_uptime(proc, attacks_per_second, crit_rates) damage_breakdown[proc.proc_name] = self.get_proc_damage_contribution(proc, attacks_per_second[proc.proc_name], current_stats, average_ap, modifier_dict) self.add_special_procs_damage(current_stats, attacks_per_second, crit_rates, modifier_dict, damage_breakdown) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 43e94b6..04b2512 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -162,11 +162,12 @@ }, 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 3 ticks of damage over 1.5 sec. - 'stat':'physical_damage', + 'stat':'physical_dot', 'value': 0, # AP based 'aoe': True, - 'ap_coefficient': 2.5, # server-side, not in dbc, per tick is 2.5 / 3 - 'duration': 0, + 'ap_coefficient': 2.5 / 3., # server-side, not in dbc, per tick is 2.5 / 3 + 'duration': 1.5, + 'dot_ticks': 3, 'proc_name': 'Mark of the Distant Army', 'type': 'rppm', 'source': 'neck', @@ -207,7 +208,7 @@ }, 'bloodstained_handkerchief': { #Use: Garrote your target from behind, causing them to bleed for X Physical damage every 3 sec until they die. (1 Min Cooldown) - 'stat':'physical_damage', + 'stat':'physical_damage', #modeled as icd because it's active FOREVER 'value': 0, #rpp-scaled, TODO: could be applied to adds as well, after CD 'duration': 0, 'proc_name': 'Cruel Garrote', @@ -505,12 +506,14 @@ }, 'six_feather_fan': { #Equip: Your attacks have a chance to launch a volley of 6 Wind Bolts, each dealing X Nature damage and slowing your target by 30% for 6 sec. - 'stat':'spell_damage', + 'stat':'spell_dot', 'dmg_school': 'nature', 'value': 0, #rpp-scaled - 'duration': 0, + 'duration': 5, + 'dot_ticks': 6, + 'dot_initial_tick': True, 'proc_name': 'Wind Bolt', - 'scaling': 19.01865 * 6., #6 bolts, one every second + 'scaling': 19.01865, #6 bolts, one every second 'item_level': 810, 'type': 'rppm', 'source': 'trinket', @@ -532,13 +535,14 @@ }, 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for X Physical damage every 0.75 sec. - 'stat':'physical_damage', + 'stat':'physical_dot', 'value': 0, #rpp-scaled 'aoe': True, - 'duration': 0, #accumulate all dmg + 'duration': 12, 'proc_name': 'Horrific Slam', #not the proc name but the dmg 'can_crit': True, - 'scaling': 10.1246 * 16., # 16 hits overall, hotfixed value + 'scaling': 10.1246, #hotfixed value + 'dot_ticks': 16, 'item_level': 850, 'type': 'rppm', 'source': 'trinket', @@ -547,12 +551,13 @@ }, 'tempered_egg_of_serpentrix': { #Equip: Your attacks have a chance to summon a Spawn of Serpentrix to assist you. - 'stat':'spell_damage', + 'stat':'spell_dot', 'dmg_school': 'fire', 'value': 0, #rpp-scaled 'duration': 15, + 'dot_ticks': 8, #pet might be scaling with haste, but most logs have 8 magma spits, assume that for now 'proc_name': 'Magma Spit', #not the proc name but the dmg of the add - 'scaling': 8.235604 * 8., # pet might be scaling with haste, but most logs have 8 magma spits, assume that for now + 'scaling': 8.235604, 'item_level': 805, 'source': 'trinket', 'type': 'rppm', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index f9e5ddf..66b8626 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -12,7 +12,7 @@ class Proc(object): def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True, stats=None, upgradable=False, scaling=None, buffs=None, base_value=0, type='rppm', icd=0, proc_rate=1.0, trigger='all_attacks', haste_scales=False, item_level=1, on_crit=False, on_procced_strikes=True, proc_rate_modifier=1., source='generic', att_spd_scales=False, - ap_coefficient=0., dmg_school=None, crm_scales=False, aoe=False): + ap_coefficient=0., dmg_school=None, crm_scales=False, aoe=False, dot_ticks=1, dot_initial_tick=False): self.stat = stat if stats is not None: self.stats = set(stats) @@ -41,6 +41,8 @@ def __init__(self, stat, value, duration, proc_name, max_stacks=1, can_crit=True self.dmg_school = dmg_school self.crm_scales = crm_scales self.aoe = aoe + self.dot_ticks = dot_ticks + self.dot_initial_tick = dot_initial_tick if self.dmg_school is None and stat in ['physical_damage', 'physical_dot']: self.dmg_school = 'physical' @@ -218,7 +220,7 @@ def get_all_damage_procs(self): for proc_name in self.allowed_procs: proc = getattr(self, proc_name) if proc: - if proc.stat in ('spell_damage', 'physical_damage'): + if proc.stat in ('spell_damage', 'physical_damage', 'physical_dot', 'spell_dot'): procs.append(proc) return procs \ No newline at end of file From f6b144c755aa934e144382732764dbd4bdc1c7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 17:39:31 +0100 Subject: [PATCH 157/265] Add more gear_buffs - Journey Through Time - Karazhan Empowered Trinkets - Rogue Class Order Hall Set --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 18 ++++++++++++++++++ shadowcraft/objects/proc_data.py | 13 ++++++++++++- shadowcraft/objects/stats.py | 4 ++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 22e5a73..6eb5658 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -221,6 +221,9 @@ def set_constants(self): 'versatility': 1. + (0.02 * self.race.human_spirit), } + if self.stats.gear_buffs.rogue_orderhall_6pc: + self.base_stats['agi'] += 500 + for boost in self.race.get_racial_stat_boosts(): if boost['stat'] in self.base_stats: self.base_stats[boost['stat']] += boost['value'] * boost['duration'] * 1.0 / (boost['cooldown'] + self.settings.response_time) @@ -579,6 +582,21 @@ def determine_stats(self, attack_counts_function): self.stats.procs.set_proc('jacins_ruse_2pc') if self.stats.gear_buffs.march_of_the_legion_2pc and self.settings.is_demon: self.stats.procs.set_proc('march_of_the_legion_2pc') + if self.stats.gear_buffs.rogue_orderhall_8pc: + self.stats.procs.set_proc('rogue_orderhall_8pc') + if self.stats.gear_buffs.journey_through_time_2pc and self.stats.procs.chrono_shard: + self.stats.procs.chrono_shard.update_proc_value() + self.stats.procs.chrono_shard.value['haste'] += 1000 + if self.stats.gear_buffs.kara_empowered_2pc: + if self.stats.procs.bloodstained_handkerchief: + self.stats.procs.bloodstained_handkerchief.update_proc_value() + self.stats.procs.bloodstained_handkerchief.value *= 1.3 + if self.stats.procs.eye_of_command: + self.stats.procs.eye_of_command.update_proc_value() + self.stats.procs.eye_of_command.value['crit'] *= 1.3 + if self.stats.procs.toe_knees_promise: + self.stats.procs.toe_knees_promise.update_proc_value() + self.stats.procs.toe_knees_promise.value *= 1.3 #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 04b2512..d637b02 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -253,7 +253,7 @@ 'chrono_shard': { #Equip: Your spells and abilities have a chance to grant you X Haste and 15% movement speed for 10 sec. 'stat':'stats', - 'value': {'haste': 0}, #rpp-scaled, TODO: set bonus? + 'value': {'haste': 0}, #rpp-scaled 'duration': 10, 'proc_name': 'Acceleration', 'scaling': 2.741159, @@ -695,6 +695,17 @@ 'trigger': 'all_attacks' }, + 'rogue_orderhall_8pc': { #Your finishing moves have a chance to increase your Haste by 2000 for 12 sec. + 'stat': 'stats', + 'value': {'haste': 2000}, + 'duration': 12, + 'proc_name': "Jacin's Ruse", + 'type': 'rppm', + 'source': 'unique', + 'proc_rate': 2, + 'trigger': 'all_attacks' #should be only finishing moves, but since it's rppm that doesn't matter + }, + #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 0284189..4bbae19 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -158,6 +158,10 @@ class GearBuffs(object): 'rogue_t19_4pc', # 10% envenom damage per bleed, 30% SSk generates additional CP if nightblade up 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM + 'journey_through_time_2pc', # The effect from Chrono Shard now increases your movement speed by 30%, and grants an additional 1000 Haste. + 'kara_empowered_2pc', # 30% increase to paired trinkets + 'rogue_orderhall_6pc', # Agility increased by 500 + 'rogue_orderhall_8pc', # Your finishing moves have a chance to increase your Haste by 2000 for 12 sec. #Legendaries 'the_dreadlords_deceit', #fok/ssk damage increased by 35% per 2 seconds up to 1 minute 'duskwalker_footpads', #Vendetta CD reduced by 1 second for each 50 energy spent From 297c7004a9ef161e7bf15aef7440e3f622d5ca9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 18:26:15 +0100 Subject: [PATCH 158/265] Add function to get stats from the calculator --- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index c13185a..3c91a64 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -110,7 +110,7 @@ def set_rppm_uptime(self, proc): #http://iam.yellingontheinternet.com/2013/04/12/theorycraft-201-advanced-rppm/ haste = 1. if proc.haste_scales: - haste *= self.stats.get_haste_multiplier_from_rating(self.base_stats['haste']) * self.true_haste_mod + haste *= self.stats.get_haste_multiplier_from_rating(self.base_stats['haste'] * self.stat_multipliers['haste']) * self.true_haste_mod if proc.att_spd_scales: haste *= 1.4 #The 1.1307 is a value that increases the proc rate due to bad luck prevention. It /should/ be constant among all rppm proc styles diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 6eb5658..a886c6d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -254,6 +254,17 @@ def set_constants(self): self.dw_oh_hit_chance = self.dual_wield_oh_hit_chance() return self + # returns stats used for calculation, including all static gear and buff bonuses that are also displayed in the character panel + def get_character_stats(self): + stats = { } + try: #need to do it this way for now because object.__getattribute__ in calcs __getattr__ will throw up :/ + noop = self.base_stats + except: + self.set_constants() #make this function work before dps calculation if needed + for stat in self.base_stats: + stats[stat] = self.base_stats[stat] * self.stat_multipliers[stat] + return stats + def load_from_advanced_parameters(self): self.true_haste_mod = self.get_adv_param('haste_buff', 1., min_bound=.1, max_bound=3.) @@ -480,7 +491,7 @@ def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): #http://us.battle.net/wow/en/forum/topic/8197741003?page=4#79 haste = 1. if proc.haste_scales: - haste *= self.true_haste_mod * self.stats.get_haste_multiplier_from_rating(self.base_stats['haste']) + haste *= self.true_haste_mod * self.stats.get_haste_multiplier_from_rating(self.base_stats['haste'] * self.stat_multipliers['haste']) if proc.att_spd_scales: haste *= 1.4 #The 1.1307 is a value that increases the proc rate due to bad luck prevention. It /should/ be constant among all rppm proc styles From 3353f646991f84f92c52b6d15a4814df6162db9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Feb 2017 23:11:50 +0100 Subject: [PATCH 159/265] Make Terrorbound Nexus not OP --- shadowcraft/objects/proc_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index d637b02..6bba682 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -574,7 +574,7 @@ 'aoe': True, 'duration': 0, 'proc_name': 'Shadow Wave', - 'scaling': 46.22871 * 8., #assuming 4 hits out and 4 in + 'scaling': 46.22871 * 2., #judging from logs wave damage only occurs twice (forth and back), 4 waves seem to be visual 'item_level': 805, 'type': 'rppm', 'source': 'trinket', From 18f573b0c123edcaffbde3340ffd233daf13f1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 1 Mar 2017 16:57:47 +0100 Subject: [PATCH 160/265] Nerf Tiny Oozeling --- shadowcraft/calcs/rogue/__init__.py | 8 ++++++++ shadowcraft/objects/proc_data.py | 14 ++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index ef49885..30fe48a 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -576,3 +576,11 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates proc_damage += stack_count * stack_damage * autoattacks_per_second damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) + + # Tiny Oozeling in a Jar + oozeling = self.stats.procs.tiny_oozeling_in_a_jar + if oozeling: + haste = self.get_haste_multiplier(current_stats) + stacks_per_use = min(oozeling.icd * haste * 3 / 60, 6) #3 rppm, capped at 6 stacks + damage_per_use = stacks_per_use * self.get_proc_damage_contribution(oozeling, 1, current_stats, ap, modifier_dict) + damage_breakdown[oozeling.proc_name] = damage_per_use / oozeling.icd diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 6bba682..56605c4 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -600,20 +600,18 @@ }, 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting X Nature damage per Goo consumed. (20 Sec Cooldown) - 'stat':'spell_damage', + 'stat':'special_model', 'dmg_school': 'nature', - 'value': 0, #rpp-scaled + 'value': 0, #rpp-scaled, trinket damage modeled in add_special_procs_damage 'aoe': True, 'duration': 0, - #'max_stacks': 6, 'proc_name': 'Fetid Regurgitation', - 'scaling': 17.123 * 6., #assume 6 stacks, scaling value not found in DBC, server-side/hotfixed? calculated by hand + 'scaling': 2.853606 * 6, #hit per stack, hitting for scaled value every 0.5 for 3 seconds = 6 ticks 'item_level': 805, - 'type': 'icd', #actually rppm + 'type': 'icd', 'source': 'trinket', - #'proc_rate': 3, - 'icd': 20, #modeled as used on cooldown with max stacks every time - #'haste_scales': True, + 'proc_rate': 1, #for on use, rppm for stacks is 3 (w/ haste), max is 6 stacks + 'icd': 20, 'can_crit': True, 'trigger': 'all_attacks' }, From 5eeeeca6d133730abb64827a649c3623e5b9a665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 1 Mar 2017 19:16:21 +0100 Subject: [PATCH 161/265] Basic implementation for Tirathon's Betrayal and Faulty Countermeasure --- shadowcraft/calcs/rogue/__init__.py | 14 +++++++++++--- shadowcraft/objects/proc_data.py | 10 ++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 30fe48a..7b9fd38 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -575,12 +575,20 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates for stack_count in stack_list: proc_damage += stack_count * stack_damage * autoattacks_per_second - damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) + damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) * 1.1307 #BLP # Tiny Oozeling in a Jar oozeling = self.stats.procs.tiny_oozeling_in_a_jar if oozeling: haste = self.get_haste_multiplier(current_stats) - stacks_per_use = min(oozeling.icd * haste * 3 / 60, 6) #3 rppm, capped at 6 stacks - damage_per_use = stacks_per_use * self.get_proc_damage_contribution(oozeling, 1, current_stats, ap, modifier_dict) + stacks_per_use = min(oozeling.icd * haste * 1.1307 * 3 / 60, 6) #3 rppm, capped at 6 stacks, 1.1307 bad luck protection + damage_per_use = self.get_proc_damage_contribution(oozeling, stacks_per_use, current_stats, ap, modifier_dict) damage_breakdown[oozeling.proc_name] = damage_per_use / oozeling.icd + + # Tirathon's Betrayal and Faulty Countermeasure + for proc in [self.stats.procs.tirathons_betrayal, self.stats.procs.faulty_countermeasure]: + if proc: + # both 20 RPPM with haste mod + procs_per_use = proc.duration * 20 * 1.1307 * self.get_haste_multiplier(current_stats) / 60 + damage_per_use = self.get_proc_damage_contribution(proc, procs_per_use, current_stats, ap, modifier_dict) + damage_breakdown[proc.proc_name] = damage_per_use / proc.icd diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 56605c4..4ee8d0c 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -339,10 +339,11 @@ }, 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause X additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) - 'stat':'ability_modifier', + 'stat':'ability_modifier', #modeled in add_special_procs_damage 'value': 0, #rpp-scaled 'duration': 30, - 'proc_name': 'Sheathed in Frost', #TODO: need special handling, rppm during uptime is 20 and scales with haste + 'proc_name': 'Sheathed in Frost', + 'dmg_school': 'frost', 'scaling': 17.47413, 'item_level': 805, 'type': 'icd', @@ -617,10 +618,11 @@ }, 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) - 'stat':'ability_modifier', + 'stat':'ability_modifier', #modeled in add_special_procs_damage 'value': 0, 'duration': 15, - 'proc_name': 'Darkstrikes', #TODO: need special handling + 'proc_name': 'Darkstrikes', + 'dmg_school': 'shadow', 'scaling': 16.11315, 'item_level': 805, 'type': 'icd', From fd2568acaaaf1f1ae9c8cffb9af9a638d90d99c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 5 Mar 2017 17:35:46 +0100 Subject: [PATCH 162/265] Refactor code to get character stats This was changed for giving us the ability to return armory comparable stats for the UI --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 40 ++--------------- shadowcraft/objects/buffs.py | 10 +++++ shadowcraft/objects/stats.py | 46 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a886c6d..8303612 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -203,26 +203,8 @@ def set_constants(self): #only include if general multiplier applies to spec calculations self.true_haste_mod *= self.get_heroism_haste_multiplier() - self.base_stats = { - 'agi': (self.stats.agi + self.buffs.buff_agi(race=self.race.epicurean) + self.race.racial_agi), - 'ap': (self.stats.ap), - 'crit': (self.stats.crit + self.buffs.buff_crit(race=self.race.epicurean)), - 'haste': (self.stats.haste + self.buffs.buff_haste(race=self.race.epicurean)), - 'mastery': (self.stats.mastery + self.buffs.buff_mast(race=self.race.epicurean)), - 'versatility': (self.stats.versatility + self.buffs.buff_versatility(race=self.race.epicurean)), - } - self.stat_multipliers = { - 'str': 1., - 'agi': self.stats.gear_buffs.gear_specialization_multiplier(), - 'ap': 1, - 'crit': 1. + (0.02 * self.race.human_spirit), - 'haste': 1. + (0.02 * self.race.human_spirit), - 'mastery': 1. + (0.02 * self.race.human_spirit), - 'versatility': 1. + (0.02 * self.race.human_spirit), - } - - if self.stats.gear_buffs.rogue_orderhall_6pc: - self.base_stats['agi'] += 500 + self.base_stats = self.stats.get_character_base_stats(self.race, self.buffs) + self.stat_multipliers = self.stats.get_character_stat_multipliers(self.race) for boost in self.race.get_racial_stat_boosts(): if boost['stat'] in self.base_stats: @@ -237,9 +219,6 @@ def set_constants(self): if self.stats.procs.draenic_agi_prepot: getattr(self.stats.procs, 'draenic_agi_prepot').icd = self.settings.duration - self.base_strength = self.stats.str + self.race.racial_str - self.base_intellect = self.stats.int + self.race.racial_int - self.relentless_strikes_energy_return_per_cp = 5 #.20 * 25 #should only include bloodlust if the spec can average it in, deal with this later @@ -254,17 +233,6 @@ def set_constants(self): self.dw_oh_hit_chance = self.dual_wield_oh_hit_chance() return self - # returns stats used for calculation, including all static gear and buff bonuses that are also displayed in the character panel - def get_character_stats(self): - stats = { } - try: #need to do it this way for now because object.__getattribute__ in calcs __getattr__ will throw up :/ - noop = self.base_stats - except: - self.set_constants() #make this function work before dps calculation if needed - for stat in self.base_stats: - stats[stat] = self.base_stats[stat] * self.stat_multipliers[stat] - return stats - def load_from_advanced_parameters(self): self.true_haste_mod = self.get_adv_param('haste_buff', 1., min_bound=.1, max_bound=3.) @@ -569,7 +537,7 @@ def get_average_alacrity(self, attacks_per_second): def determine_stats(self, attack_counts_function): current_stats = { - 'str': self.base_strength, + 'str': self.base_stats['str'] * self.stat_multipliers['str'], 'agi': self.base_stats['agi'] * self.stat_multipliers['agi'], 'ap': self.base_stats['ap'] * self.stat_multipliers['ap'], 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], @@ -681,7 +649,7 @@ def determine_stats(self, attack_counts_function): need_converge = True while (need_converge or self.spec_needs_converge): current_stats = { - 'str': self.base_strength, + 'str': self.base_stats['str'] * self.stat_multipliers['str'], 'agi': self.base_stats['agi'] * self.stat_multipliers['agi'], 'ap': self.base_stats['ap'] * self.stat_multipliers['ap'], 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index a842a48..a7c8732 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -89,6 +89,16 @@ def __getattr__(self, name): def __setattr__(self, name, value): object.__setattr__(self, name, value) + def get_stat_bonuses(self, epicurean=False): + bonuses = { + 'agi': self.buff_agi(epicurean), + 'crit': self.buff_crit(epicurean), + 'haste': self.buff_haste(epicurean), + 'mastery': self.buff_mast(epicurean), + 'versatility': self.buff_versatility(epicurean), + } + return bonuses + def buff_agi(self, race=False): bonus_agi = 0 bonus_agi += 200 * self.flask_wod_agi_200 diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 4bbae19..e688bb8 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -1,5 +1,7 @@ +from shadowcraft.objects import buffs from shadowcraft.objects import procs from shadowcraft.objects import proc_data +from shadowcraft.objects import race from shadowcraft.core import exceptions class Stats(object): @@ -48,6 +50,50 @@ def __setattr__(self, name, value): if name == 'level' and value is not None: self._set_constants_for_level() + def get_character_base_stats(self, race, buffs=None): + base_stats = { + 'str': self.str + race.racial_str, + 'int': self.int + race.racial_int, + 'agi': self.agi + race.racial_agi, + 'ap': self.ap, + 'crit': self.crit, + 'haste': self.haste, + 'mastery': self.mastery, + 'versatility': self.versatility, + } + if buffs: + buff_bonuses = buffs.get_stat_bonuses(race.epicurean) + for bonus in buff_bonuses: + base_stats[bonus] += buff_bonuses[bonus] + + # Other bonuses + if self.gear_buffs.rogue_orderhall_6pc: + base_stats['agi'] += 500 + + return base_stats + + def get_character_stat_multipliers(self, race): + # assume rogue for gear spec + stat_multipliers = { + 'str': 1., + 'int': 1., + 'agi': self.gear_buffs.gear_specialization_multiplier(), + 'ap': 1, + 'crit': 1. + (0.02 * race.human_spirit), + 'haste': 1. + (0.02 * race.human_spirit), + 'mastery': 1. + (0.02 * race.human_spirit), + 'versatility': 1. + (0.02 * race.human_spirit), + } + return stat_multipliers + + def get_character_stats(self, race, buffs=None): + base = self.get_character_base_stats(race, buffs) + mult = self.get_character_stat_multipliers(race) + stats = { } + for stat in base: + stats[stat] = base[stat] * mult[stat] + return stats + def get_mastery_from_rating(self, rating=None): if rating is None: rating = self.mastery From b21d445fe485fbc1da5073d80dbdd44b04a0dba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 6 Mar 2017 18:28:07 +0100 Subject: [PATCH 163/265] Fix for Chaos Talisman and Eye of Command stacking --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 +-- shadowcraft/objects/proc_data.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 8303612..bfc7b6e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -663,8 +663,7 @@ def determine_stats(self, attack_counts_function): for proc in active_procs_no_icd: self.set_uptime(proc, attacks_per_second, crit_rates) for e in proc.value: - if e in self.spec_convergence_stats: - convergence_stats = True + convergence_stats = True if e == 'crit': recalculate_crit = True current_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 4ee8d0c..0342c84 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -246,7 +246,7 @@ 'item_level': 805, 'source': 'trinket', 'type': 'icd', - 'icd': 1, + 'icd': 0, # stacks with every autohit 'proc_rate': 1, 'trigger': 'auto_attacks', }, @@ -333,7 +333,7 @@ 'item_level': 860, 'source': 'trinket', 'type': 'icd', - 'icd': 1, + 'icd': 0, # stacks with every autohit 'proc_rate': 1, 'trigger': 'auto_attacks', }, From b4cb2793f23f29f6d8a1f1f69b3ef05c057dfa2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=83=3Fner?= Date: Tue, 7 Mar 2017 15:54:15 +0100 Subject: [PATCH 164/265] Set racial_X base stats when changing level externally Also toss unused function and update comment in Stats --- shadowcraft/objects/race.py | 12 +----------- shadowcraft/objects/stats.py | 9 ++++----- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/shadowcraft/objects/race.py b/shadowcraft/objects/race.py index f244a86..fe76c57 100755 --- a/shadowcraft/objects/race.py +++ b/shadowcraft/objects/race.py @@ -139,6 +139,7 @@ def _set_constants_for_level(self): self.activated_racial_data["blood_fury_spell"]["value"] = self.blood_fury_bonuses[self.level]["sp"] # this merges racial stats with class stats (ie, racial_stat_offset and rogue_base_stats) self.stats = map(sum, zip(self.stats, Race.racial_stat_offset[self.race_name])) + self.set_racials() except KeyError as e: raise InvalidRaceException(_('Unsupported class/level combination {character_class}/{level}').format(character_class=self.character_class, level=self.level)) @@ -149,17 +150,6 @@ def __getattr__(self, name): else: object.__getattribute__(self, name) - def get_stats_from_race(self, level, secondaries=False): - str = Race.rogue_base_stats[level][0] + Race.racial_stat_offset[self.race_name][0] - agi = Race.rogue_base_stats[level][1] + Race.racial_stat_offset[self.race_name][1] - sta = Race.rogue_base_stats[level][2] + Race.racial_stat_offset[self.race_name][2] - int = Race.rogue_base_stats[level][3] + Race.racial_stat_offset[self.race_name][3] - spi = Race.rogue_base_stats[level][4] + Race.racial_stat_offset[self.race_name][4] - if secondaries: - return {'agi':agi, 'str':str, 'sta':sta, 'int':int, 'spi':spi, - 'readiness':0, 'multistrike':0, 'versatility':0, 'haste':0, 'crit':0, 'mastery':0} - return {'agi':agi, 'str':str, 'sta':sta, 'int':int, 'spi':spi} - def get_racial_crit(self, is_day=False): crit_bonus = 0 if self.viciousness: diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index e688bb8..5e3ad3d 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -5,11 +5,10 @@ from shadowcraft.core import exceptions class Stats(object): - # For the moment, lets define this as raw stats from gear + race; AP is - # only AP bonuses from gear and level. Do not include multipliers like - # Vitality and Sinister Calling; this is just raw stats. See calcs page - # rows 1-9 from my WotLK spreadsheets to see how these are typically - # defined, though the numbers will need to updated for level 85. + # For the moment, lets define this as raw stats from gear + # AP is only AP bonuses from gear (as of Legion usually 0) + # Other base stat bonuses are added in get_character_base_stats + # Multipliers are added in get_character_stat_multipliers crit_rating_conversion_values = {60:13.0, 70:14.0, 80:15.0, 85:17.0, 90:23.0, 100:110.0, 110:400.0} haste_rating_conversion_values = {60:9.00, 70:10.0, 80:12.0, 85:14.0, 90:20.0, 100:100.0, 110:375.0} From f23530939cecef34d47492d932dbd5057e2a4471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 8 Mar 2017 15:48:28 +0100 Subject: [PATCH 165/265] Use git commits for build number --- shadowcraft/calcs/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 3c91a64..30b1e3d 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -1,6 +1,8 @@ import gettext import __builtin__ import math +import os +import subprocess __builtin__._ = gettext.gettext @@ -28,7 +30,7 @@ class DamageCalculator(object): def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): self.WOW_BUILD_TARGET = '7.1.5' # should reflect the game patch being targetted - self.SHADOWCRAFT_BUILD = '0.02' # <1 for beta builds, 1.00 is GM, >1 for any bug fixes, reset for each warcraft patch + self.SHADOWCRAFT_BUILD = self.get_version_string() self.tools = class_data.Util() self.stats = stats self.talents = talents @@ -89,6 +91,14 @@ def _set_constants_for_class(self): # datamine for all classes/specs at once. self.game_class = self.talents.game_class + def get_version_string(self): + thisdir = os.path.dirname(os.path.abspath(__file__)) + build = subprocess.check_output('git rev-list --count HEAD', cwd=thisdir).strip() + commit = subprocess.check_output('git rev-parse --short HEAD', cwd=thisdir).strip() + if build and commit: + return '{0} ({1})'.format(build, commit) + return 'UNKNOWN' + def recalculate_hit_constants(self): self.base_dw_miss_rate = self.base_one_hand_miss_rate + self.dw_miss_penalty From baa6be3d8260bedd080a7f7744ebad429c0d3f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=83=3Fner?= Date: Wed, 8 Mar 2017 17:11:08 +0100 Subject: [PATCH 166/265] Fix build number for Linux systems and add number check --- shadowcraft/calcs/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 30b1e3d..a23e309 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -92,11 +92,14 @@ def _set_constants_for_class(self): self.game_class = self.talents.game_class def get_version_string(self): - thisdir = os.path.dirname(os.path.abspath(__file__)) - build = subprocess.check_output('git rev-list --count HEAD', cwd=thisdir).strip() - commit = subprocess.check_output('git rev-parse --short HEAD', cwd=thisdir).strip() - if build and commit: - return '{0} ({1})'.format(build, commit) + try: + thisdir = os.path.dirname(os.path.abspath(__file__)) + build = subprocess.check_output(['git', 'rev-list', '--count', 'HEAD'], cwd=thisdir).strip() + commit = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=thisdir).strip() + if build.isdigit() and commit: + return '{0} ({1})'.format(build, commit) + except: + pass return 'UNKNOWN' def recalculate_hit_constants(self): From 8b02357542d399e7bc181d6374c9b5b1c1865b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 8 Mar 2017 23:08:45 +0100 Subject: [PATCH 167/265] Run futurize over source code --- scripts/assassination.py | 20 +- scripts/assassination_import.py | 28 +- scripts/combat_import.py | 26 +- scripts/dm_combat.py | 22 +- scripts/import_character.py | 26 +- scripts/outlaw.py | 20 +- scripts/subtlety.py | 110 ++++-- scripts/subtlety_import.py | 26 +- scripts/wowapi/wowapi/api.py | 19 +- scripts/wowapi/wowapi/utilities.py | 4 +- shadowcraft/__init__.py | 6 +- shadowcraft/calcs/__init__.py | 43 ++- shadowcraft/calcs/rogue/Aldriana/__init__.py | 350 ++++++++++--------- shadowcraft/calcs/rogue/Aldriana/settings.py | 1 + shadowcraft/calcs/rogue/__init__.py | 17 +- shadowcraft/core/__init__.py | 6 +- shadowcraft/core/exceptions.py | 1 + shadowcraft/core/i18n.py | 15 +- shadowcraft/core/jsoninput.py | 16 +- shadowcraft/objects/__init__.py | 6 +- shadowcraft/objects/artifact.py | 4 +- shadowcraft/objects/buffs.py | 1 + shadowcraft/objects/class_data.py | 4 +- shadowcraft/objects/modifiers.py | 7 +- shadowcraft/objects/priority_list.py | 4 +- shadowcraft/objects/proc_data.py | 10 +- shadowcraft/objects/procs.py | 1 + shadowcraft/objects/race.py | 9 +- shadowcraft/objects/stats.py | 17 +- shadowcraft/objects/talents.py | 9 +- test_ui/testing_ui.py | 27 +- test_ui/ui_data.py | 11 +- tests/calcs_tests/rogue_tests/__init__.py | 5 +- tests/core_tests/exceptions_tests.py | 1 + tests/objects_tests/stats_tests.py | 18 +- tests/runtests.py | 31 +- 36 files changed, 531 insertions(+), 390 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index e355dc4..14b01b8 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with assassination models. +from builtins import str +from past.utils import old_div from os import path import sys from pprint import pprint @@ -95,7 +99,7 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) # Compute EP values. #ep_values = calculator.get_ep(baseline_dps=total_dps) @@ -108,7 +112,7 @@ def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -118,15 +122,15 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1])/total_sum)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' + if show_percent and ("{0:.2f}".format(old_div(float(value[1]),total_sum))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [ #ep_values, @@ -139,6 +143,6 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): pretty_print(dicts_for_pretty_print) #pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) -print ' ' * (max_length([dps_breakdown]) + 1), total_dps, ("total damage per second.") +print(' ' * (max_length([dps_breakdown]) + 1), total_dps, ("total damage per second.")) #pprint(talent_ranks) diff --git a/scripts/assassination_import.py b/scripts/assassination_import.py index 7845b92..cb16a9f 100755 --- a/scripts/assassination_import.py +++ b/scripts/assassination_import.py @@ -1,4 +1,6 @@ +from __future__ import print_function # Simple test program to debug + play with assassination models. +from builtins import str from os import path import sys from import_character import CharacterData @@ -31,7 +33,7 @@ charInfo[ terms[0] ] = terms[1] key += 1 -print "Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n" +print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) character_data.do_import() @@ -62,7 +64,7 @@ # Set up procs. character_procs = character_data.get_procs() -character_procs_allowed = filter(lambda p: p in proc_data.allowed_procs, character_procs) +character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] #not_allowed_procs = set(character_procs) - set(character_procs_allowed) #print not_allowed_procs @@ -105,15 +107,15 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() non_execute_breakdown = calculator.assassination_dps_breakdown_non_execute() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) -non_execute_total = sum(entry[1] for entry in non_execute_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) +non_execute_total = sum(entry[1] for entry in list(non_execute_breakdown.items())) talent_ranks = calculator.get_talents_ranking() heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -123,15 +125,15 @@ def pretty_print(dict_list, total_sum = 1.): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) if ("{0:.2f}".format(10*float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [ ep_values, @@ -140,8 +142,8 @@ def pretty_print(dict_list, total_sum = 1.): dps_breakdown ] pretty_print(dicts_for_pretty_print, total_sum=total_dps) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") -print '' -print 'non-execute breakdown: ' +print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) +print('') +print('non-execute breakdown: ') pretty_print([non_execute_breakdown], total_sum=non_execute_total) -print ' ' * (max_length([non_execute_breakdown]) + 1), non_execute_total, _("total damage per second.") +print(' ' * (max_length([non_execute_breakdown]) + 1), non_execute_total, _("total damage per second.")) diff --git a/scripts/combat_import.py b/scripts/combat_import.py index 7d32248..08235a1 100755 --- a/scripts/combat_import.py +++ b/scripts/combat_import.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with combat models. +from builtins import str +from past.utils import old_div from os import path import sys from import_character import CharacterData @@ -32,7 +36,7 @@ charInfo[ terms[0] ] = terms[1] key += 1 -print "Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n" +print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) character_data.do_import() @@ -64,7 +68,7 @@ # Set up procs. character_procs = character_data.get_procs() -character_procs_allowed = filter(lambda p: p in proc_data.allowed_procs, character_procs) +character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] #not_allowed_procs = set(character_procs) - set(character_procs_allowed) #print not_allowed_procs @@ -93,7 +97,7 @@ # Set up settings. if character_data.get_mh_type() == 'dagger': - print "\nALERT: Dagger found. Playing combat with a dagger should be a last resort, and is not recommended. \n\n" + print("\nALERT: Dagger found. Playing combat with a dagger should be a last resort, and is not recommended. \n\n") test_cycle = settings.OutlawCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) @@ -106,7 +110,7 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) talent_ranks = calculator.get_talents_ranking() heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) @@ -116,7 +120,7 @@ def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -126,14 +130,14 @@ def pretty_print(dict_list): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)' + if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [ weapon_type_mod, @@ -143,4 +147,4 @@ def pretty_print(dict_list): dps_breakdown ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") +print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) diff --git a/scripts/dm_combat.py b/scripts/dm_combat.py index d5b7160..1dbe4ec 100644 --- a/scripts/dm_combat.py +++ b/scripts/dm_combat.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with assassination models. +from builtins import str +from past.utils import old_div from os import path import sys sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) @@ -85,12 +89,12 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -100,16 +104,16 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)' + if show_percent and ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) -print ' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.") -print "Request time: %s sec" % (time.time() - start) \ No newline at end of file +print(' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.")) +print("Request time: %s sec" % (time.time() - start)) \ No newline at end of file diff --git a/scripts/import_character.py b/scripts/import_character.py index 8b70674..f69013e 100755 --- a/scripts/import_character.py +++ b/scripts/import_character.py @@ -1,5 +1,11 @@ +from __future__ import division +from __future__ import print_function # Original Code by by Ayliex @ EJ ( https://github.com/postrov/sc-character-import ) # -*- coding: utf-8 -*- +from builtins import str +from builtins import range +from past.utils import old_div +from builtins import object from os import path from types import * import sys @@ -15,7 +21,7 @@ pp = pprint.PrettyPrinter(indent=4) -class ItemDB: +class ItemDB(object): def __init__(self): pass @@ -66,7 +72,7 @@ def get_item_cached(region, id): item_db.add_item(id, item) return item -class CharacterData: +class CharacterData(object): races = {1 : 'human', 2 : 'orc', 3 : 'dwarf', @@ -257,7 +263,7 @@ def get_weapon(self, weapon_data, item_data): 20:'fishing_pole'} tmpItem = get_item_cached(self.region, item_data[u'id']) damage_info = weapon_info[u'damage'] - damage = (damage_info[u'max'] + damage_info[u'min']) / 2 + damage = old_div((damage_info[u'max'] + damage_info[u'min']), 2) speed = weapon_info[u'weaponSpeed'] type = weaponMap[ tmpItem['data'][u'itemSubClass'] ] enchant = CharacterData.enchants[item_data[u'tooltipParams'][u'enchant']] @@ -349,7 +355,7 @@ def get_gear_stats(self): if u'reforge' in self.raw_data['data'][u'items'][p][u'tooltipParams']: reforgeID = self.raw_data['data'][u'items'][p][u'tooltipParams'][u'reforge'] #if we have data on the reforge - if reforgeID in CharacterData.reforgeMap.keys(): + if reforgeID in list(CharacterData.reforgeMap.keys()): reforge = CharacterData.reforgeMap[reforgeID] #for each stat on the gear for key in tmpItem[u'data'][u'bonusStats']: @@ -375,7 +381,7 @@ def get_gear_stats(self): gemCount = 0 for gemNumber in range(3): gemId = 'gem' + str(gemNumber) - if gemId in params.keys(): + if gemId in list(params.keys()): gemCount += 1 tmpGem = get_item_cached(self.region, params[gemId]) if not socketInfo == None: @@ -404,7 +410,7 @@ def get_gear_stats(self): lst[ tmpStat ] += tmpVal self.verbose_print('Socket bonus +' + str(tmpVal) + ' ' + tmpStat) #add stats from enchants - if u'enchant' in params.keys(): + if u'enchant' in list(params.keys()): if not type( CharacterData.enchants[ params[u'enchant'] ] ) == type(''): for key in CharacterData.enchants[ params[u'enchant'] ]: lst[ key['stat'] ] += key['value'] @@ -415,9 +421,9 @@ def get_gear_stats(self): self.verbose_print('Unenchanted') except Exception as inst: #it's okay, we can keep going, just so long as we pretend to handle the exception - print "\n" - print "Error at slot: ", p - print "Error type: ", type(inst) + print("\n") + print("Error at slot: ", p) + print("Error type: ", type(inst)) raise if self.verbose: pp.pprint(lst) @@ -455,4 +461,4 @@ def get_glyphs(self): def verbose_print(self, str): if self.verbose: - print str + print(str) diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 0c4013f..311e336 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with assassination models. +from builtins import str +from past.utils import old_div from os import path import sys from pprint import pprint @@ -78,7 +82,7 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) # Compute EP values. #ep_values = calculator.get_ep(baseline_dps=total_dps) @@ -92,7 +96,7 @@ def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -102,16 +106,16 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), #str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1]) / total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' (' + str("{0:.2f}".format(100 * float(value[1]) / total_sum)) + '%)' + if show_percent and ("{0:.2f}".format(old_div(float(value[1]), total_dps))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' (' + str("{0:.2f}".format(100 * float(value[1]) / total_sum)) + '%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [#ep_values, #tier_ep_values, @@ -121,6 +125,6 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): #trait_ranks ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") +print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.")) #pprint(talent_ranks) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 5a91386..a8772cb 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with subtlety models. +from builtins import str +from past.utils import old_div from os import path import sys from pprint import pprint @@ -22,65 +26,88 @@ # Set up level/class/race test_level = 110 -test_race = race.Race('human') +test_race = race.Race('blood_elf', level=110) test_class = 'rogue' test_spec = 'subtlety' # Set up buffs. test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'flask_wod_agi', - 'food_wod_versatility', + 'flask_legion_agi', + 'food_legion_mastery_375', #'food_legion_feast_150' ) # Set up weapons. -test_mh = stats.Weapon(4821.0, 1.8, 'dagger', None) -test_oh = stats.Weapon(4821.0, 1.8, 'dagger', None) +test_mh = stats.Weapon(5442.0, 1.8, 'dagger', None) +test_oh = stats.Weapon(5442.0, 1.8, 'dagger', None) # Set up procs. - trinkets, other things (legendary procs) #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), # 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') +test_procs = procs.ProcsList( + 'mark_of_the_hidden_satyr', + #'mark_of_the_distant_army', + ('convergence_of_fates', 890), + ('nightblooming_frond', 905), + #('tirathons_betrayal', 840), + #('faulty_countermeasure', 840), + #('kiljaedens_burning_wish', 940) +) + +""" +# test all procs +from shadowcraft.objects import proc_data test_procs = procs.ProcsList() +for key in proc_data.allowed_procs.keys(): + test_procs.set_proc(key) + + +# Debug prints for scaled trinket values +for proc in test_procs.get_all_procs_for_stat(): + if proc.scaling: + print proc.proc_name + " - " + str(proc.item_level) + " - " + str(proc.value) +""" # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization', -'the_dreadlords_deceit', -#'rogue_t19_2pc', +'denial_of_the_half_giants', +'rogue_t19_2pc', +'rogue_t19_4pc', +'insignia_of_ravenholdt', ) #tier buffs located here # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=21122, - stam=19566, - crit=4188, - haste=3630, - mastery=4373, - versatility=4153,) + agi=round(31794 * 0.95238 - test_race.racial_agi), #gear spec and racial agi are added during calc again + stam=54585, + crit=7010, + haste=4209, + mastery=6481, + versatility=5428,) # Initialize talents.. -test_talents = talents.Talents('2110011', test_spec, test_class, level=test_level) +test_talents = talents.Talents('2223211', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ 'goremaws_bite': 1, 'shadow_fangs': 1, 'gutripper': 3, - 'fortunes_bite': 1, + 'fortunes_bite': 3, 'catlike_reflexes': 3, - 'embrace_of_darkness': 0, + 'embrace_of_darkness': 1, 'ghost_armor': 3, - 'precision_strike': 0, + 'precision_strike': 3, 'energetic_stabbing': 3+3, 'flickering_shadows': 1, - 'second_shuriken': 0, - 'demons_kiss': 0, + 'second_shuriken': 1, + 'demons_kiss': 3, 'finality': 1, 'the_quiet_knife': 3, 'akarris_soul': 1, - 'soul_shadows': 0, - 'shadow_nova': 0, - 'legionblade': 0, + 'soul_shadows': 3, + 'shadow_nova': 1, + 'legionblade': 20, }) # Set up settings. @@ -88,20 +115,22 @@ dance_finishers_allowed=True, positional_uptime=1. ) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=450, - adv_params="", is_demon=True, num_boss_adds=0, marked_for_death_resets=0.0) +test_settings = settings.Settings(test_cycle, response_time=.5, duration=300, + adv_params="", is_demon=False, num_boss_adds=0, marked_for_death_resets=0.0) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) +print(str(test_stats.get_character_stats(test_race))) + # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) # Compute EP values. -#ep_values = calculator.get_ep(baseline_dps=total_dps) +ep_values = calculator.get_ep(baseline_dps=total_dps) #ep_values = calculator.get_ep() -tier_ep_values = calculator.get_other_ep(['rogue_t19_2pc', 'rogue_t19_4pc', 'the_dreadlords_deceit']) +tier_ep_values = calculator.get_other_ep(['rogue_t19_2pc', 'rogue_t19_4pc', 'denial_of_the_half_giants', 'insignia_of_ravenholdt']) #talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() @@ -109,7 +138,7 @@ def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -119,23 +148,32 @@ def pretty_print(dict_list): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)' + if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [ - #ep_values, + ep_values, tier_ep_values, #trinkets_ep_value, dps_breakdown, #trait_ranks ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.") +print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.")) + +""" +for value in aps.items(): + if type(value[1]) is float: + val = value[1] * 300. + else: + val = sum(value[1]) * 300. + print str(value[0]) + ' - ' + str(val) +""" #pprint(talent_ranks) \ No newline at end of file diff --git a/scripts/subtlety_import.py b/scripts/subtlety_import.py index d12f230..d3814c1 100755 --- a/scripts/subtlety_import.py +++ b/scripts/subtlety_import.py @@ -1,4 +1,8 @@ +from __future__ import division +from __future__ import print_function # Simple test program to debug + play with subtlety models. +from builtins import str +from past.utils import old_div from os import path import sys from import_character import CharacterData @@ -28,7 +32,7 @@ charInfo[ terms[0] ] = terms[1] key += 1 -print "Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n" +print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) character_data.do_import() @@ -60,7 +64,7 @@ # Set up procs. character_procs = character_data.get_procs() -character_procs_allowed = filter(lambda p: p in proc_data.allowed_procs, character_procs) +character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] #not_allowed_procs = set(character_procs) - set(character_procs_allowed) #print not_allowed_procs @@ -92,7 +96,7 @@ hemo_interval = 24 #'always', 'never', 24, 25, 26... if not character_data.get_mh_type() == 'dagger' and not test_talents.shuriken_toss: if not hemo_interval == 'always': - print "\nALERT: Viable dagger cycle not found, forced rotation to strictly Hemo \n" + print("\nALERT: Viable dagger cycle not found, forced rotation to strictly Hemo \n") hemo_interval = 'always' test_cycle = settings.SubtletyCycle(raid_crits_per_second, use_hemorrhage=hemo_interval) test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], @@ -106,7 +110,7 @@ # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in dps_breakdown.items()) +total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) talent_ranks = calculator.get_talents_ranking() heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) @@ -114,7 +118,7 @@ def max_length(dict_list): max_len = 0 for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) if max_len < max(len(entry[0]) for entry in dict_values): max_len = max(len(entry[0]) for entry in dict_values) @@ -124,14 +128,14 @@ def pretty_print(dict_list): max_len = max_length(dict_list) for i in dict_list: - dict_values = i.items() + dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(float(value[1])/total_dps)) != '0.00': - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)' + if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: - print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - print '-' * (max_len + 15) + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) + print('-' * (max_len + 15)) dicts_for_pretty_print = [ ep_values, @@ -140,4 +144,4 @@ def pretty_print(dict_list): dps_breakdown ] pretty_print(dicts_for_pretty_print) -print ' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.") +print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) diff --git a/scripts/wowapi/wowapi/api.py b/scripts/wowapi/wowapi/api.py index 43e3bd7..082dcce 100755 --- a/scripts/wowapi/wowapi/api.py +++ b/scripts/wowapi/wowapi/api.py @@ -1,6 +1,13 @@ -from urllib2 import Request, urlopen, URLError,quote +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import map +from builtins import object +from urllib.request import Request, urlopen +from urllib.error import URLError +from urllib.parse import quote import gzip -import StringIO +import io try: import simplejson as json except ImportError: @@ -105,7 +112,7 @@ } } -class WoWApi(): +class WoWApi(object): def __init__(self,privatekey=None,publickey=None,ssl=None): @@ -126,9 +133,9 @@ def __init__(self,privatekey=None,publickey=None,ssl=None): def _decode_response(self,response): if 'content-encoding' in response.info() and response.info()['content-encoding'] == 'gzip': - response = gzip.GzipFile(fileobj=StringIO.StringIO(response.read())) + response = gzip.GzipFile(fileobj=io.StringIO(response.read())) try: - data = json.loads(unicode(response.read(),'UTF-8')) + data = json.loads(str(response.read(),'UTF-8')) except json.JSONDecodeError: raise APIError('Non-JSON Response') return data @@ -137,7 +144,7 @@ def _decode_response(self,response): def _do_request(self,request): try: response = urlopen(request) - except URLError, e: + except URLError as e: if hasattr(e, 'reason'): raise APIError(e.reason,request.get_full_url()) elif hasattr(e, 'code'): diff --git a/scripts/wowapi/wowapi/utilities.py b/scripts/wowapi/wowapi/utilities.py index c0412d7..88e2dfb 100755 --- a/scripts/wowapi/wowapi/utilities.py +++ b/scripts/wowapi/wowapi/utilities.py @@ -1,3 +1,5 @@ +from __future__ import division +from past.utils import old_div def http_datetime( dt=None ): if not dt: @@ -42,7 +44,7 @@ def parse_http_datetime( datestring, utc_tzinfo=None, strict=False ): raise ValueError('HTTP date has an unrecognizable month') y = int(m.group('Y')) if y < 100: - century = datetime.datetime.utcnow().year / 100 + century = old_div(datetime.datetime.utcnow().year, 100) if y < 50: y = century * 100 + y else: diff --git a/shadowcraft/__init__.py b/shadowcraft/__init__.py index 4974700..95b6622 100644 --- a/shadowcraft/__init__.py +++ b/shadowcraft/__init__.py @@ -1,4 +1,6 @@ +from future import standard_library +standard_library.install_aliases() import gettext -import __builtin__ +import builtins -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index a23e309..c21106d 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -1,10 +1,17 @@ +from __future__ import division +from future import standard_library +standard_library.install_aliases() +from builtins import zip +from builtins import str +from builtins import object +from past.utils import old_div import gettext -import __builtin__ +import builtins import math import os import subprocess -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext from shadowcraft.core import exceptions from shadowcraft.objects import class_data @@ -136,7 +143,7 @@ def set_rppm_uptime(self, proc): e_minus_lambda = math.e ** (-1 * lambd) proc.uptime = 1.1307 * (e_lambda - 1) * (1 - ((1 - e_minus_lambda) ** proc.max_stacks)) else: - mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) + mean_proc_time = old_div(60., (haste * proc.get_rppm_proc_rate(spec=self.spec))) + proc.icd - min(proc.icd, 10) proc.uptime = 1.1307 * proc.duration / mean_proc_time def set_uptime(self, proc, attacks_per_second, crit_rates): @@ -146,7 +153,7 @@ def set_uptime(self, proc, attacks_per_second, crit_rates): procs_per_second = self.get_procs_per_second(proc, attacks_per_second, crit_rates) if proc.icd: - proc.uptime = proc.duration / (proc.icd + 1. / procs_per_second) + proc.uptime = old_div(proc.duration, (proc.icd + old_div(1., procs_per_second))) else: if procs_per_second >= 1: self.set_uptime_for_ramping_proc(proc, procs_per_second) @@ -170,9 +177,9 @@ def average_damage_breakdowns(self, aps_dict, denom=180): for key in aps_dict: for entry in aps_dict[key][1]: if entry in final_breakdown: - final_breakdown[entry] += aps_dict[key][1][entry] * (aps_dict[key][0]/denom) + final_breakdown[entry] += aps_dict[key][1][entry] * (old_div(aps_dict[key][0],denom)) else: - final_breakdown[entry] = aps_dict[key][1][entry] * (aps_dict[key][0]/denom) + final_breakdown[entry] = aps_dict[key][1][entry] * (old_div(aps_dict[key][0],denom)) return final_breakdown def ep_helper(self, stat): @@ -203,7 +210,7 @@ def get_ep(self, ep_stats=None, normalize_ep_stat=None, baseline_dps=None): ep_values[stat] = 1.0 if normalize_ep_stat != stat: dps = self.ep_helper(stat) - ep_values[stat] = abs(dps - baseline_dps) / normalize_dps_difference + ep_values[stat] = old_div(abs(dps - baseline_dps), normalize_dps_difference) return ep_values @@ -222,7 +229,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep if dps: getattr(self.stats, hand).weapon_dps += 1. new_dps = self.get_dps() - ep = abs(new_dps - baseline_dps) / (normalize_dps - baseline_dps) + ep = old_div(abs(new_dps - baseline_dps), (normalize_dps - baseline_dps)) ep_values[hand + '_dps'] = ep getattr(self.stats, hand).weapon_dps -= 1. @@ -239,7 +246,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep getattr(self.stats, hand).set_enchant(enchant) new_dps = self.get_dps() if new_dps != no_enchant_dps: - ep = abs(new_dps - no_enchant_dps) / (no_enchant_normalize_dps - no_enchant_dps) + ep = old_div(abs(new_dps - no_enchant_dps), (no_enchant_normalize_dps - no_enchant_dps)) ep_values[hand + '_' + enchant] = ep getattr(self.stats, hand).set_enchant(old_enchant) @@ -249,7 +256,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep for speed in speed_list: getattr(self.stats, hand).speed = speed new_dps = self.get_dps() - ep = (new_dps - baseline_dps) / (normalize_dps - baseline_dps) + ep = old_div((new_dps - baseline_dps), (normalize_dps - baseline_dps)) ep_values[hand + '_' + str(speed)] = ep getattr(self.stats, hand).speed = old_speed @@ -277,7 +284,7 @@ def get_weapon_type_ep(self, normalize_ep_stat=None): for wtype in ('dagger', 'one-hander'): getattr(self.stats, hand).type = wtype new_dps = self.get_dps() - ep = (new_dps - baseline_dps) / (normalize_dps - baseline_dps) + ep = old_div((new_dps - baseline_dps), (normalize_dps - baseline_dps)) ep_values[hand + '_type_' + wtype] = ep getattr(self.stats, hand).type = old_type @@ -320,7 +327,7 @@ def get_weapon_type_modifier_helper(self, setups=None): try: new_dps = self.get_dps() if new_dps != baseline_dps: - modifiers[tuple(current_setup)] = new_dps / baseline_dps + modifiers[tuple(current_setup)] = old_div(new_dps, baseline_dps) except InputNotModeledException: modifiers[tuple(current_setup)] = _('not allowed') for hand in baseline_setup: @@ -383,7 +390,7 @@ def get_other_ep(self, list, normalize_ep_stat=None): # engineering gizmos are handled as gear buffs by the engine. setattr(self.stats.gear_buffs, i, not getattr(self.stats.gear_buffs, i)) new_dps = self.get_dps() - ep_values[i] = abs(new_dps - baseline_dps) / (normalize_dps_difference) + ep_values[i] = old_div(abs(new_dps - baseline_dps), (normalize_dps_difference)) setattr(self.stats.gear_buffs, i, not getattr(self.stats.gear_buffs, i)) for i in procs_list: @@ -393,7 +400,7 @@ def get_other_ep(self, list, normalize_ep_stat=None): else: self.stats.procs.set_proc(i) new_dps = self.get_dps() - ep_values[i] = abs(new_dps - baseline_dps) / (normalize_dps_difference) + ep_values[i] = old_div(abs(new_dps - baseline_dps), (normalize_dps_difference)) if getattr(self.stats.procs, i): delattr(self.stats.procs, i) else: @@ -449,7 +456,7 @@ def get_upgrades_ep(self, _list, normalize_ep_stat=None): proc.update_proc_value() # after setting item_level re-set the proc value new_dps = self.get_dps() if new_dps != base_dps: - ep = abs(new_dps - base_dps) / (base_normalize_dps - base_dps) + ep = old_div(abs(new_dps - base_dps), (base_normalize_dps - base_dps)) ep_values[proc_name][l] = ep if old_proc: self.stats.procs.set_proc(proc_name) @@ -519,10 +526,10 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) new_dps = self.get_dps() if new_dps != base_dps: for l in group: - ep = abs(new_dps - base_dps) / (base_normalize_dps - base_dps) + ep = old_div(abs(new_dps - base_dps), (base_normalize_dps - base_dps)) if l > proc.item_level: upgraded_scale_factor = self.tools.get_random_prop_point(l) - ep *= float(upgraded_scale_factor) / float(scale_factor) + ep *= old_div(float(upgraded_scale_factor), float(scale_factor)) ep_values[proc_name][l] = ep if old_proc: self.stats.procs.set_proc(proc_name) @@ -608,7 +615,7 @@ def get_dps(self): def armor_mitigation_multiplier(self, armor=None): if not armor: armor = self.target_base_armor - return self.attacker_k_value / (self.attacker_k_value + armor) + return old_div(self.attacker_k_value, (self.attacker_k_value + armor)) def armor_mitigate(self, damage, armor): # Pass in raw physical damage and armor value, get armor-mitigated diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index bfc7b6e..3376a38 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1,11 +1,17 @@ +from __future__ import division #import copy +from future import standard_library +standard_library.install_aliases() +from builtins import map +from builtins import range +from past.utils import old_div import gettext -import __builtin__ +import builtins import math from operator import add from copy import copy -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext from shadowcraft.calcs.rogue import RogueDamageCalculator from shadowcraft.core import exceptions @@ -148,7 +154,7 @@ def get_crit_rates(self, stats): else: crit_rates[attack] = base_melee_crit_rate - for attack, crit_rate in crit_rates.items(): + for attack, crit_rate in list(crit_rates.items()): if crit_rate > 1: crit_rates[attack] = 1 @@ -195,10 +201,10 @@ def set_constants(self): self.spec_needs_converge = False #racials if self.race.arcane_torrent: - self.bonus_energy_regen += 15. / (120 + self.settings.response_time) + self.bonus_energy_regen += old_div(15., (120 + self.settings.response_time)) #auxiliary rotational effects if self.settings.feint_interval != 0: - self.bonus_energy_regen -= self.get_spell_stats('feint')[0] / self.settings.feint_interval + self.bonus_energy_regen -= old_div(self.get_spell_stats('feint')[0], self.settings.feint_interval) #only include if general multiplier applies to spec calculations @@ -272,9 +278,9 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ # There are 4 mirrors, 2 spawn in front of the get and are parryable # Each mirror swings a weapon with weapon damage based on 100% of AP haste_mult = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) - swings_per_mirror = 20.0/(2.0/haste_mult) + swings_per_mirror = old_div(20.0,(old_div(2.0,haste_mult))) total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror - proc_value = total_swings*(average_ap/3.5) * (1+ self.settings.num_boss_adds) + proc_value = total_swings*(old_div(average_ap,3.5)) * (1+ self.settings.num_boss_adds) #.424*max(AP, SP) if proc is getattr(self.stats.procs, 'felmouth_frenzy'): @@ -285,7 +291,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc.stat in ['physical_dot', 'spell_dot']: initial_tick = 1. if proc.dot_initial_tick else 0. - ticks_per_second = float(proc.dot_ticks - initial_tick) / float(proc.duration) + ticks_per_second = old_div(float(proc.dot_ticks - initial_tick), float(proc.duration)) average_damage *= initial_tick + ticks_per_second * proc.uptime / proc_count if proc.aoe: @@ -300,9 +306,9 @@ def set_openers(self): opener_cd = 30 if self.settings.use_opener == 'always': opener_spacing = (self.get_spell_cd('vanish') + self.settings.response_time) - total_openers_per_second = (1. + math.floor((self.settings.duration - opener_cd) / opener_spacing)) / self.settings.duration + total_openers_per_second = old_div((1. + math.floor(old_div((self.settings.duration - opener_cd), opener_spacing))), self.settings.duration) elif self.settings.use_opener == 'opener': - total_openers_per_second = 1. / self.settings.duration + total_openers_per_second = old_div(1., self.settings.duration) opener_spacing = None else: total_openers_per_second = 0 @@ -439,20 +445,20 @@ def lost_swings_from_swing_delay(self, delay, swing_timer): # : OH is the same value but 1 lower t0 = max(min( delay_remainder/swing_timer*1.5, 1.5 ), 0) - t1 = max(min( num_sum - delay_remainder, .5 )/swing_timer, 0) + t1 = max(old_div(min( num_sum - delay_remainder, .5 ),swing_timer), 0) t2 = max(min( num_sum - delay_remainder - .5, .5 )/swing_timer * .5, 0) - return (t0+t1+t2)/swing_timer + return old_div((t0+t1+t2),swing_timer) def set_uptime_for_ramping_proc(self, proc, procs_per_second): - time_for_one_stack = 1 / procs_per_second + time_for_one_stack = old_div(1, procs_per_second) if time_for_one_stack * proc.max_stacks > self.settings.duration: max_stacks_reached = self.settings.duration * procs_per_second - proc.uptime = max_stacks_reached / 2 + proc.uptime = old_div(max_stacks_reached, 2) else: missing_stacks = proc.max_stacks * (proc.max_stacks + 1) / 2 stack_time_lost = missing_stacks * time_for_one_stack - proc.uptime = proc.max_stacks - stack_time_lost / self.settings.duration + proc.uptime = proc.max_stacks - old_div(stack_time_lost, self.settings.duration) def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): if proc.is_real_ppm(): @@ -466,13 +472,13 @@ def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): if not proc.icd: frequency = haste * 1.1307 * proc.get_rppm_proc_rate(spec=self.spec) / 60 else: - mean_proc_time = 60. / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) + mean_proc_time = old_div(60., (haste * proc.get_rppm_proc_rate(spec=self.spec))) + proc.icd - min(proc.icd, 10) if proc.max_stacks > 1: # just correct if you only do damage on max_stacks, e.g. legendary_capacitive_meta mean_proc_time *= proc.max_stacks - frequency = 1.1307 / mean_proc_time + frequency = old_div(1.1307, mean_proc_time) else: if proc.icd: - frequency = 1. / (proc.icd + 0.5 / self.get_procs_per_second(proc, attacks_per_second, crit_rates)) + frequency = old_div(1., (proc.icd + old_div(0.5, self.get_procs_per_second(proc, attacks_per_second, crit_rates)))) else: frequency = self.get_procs_per_second(proc, attacks_per_second, crit_rates) @@ -509,31 +515,31 @@ def get_poison_counts(self, attacks_per_second, current_stats): poison_envenom_proc_rate = poison_base_proc_rate + 0.3 aps_envenom = attacks_per_second['envenom'] if self.talents.death_from_above: - aps_envenom = map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom']) - envenom_uptime = min(sum([(1 + cps) * aps_envenom[cps] for cps in xrange(1, 6)]), 1) + aps_envenom = list(map(add, attacks_per_second['death_from_above_strike'], attacks_per_second['envenom'])) + envenom_uptime = min(sum([(1 + cps) * aps_envenom[cps] for cps in range(1, 6)]), 1) avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime if self.talents.agonizing_poison: attacks_per_second['agonizing_poison'] = total_hits_per_second * avg_poison_proc_rate else: - poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration + poison_procs = avg_poison_proc_rate * total_hits_per_second - old_div(1, self.settings.duration) attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = 1. / 3 + attacks_per_second['deadly_poison'] = old_div(1., 3) def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 for finisher in self.finisher_damage_sources: #Don't double count DfA if finisher in attacks_per_second and finisher != 'death_from_above_pulse': - for cp in xrange(7): + for cp in range(7): stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] - stack_time = 10/stacks_per_second + stack_time = old_div(10,stacks_per_second) if stack_time > self.settings.duration: max_stacks = self.settings.duration * stacks_per_second - return max_stacks/2 + return old_div(max_stacks,2) else: max_time = self.settings.duration - stack_time - return (max_time/self.settings.duration) * 10 + (stack_time/self.settings.duration) * 5 + return (old_div(max_time,self.settings.duration)) * 10 + (old_div(stack_time,self.settings.duration)) * 5 def determine_stats(self, attack_counts_function): current_stats = { @@ -712,7 +718,7 @@ def add_special_aps_penalties(self, attacks_per_second): dos = self.stats.procs.draught_of_souls if dos: lost_seconds = self.settings.duration * float(dos.duration) / float(dos.icd) - loss_ratio = (self.settings.duration - lost_seconds) / self.settings.duration + loss_ratio = old_div((self.settings.duration - lost_seconds), self.settings.duration) for attack in attacks_per_second: if attack not in ['mh_autoattacks', 'oh_autoattacks', 'shadow_blades', 'nightblade_ticks', 'rupture_ticks', 'from_the_shadows', 'kingsbane_ticks', 'garrote_ticks', 'deadly_poison']: @@ -845,7 +851,7 @@ def assassination_dps_breakdown(self): self.set_constants() self.vendetta_cd = self.get_spell_cd('vendetta') - self.vendetta_multiplier = 0.3 * (20 / self.vendetta_cd) + self.vendetta_multiplier = 0.3 * (old_div(20, self.vendetta_cd)) #cd stacking handlers if self.settings.cycle.kingsbane_with_vendetta == 'only': @@ -853,14 +859,14 @@ def assassination_dps_breakdown(self): kb_venn_uptime = 1.0 else: self.kingsbane_cd = self.get_spell_cd('kingsbane') - kb_venn_uptime = self.kingsbane_cd/self.vendetta_cd + kb_venn_uptime = old_div(self.kingsbane_cd,self.vendetta_cd) if self.settings.cycle.exsang_with_vendetta == 'only': self.exsang_cd = min(self.vendetta_cd), self.get_spell_cd('exsanguinate') exsang_venn_uptime = 1.0 else: self.exsang_cd = self.get_spell_cd('exsanguinate') - exsang_venn_uptime = self.exsang_cd/self.vendetta_cd + exsang_venn_uptime = old_div(self.exsang_cd,self.vendetta_cd) self.damage_modifiers.update_modifier_value('vendetta_time_average', 1 + self.vendetta_multiplier) self.damage_modifiers.update_modifier_value('vendetta_exsang', 1 + (self.vendetta_multiplier * exsang_venn_uptime)) @@ -887,7 +893,7 @@ def assassination_dps_breakdown(self): finisher_cpps = 0.0 #finisher cps per second for ability in aps: if ability in self.finisher_damage_sources and 'ticks' not in ability: - finisher_cpps += sum([min(cp, 5) * aps[ability][cp] for cp in xrange(len(aps[ability]))]) + finisher_cpps += sum([min(cp, 5) * aps[ability][cp] for cp in range(len(aps[ability]))]) surge_uptime = finisher_aps * 5 #attacks/second * seconds/attack surge_of_toxins_multiplier = 1. + ((0.02 * finisher_cpps) * surge_uptime) self.damage_modifiers.update_modifier_value('surge_of_toxins', surge_of_toxins_multiplier) @@ -898,12 +904,12 @@ def assassination_dps_breakdown(self): if self.talents.agonizing_poison: - stack_time = 5./aps['agonizing_poison'] + stack_time = old_div(5.,aps['agonizing_poison']) max_time = self.settings.duration - stack_time - agonizing_poison_stacks = (max_time/self.settings.duration) * 5 + (stack_time/self.settings.duration) * 2.5 + agonizing_poison_stacks = (old_div(max_time,self.settings.duration)) * 5 + (old_div(stack_time,self.settings.duration)) * 2.5 agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives - agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 + agonizing_poison_adder += 1 + old_div((self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])), 2) #12% reduction from 4% per stack agonizing_poison_mod_per_stack= 0.0352 * agonizing_poison_adder @@ -917,12 +923,12 @@ def assassination_dps_breakdown(self): self.damage_modifiers.update_modifier_value('agonizing_poison', agonizing_poison_mod) if self.stats.gear_buffs.the_dreadlords_deceit: - avg_dreadlord_stacks = 0.5/aps['fan_of_knives'] + avg_dreadlord_stacks = old_div(0.5,aps['fan_of_knives']) self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) if self.stats.gear_buffs.rogue_t19_4pc: if aps['mutilate'] < 0.125: - t19_4pc_multiplier = 0.1 * (aps['mutilate'] / 0.125) + t19_4pc_multiplier = 0.1 * (old_div(aps['mutilate'], 0.125)) self.damage_modifiers.update_modifier_value('t19_4pc', 1.2 + t19_4pc_multiplier) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -958,7 +964,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #if anticipation we can just assume no waste if self.talents.anticipation: avg_cp_per_builder = sum([cp * cpg_cps[cp] for cp in cpg_cps]) - builders_per_finisher = self.settings.finisher_threshold/avg_cp_per_builder + builders_per_finisher = old_div(self.settings.finisher_threshold,avg_cp_per_builder) avg_finisher_size = self.settings.finisher_threshold finisher_list = [0, 0, 0, 0, 0, 0, 0] finisher_list[self.settings.finisher_threshold] = 1.0 @@ -1003,18 +1009,18 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_rupture_duration = 4 * (1 + avg_finisher_size) if self.talents.exsanguinate: #assume full pandemic on exsanged ruptures - exsang_rupture_duration = (1.3 * base_rupture_duration)/2 + exsang_rupture_duration = old_div((1.3 * base_rupture_duration),2) #rupture we're pandemicing from exsang_from_duration = 0.7 * base_rupture_duration - normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration)/base_rupture_duration - ruptures_per_second = (2. + normal_ruptures_per_exsang_cd) / self.exsang_cd + normal_ruptures_per_exsang_cd = old_div((self.exsang_cd - exsang_from_duration - exsang_rupture_duration),base_rupture_duration) + ruptures_per_second = old_div((2. + normal_ruptures_per_exsang_cd), self.exsang_cd) rupture_ticks_per_second = 1. * float(exsang_rupture_duration)/ self.exsang_cd + \ 0.5 * float(self.exsang_cd - exsang_rupture_duration)/self.exsang_cd else: - ruptures_per_second = 1. / base_rupture_duration + ruptures_per_second = old_div(1., base_rupture_duration) rupture_ticks_per_second = 0.5 - for cp in xrange(7): + for cp in range(7): attacks_per_second['rupture'][cp] = ruptures_per_second * finisher_list[cp] attacks_per_second['rupture_ticks'][cp] = rupture_ticks_per_second * finisher_list[cp] rupture_cost_per_second = self.get_spell_cost('rupture') * ruptures_per_second @@ -1025,15 +1031,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_garrote_duration = 18. garrote_cooldown = self.get_spell_cd('garrote') if self.talents.exsanguinate: - exsang_garrote_duration = base_garrote_duration / 2 + exsang_garrote_duration = old_div(base_garrote_duration, 2) exsang_downtime = garrote_cooldown - exsang_garrote_duration - normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration - attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd + normal_garrote_per_exsang = old_div((self.exsang_cd - garrote_cooldown), base_garrote_duration) + attacks_per_second['garrote'] = old_div((1 + normal_garrote_per_exsang), self.exsang_cd) attacks_per_second['garrote_ticks'] = 2./3 * float(exsang_garrote_duration) / self.exsang_cd + \ 1./3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd else: - attacks_per_second['garrote'] = 1. / base_garrote_duration - attacks_per_second['garrote_ticks'] = 1. / 3 + attacks_per_second['garrote'] = old_div(1., base_garrote_duration) + attacks_per_second['garrote_ticks'] = old_div(1., 3) cp_budget = attacks_per_second['garrote'] * self.settings.duration garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] @@ -1050,14 +1056,14 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): cp_budget += mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: - fok_interval = 1./60 + fok_interval = old_div(1.,60) attacks_per_second['fan_of_knives'] = fok_interval cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) net_energy_per_second -= fok_interval * 35 if self.traits.kingsbane: - attacks_per_second['kingsbane'] = 1./self.kingsbane_cd - attacks_per_second['kingsbane_ticks'] = 7. / self.kingsbane_cd + attacks_per_second['kingsbane'] = old_div(1.,self.kingsbane_cd) + attacks_per_second['kingsbane_ticks'] = old_div(7., self.kingsbane_cd) kb_crit = crit_rates['kingsbane'] cpg_cps = {1: (1 - kb_crit) ** 2, 2: 2 * (1 - kb_crit) * kb_crit, @@ -1067,7 +1073,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] if self.talents.hemorrhage: - hemos_per_second = 1./20 + hemos_per_second = old_div(1.,20) attacks_per_second['hemorrhage'] = hemos_per_second hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) cp_budget += hemo_cps @@ -1075,10 +1081,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_per_second = 1./dfa_cd + dfa_per_second = old_div(1.,dfa_cd) attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): + for cp in range(7): attacks_per_second['death_from_above_pulse'][cp] = dfa_per_second * finisher_list[cp] attacks_per_second['death_from_above_strike'][cp] = dfa_per_second * finisher_list[cp] attacks_per_second[self.cp_builder] += dfa_per_second * builders_per_finisher @@ -1094,15 +1100,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += max_energy #assume you get 50% of max energy back each time if self.traits.urge_to_kill: - energy_budget += (self.settings.duration/self.vendetta_cd) * 0.5 * max_energy + energy_budget += (old_div(self.settings.duration,self.vendetta_cd)) * 0.5 * max_energy attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] #spend those extra cps if cp_budget > 0: - extra_envenom = float(cp_budget)/avg_finisher_size + extra_envenom = old_div(float(cp_budget),avg_finisher_size) energy_budget -= self.get_spell_cost('envenom') * extra_envenom - extra_envenom_per_second = extra_envenom/self.settings.duration - for cp in xrange(7): + extra_envenom_per_second = old_div(extra_envenom,self.settings.duration) + for cp in range(7): attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] #now burn whats left in a minicycle @@ -1115,10 +1121,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - total_minicycles = float(energy_budget) / mini_cycle_energy - attacks_per_second[self.cp_builder] += float(total_minicycles * builders_per_finisher) / self.settings.duration - finishers_per_second = total_minicycles / self.settings.duration - for cp in xrange(7): + total_minicycles = old_div(float(energy_budget), mini_cycle_energy) + attacks_per_second[self.cp_builder] += old_div(float(total_minicycles * builders_per_finisher), self.settings.duration) + finishers_per_second = old_div(total_minicycles, self.settings.duration) + for cp in range(7): attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second energy_budget -= total_minicycles * mini_cycle_energy @@ -1129,15 +1135,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01)))/self.stats.mh.speed + attacks_per_second['mh_autoattacks'] = old_div((haste_multiplier * (1 + (alacrity_stacks * 0.01))),self.stats.mh.speed) attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] if self.traits.bag_of_tricks: - bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (1./sum(attacks_per_second['envenom'])) / 60 + bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (old_div(1.,sum(attacks_per_second['envenom']))) / 60 attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) if self.traits.from_the_shadows: - attacks_per_second['from_the_shadows'] = 1. / self.vendetta_cd + attacks_per_second['from_the_shadows'] = old_div(1., self.vendetta_cd) #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) @@ -1336,7 +1342,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): if self.talents.ghostly_strike: self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer - self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') + self.white_swing_downtime = old_div(self.settings.response_time, self.get_spell_cd('vanish')) # Compute dps phases each non-rerolling RtB buff combo AR and not phases = {} ar_phases = {} @@ -1359,7 +1365,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): true_bearing = 'tb' in phase shark = 's' in phase - chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] + chance = old_div(self.rtb_probabilities[len(phase)],self.rtb_buff_count[len(phase)]) aps = self.outlaw_attack_counts_mincycle(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, duration=maintainence_buff_duration) @@ -1375,9 +1381,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): keep_tb_chance += chance if shark: keep_shark_chance += chance - keep_gm_uptime = keep_gm_chance/keep_chance - keep_tb_uptime = keep_tb_chance/keep_chance - keep_shark_uptime = keep_shark_chance/keep_chance + keep_gm_uptime = old_div(keep_gm_chance,keep_chance) + keep_tb_uptime = old_div(keep_tb_chance,keep_chance) + keep_shark_uptime = old_div(keep_shark_chance,keep_chance) # Merge AR and non-AR into single phases aps_keep = self.merge_attacks_per_second(phases, total_time=keep_chance) aps_keep_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) @@ -1404,7 +1410,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): true_bearing = 'tb' in phase shark = 's' in phase - chance = self.rtb_probabilities[len(phase)]/self.rtb_buff_count[len(phase)] + chance = old_div(self.rtb_probabilities[len(phase)],self.rtb_buff_count[len(phase)]) aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks) aps_ar, reroll_time_ar = self.outlaw_attack_counts_reroll(current_stats, ar=True, jolly=jolly, @@ -1422,9 +1428,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): # Check for reroll time, to protect from divide by zero if net_reroll_time: - reroll_tb_uptime = reroll_tb_time/net_reroll_time - reroll_shark_uptime = reroll_shark_time/net_reroll_time - reroll_gm_uptime = reroll_gm_time/net_reroll_time + reroll_tb_uptime = old_div(reroll_tb_time,net_reroll_time) + reroll_shark_uptime = old_div(reroll_shark_time,net_reroll_time) + reroll_gm_uptime = old_div(reroll_gm_time,net_reroll_time) else: reroll_tb_uptime = 0 reroll_shark_uptime = 0 @@ -1447,7 +1453,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): 'reroll': (ar_reroll_duration, aps_reroll_ar)} aps_ar = self.merge_attacks_per_second(phases, rtb_keep_duration + ar_reroll_duration) - keep_uptime = rtb_keep_duration/(rtb_keep_duration + reroll_duration) + keep_uptime = old_div(rtb_keep_duration,(rtb_keep_duration + reroll_duration)) tb_uptime = (keep_uptime * keep_tb_uptime) + (1 - keep_uptime) * reroll_tb_uptime gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime @@ -1455,7 +1461,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): # Determine AR uptime and merge the two distributions attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) - ar_uptime = self.ar_duration / self.ar_cd + ar_uptime = old_div(self.ar_duration, self.ar_cd) tb_seconds_per_second = 0 ar_cd_modifier = 1 @@ -1468,10 +1474,10 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cp_spend_per_second = 0 for ability in attacks_per_second: if ability in self.finisher_damage_sources: - for cp in xrange(7): + for cp in range(7): cp_spend_per_second += attacks_per_second[ability][cp] * cp #tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime - ar_cd_modifier = (1 - (2 * tb_uptime)/(1. / cp_spend_per_second + 2 * tb_uptime)) + ar_cd_modifier = (1 - old_div((2 * tb_uptime),(old_div(1., cp_spend_per_second) + 2 * tb_uptime))) new_ar_cd = self.ar_cd * ar_cd_modifier attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) @@ -1480,24 +1486,24 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #else: #old_ar_cd = new_ar_cd - ar_uptime = self.ar_duration / ar_cd + ar_uptime = old_div(self.ar_duration, ar_cd) # Add in Cannonball and Killing Spree if self.talents.killing_spree: - ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) + ksp_cd = old_div(self.get_spell_cd('killing_spree'), (1. + tb_seconds_per_second)) #ksp is 7 hits per hand - attacks_per_second['killing_spree'] = 7./ksp_cd + attacks_per_second['killing_spree'] = old_div(7.,ksp_cd) if self.talents.cannonball_barrage: - cannonball_barrage_cd = self.get_spell_cd('cannonball_barrage') / (1. + tb_seconds_per_second) - attacks_per_second['cannonball_barrage'] = 1./cannonball_barrage_cd + cannonball_barrage_cd = old_div(self.get_spell_cd('cannonball_barrage'), (1. + tb_seconds_per_second)) + attacks_per_second['cannonball_barrage'] = old_div(1.,cannonball_barrage_cd) # Figure swing timer and add Main Gauche attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) - swing_timer = self.stats.mh.speed / attack_speed_multiplier - attacks_per_second['mh_autoattacks'] = 1./swing_timer - attacks_per_second['oh_autoattacks'] = 1./swing_timer + swing_timer = old_div(self.stats.mh.speed, attack_speed_multiplier) + attacks_per_second['mh_autoattacks'] = old_div(1.,swing_timer) + attacks_per_second['oh_autoattacks'] = old_div(1.,swing_timer) attacks_per_second['main_gauche'] = self.main_gauche_proc_rate * attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() # Add in Main Gauche @@ -1547,13 +1553,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll # set up our initial budgets energy_budget = duration * energy_regen - gcd_budget = duration/gcd_size + gcd_budget = old_div(duration,gcd_size) #since artifacts we'll just compute a one handed swing timer if self.talents.death_from_above and not ar: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - (10 * true_bearing) - dfa_count = duration/dfa_cd - dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed/attack_speed_multiplier) + dfa_count = old_div(duration,dfa_cd) + dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.mh.speed,attack_speed_multiplier)) dfa_energy_lost = dfa_lost_swings * (self.main_gauche_proc_rate * self.combat_potency_from_mg + self.combat_potency_regen_per_oh) energy_budget -= dfa_energy_lost @@ -1570,16 +1576,16 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll else: energy_budget -= self.roll_the_bones_cost gcd_budget -= (ss_count + ps_count + 1) - attacks_per_second['saber_slash'] = float(ss_count + ps_count)/duration - attacks_per_second['pistol_shot'] = float(ps_count)/duration + attacks_per_second['saber_slash'] = old_div(float(ss_count + ps_count),duration) + attacks_per_second['pistol_shot'] = old_div(float(ps_count),duration) - attacks_per_second[maintainence_buff] = [v / duration for v in finisher_list] + attacks_per_second[maintainence_buff] = [old_div(v, duration) for v in finisher_list] if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': - bte_count = duration / (20 + self.settings.response_time - (10 * true_bearing)) - attacks_per_second['between_the_eyes'] = [float(v * bte_count) / duration for v in finisher_list] - attacks_per_second['pistol_shot'] += float(bte_count * ps_count) / duration - attacks_per_second['saber_slash'] += float(bte_count * (ss_count + ps_count)) / duration + bte_count = old_div(duration, (20 + self.settings.response_time - (10 * true_bearing))) + attacks_per_second['between_the_eyes'] = [old_div(float(v * bte_count), duration) for v in finisher_list] + attacks_per_second['pistol_shot'] += old_div(float(bte_count * ps_count), duration) + attacks_per_second['saber_slash'] += old_div(float(bte_count * (ss_count + ps_count)), duration) energy_budget -= (bte_count * ss_count) * self.saber_slash_energy_cost energy_budget -= bte_count * self.between_the_eyes_energy_cost gcd_budget -= bte_count * (ss_count + ps_count + 1) @@ -1588,10 +1594,10 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll if self.talents.death_from_above and not ar: energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost energy_budget -= dfa_count * self.death_from_above_energy_cost - attacks_per_second['saber_slash'] += float((ss_count + ps_count) * dfa_count) / duration - attacks_per_second['pistol_shot'] += float(ps_count * dfa_count) / duration - attacks_per_second['death_from_above_strike'] = [float(v * dfa_count) / duration for v in finisher_list] - attacks_per_second['death_from_above_pulse'] = [float(v * dfa_count) / duration for v in finisher_list] + attacks_per_second['saber_slash'] += old_div(float((ss_count + ps_count) * dfa_count), duration) + attacks_per_second['pistol_shot'] += old_div(float(ps_count * dfa_count), duration) + attacks_per_second['death_from_above_strike'] = [old_div(float(v * dfa_count), duration) for v in finisher_list] + attacks_per_second['death_from_above_pulse'] = [old_div(float(v * dfa_count), duration) for v in finisher_list] #DfA forces a 2 second GCD gcd_budget -= dfa_count * (ss_count + ps_count + 2) @@ -1600,36 +1606,36 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #consider ghostly strike if self.talents.ghostly_strike: - gs_count = duration/15. + gs_count = old_div(duration,15.) bonus_cps += gs_count * (1 + broadsides) gs_energy = self.ghostly_strike_cost * gs_count energy_budget -= gs_energy gcd_budget -= gs_count - attacks_per_second['ghostly_strike'] = float(gs_count) / duration + attacks_per_second['ghostly_strike'] = old_div(float(gs_count), duration) #consider MfD if self.talents.marked_for_death: - mfd_count = (1 + self.settings.marked_for_death_resets) / duration + mfd_count = old_div((1 + self.settings.marked_for_death_resets), duration) bonus_cps += 5 * (1 + self.settings.marked_for_death_resets) * mfd_count #consider Curse of the Dreadblades if self.traits.curse_of_the_dreadblades: - curse_cd_multiplier = duration / self.cotd_cd + curse_cd_multiplier = old_div(duration, self.cotd_cd) #curse lasts 12 seconds, half to RT, half to CP builders - curse_gcds = (12. / gcd_size) * curse_cd_multiplier - rt_count = curse_gcds / 2 + curse_gcds = (old_div(12., gcd_size)) * curse_cd_multiplier + rt_count = old_div(curse_gcds, 2) ps_per_ss = 0.35 if self.talents.swordmaster: ps_per_ss += 0.1 if jolly: ps_per_ss += 0.25 - ss_count = (curse_gcds / 2) * (1 / (ps_per_ss + 1)) - ps_count = (curse_gcds / 2) * (ps_per_ss / (ps_per_ss + 1)) + ss_count = (old_div(curse_gcds, 2)) * (old_div(1, (ps_per_ss + 1))) + ps_count = (old_div(curse_gcds, 2)) * (old_div(ps_per_ss, (ps_per_ss + 1))) - attacks_per_second['saber_slash'] += ss_count / self.cotd_cd - attacks_per_second['pistol_shot'] += ps_count / self.cotd_cd - attacks_per_second['run_through'][max_cps] += rt_count / self.cotd_cd + attacks_per_second['saber_slash'] += old_div(ss_count, self.cotd_cd) + attacks_per_second['pistol_shot'] += old_div(ps_count, self.cotd_cd) + attacks_per_second['run_through'][max_cps] += old_div(rt_count, self.cotd_cd) gcd_budget -= curse_gcds energy_budget -= (ss_count * self.saber_slash_energy_cost) + (rt_count * self.run_through_energy_cost) @@ -1639,7 +1645,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #spend bonus cps for max cp RTs - extra_rt = (bonus_cps / max_cps) / duration + extra_rt = old_div((old_div(bonus_cps, max_cps)), duration) gcd_budget -= extra_rt energy_budget -= extra_rt * self.run_through_energy_cost attacks_per_second['run_through'][max_cps] += extra_rt @@ -1655,11 +1661,11 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - minicycle_count = min(gcd_budget / gcds_per_minicycle, energy_budget / energy_per_minicycle) - attacks_per_second['saber_slash'] += float(minicycle_count * (ss_count + ps_count)) / duration - attacks_per_second['pistol_shot'] += float(minicycle_count * ps_count) / duration + minicycle_count = min(old_div(gcd_budget, gcds_per_minicycle), old_div(energy_budget, energy_per_minicycle)) + attacks_per_second['saber_slash'] += old_div(float(minicycle_count * (ss_count + ps_count)), duration) + attacks_per_second['pistol_shot'] += old_div(float(minicycle_count * ps_count), duration) for i, v in enumerate(finisher_list): - attacks_per_second['run_through'][i] += float(minicycle_count * v) / duration + attacks_per_second['run_through'][i] += old_div(float(minicycle_count * v), duration) #Don't need to converge if we don't have alacrity if not self.talents.alacrity: @@ -1695,11 +1701,11 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) total_regen = energy_regen + mg_cp_energy - reroll_time = reroll_energy_cost / total_regen + reroll_time = old_div(reroll_energy_cost, total_regen) attacks_per_second = {} - attacks_per_second['saber_slash'] = float(ss_count + ps_count) / reroll_time - attacks_per_second['pistol_shot'] = float(ps_count) / reroll_time - attacks_per_second['roll_the_bones'] = [v / reroll_time for v in finisher_list] + attacks_per_second['saber_slash'] = old_div(float(ss_count + ps_count), reroll_time) + attacks_per_second['pistol_shot'] = old_div(float(ps_count), reroll_time) + attacks_per_second['roll_the_bones'] = [old_div(v, reroll_time) for v in finisher_list] return attacks_per_second, reroll_time #dict of (probability, aps) pairs @@ -1707,25 +1713,25 @@ def merge_attacks_per_second(self, aps_dicts, total_time=1.0): attacks_per_second = {} for key in aps_dicts: proportion, aps = aps_dicts[key] - uptime = float(proportion)/total_time + uptime = old_div(float(proportion),total_time) for ability in aps: if ability in attacks_per_second: if isinstance(attacks_per_second[ability], list): - for cp in xrange(7): + for cp in range(7): attacks_per_second[ability][cp] += uptime * aps[ability][cp] else: attacks_per_second[ability] += uptime * aps[ability] else: if isinstance(aps[ability], list): attacks_per_second[ability] = copy(aps[ability]) - for cp in xrange(7): + for cp in range(7): attacks_per_second[ability][cp] *= uptime else: attacks_per_second[ability] = uptime * aps[ability] return attacks_per_second def get_mg_cp_regen_from_haste(self, haste_multiplier): - swing_per_second = (self.stats.mh.speed * self.dw_mh_hit_chance)/haste_multiplier + swing_per_second = old_div((self.stats.mh.speed * self.dw_mh_hit_chance),haste_multiplier) mg_regen = self.main_gauche_proc_rate * self.combat_potency_from_mg * swing_per_second cp_regen = self.combat_potency_regen_per_oh * swing_per_second return mg_regen + cp_regen @@ -1867,7 +1873,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.update_modifier_value('finality', finality_damage_boost) if self.stats.gear_buffs.the_dreadlords_deceit: - avg_dreadlord_stacks = 0.5/aps['shuriken_storm'] + avg_dreadlord_stacks = old_div(0.5,aps['shuriken_storm']) self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -1909,16 +1915,16 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy #set initial dance budget - self.dance_budget = 3 + self.settings.duration/60. + self.dance_budget = 3 + old_div(self.settings.duration,60.) shadow_blades_duration = 15. + (3.3333 * self.traits.soul_shadows) - self.shadow_blades_uptime = shadow_blades_duration/self.get_spell_cd('shadow_blades') + self.shadow_blades_uptime = old_div(shadow_blades_duration,self.get_spell_cd('shadow_blades')) #swing timer white_swing_downtime = 0 self.swing_reset_spacing = self.get_spell_cd('vanish') if self.swing_reset_spacing is not None: - white_swing_downtime += .5 / self.swing_reset_spacing + white_swing_downtime += old_div(.5, self.swing_reset_spacing) attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) @@ -1930,9 +1936,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps #2 net energy per 6 seconds from relentless strikes if self.talents.enveloping_shadows: - self.cp_budget += self.settings.duration/6. - self.energy_budget += (2./6) * self.settings.duration - self.dance_budget += (0.5 * self.settings.duration)/60 + self.cp_budget += old_div(self.settings.duration,6.) + self.energy_budget += (old_div(2.,6)) * self.settings.duration + self.dance_budget += old_div((0.5 * self.settings.duration),60) #setup timelines sod_duration = 35 @@ -1948,8 +1954,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Leaving space for opener handling for the first cast - sod_timeline = range(0, self.settings.duration, sod_duration) - nightblade_timeline = range(nightblade_duration, self.settings.duration, nightblade_duration) + sod_timeline = list(range(0, self.settings.duration, sod_duration)) + nightblade_timeline = list(range(nightblade_duration, self.settings.duration, nightblade_duration)) dance_nb_uptime = 0.0 for finisher in ['nightblade', 'eviscerate']: @@ -1960,7 +1966,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if finisher == 'nightblade': joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) dance_count = len(joint) - dance_nb_uptime = dance_count/len(nightblade_timeline) + dance_nb_uptime = old_div(dance_count,len(nightblade_timeline)) elif finisher == 'eviscerate': dance_count = len(sod_timeline) sod_timeline = [] @@ -1975,43 +1981,43 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=True, finisher=finisher) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += ((3. * spent_cps* dance_count)/60) - dance_count + self.dance_budget += (old_div((3. * spent_cps* dance_count),60)) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) #Add in ruptures not previously covered nightblade_count = len(nightblade_timeline) - attacks_per_second['nightblade'][self.settings.finisher_threshold] += float(nightblade_count)/self.settings.duration + attacks_per_second['nightblade'][self.settings.finisher_threshold] += old_div(float(nightblade_count),self.settings.duration) self.cp_budget -= self.settings.finisher_threshold * nightblade_count self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('nightblade')) * nightblade_count - self.dance_budget += (3. * self.settings.finisher_threshold * nightblade_count)/60. + self.dance_budget += old_div((3. * self.settings.finisher_threshold * nightblade_count),60.) #Add in various cooldown abilities #This could be made better with timelining but for now simple time average will do if self.traits.goremaws_bite: goremaws_bite_cd = self.get_spell_cd('goremaws_bite') + self.settings.response_time - attacks_per_second['goremaws_bite'] = 1./goremaws_bite_cd - self.cp_budget += 3 * (self.settings.duration/goremaws_bite_cd) - self.energy_budget += 30 * (self.settings.duration/goremaws_bite_cd) + attacks_per_second['goremaws_bite'] = old_div(1.,goremaws_bite_cd) + self.cp_budget += 3 * (old_div(self.settings.duration,goremaws_bite_cd)) + self.energy_budget += 30 * (old_div(self.settings.duration,goremaws_bite_cd)) if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_count = self.settings.duration/dfa_cd + dfa_count = old_div(self.settings.duration,dfa_cd) - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / haste_multiplier) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / haste_multiplier) + lost_swings_mh = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.mh.speed, haste_multiplier)) + lost_swings_oh = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.oh.speed, haste_multiplier)) - attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd - attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd + attacks_per_second['mh_autoattacks'] -= old_div(lost_swings_mh, dfa_cd) + attacks_per_second['oh_autoattacks'] -= old_div(lost_swings_oh, dfa_cd) attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_strike'][self.max_spend_cps] += 1./dfa_cd + attacks_per_second['death_from_above_strike'][self.max_spend_cps] += old_div(1.,dfa_cd) attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += 1./dfa_cd + attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += old_div(1.,dfa_cd) self.cp_budget -= self.max_spend_cps * dfa_count self.energy_budget += (40 * (0.2 * self.max_spend_cps) - self.get_spell_cost('death_from_above')) * dfa_count - self.dance_budget += (3. * self.max_spend_cps * dfa_count)/60. + self.dance_budget += old_div((3. * self.max_spend_cps * dfa_count),60.) #Need to handle shadow techniques now to account for swing timer loss attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance @@ -2023,7 +2029,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget += shadow_techniques_cps #vanish handling - vanish_count = self.settings.duration/self.get_spell_cd('vanish') + vanish_count = old_div(self.settings.duration,self.get_spell_cd('vanish')) #Treat subterfuge as a mini-dance if self.talents.subterfuge: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) @@ -2031,7 +2037,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy self.cp_budget += vanish_count * net_cps - self.dance_budget += ((3. * spent_cps* vanish_count)/60) + self.dance_budget += (old_div((3. * spent_cps* vanish_count),60)) self.rotation_merge(attacks_per_second, attack_counts, vanish_count) #Generate one final dance templates @@ -2044,14 +2050,14 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): cp_per_builder = 1 + self.shadow_blades_uptime if self.cp_builder == 'shuriken_storm': cp_per_builder += self.settings.num_boss_adds - energy_per_cp = self.get_spell_cost(self.cp_builder) /(cp_per_builder) + energy_per_cp = old_div(self.get_spell_cost(self.cp_builder),(cp_per_builder)) extra_evis = 0 extra_builders = 0 #Not enough dances, generate some more if self.dance_budget<0: cps_required = abs(self.dance_budget) * 20 - extra_evis += cps_required/self.settings.finisher_threshold + extra_evis += old_div(cps_required,self.settings.finisher_threshold) self.energy_budget += self.net_evis_cost #just subtract the cps because we'll fix those next self.cp_budget -= cps_required @@ -2066,7 +2072,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): dance_count = abs(self.dance_budget) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += ((3. * spent_cps* dance_count)/60.) - dance_count + self.dance_budget += (old_div((3. * spent_cps* dance_count),60.)) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) loop_counter += 1 @@ -2075,20 +2081,20 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.cp_budget <0: #can add since we know cp_budget is negative self.energy_budget += self.cp_budget * energy_per_cp - extra_builders += abs(self.cp_budget) / cp_per_builder + extra_builders += old_div(abs(self.cp_budget), cp_per_builder) self.cp_budget = 0 if self.cp_builder == 'shuriken_storm': - attacks_per_second['shuriken_storm-no-dance'] = extra_builders / self.settings.duration + attacks_per_second['shuriken_storm-no-dance'] = old_div(extra_builders, self.settings.duration) else: - attacks_per_second[self.cp_builder] = extra_builders / self.settings.duration + attacks_per_second[self.cp_builder] = old_div(extra_builders, self.settings.duration) attacks_per_second['eviscerate'][self.settings.finisher_threshold] += extra_evis #Hopefully energy budget here isn't negative, if it is we're in trouble #Now we convert all the energy we have left into mini-cycles #Each mini-cycle contains enough 1 dance and generators+finishers for one dance cps_per_dance = 20 - finishers_per_minicycle = cps_per_dance/self.settings.finisher_threshold + finishers_per_minicycle = old_div(cps_per_dance,self.settings.finisher_threshold) attack_counts_mini_cycle = attack_counts attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] @@ -2100,12 +2106,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 cps_to_generate = max(cps_per_dance - self.cp_budget, 0) - builders_per_minicycle = cps_to_generate / cp_per_builder + builders_per_minicycle = old_div(cps_to_generate, cp_per_builder) mini_cycle_energy = 5 * finishers_per_minicycle - (cps_to_generate * energy_per_cp) #add in dance energy mini_cycle_energy += net_energy if cps_to_generate: - mini_cycle_count = float(self.energy_budget) / abs(mini_cycle_energy) + mini_cycle_count = old_div(float(self.energy_budget), abs(mini_cycle_energy)) else: mini_cycle_count = 1 @@ -2133,7 +2139,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #convert nightblade casts into nightblade ticks if 'nightblade' in attacks_per_second: attacks_per_second['nightblade_ticks'] = [0, 0, 0, 0, 0, 0, 0] - for cp in xrange(7): + for cp in range(7): attacks_per_second['nightblade_ticks'][cp] = (3 + cp) * attacks_per_second['nightblade'][cp] if self.stats.gear_buffs.rogue_t19_2pc: attacks_per_second['nightblade_ticks'][cp] = (3 + (2 * cp)) * attacks_per_second['nightblade'][cp] @@ -2148,18 +2154,18 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.akarris_soul and 'shadowstrike' in attacks_per_second: attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] if self.traits.shadow_nova: - attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], 1./5.) + attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], old_div(1.,5.)) #FIXME: Kinda hackish, better approach would be to compute a seperate dance rotation if self.stats.gear_buffs.the_dreadlords_deceit and (self.cp_builder =='backstab' or self.cp_builder == 'gloomblade'): - shuriken_interval = 1./60 + shuriken_interval = old_div(1.,60) attacks_per_second['shadowstrike'] -= shuriken_interval attacks_per_second['shuriken_storm'] = shuriken_interval self.stealth_shuriken_uptime = 1. self.stealth_shuriken_uptime = 0. if self.cp_builder == 'shuriken_storm': - self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) + self.stealth_shuriken_uptime = old_div(attacks_per_second['shuriken_storm'], (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'])) attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] del attacks_per_second['shuriken_storm-no-dance'] @@ -2168,12 +2174,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_time = 8. * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] if self.talents.subterfuge: stealth_time = 10. * attacks_per_second['shadow_dance'] + 8 * attacks_per_second['vanish'] - self.mos_time = float(stealth_time)/self.settings.duration + self.mos_time = old_div(float(stealth_time),self.settings.duration) if self.talents.nightstalker: self.dance_nb_uptime = dance_nb_uptime - for ability in attacks_per_second.keys(): + for ability in list(attacks_per_second.keys()): if not attacks_per_second[ability]: del attacks_per_second[ability] elif isinstance(attacks_per_second[ability], list) and not any(attacks_per_second[ability]): @@ -2186,20 +2192,20 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_evis += attacks_per_second['vanish'] else: stealth_evis = 0 - self.stealth_evis_uptime = stealth_evis/sum(attacks_per_second['eviscerate']) + self.stealth_evis_uptime = old_div(stealth_evis,sum(attacks_per_second['eviscerate'])) if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] #add SoD auto crits if 'shadowstrike' in attacks_per_second: - sod_shadowstrikes = attacks_per_second['symbols_of_death']/attacks_per_second['shadowstrike'] + sod_shadowstrikes = old_div(attacks_per_second['symbols_of_death'],attacks_per_second['shadowstrike']) crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes if self.talents.weaponmaster: for ability in attacks_per_second: if isinstance(attacks_per_second[ability], list): - for cp in xrange(7): + for cp in range(7): attacks_per_second[ability][cp] *= 1.06 else: attacks_per_second[ability] *=1.06 @@ -2252,7 +2258,7 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): #fill remaining gcds with shadowstrikes cp_builder = self.dance_cp_builder cp_builder_cost = float(self.get_spell_cost(cp_builder, cost_mod=cost_mod)) - builder_count = min(dance_gcds, (net_energy+max_dance_energy)/cp_builder_cost) + builder_count = min(dance_gcds, old_div((net_energy+max_dance_energy),cp_builder_cost)) if vanish: attack_counts[cp_builder] = builder_count attack_counts['vanish'] = 1 @@ -2277,9 +2283,9 @@ def timeline_overlap(self, timeline_a, timeline_b, match_delta): match_list = [] #index of matches for removal no_match_a = [] - for a in xrange(len(timeline_a)): + for a in range(len(timeline_a)): match = False - for b in xrange(len(timeline_b)): + for b in range(len(timeline_b)): #early termination for impossible matches if timeline_b[b] > timeline_a[a]: break @@ -2294,10 +2300,10 @@ def timeline_overlap(self, timeline_a, timeline_b, match_delta): #Takes in the full attacks per second dict and a raw attack counts dict #adds attack countes into the rotation at global scope def rotation_merge (self, attacks_per_second, attack_counts, count): - rotations_per_second = float(count)/self.settings.duration + rotations_per_second = old_div(float(count),self.settings.duration) for ability in attack_counts: if ability in self.finisher_damage_sources: - for cp in xrange(7): + for cp in range(7): attacks_per_second[ability][cp] += rotations_per_second * attack_counts[ability][cp] else: attacks_per_second[ability] += rotations_per_second * attack_counts[ability] diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index d40abaf..b39f687 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -1,3 +1,4 @@ +from builtins import object from shadowcraft.core import exceptions class Settings(object): diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 7b9fd38..8874bf1 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -1,7 +1,12 @@ +from __future__ import division +from future import standard_library +standard_library.install_aliases() +from builtins import range +from past.utils import old_div import gettext -import __builtin__ +import builtins -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext from shadowcraft.calcs import DamageCalculator from shadowcraft.core import exceptions @@ -202,7 +207,7 @@ def get_ability_dps(self, ap, ability, attacks_per_second, crit_rate, modifier, base_damage = self.get_formula(a)(ap) * modifier dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second, crit_modifier) else: - for i in xrange(1, cps + 1): + for i in range(1, cps + 1): for a in ability_list: base_damage = self.get_formula(a)(ap, i) * modifier dps += self.get_dps_contribution(base_damage, crit_rate, attacks_per_second[i], crit_modifier) @@ -222,7 +227,7 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da # this removes keys with empty values, prevents errors from: # attacks_per_second['sinister_strike'] = None - for key in attacks_per_second.keys(): + for key in list(attacks_per_second.keys()): if not attacks_per_second[key]: del attacks_per_second[key] @@ -583,7 +588,7 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates haste = self.get_haste_multiplier(current_stats) stacks_per_use = min(oozeling.icd * haste * 1.1307 * 3 / 60, 6) #3 rppm, capped at 6 stacks, 1.1307 bad luck protection damage_per_use = self.get_proc_damage_contribution(oozeling, stacks_per_use, current_stats, ap, modifier_dict) - damage_breakdown[oozeling.proc_name] = damage_per_use / oozeling.icd + damage_breakdown[oozeling.proc_name] = old_div(damage_per_use, oozeling.icd) # Tirathon's Betrayal and Faulty Countermeasure for proc in [self.stats.procs.tirathons_betrayal, self.stats.procs.faulty_countermeasure]: @@ -591,4 +596,4 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates # both 20 RPPM with haste mod procs_per_use = proc.duration * 20 * 1.1307 * self.get_haste_multiplier(current_stats) / 60 damage_per_use = self.get_proc_damage_contribution(proc, procs_per_use, current_stats, ap, modifier_dict) - damage_breakdown[proc.proc_name] = damage_per_use / proc.icd + damage_breakdown[proc.proc_name] = old_div(damage_per_use, proc.icd) diff --git a/shadowcraft/core/__init__.py b/shadowcraft/core/__init__.py index 4974700..95b6622 100644 --- a/shadowcraft/core/__init__.py +++ b/shadowcraft/core/__init__.py @@ -1,4 +1,6 @@ +from future import standard_library +standard_library.install_aliases() import gettext -import __builtin__ +import builtins -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext diff --git a/shadowcraft/core/exceptions.py b/shadowcraft/core/exceptions.py index 6407656..d69af65 100644 --- a/shadowcraft/core/exceptions.py +++ b/shadowcraft/core/exceptions.py @@ -1,3 +1,4 @@ +from builtins import str class InvalidInputException(Exception): # Base class for all our exceptions. All exceptions we generate should # either use or subclass this. diff --git a/shadowcraft/core/i18n.py b/shadowcraft/core/i18n.py index ba60d53..994e417 100644 --- a/shadowcraft/core/i18n.py +++ b/shadowcraft/core/i18n.py @@ -1,9 +1,13 @@ +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() import gettext import os.path import locale -import __builtin__ +import builtins +import sys -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext # Domain: this needs to be the name of our .mo files TRANSLATION_DOMAIN = 'SCE' @@ -14,6 +18,9 @@ def set_language(language): # language specified. It will fall back to code strings if given a not supported # language. Note that the 'local' value only makes sense when not running from # the hosted online version. + install_args = { } + if sys.api_version < 3: + install_args['str'] = True if language == 'local': # Setting up a list of locales in your machine and asign them to the _() function languages_list = [] @@ -26,7 +33,7 @@ def set_language(language): if (gnu_lang): languages_list += gnu_lang.split(":") - gettext.translation(TRANSLATION_DOMAIN, LOCALE_DIR, fallback=True, languages=languages_list).install(unicode=True) + gettext.translation(TRANSLATION_DOMAIN, LOCALE_DIR, fallback=True, languages=languages_list).install(**install_args) else: - gettext.translation(TRANSLATION_DOMAIN, LOCALE_DIR, fallback=True, languages=[language]).install(unicode=True) + gettext.translation(TRANSLATION_DOMAIN, LOCALE_DIR, fallback=True, languages=[language]).install(**install_args) diff --git a/shadowcraft/core/jsoninput.py b/shadowcraft/core/jsoninput.py index df147d3..e8889b5 100644 --- a/shadowcraft/core/jsoninput.py +++ b/shadowcraft/core/jsoninput.py @@ -1,3 +1,5 @@ +from __future__ import print_function +from builtins import str import json from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.calcs.rogue.Aldriana import settings @@ -142,23 +144,23 @@ def s(stat): calculator = from_json(json_string) # Compute EP values. - ep_values = calculator.get_ep().items() + ep_values = list(calculator.get_ep().items()) ep_values.sort(key=lambda entry: entry[1], reverse=True) max_len = max(len(entry[0]) for entry in ep_values) for value in ep_values: - print value[0] + ':' + ' ' * (max_len - len(value[0])), value[1] + print(value[0] + ':' + ' ' * (max_len - len(value[0])), value[1]) - print '---------' + print('---------') # Compute DPS Breakdown. - dps_breakdown = calculator.get_dps_breakdown().items() + dps_breakdown = list(calculator.get_dps_breakdown().items()) dps_breakdown.sort(key=lambda entry: entry[1], reverse=True) max_len = max(len(entry[0]) for entry in dps_breakdown) total_dps = sum(entry[1] for entry in dps_breakdown) for entry in dps_breakdown: - print entry[0] + ':' + ' ' * (max_len - len(entry[0])), entry[1] + print(entry[0] + ':' + ' ' * (max_len - len(entry[0])), entry[1]) - print '-' * (max_len + 15) + print('-' * (max_len + 15)) - print ' ' * (max_len + 1), total_dps, _("total damage per second.") + print(' ' * (max_len + 1), total_dps, _("total damage per second.")) diff --git a/shadowcraft/objects/__init__.py b/shadowcraft/objects/__init__.py index 4974700..95b6622 100644 --- a/shadowcraft/objects/__init__.py +++ b/shadowcraft/objects/__init__.py @@ -1,4 +1,6 @@ +from future import standard_library +standard_library.install_aliases() import gettext -import __builtin__ +import builtins -__builtin__._ = gettext.gettext +builtins._ = gettext.gettext diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 4338891..3b4050d 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -1,3 +1,5 @@ +from builtins import range +from builtins import object from shadowcraft.core import exceptions from shadowcraft.objects import artifact_data @@ -35,7 +37,7 @@ def initialize_traits(self, trait_string): if len(trait_string) != len(self.allowed_traits) and len(trait_string) != len(self.allowed_traits) + 1: raise InvalidTraitException(_('Trait strings must be {traits} (or {traits} + 1) characters long').format(traits=len(self.allowed_traits))) self.traits = {} - for trait in xrange(len(self.allowed_traits)): + for trait in range(len(self.allowed_traits)): #grab all charcters for final trait if trait == len(self.allowed_traits) - 1: self.set_trait(self.allowed_traits[trait], int(trait_string[trait:])) diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index a7c8732..b59cf01 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -1,3 +1,4 @@ +from builtins import object from shadowcraft.core import exceptions class InvalidBuffException(exceptions.InvalidInputException): diff --git a/shadowcraft/objects/class_data.py b/shadowcraft/objects/class_data.py index 0830c5e..8e757e2 100644 --- a/shadowcraft/objects/class_data.py +++ b/shadowcraft/objects/class_data.py @@ -1,3 +1,5 @@ +from builtins import str +from builtins import object from shadowcraft.core import exceptions class Util(object): @@ -1343,7 +1345,7 @@ class Util(object): ] def get_class_number(self, game_class): - for i in self.GAME_CLASS_NUMBER.keys(): + for i in list(self.GAME_CLASS_NUMBER.keys()): if self.GAME_CLASS_NUMBER[i] == game_class: return i raise exceptions.InvalidInputException(_('{game_class} is not a supported game class').format(game_class=game_class)) diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index 0f0ed79..bb7a724 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -1,3 +1,4 @@ +from builtins import object from shadowcraft.core import exceptions #ModifierList contains all modifiers needed for dps computation. @@ -24,14 +25,14 @@ def compile_modifier_dict(self): # mods for all damage lumped_modifier['all_damage'] = 1 - for mod in self.modifiers.values(): + for mod in list(self.modifiers.values()): if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) if mod.all_damage: lumped_modifier['all_damage'] *= mod.value # mods for damage schools - for mod in self.modifiers.values(): + for mod in list(self.modifiers.values()): if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) if mod.dmg_schools: @@ -43,7 +44,7 @@ def compile_modifier_dict(self): lumped_modifier[modname] = lumped_modifier['all_damage'] * mod.value # mods for source abilities - for mod in self.modifiers.values(): + for mod in list(self.modifiers.values()): if mod.value is None: raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) for ability in self.sources: diff --git a/shadowcraft/objects/priority_list.py b/shadowcraft/objects/priority_list.py index 6501b0f..4d7897b 100644 --- a/shadowcraft/objects/priority_list.py +++ b/shadowcraft/objects/priority_list.py @@ -1,9 +1,11 @@ +from __future__ import print_function +from builtins import object class PriorityList(object): def __init__(self, *args): #for each arg (string), read conditionals and determine checks for a in args: - print a #to implement later + print(a) #to implement later return def __getattr__(self, name): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 0342c84..da9c9b9 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -1,8 +1,10 @@ +from __future__ import division # None should be used to indicate unknown values # The Proc class takes these parameters: # stat, value, duration, proc_name, default_behaviour, max_stacks=1, can_crit=True, spell_behaviour=None # Assumed heroic trinkets have the same behaviour as the non-heroic kin. # behaviours must have a 'default' key so that the proc is properly initialized. +from past.utils import old_div allowed_procs = { #generic 'rogue_poison': { @@ -165,7 +167,7 @@ 'stat':'physical_dot', 'value': 0, # AP based 'aoe': True, - 'ap_coefficient': 2.5 / 3., # server-side, not in dbc, per tick is 2.5 / 3 + 'ap_coefficient': old_div(2.5, 3.), # server-side, not in dbc, per tick is 2.5 / 3 'duration': 1.5, 'dot_ticks': 3, 'proc_name': 'Mark of the Distant Army', @@ -313,7 +315,7 @@ 'value': {'haste': 0, 'crit': 0, 'mastery': 0}, #TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 20, 'proc_name': 'Triumvirate', - 'scaling': 2.069368 / 3., #FIXME: for now using 1/3 for each stat / assume we get all 3 for 1/3 each + 'scaling': old_div(2.069368, 3.), #FIXME: for now using 1/3 for each stat / assume we get all 3 for 1/3 each 'crm_scales': True, 'item_level': 875, 'source': 'trinket', @@ -437,7 +439,7 @@ 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #TODO: actually 1-3 stat buffs each time 'duration': 8, 'proc_name': 'Screams of the Dead', - 'scaling': 2.297781 / 3., #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'scaling': old_div(2.297781, 3.), #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci 'crm_scales': True, 'item_level': 805, 'source': 'trinket', @@ -451,7 +453,7 @@ 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #rpp-scaled, TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 10, 'proc_name': 'Allies of Nature', - 'scaling': 1.378778 / 3., #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'scaling': old_div(1.378778, 3.), #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci 'crm_scales': True, 'item_level': 850, 'source': 'trinket', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 66b8626..6bc2e3f 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -1,3 +1,4 @@ +from builtins import object from shadowcraft.core import exceptions from shadowcraft.objects import proc_data from shadowcraft.objects import class_data diff --git a/shadowcraft/objects/race.py b/shadowcraft/objects/race.py index fe76c57..72d4a3d 100755 --- a/shadowcraft/objects/race.py +++ b/shadowcraft/objects/race.py @@ -1,3 +1,6 @@ +from builtins import map +from builtins import zip +from builtins import object from shadowcraft.core import exceptions class InvalidRaceException(exceptions.InvalidInputException): @@ -105,7 +108,7 @@ def calculate_rocket_barrage(self, ap, spfi, int): def __init__(self, race, character_class="rogue", level=85): self.character_class = str.lower(character_class) self.race_name = race - if self.race_name not in Race.racial_stat_offset.keys(): + if self.race_name not in list(Race.racial_stat_offset.keys()): raise InvalidRaceException(_('Unsupported race {race}').format(race=self.race_name)) if self.character_class == "rogue": self.stat_set = Race.rogue_base_stats @@ -116,7 +119,7 @@ def __init__(self, race, character_class="rogue", level=85): def set_racials(self): # Set all racials, so we don't invoke __getattr__ all the time - for race, racials in Race.racials_by_race.items(): + for race, racials in list(Race.racials_by_race.items()): for racial in racials: setattr(self, racial, False) for racial in Race.racials_by_race[self.race_name]: @@ -138,7 +141,7 @@ def _set_constants_for_level(self): self.activated_racial_data["blood_fury_physical"]["value"] = self.blood_fury_bonuses[self.level]["ap"] self.activated_racial_data["blood_fury_spell"]["value"] = self.blood_fury_bonuses[self.level]["sp"] # this merges racial stats with class stats (ie, racial_stat_offset and rogue_base_stats) - self.stats = map(sum, zip(self.stats, Race.racial_stat_offset[self.race_name])) + self.stats = list(map(sum, list(zip(self.stats, Race.racial_stat_offset[self.race_name])))) self.set_racials() except KeyError as e: raise InvalidRaceException(_('Unsupported class/level combination {character_class}/{level}').format(character_class=self.character_class, level=self.level)) diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 5e3ad3d..110c086 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -1,3 +1,6 @@ +from __future__ import division +from builtins import object +from past.utils import old_div from shadowcraft.objects import buffs from shadowcraft.objects import procs from shadowcraft.objects import proc_data @@ -96,22 +99,22 @@ def get_character_stats(self, race, buffs=None): def get_mastery_from_rating(self, rating=None): if rating is None: rating = self.mastery - return 8 + rating / self.mastery_rating_conversion + return 8 + old_div(rating, self.mastery_rating_conversion) def get_crit_from_rating(self, rating=None): if rating is None: rating = self.crit - return rating / (100. * self.crit_rating_conversion) + return old_div(rating, (100. * self.crit_rating_conversion)) def get_haste_multiplier_from_rating(self, rating=None): if rating is None: rating = self.haste - return 1 + rating / (100. * self.haste_rating_conversion) + return 1 + old_div(rating, (100. * self.haste_rating_conversion)) def get_versatility_multiplier_from_rating(self, rating=None): if rating is None: rating = self.versatility - return 1. + rating / (100. * self.versatility_rating_conversion) + return 1. + old_div(rating, (100. * self.versatility_rating_conversion)) class Weapon(object): allowed_melee_enchants = proc_data.allowed_melee_enchants @@ -173,7 +176,7 @@ def is_melee(self): def damage(self, ap=0, weapon_speed=None): if weapon_speed == None: weapon_speed = self.speed - return weapon_speed * (self.weapon_dps + ap / 3.5) #used to be 14 + return weapon_speed * (self.weapon_dps + old_div(ap, 3.5)) #used to be 14 def normalized_damage(self, ap=0, weapon_speed=None): if weapon_speed == None: @@ -258,7 +261,7 @@ def rogue_t14_2pc_damage_bonus(self, spell): ('combat', 'ss', 'sinister_strike'): 0.15, ('subtlety', 'bs', 'backstab'): 0.1 } - for spells in bonus.keys(): + for spells in list(bonus.keys()): if spell in spells: return 1 + bonus[spells] return 1 @@ -273,7 +276,7 @@ def rogue_t15_2pc_bonus_cp(self): return 1 return 0 - def rogue_t15_4pc_reduced_cost(self, uptime= 12. / 180.): #This is for Mut calcs + def rogue_t15_4pc_reduced_cost(self, uptime= old_div(12., 180.)): #This is for Mut calcs cost_reduction = .15 if self.rogue_t15_4pc: return 1. - (cost_reduction * uptime) diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index b91f51f..07c7905 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -1,3 +1,6 @@ +from builtins import str +from builtins import range +from builtins import object from shadowcraft.core import exceptions from shadowcraft.objects import talents_data @@ -29,7 +32,7 @@ def __getattr__(self, name): def get_allowed_talents_for_level(self): allowed_talents_for_level = [] - for i in xrange(self.get_top_tier()): + for i in range(self.get_top_tier()): for talent in self.class_talents[i]: allowed_talents_for_level.append(talent) return allowed_talents_for_level @@ -54,7 +57,7 @@ def initialize_talents(self, talent_string): j = 0 self.reset_talents() for i in talent_string: - if int(i) not in range(4): + if int(i) not in list(range(4)): raise InvalidTalentException(_('Values in the talent string must be 0, 1, 2, 3, or sometimes 4')) if int(i) == 0 or i == '.': pass @@ -84,7 +87,7 @@ def get_tier_for_talent(self, name): if name not in self.allowed_talents: return None tier = 0 - for i in xrange(self.max_rows): + for i in range(self.max_rows): if name in self.class_talents[i]: return i diff --git a/test_ui/testing_ui.py b/test_ui/testing_ui.py index 92988b9..a22ff94 100644 --- a/test_ui/testing_ui.py +++ b/test_ui/testing_ui.py @@ -1,5 +1,8 @@ +from __future__ import print_function # All the imports here are either base python or shadowcraft files with the exception of wx, # which can be downloaded from http://www.wxpython.org/download.php (I worked with windows 2.6/64) +from builtins import str +from builtins import range from os import path import sys sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) @@ -134,7 +137,7 @@ def create_gem_ui_for_slot(self, slot): color_block.SetBackgroundColour(color.upper()) cb = wx.ComboBox(panel, -1, style = wx.CB_READONLY) cb.Bind(wx.EVT_COMBOBOX, self.on_gem_selected) - cb.SetItems([''] + ui_data.gems.keys()) + cb.SetItems([''] + list(ui_data.gems.keys())) cb.SetSelection(0) panel.Hide() @@ -145,7 +148,7 @@ def create_gem_ui_for_slot(self, slot): def create_enchant_ui_for_slot(self, master, slot): cb = None - enchants = [''] + self.get_enchants_for_slot(slot).keys() + enchants = [''] + list(self.get_enchants_for_slot(slot).keys()) cb = wx.ComboBox(master, -1, style = wx.CB_READONLY) cb.SetItems(enchants) cb.Bind(wx.EVT_COMBOBOX, self.on_enchant_selected) @@ -181,11 +184,11 @@ def populate_combobox_for_slot(self, combobox, slot): def get_items_for_slot(self, slot): item_names = [] items_dict = getattr(ui_data, slot) - item_names = items_dict.keys() + item_names = list(items_dict.keys()) return item_names def get_gems(self): - return ui_data.gems.keys() + return list(ui_data.gems.keys()) def get_enchants_for_slot(self, slot): enchants = [] @@ -198,7 +201,7 @@ def get_enchants_for_slot(self, slot): return enchants def update_gems_for_slot(self, slot): - for color in self.gems[slot].keys(): + for color in list(self.gems[slot].keys()): self.gems[slot][color].SetSelection(0) self.gems[slot][color].GetParent().Hide() item = self.current_gear[slot] @@ -236,7 +239,7 @@ def on_reforge(self, e, slot): def on_restore(self, e, slot): #Restoring the item to its dictionary definition - print self.current_gear[slot].name + print(self.current_gear[slot].name) self.update_item_for_slot(self.current_gear[slot].name, slot) self.reset_reforging_ui_for_slot(slot) self.calculator.calculate() @@ -263,7 +266,7 @@ def get_stats(self): current_stats = {'str': 0, 'agi': 0, 'ap': 0, 'crit': 0, 'hit': 0, 'exp': 0, 'haste': 0, 'mastery': 0, 'procs': [], 'gear_buffs': []} current_stats['procs'] = [] current_stats['gear_buffs'] = ['leather_specialization'] #Assuming this rather than give equipment an armor type - enchant_slots = self.enchants.keys() + enchant_slots = list(self.enchants.keys()) tier11_count = 0 tier12_count = 0 @@ -303,7 +306,7 @@ def get_stats(self): enchant_name = self.enchants[slot].GetValue() if len(enchant_name) > 0: enchant_data = ui_data.enchants[slot][enchant_name] - for stat in enchant_data.keys(): + for stat in list(enchant_data.keys()): current_stats[stat] += enchant_data[stat] if tier11_count >= 2: current_stats['gear_buffs'].append('rogue_t11_2pc') @@ -425,7 +428,7 @@ def add_talents_for_spec(self, spec): talents_this_tier += 1 spec_box.Add(wx.StaticText(self, -1, label = talent), flag = wx.ALIGN_RIGHT) combo = self.create_combo_with_max(talent_data[talent][0]) - if ui_data.default_talents.has_key(talent): + if talent in ui_data.default_talents: combo.SetSelection(ui_data.default_talents[talent]) self.talents[talent] = combo spec_box.Add(combo) @@ -533,7 +536,7 @@ def __init__(self, parent, calculator): self.SetSizer(sizer) def create_race_selector(self): - races = race.Race.racial_stat_offset.keys() + races = list(race.Race.racial_stat_offset.keys()) cb = self.create_combobox_with_options(races) self.race = cb return cb @@ -694,8 +697,8 @@ def calculate(self): def pretty_print(self, my_dict): ret_str = '' - max_len = max(len(entry[0]) for entry in my_dict.items()) - dict_values = my_dict.items() + max_len = max(len(entry[0]) for entry in list(my_dict.items())) + dict_values = list(my_dict.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: ret_str += value[0] + ':' + ' ' * (max_len - len(value[0])) + str(value[1]) + os.linesep diff --git a/test_ui/ui_data.py b/test_ui/ui_data.py index ee35a67..1b59069 100644 --- a/test_ui/ui_data.py +++ b/test_ui/ui_data.py @@ -1,3 +1,4 @@ +from __future__ import print_function # the following items had incorrect stats and should be corrected now # necklace of strife # wind dancer tunic @@ -9,6 +10,8 @@ # Wind Dancer's Spaulders # lots of necks/heads +from builtins import str +from builtins import object import math class Item(object): @@ -52,13 +55,13 @@ def reforgable_to(self): return reforgable def reforge(self, from_stat, to_stat): - print "before: " + from_stat + " = " + str(getattr(self, from_stat)) - print " " + to_stat + " = " + str(getattr(self, to_stat)) + print("before: " + from_stat + " = " + str(getattr(self, from_stat))) + print(" " + to_stat + " = " + str(getattr(self, to_stat))) reforged_value = math.floor(getattr(self, from_stat) * 0.4) setattr(self, from_stat, getattr(self, from_stat) - reforged_value) setattr(self, to_stat, reforged_value) - print "after: " + from_stat + " = " + str(getattr(self, from_stat)) - print " " + to_stat + " = " + str(getattr(self, to_stat)) + print("after: " + from_stat + " = " + str(getattr(self, from_stat))) + print(" " + to_stat + " = " + str(getattr(self, to_stat))) class Weapon(Item): def __init__(self, name, id=0, str=0, agi=0, ap=0, crit=0, hit=0, exp=0, haste=0, mastery=0, sockets=[], bonus_stat='', bonus_value=0, proc='', gear_buff='', damage=0, speed=0, type=''): diff --git a/tests/calcs_tests/rogue_tests/__init__.py b/tests/calcs_tests/rogue_tests/__init__.py index d68931e..a532593 100644 --- a/tests/calcs_tests/rogue_tests/__init__.py +++ b/tests/calcs_tests/rogue_tests/__init__.py @@ -1,3 +1,4 @@ +from builtins import object import unittest from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.core import exceptions @@ -10,7 +11,7 @@ from shadowcraft.objects import artifact_data as artifact_data from shadowcraft.calcs.rogue.Aldriana import settings as _settings -class RogueDamageCalculatorFactory: +class RogueDamageCalculatorFactory(object): def __init__(self, spec, **kwargs): self.class_name = 'rogue' self.talent_str = '0000000' @@ -92,7 +93,7 @@ def build(self, **kwargs): self.calculator.level = 110 return self.calculator -class RogueDamageCalculatorTestBase: +class RogueDamageCalculatorTestBase(object): def compare_dps(self, a, b): return self.compare(a, b, "get_dps") diff --git a/tests/core_tests/exceptions_tests.py b/tests/core_tests/exceptions_tests.py index 0423025..640afc1 100644 --- a/tests/core_tests/exceptions_tests.py +++ b/tests/core_tests/exceptions_tests.py @@ -1,3 +1,4 @@ +from builtins import str import unittest from shadowcraft.core.exceptions import InvalidInputException diff --git a/tests/objects_tests/stats_tests.py b/tests/objects_tests/stats_tests.py index 274729d..c0e52a8 100644 --- a/tests/objects_tests/stats_tests.py +++ b/tests/objects_tests/stats_tests.py @@ -1,3 +1,5 @@ +from __future__ import division +from past.utils import old_div import unittest from shadowcraft.core import exceptions from shadowcraft.objects import stats @@ -16,20 +18,20 @@ def test_set_constants_for_level(self): self.assertRaises(exceptions.InvalidLevelException, self.stats.__setattr__, 'level', 111) def test_get_mastery_from_rating(self): - self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 1234 / 350.0) - self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 350.0) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + old_div(1234, 350.0)) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + old_div(100, 350.0)) def test_get_versatility_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + 1222 / 40000.0) - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + 100 / 40000.0) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + old_div(1222, 40000.0)) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + old_div(100, 40000.0)) def test_get_crit_from_rating(self): - self.assertAlmostEqual(self.stats.get_crit_from_rating(), 899 / 35000.0) - self.assertAlmostEqual(self.stats.get_crit_from_rating(100), 100 / 35000.0) + self.assertAlmostEqual(self.stats.get_crit_from_rating(), old_div(899, 35000.0)) + self.assertAlmostEqual(self.stats.get_crit_from_rating(100), old_div(100, 35000.0)) def test_get_haste_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + 666 / 32500.0) - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + 100 / 32500.0) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + old_div(666, 32500.0)) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + old_div(100, 32500.0)) class TestGearBuffs(unittest.TestCase): diff --git a/tests/runtests.py b/tests/runtests.py index d6db385..cf062d7 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,24 +1,25 @@ +from __future__ import absolute_import import unittest from os import path import sys sys.path.append(path.abspath(path.dirname(__file__))) sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) -from calcs_tests import TestDamageCalculator -from calcs_tests.armor_mitigation_tests import TestArmorMitigation -from calcs_tests.rogue_tests import TestRogueDamageCalculator -from calcs_tests.rogue_tests import TestRogueDamageCalculatorLevels -from calcs_tests.rogue_tests.Aldriana_tests import TestAldrianasRogueDamageCalculator -from core_tests.exceptions_tests import TestInvalidInputException -from objects_tests.buffs_tests import TestBuffsTrue, TestBuffsFalse, TestBuffsLevel -from objects_tests.stats_tests import TestStats, TestWeapon, TestGearBuffs -from objects_tests.procs_tests import TestProcsList, TestProc -from objects_tests.race_tests import TestRace -from objects_tests.rogue_tests.rogue_glyphs_tests import TestRogueGlyphs -from objects_tests.rogue_tests.rogue_talents_tests import TestAssassinationTalents -from objects_tests.rogue_tests.rogue_talents_tests import TestCombatTalents -from objects_tests.rogue_tests.rogue_talents_tests import TestSubtletyTalents -from objects_tests.rogue_tests.rogue_talents_tests import TestRogueTalents +from .calcs_tests import TestDamageCalculator +from .calcs_tests.armor_mitigation_tests import TestArmorMitigation +from .calcs_tests.rogue_tests import TestRogueDamageCalculator +from .calcs_tests.rogue_tests import TestRogueDamageCalculatorLevels +from .calcs_tests.rogue_tests.Aldriana_tests import TestAldrianasRogueDamageCalculator +from .core_tests.exceptions_tests import TestInvalidInputException +from .objects_tests.buffs_tests import TestBuffsTrue, TestBuffsFalse, TestBuffsLevel +from .objects_tests.stats_tests import TestStats, TestWeapon, TestGearBuffs +from .objects_tests.procs_tests import TestProcsList, TestProc +from .objects_tests.race_tests import TestRace +from .objects_tests.rogue_tests.rogue_glyphs_tests import TestRogueGlyphs +from .objects_tests.rogue_tests.rogue_talents_tests import TestAssassinationTalents +from .objects_tests.rogue_tests.rogue_talents_tests import TestCombatTalents +from .objects_tests.rogue_tests.rogue_talents_tests import TestSubtletyTalents +from .objects_tests.rogue_tests.rogue_talents_tests import TestRogueTalents if __name__ == "__main__": unittest.main() From 3060e914f52e7e31d1a88f872fc7dea4270c889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 9 Mar 2017 16:12:47 +0100 Subject: [PATCH 168/265] Remove old_div Aside from old_div looking weird, we actually like that division will return floats without requiring explicit conversion now. --- .gitignore | 1 + scripts/assassination.py | 3 +- scripts/combat_import.py | 3 +- scripts/dm_combat.py | 3 +- scripts/import_character.py | 3 +- scripts/outlaw.py | 3 +- scripts/subtlety.py | 3 +- scripts/subtlety_import.py | 5 +- scripts/wowapi/wowapi/utilities.py | 5 +- shadowcraft/calcs/__init__.py | 33 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 311 +++++++++---------- shadowcraft/calcs/rogue/__init__.py | 5 +- shadowcraft/objects/proc_data.py | 9 +- shadowcraft/objects/stats.py | 13 +- tests/objects_tests/stats_tests.py | 17 +- 15 files changed, 202 insertions(+), 215 deletions(-) diff --git a/.gitignore b/.gitignore index 31e0ed1..5e48c1a 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ item_db.db /ShadowCraft-Engine.pyproj.user /ShadowCraft-Engine.sln /.vs/ShadowCraft-Engine/v14/.suo +/.vscode /TestResults diff --git a/scripts/assassination.py b/scripts/assassination.py index 14b01b8..67dc7b3 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -2,7 +2,6 @@ from __future__ import print_function # Simple test program to debug + play with assassination models. from builtins import str -from past.utils import old_div from os import path import sys from pprint import pprint @@ -126,7 +125,7 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(old_div(float(value[1]),total_sum))) != '0.00': + if show_percent and ("{0:.2f}".format(float(value[1]) / total_sum)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/combat_import.py b/scripts/combat_import.py index 08235a1..2ac6d59 100755 --- a/scripts/combat_import.py +++ b/scripts/combat_import.py @@ -2,7 +2,6 @@ from __future__ import print_function # Simple test program to debug + play with combat models. from builtins import str -from past.utils import old_div from os import path import sys from import_character import CharacterData @@ -133,7 +132,7 @@ def pretty_print(dict_list): dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/dm_combat.py b/scripts/dm_combat.py index 1dbe4ec..dd766fe 100644 --- a/scripts/dm_combat.py +++ b/scripts/dm_combat.py @@ -2,7 +2,6 @@ from __future__ import print_function # Simple test program to debug + play with assassination models. from builtins import str -from past.utils import old_div from os import path import sys sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) @@ -108,7 +107,7 @@ def pretty_print(dict_list, total_sum = 1., show_percent=False): dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + if show_percent and ("{0:.2f}".format(value[1] / total_dps)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/import_character.py b/scripts/import_character.py index f69013e..0886af3 100755 --- a/scripts/import_character.py +++ b/scripts/import_character.py @@ -4,7 +4,6 @@ # -*- coding: utf-8 -*- from builtins import str from builtins import range -from past.utils import old_div from builtins import object from os import path from types import * @@ -263,7 +262,7 @@ def get_weapon(self, weapon_data, item_data): 20:'fishing_pole'} tmpItem = get_item_cached(self.region, item_data[u'id']) damage_info = weapon_info[u'damage'] - damage = old_div((damage_info[u'max'] + damage_info[u'min']), 2) + damage = (damage_info[u'max'] + damage_info[u'min']) / 2 speed = weapon_info[u'weaponSpeed'] type = weaponMap[ tmpItem['data'][u'itemSubClass'] ] enchant = CharacterData.enchants[item_data[u'tooltipParams'][u'enchant']] diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 311e336..8b61f92 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -2,7 +2,6 @@ from __future__ import print_function # Simple test program to debug + play with assassination models. from builtins import str -from past.utils import old_div from os import path import sys from pprint import pprint @@ -111,7 +110,7 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): for value in dict_values: #print value[0] + ':' + ' ' * (max_len - len(value[0])), #str(value[1]) - if show_percent and ("{0:.2f}".format(old_div(float(value[1]), total_dps))) != '0.00': + if show_percent and ("{0:.2f}".format(value[1] / total_dps)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' (' + str("{0:.2f}".format(100 * float(value[1]) / total_sum)) + '%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index a8772cb..b794924 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -2,7 +2,6 @@ from __future__ import print_function # Simple test program to debug + play with subtlety models. from builtins import str -from past.utils import old_div from os import path import sys from pprint import pprint @@ -151,7 +150,7 @@ def pretty_print(dict_list): dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/subtlety_import.py b/scripts/subtlety_import.py index d3814c1..c9f857d 100755 --- a/scripts/subtlety_import.py +++ b/scripts/subtlety_import.py @@ -2,11 +2,10 @@ from __future__ import print_function # Simple test program to debug + play with subtlety models. from builtins import str -from past.utils import old_div from os import path import sys from import_character import CharacterData -from char_info import charInfo +from char_info import charInfo #sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator @@ -131,7 +130,7 @@ def pretty_print(dict_list): dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - if ("{0:.2f}".format(old_div(float(value[1]),total_dps))) != '0.00': + if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) diff --git a/scripts/wowapi/wowapi/utilities.py b/scripts/wowapi/wowapi/utilities.py index 88e2dfb..f7ee7e5 100755 --- a/scripts/wowapi/wowapi/utilities.py +++ b/scripts/wowapi/wowapi/utilities.py @@ -1,5 +1,4 @@ from __future__ import division -from past.utils import old_div def http_datetime( dt=None ): if not dt: @@ -16,7 +15,7 @@ def http_datetime( dt=None ): def parse_http_datetime( datestring, utc_tzinfo=None, strict=False ): - + import re, datetime m = re.match(r'(?P[a-z]+), (?P\d+) (?P[a-z]+) (?P\d+) (?P\d+):(?P\d+):(?P\d+(\.\d+)?) (?P\w+)$', datestring, re.IGNORECASE) @@ -44,7 +43,7 @@ def parse_http_datetime( datestring, utc_tzinfo=None, strict=False ): raise ValueError('HTTP date has an unrecognizable month') y = int(m.group('Y')) if y < 100: - century = old_div(datetime.datetime.utcnow().year, 100) + century = datetime.datetime.utcnow().year / 100 if y < 50: y = century * 100 + y else: diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index c21106d..1ef3db9 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -4,7 +4,6 @@ from builtins import zip from builtins import str from builtins import object -from past.utils import old_div import gettext import builtins import math @@ -143,7 +142,7 @@ def set_rppm_uptime(self, proc): e_minus_lambda = math.e ** (-1 * lambd) proc.uptime = 1.1307 * (e_lambda - 1) * (1 - ((1 - e_minus_lambda) ** proc.max_stacks)) else: - mean_proc_time = old_div(60., (haste * proc.get_rppm_proc_rate(spec=self.spec))) + proc.icd - min(proc.icd, 10) + mean_proc_time = 60 / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) proc.uptime = 1.1307 * proc.duration / mean_proc_time def set_uptime(self, proc, attacks_per_second, crit_rates): @@ -153,7 +152,7 @@ def set_uptime(self, proc, attacks_per_second, crit_rates): procs_per_second = self.get_procs_per_second(proc, attacks_per_second, crit_rates) if proc.icd: - proc.uptime = old_div(proc.duration, (proc.icd + old_div(1., procs_per_second))) + proc.uptime = proc.duration / (proc.icd + 1 / procs_per_second) else: if procs_per_second >= 1: self.set_uptime_for_ramping_proc(proc, procs_per_second) @@ -177,9 +176,9 @@ def average_damage_breakdowns(self, aps_dict, denom=180): for key in aps_dict: for entry in aps_dict[key][1]: if entry in final_breakdown: - final_breakdown[entry] += aps_dict[key][1][entry] * (old_div(aps_dict[key][0],denom)) + final_breakdown[entry] += aps_dict[key][1][entry] * (aps_dict[key][0] / denom) else: - final_breakdown[entry] = aps_dict[key][1][entry] * (old_div(aps_dict[key][0],denom)) + final_breakdown[entry] = aps_dict[key][1][entry] * (aps_dict[key][0] / denom) return final_breakdown def ep_helper(self, stat): @@ -210,7 +209,7 @@ def get_ep(self, ep_stats=None, normalize_ep_stat=None, baseline_dps=None): ep_values[stat] = 1.0 if normalize_ep_stat != stat: dps = self.ep_helper(stat) - ep_values[stat] = old_div(abs(dps - baseline_dps), normalize_dps_difference) + ep_values[stat] = abs(dps - baseline_dps) / normalize_dps_difference return ep_values @@ -229,7 +228,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep if dps: getattr(self.stats, hand).weapon_dps += 1. new_dps = self.get_dps() - ep = old_div(abs(new_dps - baseline_dps), (normalize_dps - baseline_dps)) + ep = abs(new_dps - baseline_dps) / (normalize_dps - baseline_dps) ep_values[hand + '_dps'] = ep getattr(self.stats, hand).weapon_dps -= 1. @@ -246,7 +245,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep getattr(self.stats, hand).set_enchant(enchant) new_dps = self.get_dps() if new_dps != no_enchant_dps: - ep = old_div(abs(new_dps - no_enchant_dps), (no_enchant_normalize_dps - no_enchant_dps)) + ep = abs(new_dps - no_enchant_dps) / (no_enchant_normalize_dps - no_enchant_dps) ep_values[hand + '_' + enchant] = ep getattr(self.stats, hand).set_enchant(old_enchant) @@ -256,7 +255,7 @@ def get_weapon_ep(self, speed_list=None, dps=False, enchants=False, normalize_ep for speed in speed_list: getattr(self.stats, hand).speed = speed new_dps = self.get_dps() - ep = old_div((new_dps - baseline_dps), (normalize_dps - baseline_dps)) + ep = (new_dps - baseline_dps) / (normalize_dps - baseline_dps) ep_values[hand + '_' + str(speed)] = ep getattr(self.stats, hand).speed = old_speed @@ -284,7 +283,7 @@ def get_weapon_type_ep(self, normalize_ep_stat=None): for wtype in ('dagger', 'one-hander'): getattr(self.stats, hand).type = wtype new_dps = self.get_dps() - ep = old_div((new_dps - baseline_dps), (normalize_dps - baseline_dps)) + ep = (new_dps - baseline_dps) / (normalize_dps - baseline_dps) ep_values[hand + '_type_' + wtype] = ep getattr(self.stats, hand).type = old_type @@ -327,7 +326,7 @@ def get_weapon_type_modifier_helper(self, setups=None): try: new_dps = self.get_dps() if new_dps != baseline_dps: - modifiers[tuple(current_setup)] = old_div(new_dps, baseline_dps) + modifiers[tuple(current_setup)] = new_dps / baseline_dps except InputNotModeledException: modifiers[tuple(current_setup)] = _('not allowed') for hand in baseline_setup: @@ -390,7 +389,7 @@ def get_other_ep(self, list, normalize_ep_stat=None): # engineering gizmos are handled as gear buffs by the engine. setattr(self.stats.gear_buffs, i, not getattr(self.stats.gear_buffs, i)) new_dps = self.get_dps() - ep_values[i] = old_div(abs(new_dps - baseline_dps), (normalize_dps_difference)) + ep_values[i] = abs(new_dps - baseline_dps) / (normalize_dps_difference) setattr(self.stats.gear_buffs, i, not getattr(self.stats.gear_buffs, i)) for i in procs_list: @@ -400,7 +399,7 @@ def get_other_ep(self, list, normalize_ep_stat=None): else: self.stats.procs.set_proc(i) new_dps = self.get_dps() - ep_values[i] = old_div(abs(new_dps - baseline_dps), (normalize_dps_difference)) + ep_values[i] = abs(new_dps - baseline_dps) / (normalize_dps_difference) if getattr(self.stats.procs, i): delattr(self.stats.procs, i) else: @@ -456,7 +455,7 @@ def get_upgrades_ep(self, _list, normalize_ep_stat=None): proc.update_proc_value() # after setting item_level re-set the proc value new_dps = self.get_dps() if new_dps != base_dps: - ep = old_div(abs(new_dps - base_dps), (base_normalize_dps - base_dps)) + ep = abs(new_dps - base_dps) / (base_normalize_dps - base_dps) ep_values[proc_name][l] = ep if old_proc: self.stats.procs.set_proc(proc_name) @@ -526,10 +525,10 @@ def get_upgrades_ep_fast(self, _list, normalize_ep_stat=None, exclude_list=None) new_dps = self.get_dps() if new_dps != base_dps: for l in group: - ep = old_div(abs(new_dps - base_dps), (base_normalize_dps - base_dps)) + ep = abs(new_dps - base_dps) / (base_normalize_dps - base_dps) if l > proc.item_level: upgraded_scale_factor = self.tools.get_random_prop_point(l) - ep *= old_div(float(upgraded_scale_factor), float(scale_factor)) + ep *= upgraded_scale_factor / scale_factor ep_values[proc_name][l] = ep if old_proc: self.stats.procs.set_proc(proc_name) @@ -615,7 +614,7 @@ def get_dps(self): def armor_mitigation_multiplier(self, armor=None): if not armor: armor = self.target_base_armor - return old_div(self.attacker_k_value, (self.attacker_k_value + armor)) + return self.attacker_k_value / (self.attacker_k_value + armor) def armor_mitigate(self, damage, armor): # Pass in raw physical damage and armor value, get armor-mitigated diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 3376a38..a07052b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -4,7 +4,6 @@ standard_library.install_aliases() from builtins import map from builtins import range -from past.utils import old_div import gettext import builtins import math @@ -201,10 +200,10 @@ def set_constants(self): self.spec_needs_converge = False #racials if self.race.arcane_torrent: - self.bonus_energy_regen += old_div(15., (120 + self.settings.response_time)) + self.bonus_energy_regen += 15 / (120 + self.settings.response_time) #auxiliary rotational effects if self.settings.feint_interval != 0: - self.bonus_energy_regen -= old_div(self.get_spell_stats('feint')[0], self.settings.feint_interval) + self.bonus_energy_regen -= self.get_spell_stats('feint')[0] / self.settings.feint_interval #only include if general multiplier applies to spec calculations @@ -278,9 +277,9 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ # There are 4 mirrors, 2 spawn in front of the get and are parryable # Each mirror swings a weapon with weapon damage based on 100% of AP haste_mult = self.stats.get_haste_multiplier_from_rating(current_stats['haste']) - swings_per_mirror = old_div(20.0,(old_div(2.0,haste_mult))) - total_swings = 2*swings_per_mirror + 2*(1.0-self.base_parry_chance)*swings_per_mirror - proc_value = total_swings*(old_div(average_ap,3.5)) * (1+ self.settings.num_boss_adds) + swings_per_mirror = 20 / (2 / haste_mult) + total_swings = 2 * swings_per_mirror + 2 * (1 - self.base_parry_chance) * swings_per_mirror + proc_value = total_swings*(average_ap / 3.5) * (1 + self.settings.num_boss_adds) #.424*max(AP, SP) if proc is getattr(self.stats.procs, 'felmouth_frenzy'): @@ -291,7 +290,7 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc.stat in ['physical_dot', 'spell_dot']: initial_tick = 1. if proc.dot_initial_tick else 0. - ticks_per_second = old_div(float(proc.dot_ticks - initial_tick), float(proc.duration)) + ticks_per_second = (proc.dot_ticks - initial_tick) / proc.duration average_damage *= initial_tick + ticks_per_second * proc.uptime / proc_count if proc.aoe: @@ -306,9 +305,9 @@ def set_openers(self): opener_cd = 30 if self.settings.use_opener == 'always': opener_spacing = (self.get_spell_cd('vanish') + self.settings.response_time) - total_openers_per_second = old_div((1. + math.floor(old_div((self.settings.duration - opener_cd), opener_spacing))), self.settings.duration) + total_openers_per_second = (1 + math.floor((self.settings.duration - opener_cd) / opener_spacing)) / self.settings.duration elif self.settings.use_opener == 'opener': - total_openers_per_second = old_div(1., self.settings.duration) + total_openers_per_second = 1 / self.settings.duration opener_spacing = None else: total_openers_per_second = 0 @@ -444,21 +443,21 @@ def lost_swings_from_swing_delay(self, delay, swing_timer): # : delay/swing_timer # : OH is the same value but 1 lower - t0 = max(min( delay_remainder/swing_timer*1.5, 1.5 ), 0) - t1 = max(old_div(min( num_sum - delay_remainder, .5 ),swing_timer), 0) - t2 = max(min( num_sum - delay_remainder - .5, .5 )/swing_timer * .5, 0) + t0 = max(min(delay_remainder / swing_timer * 1.5, 1.5), 0) + t1 = max(min(num_sum - delay_remainder, .5) / swing_timer, 0) + t2 = max(min(num_sum - delay_remainder - .5, .5 ) / swing_timer * .5, 0) - return old_div((t0+t1+t2),swing_timer) + return (t0 + t1 + t2) / swing_timer def set_uptime_for_ramping_proc(self, proc, procs_per_second): - time_for_one_stack = old_div(1, procs_per_second) + time_for_one_stack = 1 / procs_per_second if time_for_one_stack * proc.max_stacks > self.settings.duration: max_stacks_reached = self.settings.duration * procs_per_second - proc.uptime = old_div(max_stacks_reached, 2) + proc.uptime = max_stacks_reached / 2 else: missing_stacks = proc.max_stacks * (proc.max_stacks + 1) / 2 stack_time_lost = missing_stacks * time_for_one_stack - proc.uptime = proc.max_stacks - old_div(stack_time_lost, self.settings.duration) + proc.uptime = proc.max_stacks - stack_time_lost / self.settings.duration def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): if proc.is_real_ppm(): @@ -472,13 +471,13 @@ def update_with_damaging_proc(self, proc, attacks_per_second, crit_rates): if not proc.icd: frequency = haste * 1.1307 * proc.get_rppm_proc_rate(spec=self.spec) / 60 else: - mean_proc_time = old_div(60., (haste * proc.get_rppm_proc_rate(spec=self.spec))) + proc.icd - min(proc.icd, 10) + mean_proc_time = 60 / (haste * proc.get_rppm_proc_rate(spec=self.spec)) + proc.icd - min(proc.icd, 10) if proc.max_stacks > 1: # just correct if you only do damage on max_stacks, e.g. legendary_capacitive_meta mean_proc_time *= proc.max_stacks - frequency = old_div(1.1307, mean_proc_time) + frequency = 1.1307 / mean_proc_time else: if proc.icd: - frequency = old_div(1., (proc.icd + old_div(0.5, self.get_procs_per_second(proc, attacks_per_second, crit_rates)))) + frequency = 1 / (proc.icd + 0.5 / self.get_procs_per_second(proc, attacks_per_second, crit_rates)) else: frequency = self.get_procs_per_second(proc, attacks_per_second, crit_rates) @@ -522,9 +521,9 @@ def get_poison_counts(self, attacks_per_second, current_stats): if self.talents.agonizing_poison: attacks_per_second['agonizing_poison'] = total_hits_per_second * avg_poison_proc_rate else: - poison_procs = avg_poison_proc_rate * total_hits_per_second - old_div(1, self.settings.duration) + poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = old_div(1., 3) + attacks_per_second['deadly_poison'] = 1 / 3 def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 @@ -533,13 +532,13 @@ def get_average_alacrity(self, attacks_per_second): if finisher in attacks_per_second and finisher != 'death_from_above_pulse': for cp in range(7): stacks_per_second += 0.2 * cp * attacks_per_second[finisher][cp] - stack_time = old_div(10,stacks_per_second) + stack_time = 10 / stacks_per_second if stack_time > self.settings.duration: max_stacks = self.settings.duration * stacks_per_second - return old_div(max_stacks,2) + return max_stacks / 2 else: max_time = self.settings.duration - stack_time - return (old_div(max_time,self.settings.duration)) * 10 + (old_div(stack_time,self.settings.duration)) * 5 + return (max_time / self.settings.duration) * 10 + (stack_time / self.settings.duration) * 5 def determine_stats(self, attack_counts_function): current_stats = { @@ -718,7 +717,7 @@ def add_special_aps_penalties(self, attacks_per_second): dos = self.stats.procs.draught_of_souls if dos: lost_seconds = self.settings.duration * float(dos.duration) / float(dos.icd) - loss_ratio = old_div((self.settings.duration - lost_seconds), self.settings.duration) + loss_ratio = (self.settings.duration - lost_seconds) / self.settings.duration for attack in attacks_per_second: if attack not in ['mh_autoattacks', 'oh_autoattacks', 'shadow_blades', 'nightblade_ticks', 'rupture_ticks', 'from_the_shadows', 'kingsbane_ticks', 'garrote_ticks', 'deadly_poison']: @@ -851,7 +850,7 @@ def assassination_dps_breakdown(self): self.set_constants() self.vendetta_cd = self.get_spell_cd('vendetta') - self.vendetta_multiplier = 0.3 * (old_div(20, self.vendetta_cd)) + self.vendetta_multiplier = 0.3 * (20 / self.vendetta_cd) #cd stacking handlers if self.settings.cycle.kingsbane_with_vendetta == 'only': @@ -859,14 +858,14 @@ def assassination_dps_breakdown(self): kb_venn_uptime = 1.0 else: self.kingsbane_cd = self.get_spell_cd('kingsbane') - kb_venn_uptime = old_div(self.kingsbane_cd,self.vendetta_cd) + kb_venn_uptime = self.kingsbane_cd / self.vendetta_cd if self.settings.cycle.exsang_with_vendetta == 'only': self.exsang_cd = min(self.vendetta_cd), self.get_spell_cd('exsanguinate') exsang_venn_uptime = 1.0 else: self.exsang_cd = self.get_spell_cd('exsanguinate') - exsang_venn_uptime = old_div(self.exsang_cd,self.vendetta_cd) + exsang_venn_uptime = self.exsang_cd / self.vendetta_cd self.damage_modifiers.update_modifier_value('vendetta_time_average', 1 + self.vendetta_multiplier) self.damage_modifiers.update_modifier_value('vendetta_exsang', 1 + (self.vendetta_multiplier * exsang_venn_uptime)) @@ -904,12 +903,12 @@ def assassination_dps_breakdown(self): if self.talents.agonizing_poison: - stack_time = old_div(5.,aps['agonizing_poison']) + stack_time = 5 / aps['agonizing_poison'] max_time = self.settings.duration - stack_time - agonizing_poison_stacks = (old_div(max_time,self.settings.duration)) * 5 + (old_div(stack_time,self.settings.duration)) * 2.5 + agonizing_poison_stacks = (max_time / self.settings.duration) * 5 + (stack_time / self.settings.duration) * 2.5 agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives - agonizing_poison_adder += 1 + old_div((self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])), 2) + agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 #12% reduction from 4% per stack agonizing_poison_mod_per_stack= 0.0352 * agonizing_poison_adder @@ -923,12 +922,12 @@ def assassination_dps_breakdown(self): self.damage_modifiers.update_modifier_value('agonizing_poison', agonizing_poison_mod) if self.stats.gear_buffs.the_dreadlords_deceit: - avg_dreadlord_stacks = old_div(0.5,aps['fan_of_knives']) + avg_dreadlord_stacks = 0.5 / aps['fan_of_knives'] self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) if self.stats.gear_buffs.rogue_t19_4pc: if aps['mutilate'] < 0.125: - t19_4pc_multiplier = 0.1 * (old_div(aps['mutilate'], 0.125)) + t19_4pc_multiplier = 0.1 * (aps['mutilate'] / 0.125) self.damage_modifiers.update_modifier_value('t19_4pc', 1.2 + t19_4pc_multiplier) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -964,7 +963,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #if anticipation we can just assume no waste if self.talents.anticipation: avg_cp_per_builder = sum([cp * cpg_cps[cp] for cp in cpg_cps]) - builders_per_finisher = old_div(self.settings.finisher_threshold,avg_cp_per_builder) + builders_per_finisher = self.settings.finisher_threshold / avg_cp_per_builder avg_finisher_size = self.settings.finisher_threshold finisher_list = [0, 0, 0, 0, 0, 0, 0] finisher_list[self.settings.finisher_threshold] = 1.0 @@ -1009,15 +1008,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_rupture_duration = 4 * (1 + avg_finisher_size) if self.talents.exsanguinate: #assume full pandemic on exsanged ruptures - exsang_rupture_duration = old_div((1.3 * base_rupture_duration),2) + exsang_rupture_duration = (1.3 * base_rupture_duration) / 2 #rupture we're pandemicing from exsang_from_duration = 0.7 * base_rupture_duration - normal_ruptures_per_exsang_cd = old_div((self.exsang_cd - exsang_from_duration - exsang_rupture_duration),base_rupture_duration) - ruptures_per_second = old_div((2. + normal_ruptures_per_exsang_cd), self.exsang_cd) + normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration) / base_rupture_duration + ruptures_per_second = (2. + normal_ruptures_per_exsang_cd) / self.exsang_cd rupture_ticks_per_second = 1. * float(exsang_rupture_duration)/ self.exsang_cd + \ 0.5 * float(self.exsang_cd - exsang_rupture_duration)/self.exsang_cd else: - ruptures_per_second = old_div(1., base_rupture_duration) + ruptures_per_second = 1 / base_rupture_duration rupture_ticks_per_second = 0.5 for cp in range(7): @@ -1031,15 +1030,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_garrote_duration = 18. garrote_cooldown = self.get_spell_cd('garrote') if self.talents.exsanguinate: - exsang_garrote_duration = old_div(base_garrote_duration, 2) + exsang_garrote_duration = base_garrote_duration / 2 exsang_downtime = garrote_cooldown - exsang_garrote_duration - normal_garrote_per_exsang = old_div((self.exsang_cd - garrote_cooldown), base_garrote_duration) - attacks_per_second['garrote'] = old_div((1 + normal_garrote_per_exsang), self.exsang_cd) - attacks_per_second['garrote_ticks'] = 2./3 * float(exsang_garrote_duration) / self.exsang_cd + \ - 1./3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd + normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration + attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd + attacks_per_second['garrote_ticks'] = 2/3 * float(exsang_garrote_duration) / self.exsang_cd + \ + 1/3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd else: - attacks_per_second['garrote'] = old_div(1., base_garrote_duration) - attacks_per_second['garrote_ticks'] = old_div(1., 3) + attacks_per_second['garrote'] = 1 / base_garrote_duration + attacks_per_second['garrote_ticks'] = 1 / 3 cp_budget = attacks_per_second['garrote'] * self.settings.duration garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] @@ -1056,14 +1055,14 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): cp_budget += mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: - fok_interval = old_div(1.,60) + fok_interval = 1 / 60 attacks_per_second['fan_of_knives'] = fok_interval cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) net_energy_per_second -= fok_interval * 35 if self.traits.kingsbane: - attacks_per_second['kingsbane'] = old_div(1.,self.kingsbane_cd) - attacks_per_second['kingsbane_ticks'] = old_div(7., self.kingsbane_cd) + attacks_per_second['kingsbane'] = 1 / self.kingsbane_cd + attacks_per_second['kingsbane_ticks'] = 7 / self.kingsbane_cd kb_crit = crit_rates['kingsbane'] cpg_cps = {1: (1 - kb_crit) ** 2, 2: 2 * (1 - kb_crit) * kb_crit, @@ -1073,7 +1072,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] if self.talents.hemorrhage: - hemos_per_second = old_div(1.,20) + hemos_per_second = 1 / 20 attacks_per_second['hemorrhage'] = hemos_per_second hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) cp_budget += hemo_cps @@ -1081,7 +1080,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_per_second = old_div(1.,dfa_cd) + dfa_per_second = 1 / dfa_cd attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] for cp in range(7): @@ -1100,14 +1099,14 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += max_energy #assume you get 50% of max energy back each time if self.traits.urge_to_kill: - energy_budget += (old_div(self.settings.duration,self.vendetta_cd)) * 0.5 * max_energy + energy_budget += (self.settings.duration / self.vendetta_cd) * 0.5 * max_energy attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] #spend those extra cps if cp_budget > 0: - extra_envenom = old_div(float(cp_budget),avg_finisher_size) + extra_envenom = cp_budget / avg_finisher_size energy_budget -= self.get_spell_cost('envenom') * extra_envenom - extra_envenom_per_second = old_div(extra_envenom,self.settings.duration) + extra_envenom_per_second = extra_envenom / self.settings.duration for cp in range(7): attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] @@ -1121,9 +1120,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - total_minicycles = old_div(float(energy_budget), mini_cycle_energy) - attacks_per_second[self.cp_builder] += old_div(float(total_minicycles * builders_per_finisher), self.settings.duration) - finishers_per_second = old_div(total_minicycles, self.settings.duration) + total_minicycles = energy_budget / mini_cycle_energy + attacks_per_second[self.cp_builder] += total_minicycles * builders_per_finisher / self.settings.duration + finishers_per_second = total_minicycles / self.settings.duration for cp in range(7): attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second energy_budget -= total_minicycles * mini_cycle_energy @@ -1135,15 +1134,15 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - attacks_per_second['mh_autoattacks'] = old_div((haste_multiplier * (1 + (alacrity_stacks * 0.01))),self.stats.mh.speed) + attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01))) / self.stats.mh.speed attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] if self.traits.bag_of_tricks: - bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (old_div(1.,sum(attacks_per_second['envenom']))) / 60 + bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (1 / sum(attacks_per_second['envenom'])) / 60 attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) if self.traits.from_the_shadows: - attacks_per_second['from_the_shadows'] = old_div(1., self.vendetta_cd) + attacks_per_second['from_the_shadows'] = 1 / self.vendetta_cd #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) @@ -1342,7 +1341,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): if self.talents.ghostly_strike: self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer - self.white_swing_downtime = old_div(self.settings.response_time, self.get_spell_cd('vanish')) + self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') # Compute dps phases each non-rerolling RtB buff combo AR and not phases = {} ar_phases = {} @@ -1365,7 +1364,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): true_bearing = 'tb' in phase shark = 's' in phase - chance = old_div(self.rtb_probabilities[len(phase)],self.rtb_buff_count[len(phase)]) + chance = self.rtb_probabilities[len(phase)] / self.rtb_buff_count[len(phase)] aps = self.outlaw_attack_counts_mincycle(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, shark=shark, true_bearing=true_bearing, duration=maintainence_buff_duration) @@ -1381,9 +1380,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): keep_tb_chance += chance if shark: keep_shark_chance += chance - keep_gm_uptime = old_div(keep_gm_chance,keep_chance) - keep_tb_uptime = old_div(keep_tb_chance,keep_chance) - keep_shark_uptime = old_div(keep_shark_chance,keep_chance) + keep_gm_uptime = keep_gm_chance / keep_chance + keep_tb_uptime = keep_tb_chance / keep_chance + keep_shark_uptime = keep_shark_chance / keep_chance # Merge AR and non-AR into single phases aps_keep = self.merge_attacks_per_second(phases, total_time=keep_chance) aps_keep_ar = self.merge_attacks_per_second(ar_phases, total_time=keep_chance) @@ -1410,7 +1409,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): true_bearing = 'tb' in phase shark = 's' in phase - chance = old_div(self.rtb_probabilities[len(phase)],self.rtb_buff_count[len(phase)]) + chance = self.rtb_probabilities[len(phase)] / self.rtb_buff_count[len(phase)] aps, reroll_time = self.outlaw_attack_counts_reroll(current_stats, jolly=jolly, melee=melee, buried=buried, broadsides=broadsides, alacrity_stacks=alacrity_stacks) aps_ar, reroll_time_ar = self.outlaw_attack_counts_reroll(current_stats, ar=True, jolly=jolly, @@ -1428,9 +1427,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): # Check for reroll time, to protect from divide by zero if net_reroll_time: - reroll_tb_uptime = old_div(reroll_tb_time,net_reroll_time) - reroll_shark_uptime = old_div(reroll_shark_time,net_reroll_time) - reroll_gm_uptime = old_div(reroll_gm_time,net_reroll_time) + reroll_tb_uptime = reroll_tb_time / net_reroll_time + reroll_shark_uptime = reroll_shark_time / net_reroll_time + reroll_gm_uptime = reroll_gm_time / net_reroll_time else: reroll_tb_uptime = 0 reroll_shark_uptime = 0 @@ -1453,7 +1452,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): 'reroll': (ar_reroll_duration, aps_reroll_ar)} aps_ar = self.merge_attacks_per_second(phases, rtb_keep_duration + ar_reroll_duration) - keep_uptime = old_div(rtb_keep_duration,(rtb_keep_duration + reroll_duration)) + keep_uptime = rtb_keep_duration / (rtb_keep_duration + reroll_duration) tb_uptime = (keep_uptime * keep_tb_uptime) + (1 - keep_uptime) * reroll_tb_uptime gm_uptime = (keep_uptime * keep_gm_uptime) + (1 - keep_uptime) * reroll_gm_uptime shark_uptime = (keep_uptime * keep_shark_uptime) + (1 - keep_uptime) * reroll_shark_uptime @@ -1461,7 +1460,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): # Determine AR uptime and merge the two distributions attacks_per_second = self.merge_attacks_per_second({'normal': (self.ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=self.ar_cd) - ar_uptime = old_div(self.ar_duration, self.ar_cd) + ar_uptime = self.ar_duration / self.ar_cd tb_seconds_per_second = 0 ar_cd_modifier = 1 @@ -1477,7 +1476,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): for cp in range(7): cp_spend_per_second += attacks_per_second[ability][cp] * cp #tb_seconds_per_second = 2 * cp_spend_per_second * tb_uptime - ar_cd_modifier = (1 - old_div((2 * tb_uptime),(old_div(1., cp_spend_per_second) + 2 * tb_uptime))) + ar_cd_modifier = (1 - (2 * tb_uptime) / (1 / cp_spend_per_second + 2 * tb_uptime)) new_ar_cd = self.ar_cd * ar_cd_modifier attacks_per_second = self.merge_attacks_per_second({'normal': (new_ar_cd - self.ar_duration, aps_normal), 'ar': (self.ar_duration, aps_ar)}, total_time=new_ar_cd) @@ -1486,24 +1485,24 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #else: #old_ar_cd = new_ar_cd - ar_uptime = old_div(self.ar_duration, ar_cd) + ar_uptime = self.ar_duration / ar_cd # Add in Cannonball and Killing Spree if self.talents.killing_spree: - ksp_cd = old_div(self.get_spell_cd('killing_spree'), (1. + tb_seconds_per_second)) + ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) #ksp is 7 hits per hand - attacks_per_second['killing_spree'] = old_div(7.,ksp_cd) + attacks_per_second['killing_spree'] = 7 / ksp_cd if self.talents.cannonball_barrage: - cannonball_barrage_cd = old_div(self.get_spell_cd('cannonball_barrage'), (1. + tb_seconds_per_second)) - attacks_per_second['cannonball_barrage'] = old_div(1.,cannonball_barrage_cd) + cannonball_barrage_cd = self.get_spell_cd('cannonball_barrage') / (1. + tb_seconds_per_second) + attacks_per_second['cannonball_barrage'] = 1 / cannonball_barrage_cd # Figure swing timer and add Main Gauche attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd=self.talents.slice_and_dice) attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) - swing_timer = old_div(self.stats.mh.speed, attack_speed_multiplier) - attacks_per_second['mh_autoattacks'] = old_div(1.,swing_timer) - attacks_per_second['oh_autoattacks'] = old_div(1.,swing_timer) + swing_timer = self.stats.mh.speed / attack_speed_multiplier + attacks_per_second['mh_autoattacks'] = 1 / swing_timer + attacks_per_second['oh_autoattacks'] = 1 / swing_timer attacks_per_second['main_gauche'] = self.main_gauche_proc_rate * attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() # Add in Main Gauche @@ -1553,13 +1552,13 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll # set up our initial budgets energy_budget = duration * energy_regen - gcd_budget = old_div(duration,gcd_size) + gcd_budget = duration / gcd_size #since artifacts we'll just compute a one handed swing timer if self.talents.death_from_above and not ar: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - (10 * true_bearing) - dfa_count = old_div(duration,dfa_cd) - dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.mh.speed,attack_speed_multiplier)) + dfa_count = duration / dfa_cd + dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / attack_speed_multiplier) dfa_energy_lost = dfa_lost_swings * (self.main_gauche_proc_rate * self.combat_potency_from_mg + self.combat_potency_regen_per_oh) energy_budget -= dfa_energy_lost @@ -1576,16 +1575,16 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll else: energy_budget -= self.roll_the_bones_cost gcd_budget -= (ss_count + ps_count + 1) - attacks_per_second['saber_slash'] = old_div(float(ss_count + ps_count),duration) - attacks_per_second['pistol_shot'] = old_div(float(ps_count),duration) + attacks_per_second['saber_slash'] = (ss_count + ps_count) / duration + attacks_per_second['pistol_shot'] = ps_count / duration - attacks_per_second[maintainence_buff] = [old_div(v, duration) for v in finisher_list] + attacks_per_second[maintainence_buff] = [v / duration for v in finisher_list] if (shark and self.settings.cycle.between_the_eyes_policy == 'shark') or self.settings.cycle.between_the_eyes_policy == 'always': - bte_count = old_div(duration, (20 + self.settings.response_time - (10 * true_bearing))) - attacks_per_second['between_the_eyes'] = [old_div(float(v * bte_count), duration) for v in finisher_list] - attacks_per_second['pistol_shot'] += old_div(float(bte_count * ps_count), duration) - attacks_per_second['saber_slash'] += old_div(float(bte_count * (ss_count + ps_count)), duration) + bte_count = duration / (20 + self.settings.response_time - (10 * true_bearing)) + attacks_per_second['between_the_eyes'] = [v * bte_count / duration for v in finisher_list] + attacks_per_second['pistol_shot'] += bte_count * ps_count / duration + attacks_per_second['saber_slash'] += bte_count * (ss_count + ps_count) / duration energy_budget -= (bte_count * ss_count) * self.saber_slash_energy_cost energy_budget -= bte_count * self.between_the_eyes_energy_cost gcd_budget -= bte_count * (ss_count + ps_count + 1) @@ -1594,10 +1593,10 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll if self.talents.death_from_above and not ar: energy_budget -= ss_count * dfa_count * self.saber_slash_energy_cost energy_budget -= dfa_count * self.death_from_above_energy_cost - attacks_per_second['saber_slash'] += old_div(float((ss_count + ps_count) * dfa_count), duration) - attacks_per_second['pistol_shot'] += old_div(float(ps_count * dfa_count), duration) - attacks_per_second['death_from_above_strike'] = [old_div(float(v * dfa_count), duration) for v in finisher_list] - attacks_per_second['death_from_above_pulse'] = [old_div(float(v * dfa_count), duration) for v in finisher_list] + attacks_per_second['saber_slash'] += (ss_count + ps_count) * dfa_count / duration + attacks_per_second['pistol_shot'] += ps_count * dfa_count / duration + attacks_per_second['death_from_above_strike'] = [v * dfa_count / duration for v in finisher_list] + attacks_per_second['death_from_above_pulse'] = [v * dfa_count / duration for v in finisher_list] #DfA forces a 2 second GCD gcd_budget -= dfa_count * (ss_count + ps_count + 2) @@ -1606,36 +1605,36 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #consider ghostly strike if self.talents.ghostly_strike: - gs_count = old_div(duration,15.) + gs_count = duration / 15 bonus_cps += gs_count * (1 + broadsides) gs_energy = self.ghostly_strike_cost * gs_count energy_budget -= gs_energy gcd_budget -= gs_count - attacks_per_second['ghostly_strike'] = old_div(float(gs_count), duration) + attacks_per_second['ghostly_strike'] = gs_count / duration #consider MfD if self.talents.marked_for_death: - mfd_count = old_div((1 + self.settings.marked_for_death_resets), duration) + mfd_count = (1 + self.settings.marked_for_death_resets) / duration bonus_cps += 5 * (1 + self.settings.marked_for_death_resets) * mfd_count #consider Curse of the Dreadblades if self.traits.curse_of_the_dreadblades: - curse_cd_multiplier = old_div(duration, self.cotd_cd) + curse_cd_multiplier = duration / self.cotd_cd #curse lasts 12 seconds, half to RT, half to CP builders - curse_gcds = (old_div(12., gcd_size)) * curse_cd_multiplier - rt_count = old_div(curse_gcds, 2) + curse_gcds = (12 / gcd_size) * curse_cd_multiplier + rt_count = curse_gcds / 2 ps_per_ss = 0.35 if self.talents.swordmaster: ps_per_ss += 0.1 if jolly: ps_per_ss += 0.25 - ss_count = (old_div(curse_gcds, 2)) * (old_div(1, (ps_per_ss + 1))) - ps_count = (old_div(curse_gcds, 2)) * (old_div(ps_per_ss, (ps_per_ss + 1))) + ss_count = (curse_gcds / 2) * (1 / (ps_per_ss + 1)) + ps_count = (curse_gcds / 2) * (ps_per_ss / (ps_per_ss + 1)) - attacks_per_second['saber_slash'] += old_div(ss_count, self.cotd_cd) - attacks_per_second['pistol_shot'] += old_div(ps_count, self.cotd_cd) - attacks_per_second['run_through'][max_cps] += old_div(rt_count, self.cotd_cd) + attacks_per_second['saber_slash'] += ss_count / self.cotd_cd + attacks_per_second['pistol_shot'] += ps_count / self.cotd_cd + attacks_per_second['run_through'][max_cps] += rt_count / self.cotd_cd gcd_budget -= curse_gcds energy_budget -= (ss_count * self.saber_slash_energy_cost) + (rt_count * self.run_through_energy_cost) @@ -1645,7 +1644,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #spend bonus cps for max cp RTs - extra_rt = old_div((old_div(bonus_cps, max_cps)), duration) + extra_rt = (bonus_cps / max_cps) / duration gcd_budget -= extra_rt energy_budget -= extra_rt * self.run_through_energy_cost attacks_per_second['run_through'][max_cps] += extra_rt @@ -1661,11 +1660,11 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 - minicycle_count = min(old_div(gcd_budget, gcds_per_minicycle), old_div(energy_budget, energy_per_minicycle)) - attacks_per_second['saber_slash'] += old_div(float(minicycle_count * (ss_count + ps_count)), duration) - attacks_per_second['pistol_shot'] += old_div(float(minicycle_count * ps_count), duration) + minicycle_count = min(gcd_budget / gcds_per_minicycle, energy_budget / energy_per_minicycle) + attacks_per_second['saber_slash'] += minicycle_count * (ss_count + ps_count) / duration + attacks_per_second['pistol_shot'] += minicycle_count * ps_count / duration for i, v in enumerate(finisher_list): - attacks_per_second['run_through'][i] += old_div(float(minicycle_count * v), duration) + attacks_per_second['run_through'][i] += minicycle_count * v / duration #Don't need to converge if we don't have alacrity if not self.talents.alacrity: @@ -1701,11 +1700,11 @@ def outlaw_attack_counts_reroll(self, current_stats, ar=False, mg_cp_energy = self.get_mg_cp_regen_from_haste(attack_speed_multiplier) total_regen = energy_regen + mg_cp_energy - reroll_time = old_div(reroll_energy_cost, total_regen) + reroll_time = reroll_energy_cost / total_regen attacks_per_second = {} - attacks_per_second['saber_slash'] = old_div(float(ss_count + ps_count), reroll_time) - attacks_per_second['pistol_shot'] = old_div(float(ps_count), reroll_time) - attacks_per_second['roll_the_bones'] = [old_div(v, reroll_time) for v in finisher_list] + attacks_per_second['saber_slash'] = (ss_count + ps_count) / reroll_time + attacks_per_second['pistol_shot'] = ps_count / reroll_time + attacks_per_second['roll_the_bones'] = [v / reroll_time for v in finisher_list] return attacks_per_second, reroll_time #dict of (probability, aps) pairs @@ -1713,7 +1712,7 @@ def merge_attacks_per_second(self, aps_dicts, total_time=1.0): attacks_per_second = {} for key in aps_dicts: proportion, aps = aps_dicts[key] - uptime = old_div(float(proportion),total_time) + uptime = proportion / total_time for ability in aps: if ability in attacks_per_second: if isinstance(attacks_per_second[ability], list): @@ -1731,7 +1730,7 @@ def merge_attacks_per_second(self, aps_dicts, total_time=1.0): return attacks_per_second def get_mg_cp_regen_from_haste(self, haste_multiplier): - swing_per_second = old_div((self.stats.mh.speed * self.dw_mh_hit_chance),haste_multiplier) + swing_per_second = self.stats.mh.speed * self.dw_mh_hit_chance / haste_multiplier mg_regen = self.main_gauche_proc_rate * self.combat_potency_from_mg * swing_per_second cp_regen = self.combat_potency_regen_per_oh * swing_per_second return mg_regen + cp_regen @@ -1873,7 +1872,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.update_modifier_value('finality', finality_damage_boost) if self.stats.gear_buffs.the_dreadlords_deceit: - avg_dreadlord_stacks = old_div(0.5,aps['shuriken_storm']) + avg_dreadlord_stacks = 0.5 / aps['shuriken_storm'] self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -1915,16 +1914,16 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy #set initial dance budget - self.dance_budget = 3 + old_div(self.settings.duration,60.) + self.dance_budget = 3 + self.settings.duration / 60 shadow_blades_duration = 15. + (3.3333 * self.traits.soul_shadows) - self.shadow_blades_uptime = old_div(shadow_blades_duration,self.get_spell_cd('shadow_blades')) + self.shadow_blades_uptime = shadow_blades_duration / self.get_spell_cd('shadow_blades') #swing timer white_swing_downtime = 0 self.swing_reset_spacing = self.get_spell_cd('vanish') if self.swing_reset_spacing is not None: - white_swing_downtime += old_div(.5, self.swing_reset_spacing) + white_swing_downtime += .5 / self.swing_reset_spacing attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) @@ -1936,9 +1935,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps #2 net energy per 6 seconds from relentless strikes if self.talents.enveloping_shadows: - self.cp_budget += old_div(self.settings.duration,6.) - self.energy_budget += (old_div(2.,6)) * self.settings.duration - self.dance_budget += old_div((0.5 * self.settings.duration),60) + self.cp_budget += self.settings.duration / 6 + self.energy_budget += (2 / 6) * self.settings.duration + self.dance_budget += (0.5 * self.settings.duration) / 60 #setup timelines sod_duration = 35 @@ -1966,7 +1965,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if finisher == 'nightblade': joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) dance_count = len(joint) - dance_nb_uptime = old_div(dance_count,len(nightblade_timeline)) + dance_nb_uptime = dance_count / len(nightblade_timeline) elif finisher == 'eviscerate': dance_count = len(sod_timeline) sod_timeline = [] @@ -1981,43 +1980,43 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=True, finisher=finisher) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += (old_div((3. * spent_cps* dance_count),60)) - dance_count + self.dance_budget += ((3 * spent_cps * dance_count) / 60) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) #Add in ruptures not previously covered nightblade_count = len(nightblade_timeline) - attacks_per_second['nightblade'][self.settings.finisher_threshold] += old_div(float(nightblade_count),self.settings.duration) + attacks_per_second['nightblade'][self.settings.finisher_threshold] += nightblade_count / self.settings.duration self.cp_budget -= self.settings.finisher_threshold * nightblade_count self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('nightblade')) * nightblade_count - self.dance_budget += old_div((3. * self.settings.finisher_threshold * nightblade_count),60.) + self.dance_budget += (3 * self.settings.finisher_threshold * nightblade_count) / 60 #Add in various cooldown abilities #This could be made better with timelining but for now simple time average will do if self.traits.goremaws_bite: goremaws_bite_cd = self.get_spell_cd('goremaws_bite') + self.settings.response_time - attacks_per_second['goremaws_bite'] = old_div(1.,goremaws_bite_cd) - self.cp_budget += 3 * (old_div(self.settings.duration,goremaws_bite_cd)) - self.energy_budget += 30 * (old_div(self.settings.duration,goremaws_bite_cd)) + attacks_per_second['goremaws_bite'] = 1 / goremaws_bite_cd + self.cp_budget += 3 * (self.settings.duration / goremaws_bite_cd) + self.energy_budget += 30 * (self.settings.duration / goremaws_bite_cd) if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_count = old_div(self.settings.duration,dfa_cd) + dfa_count = self.settings.duration / dfa_cd - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.mh.speed, haste_multiplier)) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, old_div(self.stats.oh.speed, haste_multiplier)) + lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / haste_multiplier) + lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / haste_multiplier) - attacks_per_second['mh_autoattacks'] -= old_div(lost_swings_mh, dfa_cd) - attacks_per_second['oh_autoattacks'] -= old_div(lost_swings_oh, dfa_cd) + attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd + attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_strike'][self.max_spend_cps] += old_div(1.,dfa_cd) + attacks_per_second['death_from_above_strike'][self.max_spend_cps] += 1 / dfa_cd attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += old_div(1.,dfa_cd) + attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += 1 / dfa_cd self.cp_budget -= self.max_spend_cps * dfa_count self.energy_budget += (40 * (0.2 * self.max_spend_cps) - self.get_spell_cost('death_from_above')) * dfa_count - self.dance_budget += old_div((3. * self.max_spend_cps * dfa_count),60.) + self.dance_budget += (3 * self.max_spend_cps * dfa_count) / 60 #Need to handle shadow techniques now to account for swing timer loss attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance @@ -2029,7 +2028,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget += shadow_techniques_cps #vanish handling - vanish_count = old_div(self.settings.duration,self.get_spell_cd('vanish')) + vanish_count = self.settings.duration / self.get_spell_cd('vanish') #Treat subterfuge as a mini-dance if self.talents.subterfuge: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) @@ -2037,7 +2036,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy self.cp_budget += vanish_count * net_cps - self.dance_budget += (old_div((3. * spent_cps* vanish_count),60)) + self.dance_budget += (3. * spent_cps* vanish_count) / 60 self.rotation_merge(attacks_per_second, attack_counts, vanish_count) #Generate one final dance templates @@ -2050,14 +2049,14 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): cp_per_builder = 1 + self.shadow_blades_uptime if self.cp_builder == 'shuriken_storm': cp_per_builder += self.settings.num_boss_adds - energy_per_cp = old_div(self.get_spell_cost(self.cp_builder),(cp_per_builder)) + energy_per_cp = self.get_spell_cost(self.cp_builder) / cp_per_builder extra_evis = 0 extra_builders = 0 #Not enough dances, generate some more if self.dance_budget<0: cps_required = abs(self.dance_budget) * 20 - extra_evis += old_div(cps_required,self.settings.finisher_threshold) + extra_evis += cps_required / self.settings.finisher_threshold self.energy_budget += self.net_evis_cost #just subtract the cps because we'll fix those next self.cp_budget -= cps_required @@ -2072,7 +2071,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): dance_count = abs(self.dance_budget) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += (old_div((3. * spent_cps* dance_count),60.)) - dance_count + self.dance_budget += (3 * spent_cps * dance_count / 60) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) loop_counter += 1 @@ -2081,20 +2080,20 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.cp_budget <0: #can add since we know cp_budget is negative self.energy_budget += self.cp_budget * energy_per_cp - extra_builders += old_div(abs(self.cp_budget), cp_per_builder) + extra_builders += abs(self.cp_budget) / cp_per_builder self.cp_budget = 0 if self.cp_builder == 'shuriken_storm': - attacks_per_second['shuriken_storm-no-dance'] = old_div(extra_builders, self.settings.duration) + attacks_per_second['shuriken_storm-no-dance'] = extra_builders / self.settings.duration else: - attacks_per_second[self.cp_builder] = old_div(extra_builders, self.settings.duration) + attacks_per_second[self.cp_builder] = extra_builders / self.settings.duration attacks_per_second['eviscerate'][self.settings.finisher_threshold] += extra_evis #Hopefully energy budget here isn't negative, if it is we're in trouble #Now we convert all the energy we have left into mini-cycles #Each mini-cycle contains enough 1 dance and generators+finishers for one dance cps_per_dance = 20 - finishers_per_minicycle = old_div(cps_per_dance,self.settings.finisher_threshold) + finishers_per_minicycle = cps_per_dance / self.settings.finisher_threshold attack_counts_mini_cycle = attack_counts attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] @@ -2106,12 +2105,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) loop_counter += 1 cps_to_generate = max(cps_per_dance - self.cp_budget, 0) - builders_per_minicycle = old_div(cps_to_generate, cp_per_builder) + builders_per_minicycle = cps_to_generate / cp_per_builder mini_cycle_energy = 5 * finishers_per_minicycle - (cps_to_generate * energy_per_cp) #add in dance energy mini_cycle_energy += net_energy if cps_to_generate: - mini_cycle_count = old_div(float(self.energy_budget), abs(mini_cycle_energy)) + mini_cycle_count = self.energy_budget / abs(mini_cycle_energy) else: mini_cycle_count = 1 @@ -2154,18 +2153,18 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.akarris_soul and 'shadowstrike' in attacks_per_second: attacks_per_second['soul_rip'] = attacks_per_second['shadowstrike'] if self.traits.shadow_nova: - attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], old_div(1.,5.)) + attacks_per_second['shadow_nova'] = min(attacks_per_second['shadow_dance'], 1 / 5) #FIXME: Kinda hackish, better approach would be to compute a seperate dance rotation if self.stats.gear_buffs.the_dreadlords_deceit and (self.cp_builder =='backstab' or self.cp_builder == 'gloomblade'): - shuriken_interval = old_div(1.,60) + shuriken_interval = 1 / 60 attacks_per_second['shadowstrike'] -= shuriken_interval attacks_per_second['shuriken_storm'] = shuriken_interval self.stealth_shuriken_uptime = 1. self.stealth_shuriken_uptime = 0. if self.cp_builder == 'shuriken_storm': - self.stealth_shuriken_uptime = old_div(attacks_per_second['shuriken_storm'], (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'])) + self.stealth_shuriken_uptime = attacks_per_second['shuriken_storm'] / (attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance']) attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] del attacks_per_second['shuriken_storm-no-dance'] @@ -2174,7 +2173,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_time = 8. * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] if self.talents.subterfuge: stealth_time = 10. * attacks_per_second['shadow_dance'] + 8 * attacks_per_second['vanish'] - self.mos_time = old_div(float(stealth_time),self.settings.duration) + self.mos_time = stealth_time / self.settings.duration if self.talents.nightstalker: self.dance_nb_uptime = dance_nb_uptime @@ -2192,14 +2191,14 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_evis += attacks_per_second['vanish'] else: stealth_evis = 0 - self.stealth_evis_uptime = old_div(stealth_evis,sum(attacks_per_second['eviscerate'])) + self.stealth_evis_uptime = stealth_evis / sum(attacks_per_second['eviscerate']) if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] #add SoD auto crits if 'shadowstrike' in attacks_per_second: - sod_shadowstrikes = old_div(attacks_per_second['symbols_of_death'],attacks_per_second['shadowstrike']) + sod_shadowstrikes = attacks_per_second['symbols_of_death'] / attacks_per_second['shadowstrike'] crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes if self.talents.weaponmaster: @@ -2258,7 +2257,7 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): #fill remaining gcds with shadowstrikes cp_builder = self.dance_cp_builder cp_builder_cost = float(self.get_spell_cost(cp_builder, cost_mod=cost_mod)) - builder_count = min(dance_gcds, old_div((net_energy+max_dance_energy),cp_builder_cost)) + builder_count = min(dance_gcds, (net_energy + max_dance_energy) / cp_builder_cost) if vanish: attack_counts[cp_builder] = builder_count attack_counts['vanish'] = 1 @@ -2300,7 +2299,7 @@ def timeline_overlap(self, timeline_a, timeline_b, match_delta): #Takes in the full attacks per second dict and a raw attack counts dict #adds attack countes into the rotation at global scope def rotation_merge (self, attacks_per_second, attack_counts, count): - rotations_per_second = old_div(float(count),self.settings.duration) + rotations_per_second = count / self.settings.duration for ability in attack_counts: if ability in self.finisher_damage_sources: for cp in range(7): diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8874bf1..d8847f6 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -2,7 +2,6 @@ from future import standard_library standard_library.install_aliases() from builtins import range -from past.utils import old_div import gettext import builtins @@ -588,7 +587,7 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates haste = self.get_haste_multiplier(current_stats) stacks_per_use = min(oozeling.icd * haste * 1.1307 * 3 / 60, 6) #3 rppm, capped at 6 stacks, 1.1307 bad luck protection damage_per_use = self.get_proc_damage_contribution(oozeling, stacks_per_use, current_stats, ap, modifier_dict) - damage_breakdown[oozeling.proc_name] = old_div(damage_per_use, oozeling.icd) + damage_breakdown[oozeling.proc_name] = damage_per_use / oozeling.icd # Tirathon's Betrayal and Faulty Countermeasure for proc in [self.stats.procs.tirathons_betrayal, self.stats.procs.faulty_countermeasure]: @@ -596,4 +595,4 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates # both 20 RPPM with haste mod procs_per_use = proc.duration * 20 * 1.1307 * self.get_haste_multiplier(current_stats) / 60 damage_per_use = self.get_proc_damage_contribution(proc, procs_per_use, current_stats, ap, modifier_dict) - damage_breakdown[proc.proc_name] = old_div(damage_per_use, proc.icd) + damage_breakdown[proc.proc_name] = damage_per_use / proc.icd diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index da9c9b9..271ba20 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -4,7 +4,6 @@ # stat, value, duration, proc_name, default_behaviour, max_stacks=1, can_crit=True, spell_behaviour=None # Assumed heroic trinkets have the same behaviour as the non-heroic kin. # behaviours must have a 'default' key so that the proc is properly initialized. -from past.utils import old_div allowed_procs = { #generic 'rogue_poison': { @@ -167,7 +166,7 @@ 'stat':'physical_dot', 'value': 0, # AP based 'aoe': True, - 'ap_coefficient': old_div(2.5, 3.), # server-side, not in dbc, per tick is 2.5 / 3 + 'ap_coefficient': 2.5 / 3, # server-side, not in dbc, per tick is 2.5 / 3 'duration': 1.5, 'dot_ticks': 3, 'proc_name': 'Mark of the Distant Army', @@ -315,7 +314,7 @@ 'value': {'haste': 0, 'crit': 0, 'mastery': 0}, #TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 20, 'proc_name': 'Triumvirate', - 'scaling': old_div(2.069368, 3.), #FIXME: for now using 1/3 for each stat / assume we get all 3 for 1/3 each + 'scaling': 2.069368 / 3, #FIXME: for now using 1/3 for each stat / assume we get all 3 for 1/3 each 'crm_scales': True, 'item_level': 875, 'source': 'trinket', @@ -439,7 +438,7 @@ 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #TODO: actually 1-3 stat buffs each time 'duration': 8, 'proc_name': 'Screams of the Dead', - 'scaling': old_div(2.297781, 3.), #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'scaling': 2.297781 / 3, #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci 'crm_scales': True, 'item_level': 805, 'source': 'trinket', @@ -453,7 +452,7 @@ 'value': {'mastery': 0, 'crit': 0, 'haste': 0}, #rpp-scaled, TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time 'duration': 10, 'proc_name': 'Allies of Nature', - 'scaling': old_div(1.378778, 3.), #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci + 'scaling': 1.378778 / 3, #FIXME: for now using 1/3 for each stat, similar to entwined elemental foci 'crm_scales': True, 'item_level': 850, 'source': 'trinket', diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 110c086..463e370 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -1,6 +1,5 @@ from __future__ import division from builtins import object -from past.utils import old_div from shadowcraft.objects import buffs from shadowcraft.objects import procs from shadowcraft.objects import proc_data @@ -99,22 +98,22 @@ def get_character_stats(self, race, buffs=None): def get_mastery_from_rating(self, rating=None): if rating is None: rating = self.mastery - return 8 + old_div(rating, self.mastery_rating_conversion) + return 8 + rating / self.mastery_rating_conversion def get_crit_from_rating(self, rating=None): if rating is None: rating = self.crit - return old_div(rating, (100. * self.crit_rating_conversion)) + return rating / (100. * self.crit_rating_conversion) def get_haste_multiplier_from_rating(self, rating=None): if rating is None: rating = self.haste - return 1 + old_div(rating, (100. * self.haste_rating_conversion)) + return 1 + rating / (100. * self.haste_rating_conversion) def get_versatility_multiplier_from_rating(self, rating=None): if rating is None: rating = self.versatility - return 1. + old_div(rating, (100. * self.versatility_rating_conversion)) + return 1. + rating / (100. * self.versatility_rating_conversion) class Weapon(object): allowed_melee_enchants = proc_data.allowed_melee_enchants @@ -176,7 +175,7 @@ def is_melee(self): def damage(self, ap=0, weapon_speed=None): if weapon_speed == None: weapon_speed = self.speed - return weapon_speed * (self.weapon_dps + old_div(ap, 3.5)) #used to be 14 + return weapon_speed * (self.weapon_dps + ap / 3.5) #used to be 14 def normalized_damage(self, ap=0, weapon_speed=None): if weapon_speed == None: @@ -276,7 +275,7 @@ def rogue_t15_2pc_bonus_cp(self): return 1 return 0 - def rogue_t15_4pc_reduced_cost(self, uptime= old_div(12., 180.)): #This is for Mut calcs + def rogue_t15_4pc_reduced_cost(self, uptime=12/180): #This is for Mut calcs cost_reduction = .15 if self.rogue_t15_4pc: return 1. - (cost_reduction * uptime) diff --git a/tests/objects_tests/stats_tests.py b/tests/objects_tests/stats_tests.py index c0e52a8..3c3eac2 100644 --- a/tests/objects_tests/stats_tests.py +++ b/tests/objects_tests/stats_tests.py @@ -1,5 +1,4 @@ from __future__ import division -from past.utils import old_div import unittest from shadowcraft.core import exceptions from shadowcraft.objects import stats @@ -18,20 +17,20 @@ def test_set_constants_for_level(self): self.assertRaises(exceptions.InvalidLevelException, self.stats.__setattr__, 'level', 111) def test_get_mastery_from_rating(self): - self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + old_div(1234, 350.0)) - self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + old_div(100, 350.0)) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 1234 / 350) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 350) def test_get_versatility_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + old_div(1222, 40000.0)) - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + old_div(100, 40000.0)) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + 1222 / 40000) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + 100 / 40000) def test_get_crit_from_rating(self): - self.assertAlmostEqual(self.stats.get_crit_from_rating(), old_div(899, 35000.0)) - self.assertAlmostEqual(self.stats.get_crit_from_rating(100), old_div(100, 35000.0)) + self.assertAlmostEqual(self.stats.get_crit_from_rating(), 899 / 35000) + self.assertAlmostEqual(self.stats.get_crit_from_rating(100), 100 / 35000) def test_get_haste_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + old_div(666, 32500.0)) - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + old_div(100, 32500.0)) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + 666 / 32500) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + 100 / 32500) class TestGearBuffs(unittest.TestCase): From a2aa6a2209cc8d8458032717899f18162f36b7c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 9 Mar 2017 16:39:01 +0100 Subject: [PATCH 169/265] Thou shalt not tab --- scripts/wowapi/setup.py | 19 +++--- scripts/wowapi/wowapi/api.py | 12 ++-- setup.py | 10 ++- shadowcraft/objects/modifiers.py | 104 +++++++++++++++---------------- shadowcraft/objects/proc_data.py | 98 ++++++++++++++--------------- 5 files changed, 124 insertions(+), 119 deletions(-) diff --git a/scripts/wowapi/setup.py b/scripts/wowapi/setup.py index 934e579..5d75f79 100755 --- a/scripts/wowapi/setup.py +++ b/scripts/wowapi/setup.py @@ -1,11 +1,12 @@ from distutils.core import setup -setup(name='wowapi', - version='0.3.0', - description='Python module to access the WoW Api', - author='Dorwido', - author_email='darkz@gmx.de', - url='https://github.com/Dorwido/wowapi', - license='MIT', - packages=['wowapi'] -) \ No newline at end of file +setup( + name='wowapi', + version='0.3.0', + description='Python module to access the WoW Api', + author='Dorwido', + author_email='darkz@gmx.de', + url='https://github.com/Dorwido/wowapi', + license='MIT', + packages=['wowapi'] +) diff --git a/scripts/wowapi/wowapi/api.py b/scripts/wowapi/wowapi/api.py index 082dcce..53c9aa6 100755 --- a/scripts/wowapi/wowapi/api.py +++ b/scripts/wowapi/wowapi/api.py @@ -113,7 +113,7 @@ } class WoWApi(object): - + def __init__(self,privatekey=None,publickey=None,ssl=None): self.privkey = privatekey @@ -125,15 +125,15 @@ def __init__(self,privatekey=None,publickey=None,ssl=None): self.ssl = False else: self.ssl = ssl - + def _decode_response(self,response): - + if 'content-encoding' in response.info() and response.info()['content-encoding'] == 'gzip': - response = gzip.GzipFile(fileobj=io.StringIO(response.read())) + response = gzip.GzipFile(fileobj=io.StringIO(response.read())) try: data = json.loads(str(response.read(),'UTF-8')) except json.JSONDecodeError: @@ -175,7 +175,7 @@ def _get_data(self,region,data,params=None,lastmodified=None,lang=None,datatype= signature = None if self.privkey and self.pubkey: signature = self._sign_request('/api/wow/'+data,httpdate) - + if params: data += '?'+datatypes[datatype]['param']+'='+','.join(map(str, params)) @@ -348,7 +348,7 @@ def get_item_classes(self,region,lastmodified=None,lang=None): """ return self._get_data(region,datatypes['item_classes']['path'],None,lastmodified,lang) - + def get_quest(self,region,questid,lastmodified=None,lang=None): """ .. versionadded:: 0.2.3 diff --git a/setup.py b/setup.py index 1868c51..b438cf0 100644 --- a/setup.py +++ b/setup.py @@ -4,10 +4,14 @@ name='ShadowCraft-Engine', url='http://github.com/Aldriana/ShadowCraft-Engine/', version='0.1', - packages=['shadowcraft', - 'shadowcraft.calcs', 'shadowcraft.calcs.rogue', 'shadowcraft.calcs.rogue.Aldriana', + packages=[ + 'shadowcraft', + 'shadowcraft.calcs', + 'shadowcraft.calcs.rogue', + 'shadowcraft.calcs.rogue.Aldriana', 'shadowcraft.core', - 'shadowcraft.objects'], + 'shadowcraft.objects' + ], license='LGPL', long_description=open('README').read(), ) diff --git a/shadowcraft/objects/modifiers.py b/shadowcraft/objects/modifiers.py index bb7a724..e8c048a 100644 --- a/shadowcraft/objects/modifiers.py +++ b/shadowcraft/objects/modifiers.py @@ -7,70 +7,70 @@ #None value and then updates later. #Before final damage computation a dict is compiled and used for damage computation class ModifierList(object): - def __init__(self, sources): - self.sources = sources - self.modifiers = {} + def __init__(self, sources): + self.sources = sources + self.modifiers = {} - def register_modifier(self, modifier): - self.modifiers[modifier.name] = modifier - for ability in modifier.ability_list: - if ability not in self.sources: - raise exceptions.InvalidInputException(_('Unknown source {source} in damage modifier {mod}').format(source=ability, mod=modifier.name)) + def register_modifier(self, modifier): + self.modifiers[modifier.name] = modifier + for ability in modifier.ability_list: + if ability not in self.sources: + raise exceptions.InvalidInputException(_('Unknown source {source} in damage modifier {mod}').format(source=ability, mod=modifier.name)) - def update_modifier_value(self, modifier_name, value): - self.modifiers[modifier_name].value = value + def update_modifier_value(self, modifier_name, value): + self.modifiers[modifier_name].value = value - def compile_modifier_dict(self): - lumped_modifier = {s:1 for s in self.sources} + def compile_modifier_dict(self): + lumped_modifier = {s:1 for s in self.sources} - # mods for all damage - lumped_modifier['all_damage'] = 1 - for mod in list(self.modifiers.values()): - if mod.value is None: - raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - if mod.all_damage: - lumped_modifier['all_damage'] *= mod.value + # mods for all damage + lumped_modifier['all_damage'] = 1 + for mod in list(self.modifiers.values()): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) + if mod.all_damage: + lumped_modifier['all_damage'] *= mod.value - # mods for damage schools - for mod in list(self.modifiers.values()): - if mod.value is None: - raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - if mod.dmg_schools: - for school in mod.dmg_schools: - modname = 'school_' + school - if modname in lumped_modifier: - lumped_modifier[modname] *= mod.value - else: - lumped_modifier[modname] = lumped_modifier['all_damage'] * mod.value + # mods for damage schools + for mod in list(self.modifiers.values()): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) + if mod.dmg_schools: + for school in mod.dmg_schools: + modname = 'school_' + school + if modname in lumped_modifier: + lumped_modifier[modname] *= mod.value + else: + lumped_modifier[modname] = lumped_modifier['all_damage'] * mod.value - # mods for source abilities - for mod in list(self.modifiers.values()): - if mod.value is None: - raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) - for ability in self.sources: - if mod.blacklist: - if ability in mod.ability_list: - continue - else: - lumped_modifier[ability] *= mod.value - elif mod.all_damage or ability in mod.ability_list: - lumped_modifier[ability] *= mod.value + # mods for source abilities + for mod in list(self.modifiers.values()): + if mod.value is None: + raise exceptions.InvalidInputException(_('Modifier {mod} is uninitialized').format(mod=mod.name)) + for ability in self.sources: + if mod.blacklist: + if ability in mod.ability_list: + continue + else: + lumped_modifier[ability] *= mod.value + elif mod.all_damage or ability in mod.ability_list: + lumped_modifier[ability] *= mod.value - return lumped_modifier + return lumped_modifier #DamageModifier specifies any type of modifier applied to ability damage. #Each modifier is specified as a value applied to either a whitelist or blacklist of abilities. #Whitelist is default since it is more compact for most modifiers #but all damage modifiers can be represented either way class DamageModifier(object): - def __init__(self, name, value, ability_list, blacklist=False, all_damage=False, dmg_schools=None): - self.name = name - self.value = value - self.ability_list = ability_list - self.blacklist = blacklist - self.all_damage = all_damage - self.dmg_schools = dmg_schools + def __init__(self, name, value, ability_list, blacklist=False, all_damage=False, dmg_schools=None): + self.name = name + self.value = value + self.ability_list = ability_list + self.blacklist = blacklist + self.all_damage = all_damage + self.dmg_schools = dmg_schools - if self.all_damage and self.dmg_schools is not None: - raise exceptions.InvalidInputException(_('Modifier {mod} should only specify either all_damage or dmg_schools').format(mod=mod.name)) + if self.all_damage and self.dmg_schools is not None: + raise exceptions.InvalidInputException(_('Modifier {mod} should only specify either all_damage or dmg_schools').format(mod=mod.name)) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 271ba20..00b977f 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -160,7 +160,7 @@ 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'mark_of_the_distant_army': { #A distant army fires a volley of arrows, dealing 3 ticks of damage over 1.5 sec. 'stat':'physical_dot', @@ -176,7 +176,7 @@ 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'mark_of_the_claw': { #Permanently enchants a necklace to sometimes increase critical strike and haste by 1000 for 6 sec. 'stat':'stats', @@ -187,9 +187,9 @@ 'type': 'rppm', 'proc_rate': 3, 'trigger': 'all_attacks', - }, + }, - #Legion trinket procs + #Legion trinket procs 'arcanogolem_digit': { #Equip: Your attacks have a chance to rake all enemies in front of you for X Arcane damage. 'stat':'spell_damage', 'value': 0, #rpp-scaled @@ -206,7 +206,7 @@ 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'bloodstained_handkerchief': { #Use: Garrote your target from behind, causing them to bleed for X Physical damage every 3 sec until they die. (1 Min Cooldown) 'stat':'physical_damage', #modeled as icd because it's active FOREVER @@ -221,7 +221,7 @@ 'icd': 3, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'bloodthirsty_instinct': { #Equip: Your melee attacks have a chance to increase your Haste by X for 10 sec. This effect occurs more often against targets at low health. 'stat':'stats', @@ -235,7 +235,7 @@ 'type': 'rppm', 'proc_rate': 3, 'trigger': 'all_attacks', - }, + }, 'chaos_talisman': { #Equip: Your melee autoattacks grant you Chaotic Energy, increasing your Strength or Agility by X, stacking up to 20 times. If you do not autoattack an enemy for 4 sec, this effect will decrease by 1 stack every sec. 'stat':'stats', @@ -250,7 +250,7 @@ 'icd': 0, # stacks with every autohit 'proc_rate': 1, 'trigger': 'auto_attacks', - }, + }, 'chrono_shard': { #Equip: Your spells and abilities have a chance to grant you X Haste and 15% movement speed for 10 sec. 'stat':'stats', @@ -264,7 +264,7 @@ 'type': 'rppm', 'proc_rate': 1, 'trigger': 'all_attacks', - }, + }, 'convergence_of_fates': { #Equip: Your attacks have a chance to reduce the remaining cooldown on one of your powerful abilities by 5 sec. 'stat':'ability_modifier', @@ -276,7 +276,7 @@ 'type': 'rppm', 'proc_rate': {'assassination': 3.51, 'outlaw': 8.4, 'subtlety': 9}, 'trigger': 'all_attacks', - }, + }, #removed the ":" not sure which way it should be 'darkmoon_deck_dominion': { #Equip: Increase critical strike by X-Y. The amount of critical strike depends on the topmost card in the deck. Equip: Periodically shuffle the deck while in combat. @@ -307,7 +307,7 @@ 'proc_rate': 1, 'can_crit': True, 'trigger': 'all_attacks', - }, + }, 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant you a Fiery, Frost, or Arcane enchants for 20 sec. 'stat':'stats', @@ -321,7 +321,7 @@ 'type': 'rppm', 'proc_rate': 0.7, 'trigger': 'all_attacks', - }, + }, 'eye_of_command': { #Equip: Your melee auto attacks increase your Critical Strike by 148 for 10 sec, stacking up to 10 times. This effect is reset if you auto attack a different target. 'stat':'stats', @@ -337,7 +337,7 @@ 'icd': 0, # stacks with every autohit 'proc_rate': 1, 'trigger': 'auto_attacks', - }, + }, 'faulty_countermeasure': { #Use: Sheathe your weapons in ice for 30 sec, giving your attacks a chance to cause X additional Frost damage and slow the target's movement speed by 30% for 8 sec. (2 Min Cooldown) 'stat':'ability_modifier', #modeled in add_special_procs_damage @@ -353,7 +353,7 @@ 'icd': 120, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'giant_ornamental_pearl': { #Use: Become enveloped by a Gaseous Bubble that absorbs up to X damage for 8 sec. When the bubble is consumed or expires, it explodes and deals Y Frost damage to all nearby enemies within 10 yards. (1 Min Cooldown) 'stat':'spell_damage', @@ -371,7 +371,7 @@ 'proc_rate': 1, 'can_crit': True, 'trigger': 'all_attacks', - }, + }, 'horn_of_valor': { #Use: Sound the horn, increasing your primary stat by X for 30 sec. (2 Min Cooldown) 'stat':'stats', @@ -385,7 +385,7 @@ 'icd': 120, 'proc_rate': 1, 'trigger': 'all_attacks', - }, + }, 'infernal_alchemist_stone': { #Equip: When you heal or deal damage you have a chance to increase your Strength, Agility, or Intellect by X for 15 sec. Your highest stat is always chosen. 'stat': 'stats', @@ -414,7 +414,7 @@ 'proc_rate': 1, 'icd': 75, 'trigger': 'all_attacks' - }, + }, 'mark_of_dargrul': { #Equip: Your melee attacks have a chance to trigger a Landslide, dealing X Physical damage to all enemies directly in front of you. 'stat':'physical_damage', @@ -431,7 +431,7 @@ 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'memento_of_angerboda': { #Equip: Your melee attacks have a chance to activate Screams of the Dead, granting you a random combat enhancement for 8 sec. 'stat':'stats', @@ -445,7 +445,7 @@ 'type': 'rppm', 'proc_rate': 1.5, 'trigger': 'all_attacks', - }, + }, 'natures_call': { #Equip: Your melee attacks have a chance to grant you a blessing of one of the Allies of Nature for 10 sec. 'stat':'stats', @@ -460,7 +460,7 @@ 'proc_rate': 2, 'can_crit': True, #TODO: according to wowhead this can also proc Cleansed Drake's Breath for (scale factor 48.72993) damage 'trigger': 'all_attacks', - }, + }, 'nightblooming_frond': { #Equip: Your attacks have a chance to grant Recursive Strikes for 15 sec, causing your auto attacks to deal an additional X damage and increase the intensity of Recursive Strikes. 'stat':'ability_modifier', @@ -476,7 +476,7 @@ 'proc_rate': 1, 'can_crit': True, 'trigger': 'all_attacks', - }, + }, 'nightmare_egg_shell': { #Equip: Your melee attacks have a chance to grant you X Haste every 1 sec for 20 sec. 'stat':'stats', @@ -490,7 +490,7 @@ 'icd': 20, 'proc_rate': .7, 'trigger': 'all_attacks', - }, + }, 'ravaged_seed_pod': { #Use: Contaminate the ground beneath your feet for 10 sec, dealing X Shadow damage to enemies in the area each second. While you remain in this area, you gain Y Leech. (1 Min Cooldown) 'stat':'spell_damage', @@ -505,7 +505,7 @@ 'icd': 60, 'source': 'trinket', 'proc_rate': 1, - }, + }, 'six_feather_fan': { #Equip: Your attacks have a chance to launch a volley of 6 Wind Bolts, each dealing X Nature damage and slowing your target by 30% for 6 sec. 'stat':'spell_dot', @@ -522,7 +522,7 @@ 'proc_rate': 1, 'haste_scales': True, 'can_crit': True - }, + }, 'spiked_counterweight': { #Your melee attacks have a chance to deal X Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to Y extra damage dealt. 'stat':'physical_damage', @@ -534,7 +534,7 @@ 'type': 'rppm', 'source': 'trinket', 'proc_rate': .92, - }, + }, 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for X Physical damage every 0.75 sec. 'stat':'physical_dot', @@ -550,7 +550,7 @@ 'source': 'trinket', 'proc_rate': .7, 'haste_scales': True, - }, + }, 'tempered_egg_of_serpentrix': { #Equip: Your attacks have a chance to summon a Spawn of Serpentrix to assist you. 'stat':'spell_dot', @@ -567,9 +567,9 @@ 'can_crit': True, 'haste_scales': True, 'trigger': 'all_attacks', - }, + }, - 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal X Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. + 'terrorbound_nexus': { #Equip: Your melee attacks have a chance to unleash 4 Shadow Waves that deal X Shadow damage to enemies in their path. The waves travel 15 yards away from you, and then return. 'stat':'spell_damage', 'dmg_school': 'shadow', 'value': 0, #rpp-scaled @@ -585,7 +585,7 @@ 'haste_scales': True, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'the_devilsaurs_bite': { #Equip: Your attacks have a chance to inflict X Physical damage and stun the target for 1 sec. 'stat':'physical_damage', @@ -599,7 +599,7 @@ 'proc_rate': 2, 'haste_scales': True, 'can_crit': True - }, + }, 'tiny_oozeling_in_a_jar': { #Equip: Your melee attacks have a chance to grant you Congealing Goo, stacking up to 6 times. Use: Consume all Congealing Goo to vomit on enemies in front of you for 3 sec, inflicting X Nature damage per Goo consumed. (20 Sec Cooldown) 'stat':'special_model', @@ -616,7 +616,7 @@ 'icd': 20, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'tirathons_betrayal': { #Use: Empower yourself with dark energy, causing your attacks to have a chance to inflict 38847 additional Shadow damage and grant you a shield for 38847. Lasts 15 sec. (1 Min, 15 Sec Cooldown) 'stat':'ability_modifier', #modeled in add_special_procs_damage @@ -632,7 +632,7 @@ 'icd': 75, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, 'toe_knees_promise': { #Use: Create a Flame Gale at an enemy's location, dealing X Fire damage over 8 sec. If Flame Gale strikes an enemy affected by Thunder Ritual, Flame Gale's damage is increased by 30%, and its radius by 50%. (1 Min Cooldown) 'stat':'spell_damage', @@ -648,7 +648,7 @@ 'proc_rate': 1, 'can_crit': True, 'trigger': 'all_attacks', - }, + }, 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting X Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) 'stat':'physical_damage', @@ -664,21 +664,21 @@ 'proc_rate': 1, 'can_crit': True, 'trigger': 'all_attacks' - }, + }, - #Other Legion procs - 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. - 'stat':'stats', - 'value':{'mastery':3000}, - 'duration':15, - 'proc_name': "Jacin's Ruse", - 'item_level': 820, - 'type': 'rppm', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1, - 'can_crit': False, - 'trigger': 'all_attacks' + #Other Legion procs + 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. + 'stat':'stats', + 'value':{'mastery':3000}, + 'duration':15, + 'proc_name': "Jacin's Ruse", + 'item_level': 820, + 'type': 'rppm', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1, + 'can_crit': False, + 'trigger': 'all_attacks' }, 'march_of_the_legion_2pc': { #Equip: Your spells and attacks against Demons have a chance to deal an additional 27200 to 36800 Fire damage. @@ -721,7 +721,7 @@ 'haste_scales': False, 'can_crit': False, 'trigger': 'all_attacks' - }, + }, 'infallible_tracking_charm_mod': { 'stat':'damage_modifier', @@ -735,7 +735,7 @@ 'proc_rate': 3, 'haste_scales': False, 'trigger': 'all_attacks' - }, + }, #6.2 procs 'maalus': { 'stat': 'damage_modifier', From 098135fb4c7bf1acf92d42f8b09d0dd0a49e469b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 10 Mar 2017 14:20:56 +0100 Subject: [PATCH 170/265] Make the tests run again I didn't bother fixing a few Outlaw failures (e.g. outdated best talents) since Outlaw needs further modeling work anyway --- shadowcraft/__init__.py | 2 +- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- shadowcraft/calcs/rogue/__init__.py | 2 +- shadowcraft/core/__init__.py | 2 +- shadowcraft/core/i18n.py | 2 +- shadowcraft/objects/__init__.py | 2 +- shadowcraft/objects/buffs.py | 3 ++ shadowcraft/objects/procs.py | 3 ++ shadowcraft/objects/race.py | 3 ++ shadowcraft/objects/stats.py | 3 ++ shadowcraft/objects/talents.py | 2 +- tests/calcs_tests/__init__.py | 10 +++---- tests/objects_tests/procs_tests.py | 2 +- tests/objects_tests/stats_tests.py | 25 +++++++--------- tests/runtests.py | 31 ++++++++++---------- 16 files changed, 52 insertions(+), 44 deletions(-) diff --git a/shadowcraft/__init__.py b/shadowcraft/__init__.py index 95b6622..d133f1c 100644 --- a/shadowcraft/__init__.py +++ b/shadowcraft/__init__.py @@ -3,4 +3,4 @@ import gettext import builtins -builtins._ = gettext.gettext +_ = gettext.gettext diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 1ef3db9..4ef4cf6 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -10,7 +10,7 @@ import os import subprocess -builtins._ = gettext.gettext +_ = gettext.gettext from shadowcraft.core import exceptions from shadowcraft.objects import class_data diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a07052b..a9f50ec 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -10,7 +10,7 @@ from operator import add from copy import copy -builtins._ = gettext.gettext +_ = gettext.gettext from shadowcraft.calcs.rogue import RogueDamageCalculator from shadowcraft.core import exceptions diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index d8847f6..15c0193 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -5,7 +5,7 @@ import gettext import builtins -builtins._ = gettext.gettext +_ = gettext.gettext from shadowcraft.calcs import DamageCalculator from shadowcraft.core import exceptions diff --git a/shadowcraft/core/__init__.py b/shadowcraft/core/__init__.py index 95b6622..d133f1c 100644 --- a/shadowcraft/core/__init__.py +++ b/shadowcraft/core/__init__.py @@ -3,4 +3,4 @@ import gettext import builtins -builtins._ = gettext.gettext +_ = gettext.gettext diff --git a/shadowcraft/core/i18n.py b/shadowcraft/core/i18n.py index 994e417..baa7d04 100644 --- a/shadowcraft/core/i18n.py +++ b/shadowcraft/core/i18n.py @@ -7,7 +7,7 @@ import builtins import sys -builtins._ = gettext.gettext +_ = gettext.gettext # Domain: this needs to be the name of our .mo files TRANSLATION_DOMAIN = 'SCE' diff --git a/shadowcraft/objects/__init__.py b/shadowcraft/objects/__init__.py index 95b6622..d133f1c 100644 --- a/shadowcraft/objects/__init__.py +++ b/shadowcraft/objects/__init__.py @@ -3,4 +3,4 @@ import gettext import builtins -builtins._ = gettext.gettext +_ = gettext.gettext diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index b59cf01..c4408ae 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -1,6 +1,9 @@ from builtins import object from shadowcraft.core import exceptions +import gettext +_ = gettext.gettext + class InvalidBuffException(exceptions.InvalidInputException): pass diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 6bc2e3f..5d6ea55 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -5,6 +5,9 @@ import sys, traceback +import gettext +_ = gettext.gettext + class InvalidProcException(exceptions.InvalidInputException): pass diff --git a/shadowcraft/objects/race.py b/shadowcraft/objects/race.py index 72d4a3d..097a175 100755 --- a/shadowcraft/objects/race.py +++ b/shadowcraft/objects/race.py @@ -3,6 +3,9 @@ from builtins import object from shadowcraft.core import exceptions +import gettext +_ = gettext.gettext + class InvalidRaceException(exceptions.InvalidInputException): pass diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 463e370..94cec68 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -6,6 +6,9 @@ from shadowcraft.objects import race from shadowcraft.core import exceptions +import gettext +_ = gettext.gettext + class Stats(object): # For the moment, lets define this as raw stats from gear # AP is only AP bonuses from gear (as of Legion usually 0) diff --git a/shadowcraft/objects/talents.py b/shadowcraft/objects/talents.py index 07c7905..3315d43 100755 --- a/shadowcraft/objects/talents.py +++ b/shadowcraft/objects/talents.py @@ -10,7 +10,7 @@ class InvalidTalentException(exceptions.InvalidInputException): class Talents(object): - def __init__(self, talent_string, class_spec, game_class, level='110'): + def __init__(self, talent_string, class_spec, game_class, level=110): self.game_class = game_class self.class_spec = class_spec self.class_talents = talents_data.talents[(game_class,class_spec)] diff --git a/tests/calcs_tests/__init__.py b/tests/calcs_tests/__init__.py index cc2e62d..b3fcb6a 100644 --- a/tests/calcs_tests/__init__.py +++ b/tests/calcs_tests/__init__.py @@ -16,7 +16,7 @@ def make_calculator(self, buffs_list=[], gear_buffs_list=[], race_name='night_el test_mh = stats.Weapon(737, 1.8, 'dagger') test_oh = stats.Weapon(573, 1.4, 'dagger') test_ranged = stats.Weapon(1104, 2.0, 'thrown') - test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, + test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, agi=20909, stam=19566, crit=4402, @@ -45,7 +45,7 @@ def test_one_hand_melee_hit_chance(self): self.calculator.one_hand_melee_hit_chance(dodgeable=False, parryable=True), 1.0 - 0.03) def test_dual_wield_mh_hit_chance(self): - self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=False), 1.0 - 0.17) - self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=False), 1.0 - 0.17) - self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=True), 1.0 - 0.17 - 0.03) - self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=True), 1.0 - 0.17 - 0.03) \ No newline at end of file + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=False), 1.0 - 0.19) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=False), 1.0 - 0.19) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=False, parryable=True), 1.0 - 0.19 - 0.03) + self.assertAlmostEqual(self.calculator.dual_wield_mh_hit_chance(dodgeable=True, parryable=True), 1.0 - 0.19 - 0.03) diff --git a/tests/objects_tests/procs_tests.py b/tests/objects_tests/procs_tests.py index e1b3a73..0423ddb 100644 --- a/tests/objects_tests/procs_tests.py +++ b/tests/objects_tests/procs_tests.py @@ -32,7 +32,7 @@ def setUp(self): def test__init__(self): self.assertEqual(self.proc.stat, 'stats') - self.assertEqual(self.proc.value['haste'], 3399) + self.assertEqual(self.proc.value['haste'], 2880) self.assertEqual(self.proc.duration, 10) self.assertEqual(self.proc.proc_rate, 3) self.assertEqual(self.proc.trigger, 'all_attacks') diff --git a/tests/objects_tests/stats_tests.py b/tests/objects_tests/stats_tests.py index 3c3eac2..2d7713d 100644 --- a/tests/objects_tests/stats_tests.py +++ b/tests/objects_tests/stats_tests.py @@ -17,37 +17,32 @@ def test_set_constants_for_level(self): self.assertRaises(exceptions.InvalidLevelException, self.stats.__setattr__, 'level', 111) def test_get_mastery_from_rating(self): - self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 1234 / 350) - self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 350) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(), 8 + 1234 / 400) + self.assertAlmostEqual(self.stats.get_mastery_from_rating(100), 8 + 100 / 400) def test_get_versatility_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + 1222 / 40000) - self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + 100 / 40000) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(), 1 + 1222 / 47500) + self.assertAlmostEqual(self.stats.get_versatility_multiplier_from_rating(100), 1 + 100 / 47500) def test_get_crit_from_rating(self): - self.assertAlmostEqual(self.stats.get_crit_from_rating(), 899 / 35000) - self.assertAlmostEqual(self.stats.get_crit_from_rating(100), 100 / 35000) + self.assertAlmostEqual(self.stats.get_crit_from_rating(), 899 / 40000) + self.assertAlmostEqual(self.stats.get_crit_from_rating(100), 100 / 40000) def test_get_haste_multiplier_from_rating(self): - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + 666 / 32500) - self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + 100 / 32500) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(), 1 + 666 / 37500) + self.assertAlmostEqual(self.stats.get_haste_multiplier_from_rating(100), 1 + 100 / 37500) class TestGearBuffs(unittest.TestCase): def setUp(self): - self.gear = stats.GearBuffs('chaotic_metagem', 'gear_specialization', 'rogue_t11_2pc', 'potion_of_the_tolvir', 'engineer_glove_enchant', 'lifeblood') + self.gear = stats.GearBuffs('gear_specialization') self.gear_none = stats.GearBuffs() def test__getattr__(self): - self.assertTrue(self.gear.chaotic_metagem) self.assertTrue(self.gear.gear_specialization) - self.assertFalse(self.gear.rogue_t16_2pc) + self.assertFalse(self.gear.rogue_t19_2pc) self.assertRaises(AttributeError, self.gear.__getattr__, 'fake_gear_buff') - def test_metagem_crit_multiplier(self): - self.assertAlmostEqual(self.gear.metagem_crit_multiplier(), 1.03) - self.assertAlmostEqual(self.gear_none.metagem_crit_multiplier(), 1.0) - def test_gear_specialization_multiplier(self): self.assertAlmostEqual(self.gear.gear_specialization_multiplier(), 1.05) self.assertAlmostEqual(self.gear_none.gear_specialization_multiplier(), 1.0) diff --git a/tests/runtests.py b/tests/runtests.py index cf062d7..349dc6b 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -5,21 +5,22 @@ sys.path.append(path.abspath(path.dirname(__file__))) sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) -from .calcs_tests import TestDamageCalculator -from .calcs_tests.armor_mitigation_tests import TestArmorMitigation -from .calcs_tests.rogue_tests import TestRogueDamageCalculator -from .calcs_tests.rogue_tests import TestRogueDamageCalculatorLevels -from .calcs_tests.rogue_tests.Aldriana_tests import TestAldrianasRogueDamageCalculator -from .core_tests.exceptions_tests import TestInvalidInputException -from .objects_tests.buffs_tests import TestBuffsTrue, TestBuffsFalse, TestBuffsLevel -from .objects_tests.stats_tests import TestStats, TestWeapon, TestGearBuffs -from .objects_tests.procs_tests import TestProcsList, TestProc -from .objects_tests.race_tests import TestRace -from .objects_tests.rogue_tests.rogue_glyphs_tests import TestRogueGlyphs -from .objects_tests.rogue_tests.rogue_talents_tests import TestAssassinationTalents -from .objects_tests.rogue_tests.rogue_talents_tests import TestCombatTalents -from .objects_tests.rogue_tests.rogue_talents_tests import TestSubtletyTalents -from .objects_tests.rogue_tests.rogue_talents_tests import TestRogueTalents +from calcs_tests import TestDamageCalculator +from calcs_tests.rogue_tests import TestOutlawRogueDamageCalculator +from calcs_tests.rogue_tests import TestAssassinationRogueDamageCalculator +from calcs_tests.rogue_tests import TestSubtletyRogueDamageCalculator +from calcs_tests.rogue_tests import TestAOEOutlawRogueDamageCalculator +from calcs_tests.rogue_tests import TestAOEAssassinationRogueDamageCalculator +from calcs_tests.rogue_tests import TestAOESubtletyRogueDamageCalculator +from core_tests.exceptions_tests import TestInvalidInputException +from objects_tests.buffs_tests import TestBuffsTrue, TestBuffsFalse +from objects_tests.stats_tests import TestStats, TestGearBuffs +from objects_tests.procs_tests import TestProcsList, TestProc +from objects_tests.race_tests import TestRace +from objects_tests.rogue_tests.rogue_talents_tests import TestAssassinationTalents +from objects_tests.rogue_tests.rogue_talents_tests import TestCombatTalents +from objects_tests.rogue_tests.rogue_talents_tests import TestSubtletyTalents +from objects_tests.rogue_tests.rogue_talents_tests import TestRogueTalents if __name__ == "__main__": unittest.main() From fa2acf2a9c05c75492c36fe4a5f4432424bb68e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 10 Mar 2017 16:57:26 +0100 Subject: [PATCH 171/265] Toss old and outdated scripts --- MANIFEST.in | 2 - scripts/assassination_import.py | 149 ------ scripts/char_info.py | 2 - scripts/combat_import.py | 149 ------ scripts/dm_combat.py | 118 ----- scripts/import_character.py | 463 ----------------- scripts/reinstall.bat | 12 - scripts/reinstall.sh | 8 - scripts/reinstall_dm.sh | 8 - scripts/subtlety_import.py | 146 ------ scripts/wowapi/LICENSE | 17 - scripts/wowapi/README.rst | 14 - scripts/wowapi/setup.py | 12 - scripts/wowapi/tests/__init__.py | 2 - scripts/wowapi/tests/test_data.py | 75 --- scripts/wowapi/tests/test_locales.py | 51 -- scripts/wowapi/tests/test_regions.py | 29 -- scripts/wowapi/wowapi/__init__.py | 0 scripts/wowapi/wowapi/api.py | 406 --------------- scripts/wowapi/wowapi/exceptions.py | 21 - scripts/wowapi/wowapi/utilities.py | 59 --- setup.py | 17 - setup_dm.py | 14 - test_ui/old_items.py | 328 ------------ test_ui/testing_ui.py | 712 --------------------------- test_ui/ui_data.py | 322 ------------ 26 files changed, 3136 deletions(-) delete mode 100644 MANIFEST.in delete mode 100755 scripts/assassination_import.py delete mode 100644 scripts/char_info.py delete mode 100755 scripts/combat_import.py delete mode 100644 scripts/dm_combat.py delete mode 100755 scripts/import_character.py delete mode 100755 scripts/reinstall.bat delete mode 100755 scripts/reinstall.sh delete mode 100755 scripts/reinstall_dm.sh delete mode 100755 scripts/subtlety_import.py delete mode 100755 scripts/wowapi/LICENSE delete mode 100755 scripts/wowapi/README.rst delete mode 100755 scripts/wowapi/setup.py delete mode 100755 scripts/wowapi/tests/__init__.py delete mode 100755 scripts/wowapi/tests/test_data.py delete mode 100755 scripts/wowapi/tests/test_locales.py delete mode 100755 scripts/wowapi/tests/test_regions.py delete mode 100755 scripts/wowapi/wowapi/__init__.py delete mode 100755 scripts/wowapi/wowapi/api.py delete mode 100755 scripts/wowapi/wowapi/exceptions.py delete mode 100755 scripts/wowapi/wowapi/utilities.py delete mode 100644 setup.py delete mode 100644 setup_dm.py delete mode 100644 test_ui/old_items.py delete mode 100644 test_ui/testing_ui.py delete mode 100644 test_ui/ui_data.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 4666151..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include license.txt -recursive-include shadowcraft/core/locale * diff --git a/scripts/assassination_import.py b/scripts/assassination_import.py deleted file mode 100755 index cb16a9f..0000000 --- a/scripts/assassination_import.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import print_function -# Simple test program to debug + play with assassination models. -from builtins import str -from os import path -import sys -from import_character import CharacterData -from char_info import charInfo - - -#sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs - -from shadowcraft.core import i18n - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. -test_language = 'local' -i18n.set_language(test_language) - - -key = 1 -while key < len(sys.argv): - terms = sys.argv[key].split(':') - charInfo[ terms[0] ] = terms[1] - key += 1 - -print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") -character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) -character_data.do_import() - - -# Set up level/class/race -test_level = 90 -test_race = race.Race(character_data.get_race()) -test_class = 'rogue' - -# Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'melee_haste_buff', - 'attack_power_buff', - 'armor_debuff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - 'agi_flask_mop', - 'food_300_agi' - ) - -# Set up weapons. -test_mh = stats.Weapon(*character_data.get_mh()) -test_oh = stats.Weapon(*character_data.get_oh()) - -# Set up procs. -character_procs = character_data.get_procs() -character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] - -#not_allowed_procs = set(character_procs) - set(character_procs_allowed) -#print not_allowed_procs - -test_procs = procs.ProcsList(*character_procs_allowed) - -# Set up a calcs object.. -lst = character_data.get_gear_stats() - -# Set up gear buffs. -character_gear_buffs = character_data.get_gear_buffs() + ['leather_specialization', 'virmens_bite', 'virmens_bite_prepot'] -if character_data.has_chaotic_metagem(): - character_gear_buffs.append('chaotic_metagem') -test_gear_buffs = stats.GearBuffs(*character_gear_buffs) - -test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, **lst) - -# Initialize talents.. -if charInfo['talents'] == None: - charInfo['talents'] = character_data.get_talents() -test_talents = talents.Talents(charInfo['talents'], test_class, test_level) - -# Set up glyphs. -glyph_list = character_data.get_glyphs() -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) - -# Set up settings. -test_cycle = settings.AssassinationCycle(min_envenom_size_non_execute=4, min_envenom_size_execute=5, - prioritize_rupture_uptime_non_execute=True, prioritize_rupture_uptime_execute=True) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], - stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) - -# Build a DPS object. -#calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level, char_class=test_class) -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - -# Compute EP values. -ep_values = calculator.get_ep() - -# Compute DPS Breakdown. -dps_breakdown = calculator.get_dps_breakdown() -non_execute_breakdown = calculator.assassination_dps_breakdown_non_execute() -total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) -non_execute_total = sum(entry[1] for entry in list(non_execute_breakdown.items())) -talent_ranks = calculator.get_talents_ranking() -heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) - -def max_length(dict_list): - max_len = 0 - for i in dict_list: - dict_values = list(i.items()) - if max_len < max(len(entry[0]) for entry in dict_values): - max_len = max(len(entry[0]) for entry in dict_values) - - return max_len - -def pretty_print(dict_list, total_sum = 1.): - max_len = max_length(dict_list) - - for i in dict_list: - dict_values = list(i.items()) - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if ("{0:.2f}".format(10*float(value[1])/total_dps)) != '0.00': - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') - else: - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) - print('-' * (max_len + 15)) - -dicts_for_pretty_print = [ - ep_values, - talent_ranks, - heal_table, - dps_breakdown -] -pretty_print(dicts_for_pretty_print, total_sum=total_dps) -print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) -print('') -print('non-execute breakdown: ') -pretty_print([non_execute_breakdown], total_sum=non_execute_total) -print(' ' * (max_length([non_execute_breakdown]) + 1), non_execute_total, _("total damage per second.")) diff --git a/scripts/char_info.py b/scripts/char_info.py deleted file mode 100644 index eeb1369..0000000 --- a/scripts/char_info.py +++ /dev/null @@ -1,2 +0,0 @@ -charInfo = {'region':'us', 'realm':'Doomhammer', 'name':'Pins', 'talents':None, - 'stormlash':False, 'pvp':False, 'shiv':0, 'verbose':False, 'blade_flurry':False} diff --git a/scripts/combat_import.py b/scripts/combat_import.py deleted file mode 100755 index 2ac6d59..0000000 --- a/scripts/combat_import.py +++ /dev/null @@ -1,149 +0,0 @@ -from __future__ import division -from __future__ import print_function -# Simple test program to debug + play with combat models. -from builtins import str -from os import path -import sys -from import_character import CharacterData -from char_info import charInfo -#sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs - -from shadowcraft.core import i18n - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. -test_language = 'local' -i18n.set_language(test_language) - - -key = 1 -while key < len(sys.argv): - terms = sys.argv[key].split(':') - if terms[0] in ['stormlash', 'shiv']: - charInfo[ terms[0] ] = float(terms[1]) - else: - charInfo[ terms[0] ] = terms[1] - key += 1 - -print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") -character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) -character_data.do_import() - - -# Set up level/class/race -test_level = 90 -test_race = race.Race(character_data.get_race()) -test_class = 'rogue' - -# Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'melee_haste_buff', - 'spell_haste_buff', - 'attack_power_buff', - 'armor_debuff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - 'agi_flask_mop', - 'food_300_agi' - ) - -# Set up weapons. -test_mh = stats.Weapon(*character_data.get_mh()) -test_oh = stats.Weapon(*character_data.get_oh()) - -# Set up procs. -character_procs = character_data.get_procs() -character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] - -#not_allowed_procs = set(character_procs) - set(character_procs_allowed) -#print not_allowed_procs - -test_procs = procs.ProcsList(*character_procs_allowed) - -# Set up a calcs object.. -lst = character_data.get_gear_stats() - -# Set up gear buffs. -character_gear_buffs = character_data.get_gear_buffs() + ['leather_specialization', 'virmens_bite', 'virmens_bite_prepot'] -if character_data.has_chaotic_metagem(): - character_gear_buffs.append('chaotic_metagem') -test_gear_buffs = stats.GearBuffs(*character_gear_buffs) - -test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, **lst) - -# Initialize talents.. -if charInfo['talents'] == None: - charInfo['talents'] = character_data.get_talents() -test_talents = talents.Talents(charInfo['talents'], test_class, test_level) - -# Set up glyphs. -glyph_list = character_data.get_glyphs() -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) - -# Set up settings. -if character_data.get_mh_type() == 'dagger': - print("\nALERT: Dagger found. Playing combat with a dagger should be a last resort, and is not recommended. \n\n") -test_cycle = settings.OutlawCycle(use_rupture=True, ksp_immediately=True, revealing_strike_pooling=True, blade_flurry=charInfo['blade_flurry']) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], - stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) - -# Build a DPS object. -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - -# Compute EP values. -ep_values = calculator.get_ep() - -# Compute DPS Breakdown. -dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) -talent_ranks = calculator.get_talents_ranking() -heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) - -# Compute weapon type modifier. -weapon_type_mod = calculator.get_oh_weapon_modifier() - -def max_length(dict_list): - max_len = 0 - for i in dict_list: - dict_values = list(i.items()) - if max_len < max(len(entry[0]) for entry in dict_values): - max_len = max(len(entry[0]) for entry in dict_values) - - return max_len - -def pretty_print(dict_list): - max_len = max_length(dict_list) - - for i in dict_list: - dict_values = list(i.items()) - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') - else: - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) - print('-' * (max_len + 15)) - -dicts_for_pretty_print = [ - weapon_type_mod, - ep_values, - talent_ranks, - heal_table, - dps_breakdown -] -pretty_print(dicts_for_pretty_print) -print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) diff --git a/scripts/dm_combat.py b/scripts/dm_combat.py deleted file mode 100644 index dd766fe..0000000 --- a/scripts/dm_combat.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import division -from __future__ import print_function -# Simple test program to debug + play with assassination models. -from builtins import str -from os import path -import sys -sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.darkmantle import DarkmantleCalculator -from shadowcraft.calcs.darkmantle.rogue import RogueDarkmantleCalculator -from shadowcraft.calcs.darkmantle import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs -from shadowcraft.objects import priority_list - -from shadowcraft.core import i18n - -import time - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. -test_language = 'local' -i18n.set_language(test_language) - -start = time.time() - -# Set up level/class/race -test_level = 90 -test_race = race.Race('pandaren') -test_class = 'rogue' - -# Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'haste_buff', - 'multistrike_buff', - 'attack_power_buff', - 'armor_debuff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - ) - -# Set up weapons. -test_mh = stats.Weapon(571.0, 2.6, 'axe', 'dancing_steel') -test_oh = stats.Weapon(571.0, 2.6, 'axe', 'dancing_steel') - -# Set up procs. -test_procs = procs.ProcsList(('assurance_of_consequence', 580), ('haromms_talisman', 580), 'legendary_capacitive_meta', 'fury_of_xuen') - -# Set up gear buffs. -test_gear_buffs = stats.GearBuffs('rogue_t16_2pc', 'rogue_t16_4pc', 'leather_specialization') - -# Set up a calcs object.. -test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=862, - stam=1000, - crit=87, - haste=553, - mastery=200, - versatility=160, - multistrike=120,) - -# Initialize talents.. -test_talents = talents.Talents('332213', test_class, test_level) - -# Just a priority list to define the course of actions -#priority_list = PriorityList()#'prepot = prefight,!buff.stealth', - #'stealth = prefight,!buff.stealth', - #'ambush = buff.stealth') - -# Set up glyphs. -glyph_list = ['recuperate'] -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) - -# Set up settings. -test_cycle = settings.CombatCycle() -test_settings = settings.Settings(test_cycle, response_time=.5, latency=.03, merge_damage=True, style='time', limit=10) - -# Build a DPS object. -calculator = RogueDarkmantleCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - -# Compute DPS Breakdown. -dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) - -def max_length(dict_list): - max_len = 0 - for i in dict_list: - dict_values = list(i.items()) - if max_len < max(len(entry[0]) for entry in dict_values): - max_len = max(len(entry[0]) for entry in dict_values) - - return max_len - -def pretty_print(dict_list, total_sum = 1., show_percent=False): - max_len = max_length(dict_list) - - for i in dict_list: - dict_values = list(i.items()) - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(value[1] / total_dps)) != '0.00': - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') - else: - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) - print('-' * (max_len + 15)) - -pretty_print([dps_breakdown], total_sum=total_dps, show_percent=True) -print(' ' * (max_length([dps_breakdown]) + 1), total_dps, _("total damage per second.")) -print("Request time: %s sec" % (time.time() - start)) \ No newline at end of file diff --git a/scripts/import_character.py b/scripts/import_character.py deleted file mode 100755 index 0886af3..0000000 --- a/scripts/import_character.py +++ /dev/null @@ -1,463 +0,0 @@ -from __future__ import division -from __future__ import print_function -# Original Code by by Ayliex @ EJ ( https://github.com/postrov/sc-character-import ) -# -*- coding: utf-8 -*- -from builtins import str -from builtins import range -from builtins import object -from os import path -from types import * -import sys -import pprint -import shelve -import math - -sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from wowapi.api import WoWApi - -wowapi = WoWApi() -pp = pprint.PrettyPrinter(indent=4) - - -class ItemDB(object): - def __init__(self): - pass - - def get_item(self, id): - return None - - def add_item(self, id, item): - pass - - def close(self): - pass - - - -class SimpleItemDB(ItemDB): - def __init__(self, path): - self.path = path - self.db = shelve.open(path, writeback = True) - - def get_item(self, id): - str_id = str(id) - if str_id in self.db: - return self.db[str_id] - else: - return None - - def add_item(self, id, item): - str_id = str(id) - self.db[str_id] = item - self.sync() # FIXME - - def close(self): - self.db.close() - - def sync(self): - self.db.sync() - - -item_db = SimpleItemDB('item_db') - - -def get_item_cached(region, id): - cached_item = item_db.get_item(id) - if cached_item: - return cached_item - else: - item = wowapi.get_item(region, id) - item_db.add_item(id, item) - return item - -class CharacterData(object): - races = {1 : 'human', - 2 : 'orc', - 3 : 'dwarf', - 4 : 'night_elf', - 5 : 'undead', - 6 : 'tauren', - 7 : 'gnome', - 8 : 'troll', - 9 : 'goblin', - 10 : 'blood_elf', - 11 : 'draenei', - 22 : 'worgen', - 24 : 'pandaren', #Neutral - 25 : 'pandaren', #Alliance - 26 : 'pandaren', #Horde - } - statMap = {3:'agi', 4:'str', 5:'int', 6:'spirit', 7:'stam', 31:'hit', 32:'crit', 36:'haste', 37:'exp', 49:'mastery', - 35:'pvp_resil', 57:'pvp_power'} - - enchants = {4441 : 'windsong', - 4443 : 'elemental_force', - 4444 : 'dancing_steel', - 4416 : [{'stat':'agi', 'value':170}], # Enchant Bracer - Greater Agility - 4359 : [{'stat':'agi', 'value':180}], #Enchanting Perk - 4411 : [{'stat':'mastery', 'value':170}], - 4416 : [{'stat':'agi', 'value':170}], - 4419 : [{'stat':'agi', 'value':80}, {'stat':'str', 'value':80}, {'stat':'stam', 'value':80}], - 4421 : [{'stat':'hit', 'value':180}], - 4424 : [{'stat':'crit', 'value':180}], - 4426 : [{'stat':'haste', 'value':175}], # Enchant Boots - Greater Haste - 4428 : [{'stat':'agi', 'value':140}], #Speed Boost - 4430 : [{'stat':'haste', 'value':170}], - 4431 : [{'stat':'exp', 'value':170}], - 4433 : [{'stat':'mastery', 'value':170}], - 4429 : [{'stat':'mastery', 'value':140}], # Pandaren's Step - 4804 : [{'stat':'agi', 'value':200}, {'stat':'crit', 'value':100}], - 4822 : [{'stat':'agi', 'value':285}, {'stat':'crit', 'value':165}], - 4875 : [{'stat':'agi', 'value':500}], #Leatherworking Perk - 4871 : [{'stat':'agi', 'value':170}, {'stat':'crit', 'value':100}], - 4880 : [{'stat':'agi', 'value':285}, {'stat':'crit', 'value':165}], - 4822 : [{'stat':'agi', 'value':285}, {'stat':'crit', 'value':165}], # Shadowleather Leg Armor - 4411 : [{'stat':'mastery', 'value':170}], - 4871 : [{'stat':'agi', 'value':170}, {'stat':'crit', 'value':100}], - 4427 : [{'stat':'hit', 'value':175}], - 4908 : [{'stat':'agi', 'value':120}, {'stat':'crit', 'value':80}], # Tiger Claw Inscrption - } - - trinkets = {87057 : 'heroic_bottle_of_infinite_stars', - 86132 : 'bottle_of_infinite_stars', - 87167 : 'heroic_terror_in_the_mists', - 79328 : 'relic_of_xuen', - 86791 : 'lfr_bottle_of_infinite_stars', - 86332 : 'terror_in_the_mists', - 87079 : 'heroic_jade_bandit_figurine', - 75274 : 'zen_alchemist_stone', - 86890 : 'lfr_terror_in_the_mists', - 89082 : 'hawkmasters_talon', - 86043 : 'jade_bandit_figurine', - 81267 : 'searing_words', - 81265 : 'flashing_steel_talisman', - 81125 : 'windswept_pages', - 86772 : 'lfr_jade_bandit_figurine', - 87574 : 'corens_cold_chromium_coaster'} - - sets = {'t14' : {'pieces': {u'head' : 'Helmet of the Thousandfold Blades', - u'shoulder' : 'Spaulders of the Thousandfold Blades', - u'chest' : 'Tunic of the Thousandfold Blades', - u'hands' : 'Gloves of the Thousandfold Blades', - u'legs' : 'Legguards of the Thousandfold Blades'}, - 'set_bonus' : {2 : 'rogue_t14_2pc', 4 : 'rogue_t14_4pc'}}} - - glyphs = {# Major - u'Glyph of Adrenaline Rush' : 'adrenaline_rush', - u'Glyph of Ambush' : 'ambush', - u'Glyph of Blade Flurry' : 'blade_flurry', - u'Glyph of Blind' : 'blind', - u'Glyph of Cheap Shot' : 'cheap_shot', - u'Glyph of Cloak of Shadows' : 'cloak_of_shadows', - u'Glyph of Crippling Poison' : 'crippling_poison', - u'Glyph of Deadly Momentum' : 'deadly_momentum', - u'Glyph of Debilitation' : 'debilitation', - u'Glyph of Evasion' : 'evasion', - u'Glyph of Expose Armor' : 'expose_armor', - u'Glyph of Feint' : 'feint', - u'Glyph of Garrote' : 'garrote', - u'Glyph of Gouge' : 'gouge', - u'Glyph of Kick' : 'kick', - u'Glyph of Recuperate' : 'recuperate', - u'Glyph of Sap' : 'sap', - u'Glyph of Shadow Walk' : 'shadow_walk', - u'Glyph of Shiv' : 'shiv', - u'Glyph of Smoke Bomb' : 'smoke_bomb', - u'Glyph of Sprint' : 'sprint', - u'Glyph of Stealth' : 'stealth', - u'Glyph of Vanish' : 'vanish', - u'Glyph of Vendetta' : 'vendetta', - # Minor - u'Glyph of Blurred Speed' : 'blurred_speed', - u'Glyph of Decoy' : 'decoy', - u'Glyph of Detection' : 'detection', - u'Glyph of Disguise' : 'disguise', - u'Glyph of Distract' : 'distract', - u'Glyph of Hemorrhage' : 'hemorrhage', - u'Glyph of Killing Spree' : 'killing_spree', - u'Glyph of Pick Lock' : 'pick_lock', - u'Glyph of Pick Pocket' : 'pick_pocket', - u'Glyph of Poisons' : 'poisons', - u'Glyph of Safe Fall' : 'safe_fall', - u'Glyph of Tricks of the Trade' : 'tricks_of_the_trade'} - - reforgeMap = {113: ('spirit', 'dodge_rating'), - 114: ('spirit','parry_rating'), - 115: ('spirit','hit'), - 116: ('spirit','crit'), - 117: ('spirit','haste'), - 118: ('spirit','exp'), - 119: ('spirit','mastery'), - 120: ('dodge_rating','spirit'), - 121: ('dodge_rating','parry_rating'), - 122: ('dodge_rating','hit'), - 123: ('dodge_rating','crit'), - 124: ('dodge_rating','haste'), - 125: ('dodge_rating','exp'), - 126: ('dodge_rating','mastery'), - 127: ('parry_rating','spirit'), - 128: ('parry_rating','dodge_rating'), - 129: ('parry_rating','hit'), - 130: ('parry_rating','crit'), - 131: ('parry_rating','haste'), - 132: ('parry_rating','exp'), - 133: ('parry_rating','mastery'), - 134: ('hit','spirit'), - 135: ('hit','dodge_rating'), - 136: ('hit','parry_rating'), - 137: ('hit','crit'), - 138: ('hit','haste'), - 139: ('hit','exp'), - 140: ('hit','mastery'), - 141: ('crit','spirit'), - 142: ('crit','dodge_rating'), - 143: ('crit','parry_rating'), - 144: ('crit','hit'), - 145: ('crit','haste'), - 146: ('crit','exp'), - 147: ('crit','mastery'), - 148: ('haste','spirit'), - 149: ('haste','dodge_rating'), - 150: ('haste','parry_rating'), - 151: ('haste','hit'), - 152: ('haste','crit'), - 153: ('haste','exp'), - 154: ('haste','mastery'), - 155: ('exp','spirit'), - 156: ('exp','dodge_rating'), - 157: ('exp','parry_rating'), - 158: ('exp','hit'), - 159: ('exp','crit'), - 160: ('exp','haste'), - 161: ('exp','mastery'), - 162: ('mastery','spirit'), - 163: ('mastery','dodge_rating'), - 164: ('mastery','parry_rating'), - 165: ('mastery','hit'), - 166: ('mastery','crit'), - 167: ('mastery','haste'), - 168: ('mastery','exp'), - } - - - def __init__(self, region, realm, name, verbose=False): - self.region = region - self.realm = realm - self.name = name - self.verbose = verbose - self.raw_data = None - self.chaotic_metagem = False - - def do_import(self): - self.raw_data = wowapi.get_character(self.region , self.realm, self.name, ['talents', 'items', 'stats']) - - def get_race(self): - return CharacterData.races[self.raw_data[u'data'][u'race']] - - def get_weapon(self, weapon_data, item_data): - weapon_info = weapon_data['data'][u'weaponInfo'] - weaponMap = {0:'axe', 1:'axe', 2:'2h_axe', 3:'bow', 4:'rifle',5:'mace', 6:'2h_mace', 7:'polearm', 8:'sword', 9:'2H_sword', 10:'staff', - 11:'exotic', 12:'2h_exotic', 13:'fist', 14:'misc', 15:'dagger', 16:'thrown', 17:'spear', 18:'xbow', 19:'wand', - 20:'fishing_pole'} - tmpItem = get_item_cached(self.region, item_data[u'id']) - damage_info = weapon_info[u'damage'] - damage = (damage_info[u'max'] + damage_info[u'min']) / 2 - speed = weapon_info[u'weaponSpeed'] - type = weaponMap[ tmpItem['data'][u'itemSubClass'] ] - enchant = CharacterData.enchants[item_data[u'tooltipParams'][u'enchant']] - return [damage, speed, type, enchant] - - def get_mh(self): - item_data = self.raw_data['data'][u'items'][u'mainHand'] - weapon_data = get_item_cached(self.region, item_data[u'id']) -# weapon_data = get_item_cached(self.region, 85924) - return self.get_weapon(weapon_data, item_data) - - def get_oh(self): - item_data = self.raw_data['data'][u'items'][u'offHand'] - weapon_data = get_item_cached(self.region, item_data[u'id']) - return self.get_weapon(weapon_data, item_data) - - def get_mh_type(self): - return self.get_mh()[2] - - def get_trinket_proc(self, item_data): - id = item_data[u'id'] - if id in CharacterData.trinkets: - return CharacterData.trinkets[id] - else: - return item_data[u'name'] # fallback, this will most likely be rejected by shadowcraft - - def get_trinket_procs(self): - trinket1 = self.raw_data['data'][u'items'][u'trinket1'] - trinket2 = self.raw_data['data'][u'items'][u'trinket2'] - return [self.get_trinket_proc(trinket1), self.get_trinket_proc(trinket2)] - - def get_procs(self): - procs = [] - procs += self.get_trinket_procs() - return procs - - def get_set_bonuses(self): - set_bonuses = [] - for set_name in CharacterData.sets: - s = CharacterData.sets[set_name] - pieces = s['pieces'] - pieces_found = 0 - for p in pieces: - if self.raw_data['data'][u'items'][p][u'name'] == pieces[p]: - pieces_found += 1 -# print 'found set piece, set: %s, pieces so far: %d' % (set_name, pieces_found) - if pieces_found in s['set_bonus']: - set_bonuses.append(s['set_bonus'][pieces_found]) - return set_bonuses - - def get_gear_buffs(self): - gear_buffs = [] - gear_buffs += self.get_set_bonuses() - return gear_buffs - - def get_stats(self): - stats_data = self.raw_data['data'][u'stats'] - agi = stats_data[u'agi'] - str = stats_data[u'str'] - ap = stats_data[u'attackPower'] - crit = stats_data[u'critRating'] - hit = stats_data[u'hitRating'] - exp = stats_data[u'expertiseRating'] - haste = stats_data[u'hasteRating'] - mast = stats_data[u'masteryRating'] -# ret = [str, agi + 956, 250, crit, hit, exp, haste, mast] -# pp.pprint(ret) - return [str, agi, ap - 2 * agi, crit, hit, exp, haste, mast] - - def get_gear_stats(self): - # - lst = {'agi': 0, 'str':0, 'int':0, 'stam':0, 'crit':0, 'haste':0, 'mastery':0, 'versatility':0} - reforge = ('none', 'none') - reforgeID = None - gemColorToSocketColors = {u'RED': (u'RED'), u'YELLOW':(u'YELLOW'), u'BLUE':(u'BLUE'), u'META':(u'META'), u'COGWHEEL':(u'COGWHEEL'), u'HYDRAULIC':(u'HYDRAULIC'), - u'ORANGE':(u'RED', u'YELLOW'), u'PURPLE':(u'RED', u'BLUE'), u'GREEN':(u'YELLOW', u'BLUE')} - verboseStatMap = {'Agility':'agi', 'Strength':'str', 'Stamina':'stam', 'Critical Strike':'crit', 'Hit':'hit', - 'Expertise':'exp', 'Haste':'haste', 'Mastery':'mastery', 'Increased Critical Effect':'chaotic_metagem', - 'PvP Resilience':'pvp_resil', 'PvP Power':'pvp_power'} - #Loops over every item - for p in self.raw_data['data'][u'items']: - try: - #ilvl is included in the gear array for some unknown reason, lets ignore it - if p != 'averageItemLevelEquipped' and p != 'averageItemLevel': - tmpItem = get_item_cached(self.region, self.raw_data['data'][u'items'][p][u'id']) - self.verbose_print('\n' + p + ': ' + self.raw_data['data'][u'items'][p][u'name']) - params = self.raw_data['data'][u'items'][p][u'tooltipParams'] - #grab the reforge if it exists - if u'reforge' in self.raw_data['data'][u'items'][p][u'tooltipParams']: - reforgeID = self.raw_data['data'][u'items'][p][u'tooltipParams'][u'reforge'] - #if we have data on the reforge - if reforgeID in list(CharacterData.reforgeMap.keys()): - reforge = CharacterData.reforgeMap[reforgeID] - #for each stat on the gear - for key in tmpItem[u'data'][u'bonusStats']: - if key[u'stat'] in CharacterData.statMap and CharacterData.statMap[key[u'stat']] == reforge[0]: - #if a reforge was found - tmpVal = math.ceil(key[u'amount'] * .6) - lst[ CharacterData.statMap[key[u'stat']] ] += tmpVal - lst[ reforge[1] ] += key[u'amount'] - tmpVal - self.verbose_print('Reforge found: +' + str(tmpVal) + ' ' + reforge[0] + ', +' + str(key[u'amount'] - tmpVal) + ' ' + reforge[1]) - else: - #otherwise, no reforge - lst[ CharacterData.statMap[key[u'stat']] ] += key[u'amount'] - self.verbose_print('+' + str(key[u'amount']) + ' ' + CharacterData.statMap[key[u'stat']]) - #prevents cached reforges from affecting subsequent items - reforge = ('none', 'none') - #add stats from gems, check if socket colors are matched along the way - if u'socketInfo' in tmpItem['data']: - socketInfo = tmpItem['data'][u'socketInfo'] - socketBonusActivated = True # we'll find out if this is not true as we process each gem - else: - socketInfo = None - socketBonusActivated = False - gemCount = 0 - for gemNumber in range(3): - gemId = 'gem' + str(gemNumber) - if gemId in list(params.keys()): - gemCount += 1 - tmpGem = get_item_cached(self.region, params[gemId]) - if not socketInfo == None: - sockets = socketInfo[u'sockets'] - if gemNumber < len(sockets): - if not sockets[gemNumber][u'type'] in gemColorToSocketColors[tmpGem['data'][u'gemInfo'][u'type'][u'type']]: - socketBonusActivated = False - self.verbose_print(tmpGem['data'][u'name'] + ' does not match socket of color ' + sockets[gemNumber][u'type'] + ', socket bonus not activated!') - for entry in tmpGem['data'][u'gemInfo'][u'bonus'][u'name'].split(' and '): - tmpLst = entry.split(' ') - if not '%' in tmpLst[0]: - tmpVal = int(tmpLst[0][1:]) - tmpStat = verboseStatMap[' '.join(tmpLst[1:])] - lst[tmpStat] += tmpVal - self.verbose_print(tmpGem['data'][u'name'] + ': +' + str(tmpVal) + ' ' + tmpStat) - else: - self.chaotic_metagem = True - self.verbose_print(tmpGem['data'][u'name'] + ' is a meta gem') - #add stats from socket bonuses - if socketBonusActivated == True and gemCount >= len(socketInfo[u'sockets']): - if u'socketBonus' in socketInfo: - for entry in socketInfo[u'socketBonus'].split(' and '): #similar to gem treatment... is there ever a socket bonus that gives multiple stats? - tmpLst = entry.split(' ') - tmpVal = int(tmpLst[0][1:]) - tmpStat = verboseStatMap[ ' '.join(tmpLst[1:]) ] - lst[ tmpStat ] += tmpVal - self.verbose_print('Socket bonus +' + str(tmpVal) + ' ' + tmpStat) - #add stats from enchants - if u'enchant' in list(params.keys()): - if not type( CharacterData.enchants[ params[u'enchant'] ] ) == type(''): - for key in CharacterData.enchants[ params[u'enchant'] ]: - lst[ key['stat'] ] += key['value'] - self.verbose_print('Enchant +' + str(key['value']) + ' ' + key['stat']) - else: - self.verbose_print(CharacterData.enchants[params[u'enchant']]) - else: - self.verbose_print('Unenchanted') - except Exception as inst: - #it's okay, we can keep going, just so long as we pretend to handle the exception - print("\n") - print("Error at slot: ", p) - print("Error type: ", type(inst)) - raise - if self.verbose: - pp.pprint(lst) - return lst - #return [lst['str'], lst['agi'], lst['int'], lst['spirit'], lst['stam'], lst['ap'], lst['crit'], lst['hit'], lst['exp'], lst['haste'], lst['mastery']] - - def has_chaotic_metagem(self): - return self.chaotic_metagem - - def get_current_spec_data(self): - specs_data = self.raw_data['data'][u'talents'] - if u'selected' in specs_data[0]: - current_spec_data = specs_data[0] - else: - current_spec_data = specs_data[1] - - return current_spec_data - - def get_talents(self): - spec_data = self.get_current_spec_data() - talents = [""] * 6 - for t in spec_data[u'talents']: - talents[t[u'tier']] = str(t[u'column'] + 1) - return "".join(talents) - - def get_glyphs(self): - glyphs = [] - spec_data = self.get_current_spec_data() - glyphs_data = spec_data[u'glyphs'] - for g in (glyphs_data[u'major'] + glyphs_data[u'minor']): - glyph_name = g[u'name'] - if glyph_name in CharacterData.glyphs: - glyphs.append(CharacterData.glyphs[glyph_name]) - return glyphs - - def verbose_print(self, str): - if self.verbose: - print(str) diff --git a/scripts/reinstall.bat b/scripts/reinstall.bat deleted file mode 100755 index d04ce2e..0000000 --- a/scripts/reinstall.bat +++ /dev/null @@ -1,12 +0,0 @@ -cd wowapi - -python setup.py build - -python setup.py install - -cd ../../ - -python setup.py build - -python setup.py install -cd scripts \ No newline at end of file diff --git a/scripts/reinstall.sh b/scripts/reinstall.sh deleted file mode 100755 index 1d07470..0000000 --- a/scripts/reinstall.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -cd wowapi -python setup.py build -python setup.py install -cd ../../ -python setup.py build -python setup.py install \ No newline at end of file diff --git a/scripts/reinstall_dm.sh b/scripts/reinstall_dm.sh deleted file mode 100755 index 3ad485e..0000000 --- a/scripts/reinstall_dm.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -cd wowapi -python setup.py build -python setup.py install -cd ../../ -python setup_dm.py build -python setup_dm.py install \ No newline at end of file diff --git a/scripts/subtlety_import.py b/scripts/subtlety_import.py deleted file mode 100755 index c9f857d..0000000 --- a/scripts/subtlety_import.py +++ /dev/null @@ -1,146 +0,0 @@ -from __future__ import division -from __future__ import print_function -# Simple test program to debug + play with subtlety models. -from builtins import str -from os import path -import sys -from import_character import CharacterData -from char_info import charInfo -#sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import proc_data -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs - -from shadowcraft.core import i18n - -# Set up language. Use 'en_US', 'es_ES', 'fr' for specific languages. -test_language = 'local' -i18n.set_language(test_language) - -key = 1 -while key < len(sys.argv): - terms = sys.argv[key].split(':') - charInfo[ terms[0] ] = terms[1] - key += 1 - -print("Loading " + charInfo['name'] + " of " + charInfo['region'] + "-" + charInfo['realm'] + "\n") -character_data = CharacterData(charInfo['region'], charInfo['realm'], charInfo['name'], verbose=charInfo['verbose']) -character_data.do_import() - - -# Set up level/class/race -test_level = 90 -test_race = race.Race(character_data.get_race()) -test_class = 'rogue' - -# Set up buffs. -test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'stat_multiplier_buff', - 'crit_chance_buff', - 'mastery_buff', - 'melee_haste_buff', - 'attack_power_buff', - 'armor_debuff', - 'physical_vulnerability_debuff', - 'spell_damage_debuff', - 'agi_flask_mop', - 'food_300_agi' - ) - -# Set up weapons. - -test_mh = stats.Weapon(*character_data.get_mh()) -test_oh = stats.Weapon(*character_data.get_oh()) - -# Set up procs. -character_procs = character_data.get_procs() -character_procs_allowed = [p for p in character_procs if p in proc_data.allowed_procs] - -#not_allowed_procs = set(character_procs) - set(character_procs_allowed) -#print not_allowed_procs - -test_procs = procs.ProcsList(*character_procs_allowed) - -# Set up a calcs object.. -lst = character_data.get_gear_stats() - -# Set up gear buffs. -character_gear_buffs = character_data.get_gear_buffs() + ['leather_specialization', 'virmens_bite', 'virmens_bite_prepot'] -if character_data.has_chaotic_metagem(): - character_gear_buffs.append('chaotic_metagem') -test_gear_buffs = stats.GearBuffs(*character_gear_buffs) - -test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, **lst) - -# Initialize talents.. -if charInfo['talents'] == None: - charInfo['talents'] = character_data.get_talents() -test_talents = talents.Talents(charInfo['talents'], test_class, test_level) - -# Set up glyphs. -glyph_list = character_data.get_glyphs() -test_glyphs = glyphs.Glyphs(test_class, *glyph_list) - -# Set up settings. -raid_crits_per_second = 5 -hemo_interval = 24 #'always', 'never', 24, 25, 26... -if not character_data.get_mh_type() == 'dagger' and not test_talents.shuriken_toss: - if not hemo_interval == 'always': - print("\nALERT: Viable dagger cycle not found, forced rotation to strictly Hemo \n") - hemo_interval = 'always' -test_cycle = settings.SubtletyCycle(raid_crits_per_second, use_hemorrhage=hemo_interval) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, dmg_poison='dp', utl_poison='lp', is_pvp=charInfo['pvp'], - stormlash=charInfo['stormlash'], shiv_interval=charInfo['shiv']) - -# Build a DPS object. -calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_glyphs, test_buffs, test_race, test_settings, test_level) - -# Compute EP values. -ep_values = calculator.get_ep() - -# Compute DPS Breakdown. -dps_breakdown = calculator.get_dps_breakdown() -total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) -talent_ranks = calculator.get_talents_ranking() -heal_sum, heal_table = calculator.get_self_healing(dps_breakdown=dps_breakdown) - - -def max_length(dict_list): - max_len = 0 - for i in dict_list: - dict_values = list(i.items()) - if max_len < max(len(entry[0]) for entry in dict_values): - max_len = max(len(entry[0]) for entry in dict_values) - - return max_len - -def pretty_print(dict_list): - max_len = max_length(dict_list) - - for i in dict_list: - dict_values = list(i.items()) - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') - else: - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) - print('-' * (max_len + 15)) - -dicts_for_pretty_print = [ - ep_values, - talent_ranks, - heal_table, - dps_breakdown -] -pretty_print(dicts_for_pretty_print) -print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, _("total damage per second.")) diff --git a/scripts/wowapi/LICENSE b/scripts/wowapi/LICENSE deleted file mode 100755 index ea2235b..0000000 --- a/scripts/wowapi/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2011 Thorsten Sanders - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software -and associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/scripts/wowapi/README.rst b/scripts/wowapi/README.rst deleted file mode 100755 index 9786f81..0000000 --- a/scripts/wowapi/README.rst +++ /dev/null @@ -1,14 +0,0 @@ -About -====== -I am using a python framework for my website and due the current python modules for the WoW Api are not updated very often, -still missing features and I prefer to get raw data, I wrote my own little module. - -| It supports: gzip compression, If-Modified-Since header, authorization, SSL - - - -Documentation -============= - -Full documentation can be found at: -http://wowapi.wowuse.com/ \ No newline at end of file diff --git a/scripts/wowapi/setup.py b/scripts/wowapi/setup.py deleted file mode 100755 index 5d75f79..0000000 --- a/scripts/wowapi/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -from distutils.core import setup - -setup( - name='wowapi', - version='0.3.0', - description='Python module to access the WoW Api', - author='Dorwido', - author_email='darkz@gmx.de', - url='https://github.com/Dorwido/wowapi', - license='MIT', - packages=['wowapi'] -) diff --git a/scripts/wowapi/tests/__init__.py b/scripts/wowapi/tests/__init__.py deleted file mode 100755 index 3d8b4e2..0000000 --- a/scripts/wowapi/tests/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/scripts/wowapi/tests/test_data.py b/scripts/wowapi/tests/test_data.py deleted file mode 100755 index 7c38d3e..0000000 --- a/scripts/wowapi/tests/test_data.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -from wowapi.api import WoWApi - -try: - import unittest2 as unittest -except ImportError: - import unittest as unittest - -wowapi = WoWApi() -class Test_GetData(unittest.TestCase): - - def test_get_character(self): - character = wowapi.get_character('eu','Doomhammer','Thetotemlord') - self.assertEqual(character['data']['name'],'Thetotemlord') - - - def test_get_item(self): - item = wowapi.get_item('eu',25) - self.assertEqual(item['data']['name'],'Worn Shortsword') - - def test_get_guild(self): - guild = wowapi.get_guild('eu','Doomhammer','Dawn Of Osiris') - self.assertEqual(guild['data']['name'],'Dawn Of Osiris') - - def test_get_realm(self): - realm = wowapi.get_realm('eu') - self.assertGreater(len(realm['data']['realms']),1) - - - def test_get_auctions(self): - auctions = wowapi.get_auctions('eu','Defias Brotherhood') - self.assertEqual(len(auctions['data']),4) - - def test_get_arena_ladder_team(self): - arena_ladder = wowapi.get_arena_ladder('eu','Blackout','2v2',1) - self.assertEqual(len(arena_ladder['data']['arenateam']),1) - arena_team = wowapi.get_arena_team('eu',arena_ladder['data']['arenateam'][0]['realm'],'2v2',arena_ladder['data']['arenateam'][0]['name']) - self.assertEqual(arena_team['data']['name'],arena_ladder['data']['arenateam'][0]['name']) - - - def test_get_character_races (self): - character_races = wowapi.get_character_races('eu') - self.assertGreater(len(character_races['data']['races']),1) - - def test_get_character_classes (self): - character_classes = wowapi.get_character_classes('eu') - self.assertGreater(len(character_classes['data']['classes']),1) - - def test_get_guild_rewards (self): - guild_rewards = wowapi.get_guild_rewards('eu') - self.assertGreater(len(guild_rewards['data']['rewards']),1) - - def test_get_guild_perks (self): - guild_perks = wowapi.get_guild_perks('eu') - self.assertGreater(len(guild_perks['data']['perks']),1) - - def test_get_item_classes (self): - item_classes = wowapi.get_item_classes('eu') - self.assertGreater(len(item_classes['data']['classes']),1) - - def test_get_quest(self): - quest_info = wowapi.get_quest('eu',25) - self.assertEqual(quest_info['data']['id'],25) - - def test_get_recipe (self): - recipe_info = wowapi.get_recipe('us',33994) - self.assertEqual(recipe_info['data']['id'],33994) - - def test_get_achievements_character(self): - char_achievements = wowapi.get_achievements_character('eu') - self.assertGreater(len(char_achievements['data']['achievements']),1) - - def test_get_achievements_guild(self): - guild_achievements = wowapi.get_achievements_guild('eu') - self.assertGreater(len(guild_achievements['data']['achievements']),1) diff --git a/scripts/wowapi/tests/test_locales.py b/scripts/wowapi/tests/test_locales.py deleted file mode 100755 index e7a5797..0000000 --- a/scripts/wowapi/tests/test_locales.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- -from wowapi.api import WoWApi - -try: - import unittest2 as unittest -except ImportError: - import unittest as unittest - -wowapi = WoWApi() -class Test_Locales(unittest.TestCase): - - def test_locale_en_US(self): - item = wowapi.get_item('us',25,None,'en_US') - self.assertEqual(item['data']['name'],'Worn Shortsword') - - def test_locale_es_MX(self): - item = wowapi.get_item('us',25,None,'es_MX') - self.assertEqual(item['data']['name'],'Espada corta desgastada') - - def test_locale_en_GB(self): - item = wowapi.get_item('eu',25,None,'en_GB') - self.assertEqual(item['data']['name'],'Worn Shortsword') - - def test_locale_es_ES(self): - item = wowapi.get_item('eu',25,None,'es_ES') - self.assertEqual(item['data']['name'],'Espada corta desgastada') - - def test_locale_fr_FR(self): - item = wowapi.get_item('eu',25,None,'fr_FR') - self.assertEqual(item['data']['name'],u'Epée courte usée') - - def test_locale_ru_RU(self): - item = wowapi.get_item('eu',25,None,'ru_RU') - self.assertEqual(item['data']['name'],u'Иссеченный короткий меч') - - def test_locale_de_DE(self): - item = wowapi.get_item('eu',25,None,'de_DE') - self.assertEqual(item['data']['name'],'Abgenutztes Kurzschwert') - - def test_locale_ko_KR(self): - item = wowapi.get_item('kr',25,None,'ko_KR') - self.assertEqual(item['data']['name'],u'낡은 쇼트소드') - - def test_locale_zh_TW(self): - item = wowapi.get_item('tw',25,None,'zh_TW') - self.assertEqual(item['data']['name'],u'破損的短劍') - - def test_locale_zh_CN(self): - item = wowapi.get_item('cn',25,None,'zh_CN') - self.assertEqual(item['data']['name'],u'破损的短剑') - diff --git a/scripts/wowapi/tests/test_regions.py b/scripts/wowapi/tests/test_regions.py deleted file mode 100755 index a8d6798..0000000 --- a/scripts/wowapi/tests/test_regions.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -from wowapi.api import WoWApi - -try: - import unittest2 as unittest -except ImportError: - import unittest as unittest - -wowapi = WoWApi() -class Test_Regions(unittest.TestCase): - def test_region_us(self): - realm = wowapi.get_realm('us') - self.assertGreater(len(realm['data']['realms']),1) - - def test_region_eu(self): - realm = wowapi.get_realm('eu') - self.assertGreater(len(realm['data']['realms']),1) - - def test_region_kr(self): - realm = wowapi.get_realm('kr') - self.assertGreater(len(realm['data']['realms']),1) - - def test_region_tw(self): - realm = wowapi.get_realm('tw') - self.assertGreater(len(realm['data']['realms']),1) - - def test_region_cn(self): - realm = wowapi.get_realm('cn') - self.assertGreater(len(realm['data']['realms']),1) \ No newline at end of file diff --git a/scripts/wowapi/wowapi/__init__.py b/scripts/wowapi/wowapi/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/scripts/wowapi/wowapi/api.py b/scripts/wowapi/wowapi/api.py deleted file mode 100755 index 53c9aa6..0000000 --- a/scripts/wowapi/wowapi/api.py +++ /dev/null @@ -1,406 +0,0 @@ -from future import standard_library -standard_library.install_aliases() -from builtins import str -from builtins import map -from builtins import object -from urllib.request import Request, urlopen -from urllib.error import URLError -from urllib.parse import quote -import gzip -import io -try: - import simplejson as json -except ImportError: - import json -import datetime -import base64 -import hmac -import hashlib -from .exceptions import APIError,NotModified,NotFound -from .utilities import parse_http_datetime,http_datetime - -regions = { - 'us' : { - 'domain':'us.battle.net', - 'locales' : [ - 'en_US', - 'es_MX' - ] - }, - 'eu' : { - 'domain':'eu.battle.net', - 'locales' : [ - 'en_GB', - 'es_ES', - 'fr_FR', - 'ru_RU', - 'de_DE' - ] - }, - 'kr' : { - 'domain':'kr.battle.net', - 'locales' : [ - 'ko_KR' - ] - }, - 'tw' : { - 'domain':'tw.battle.net', - 'locales' : [ - 'zh_TW' - ] - }, - 'cn' : { - 'domain':'www.battlenet.com.cn', - 'locales' : [ - 'zh_CN' - ] - }, -} - -datatypes = { - 'character' : { - 'path':'character/%s/%s', - 'param':'fields' - }, - 'guild' : { - 'path':'guild/%s/%s', - 'param':'fields' - }, - 'realm' : { - 'path': 'realm/status', - 'param':'realms' - }, - 'auction' : { - 'path' : 'auction/data/%s' - }, - 'item' : { - 'path' : 'item/%d' - }, - 'arena_team' : { - 'path' : 'arena/%s/%s/%s' - }, - 'arena_ladder' : { - 'path' : 'pvp/arena/%s/%s', - 'param' : 'size' - }, - 'character_races':{ - 'path' : 'data/character/races' - }, - 'character_classes':{ - 'path' : 'data/character/classes' - }, - 'guild_rewards':{ - 'path' : 'data/guild/rewards' - }, - 'guild_perks':{ - 'path':'data/guild/perks' - }, - 'item_classes':{ - 'path':'data/item/classes' - }, - 'achievements_character':{ - 'path':'data/character/achievements' - }, - 'achievements_guild':{ - 'path':'data/guild/achievements' - }, - 'quest':{ - 'path':'quest/%d' - }, - 'recipe':{ - 'path':'recipe/%d' - } -} - -class WoWApi(object): - - - def __init__(self,privatekey=None,publickey=None,ssl=None): - self.privkey = privatekey - self.pubkey = publickey - if ssl is None: - if self.privkey and self.pubkey: - self.ssl = True - else: - self.ssl = False - else: - self.ssl = ssl - - - - - - def _decode_response(self,response): - - if 'content-encoding' in response.info() and response.info()['content-encoding'] == 'gzip': - response = gzip.GzipFile(fileobj=io.StringIO(response.read())) - try: - data = json.loads(str(response.read(),'UTF-8')) - except json.JSONDecodeError: - raise APIError('Non-JSON Response') - return data - - - def _do_request(self,request): - try: - response = urlopen(request) - except URLError as e: - if hasattr(e, 'reason'): - raise APIError(e.reason,request.get_full_url()) - elif hasattr(e, 'code'): - if e.code == 304: - raise NotModified(request.get_full_url()) - elif e.code == 404: - raise NotFound(request.get_full_url()) - else: - error_response = self._decode_response(e) - if error_response['reason']: - raise APIError(e.code,error_response['reason'],request.get_full_url()) - else: - raise APIError(e.code,None,request.get_full_url()) - else: - return response - - def _sign_request(self,path,date): - stringtosign = "GET\n"+date+"\n"+path+"\n" - hash = hmac.new(self.privkey, stringtosign, hashlib.sha1).digest() - return base64.encodestring(hash) - - def _get_data(self,region,data,params=None,lastmodified=None,lang=None,datatype=None): - if region not in regions: - raise ValueError('Region not found') - if lang and lang not in regions[region]['locales']: - raise ValueError('Locales not valid for current region') - httpdate = http_datetime(datetime.datetime.utcnow()) - signature = None - if self.privkey and self.pubkey: - signature = self._sign_request('/api/wow/'+data,httpdate) - - if params: - data += '?'+datatypes[datatype]['param']+'='+','.join(map(str, params)) - - if lang and params: - data+='&locale='+lang - elif lang: - data+='?locale='+lang - if self.ssl: - url = 'https://' - else: - url = 'http://' - - url += regions[region]['domain']+'/api/wow/'+data - header = { - 'Accept-Encoding': 'gzip', - 'Date' : httpdate - } - if signature: - header['Authorization'] = 'BNET '+self.pubkey+':'+signature - - - if lastmodified: - header['If-Modified-Since'] = http_datetime(lastmodified) - - request = Request(url, None, header) - - response = self._do_request(request) - - rlastmodified = None - if 'Last-Modified' in response.info(): - rlastmodified = parse_http_datetime(response.info()['Last-Modified']) - - return {'lastmodified':(rlastmodified),'data':self._decode_response(response)} - - def get_item(self,region,itemid,lastmodified=None,lang=None): - """ - Get infos about an item - - | ``Example:`` - :: - - get_item('eu',25) - """ - if not int(itemid): - raise ValueError('Itemid must be a integer') - return self._get_data(region,datatypes['item']['path'] % (itemid),None,lastmodified,lang) - - - def get_character(self,region,realm,character,params=None,lastmodified=None,lang=None): - """ - Get infos about an character, params is a array taking optional fields to look up infos like achievements,talents etc - - | ``Example:`` - :: - - get_character('eu','Doomhammer','Thetotemlord',['talents']) - """ - return self._get_data(region,datatypes['character']['path'] % (quote(realm),quote(character)),params,lastmodified,lang,'character') - - def get_guild(self,region,realm,guild,params=None,lastmodified=None,lang=None): - """ - Get infos about an guild, params is a array taking optional fields to look up infos like achievements,members etc - - | ``Example:`` - :: - - get_guild('eu','Doomhammer','Dawn Of Osiris') - """ - return self._get_data(region,datatypes['guild']['path'] % (quote(realm),quote(guild)),params,lastmodified,lang,'guild') - - def get_realm(self,region,params=None,lastmodified=None,lang=None): - """ - Get infos about realm(s), params is a array taking optional which realms to look up otherwise returning all realms of an region - - | ``Example:`` - :: - - get_realm('eu',['Doomhammer']) - """ - return self._get_data(region,datatypes['realm']['path'],params,lastmodified,lang,'realm') - - def get_auctions(self,region,realm,lastmodified=None,lang=None): - """ - Returns all auctions of a realms - - | ``Example:`` - :: - - get_auction('eu','Doomhammer') - """ - data = self._get_data(region,datatypes['auction']['path'] % (quote(realm)),None,lastmodified,lang) - request = Request(data['data']['files'][0]['url'], None, {'Accept-Encoding': 'gzip'}) - - return {'lastmodified': data['lastmodified'],'data':self._decode_response(self._do_request(request))} - - def get_arena_team(self,region,realm,teamsize,teamname,lastmodified=None,lang=None): - """ - Get infos about a arena team - - | ``Example:`` - :: - - get_arenea_team('eu','Doomhammer','2v2','We win') - """ - return self._get_data(region,datatypes['arena_team']['path'] % (quote(realm),teamsize,quote(teamname)),None,lastmodified,lang) - - def get_arena_ladder(self,region,battlegroup,teamsize,howmany=None,lastmodified=None,lang=None): - """ - Get the arena ladder of the specified battlegroup, optional with howmany you can define how many teams should be included - - | ``Example:`` - :: - - get_arena_ladder('eu','Blackout','5v5',100) - """ - return self._get_data(region,datatypes['arena_ladder']['path'] % (quote(battlegroup),teamsize),[howmany],lastmodified,lang,'arena_ladder') - - def get_character_races(self,region,lastmodified=None,lang=None): - """ - Get infos about all character races - - | ``Example:`` - :: - - get_character_races('us',None,'es_MX') - """ - return self._get_data(region,datatypes['character_races']['path'],None,lastmodified,lang) - - def get_character_classes(self,region,lastmodified=None,lang=None): - """ - Get infos about all character classes - - | ``Example:`` - :: - - get_character_class('eu') - """ - return self._get_data(region,datatypes['character_classes']['path'],None,lastmodified,lang) - - def get_guild_rewards(self,region,lastmodified=None,lang=None): - """ - Get infos about all guild rewards - - | ``Example:`` - :: - - get_guild_rewards('cn') - """ - return self._get_data(region,datatypes['guild_rewards']['path'],None,lastmodified,lang) - - def get_guild_perks(self,region,lastmodified=None,lang=None): - """ - Get infos about all guild perks - - | ``Example:`` - :: - - get_guild_perks('tw') - """ - return self._get_data(region,datatypes['guild_perks']['path'],None,lastmodified,lang) - - def get_item_classes(self,region,lastmodified=None,lang=None): - """ - Get infos about all item classes - - | ``Example:`` - :: - - get_item_classes('eu',None,'fr_FR') - """ - return self._get_data(region,datatypes['item_classes']['path'],None,lastmodified,lang) - - - def get_quest(self,region,questid,lastmodified=None,lang=None): - """ - .. versionadded:: 0.2.3 - - Get infos about an quest - - | ``Example:`` - :: - - get_quest('eu',25) - """ - if not int(questid): - raise ValueError('Quest id must be a integer') - return self._get_data(region,datatypes['quest']['path'] % (questid),None,lastmodified,lang) - - def get_recipe(self,region,recipeid,lastmodified=None,lang=None): - """ - .. versionadded:: 0.3.0 - - Get infos about an recipe - - | ``Example:`` - :: - - get_recipe('eu',33994) - """ - if not int(recipeid): - raise ValueError('Recipe id must be a integer') - return self._get_data(region,datatypes['recipe']['path'] % (recipeid),None,lastmodified,lang) - - def get_achievements_character(self,region,lastmodified=None,lang=None): - """ - .. versionadded:: 0.2.3 - - Get all character achievements which exists with name,description etc - - | ``Example:`` - :: - - get_achievements_character('eu',None,'en_GB') - """ - return self._get_data(region,datatypes['achievements_character']['path'],None,lastmodified,lang) - - def get_achievements_guild(self,region,lastmodified=None,lang=None): - """ - .. versionadded:: 0.2.3 - - Get all guild achievements which exists with name,description etc - - | ``Example:`` - :: - - get_achievements_guild('eu',None,'fr_FR') - """ - return self._get_data(region,datatypes['achievements_guild']['path'],None,lastmodified,lang) \ No newline at end of file diff --git a/scripts/wowapi/wowapi/exceptions.py b/scripts/wowapi/wowapi/exceptions.py deleted file mode 100755 index 8f20170..0000000 --- a/scripts/wowapi/wowapi/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -class APIError(Exception): - """ - .. versionadded:: 0.2.5 - - - This is raised on all other http errors and will always return http error code, reason for fail - (if a reason is given otherwise None), url which failed - """ - pass - -class NotModified(APIError): - """ - This is raised when using the last modified option and nothing changed, since last request - """ - pass - -class NotFound(APIError): - """ - This is raised on 404 Errors - """ - pass \ No newline at end of file diff --git a/scripts/wowapi/wowapi/utilities.py b/scripts/wowapi/wowapi/utilities.py deleted file mode 100755 index f7ee7e5..0000000 --- a/scripts/wowapi/wowapi/utilities.py +++ /dev/null @@ -1,59 +0,0 @@ -from __future__ import division -def http_datetime( dt=None ): - - if not dt: - import datetime - dt = datetime.datetime.utcnow() - else: - try: - dt = dt - dt.utcoffset() - except: - pass # no timezone offset, just assume already in UTC - - s = dt.strftime('%a, %d %b %Y %H:%M:%S GMT') - return s - - -def parse_http_datetime( datestring, utc_tzinfo=None, strict=False ): - - import re, datetime - m = re.match(r'(?P[a-z]+), (?P\d+) (?P[a-z]+) (?P\d+) (?P\d+):(?P\d+):(?P\d+(\.\d+)?) (?P\w+)$', - datestring, re.IGNORECASE) - if not m and not strict: - m = re.match(r'(?P[a-z]+) (?P[a-z]+) (?P\d+) (?P\d+):(?P\d+):(?P\d+) (?P\d+)$', - datestring, re.IGNORECASE) - if not m: - m = re.match(r'(?P[a-z]+), (?P\d+)-(?P[a-z]+)-(?P\d+) (?P\d+):(?P\d+):(?P\d+(\.\d+)?) (?P\w+)$', - datestring, re.IGNORECASE) - if not m: - raise ValueError('HTTP date is not correctly formatted') - - try: - tz = m.group('TZ').upper() - except: - tz = 'GMT' - if tz not in ('GMT','UTC','0000','00:00'): - raise ValueError('HTTP date is not in GMT timezone') - - monname = m.group('MON').upper() - mdict = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6, - 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12} - month = mdict.get(monname) - if not month: - raise ValueError('HTTP date has an unrecognizable month') - y = int(m.group('Y')) - if y < 100: - century = datetime.datetime.utcnow().year / 100 - if y < 50: - y = century * 100 + y - else: - y = (century - 1) * 100 + y - d = int(m.group('D')) - hour = int(m.group('H')) - minute = int(m.group('M')) - try: - second = int(m.group('S')) - except: - second = float(m.group('S')) - dt = datetime.datetime( y, month, d, hour, minute, second, tzinfo=utc_tzinfo ) - return dt \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index b438cf0..0000000 --- a/setup.py +++ /dev/null @@ -1,17 +0,0 @@ -from distutils.core import setup - -setup( - name='ShadowCraft-Engine', - url='http://github.com/Aldriana/ShadowCraft-Engine/', - version='0.1', - packages=[ - 'shadowcraft', - 'shadowcraft.calcs', - 'shadowcraft.calcs.rogue', - 'shadowcraft.calcs.rogue.Aldriana', - 'shadowcraft.core', - 'shadowcraft.objects' - ], - license='LGPL', - long_description=open('README').read(), -) diff --git a/setup_dm.py b/setup_dm.py deleted file mode 100644 index 7527b0a..0000000 --- a/setup_dm.py +++ /dev/null @@ -1,14 +0,0 @@ -from distutils.core import setup - -setup( - name='ShadowCraft-Engine', - url='http://github.com/dazer/ShadowCraft-Engine/', - version='0.1', - packages=['shadowcraft', - 'shadowcraft.calcs', 'shadowcraft.calcs.rogue', 'shadowcraft.calcs.rogue.Aldriana', - 'shadowcraft.calcs.darkmantle', 'shadowcraft.calcs.darkmantle.rogue', - 'shadowcraft.core', - 'shadowcraft.objects'], - license='LGPL', - long_description=open('README').read(), -) diff --git a/test_ui/old_items.py b/test_ui/old_items.py deleted file mode 100644 index 0c5ab3f..0000000 --- a/test_ui/old_items.py +++ /dev/null @@ -1,328 +0,0 @@ -head = { - # ----- 4.2 ----- - 'Hood of Rampant Disdain': {'id': 71003, 'agi': 348, 'exp': 172, 'haste': 295, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30}, - '(H)Hood of Rampant Disdain': {'id': 71416, 'agi': 400, 'exp': 202, 'haste': 333, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30}, - 'Dark Phoenix Helmet': {'id': 71047, 'agi': 348, 'hit': 227, 'haste': 249, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30, 'gear_buff': 'tier_12'}, # Tier 12 - '(H)Dark Phoenix Helmet': {'id': 71539, 'agi': 400, 'hit': 259, 'haste': 285, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30, 'gear_buff': 'tier_12'}, # Tier 12 - # ----- 4.1 ----- - "The Savager's Mask": {'id': 69564, 'agi': 263, 'crit': 175, 'exp': 192, 'sockets': ['red', 'meta'], 'bonus_stat': 'crit', 'bonus_value': 30}, - # ----- 4.0 ----- - #'Agile Bio-Optic Killshades': {'id': 59455, 'agi': 301, 'sockets': ['meta'], 'bonus_stat': 'agi', 'bonus_value': 20}, # missing cogwheels - "(H)Membrane of C'Thun": {'id': 65129, 'agi': 325, 'exp': 197, 'haste': 257, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'haste', 'bonus_value': 30}, - "Membrane of C'Thun": {'id': 59490, 'agi': 281, 'exp': 168, 'haste': 228, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'haste', 'bonus_value': 30}, - "Tsanga's Helm": {'id': 60202, 'agi': 281, 'crit': 168, 'mastery': 228, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30}, - "(H)Wind Dancer's Helmet": {'id': 65241, 'agi': 325, 'crit': 257, 'hit': 197, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30, 'gear_buff': 'tier_11'}, # Tier 11 - "Wind Dancer's Helmet": {'id': 60299, 'agi': 281, 'crit': 228, 'hit': 168, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30, 'gear_buff': 'tier_11'}, # Tier 11 - 'Dunwald Winged Helm': {'id': 63833, 'agi': 268, 'haste': 178, 'mastery': 178}, - '(H)Helm of Numberless Shadows': {'id': 56344, 'agi': 242, 'crit': 162, 'hit': 182, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30}, - 'Helm of Secret Knowledge': {'id': 66936, 'agi': 208, 'crit': 117, 'haste': 171, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'mastery', 'bonus_value': 30}, - 'Hood of the Crying Rogue': {'id': 66975, 'agi': 208, 'crit': 117, 'haste': 171, 'sockets': ['yellow', 'meta'], 'bonus_stat': 'mastery', 'bonus_value': 30}, - 'Mask of Vines': {'id': 58133, 'agi': 242, 'crit': 182, 'haste': 162, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 30}, - 'Shocktrooper Hood': {'id': 63829, 'agi': 268, 'haste': 178, 'mastery': 178}, -} -neck = { - # ----- 4.2 ----- - 'Necklace of Smoke Signals': {'id': 71129, 'agi': 227, 'hit': 144, 'crit': 156}, - '(H)Necklace of Smoke Signals': {'id': 71565, 'agi': 256, 'hit': 162, 'crit': 176}, - 'Choker of Vanquished Lord': {'id': 71354, 'agi': 240, 'haste': 162, 'mastery': 156}, - '(H)Choker of the Vanquished Lord': {'id': 71610, 'agi': 271, 'haste': 183, 'mastery': 176}, - # ----- 4.1 ----- - 'Amulet of the Watcher': {'id': 69605, 'agi': 180, 'crit': 125, 'mastery': 111}, - # ----- 4.0 ----- - '(H)Necklace of Strife': {'id': 65107, 'agi': 215, 'haste': 143, 'mastery': 143}, - 'Necklace of Strife': {'id': 59517, 'agi': 190, 'haste': 127, 'mastery': 127}, - 'Acorn of the Daughter Tree': {'id': 62378, 'agi': 168, 'crit': 112, 'haste': 112}, - 'Amulet of Dull Dreaming': {'id': 57931, 'agi': 168, 'crit': 112, 'haste': 112}, - '(H)Barnacle Pendant': {'id': 56292, 'agi': 168, 'exp': 120, 'haste': 98}, - 'Brazen Elementium Medallion': {'id': 52350, 'agi': 138, 'crit': 112, 'haste': 102, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Entwined Elementium Choker': {'id': 52321, 'agi': 148, 'crit': 65, 'haste': 128, 'sockets': ['yellow'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - '(H)Mouth of the Earth': {'id': 56422, 'agi': 168, 'hit': 112, 'exp': 112}, - 'Mouth of the Earth': {'id': 56095, 'agi': 149, 'hit': 100, 'exp': 100}, - 'Nightrend Choker': {'id': 66974, 'agi': 149, 'crit': 100, 'haste': 100}, - '(H)Pendant of the Lightless Grotto': {'id': 56338, 'agi': 168, 'crit': 112, 'mastery': 112}, - 'Pendant of Victorious Fury': {'id': 63762, 'agi': 149, 'haste': 105, 'mastery': 90}, - 'Sweet Perfume Broach': {'id': 68174, 'agi': 168, 'crit': 101, 'haste': 119}, -} -shoulders = { - # ----- 4.2 ----- - 'Dark Phoenix Spaulders': {'id': 71049, 'agi': 282, 'haste': 185, 'mastery': 197, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10, 'gear_buff': 'tier_12'}, # Tier 12 - '(H)Dark Phoenix Spaulders': {'id': 71541, 'agi': 322, 'haste': 211, 'mastery': 222, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10, 'gear_buff': 'tier_12'}, # Tier 12 - 'Shoulderpads of the Forgotten Gate': {'id': 71345, 'agi': 282, 'hit': 210, 'crit': 153, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Shoulderpads of the Forgotten Gate': {'id': 71456, 'agi': 322, 'hit': 240, 'crit': 173, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - # ----- 4.1 ----- - 'Tusked Shoulderpads': {'id': 69574, 'agi': 220, 'crit': 153, 'haste': 147, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 10}, - # ----- 4.0 ----- - '(H)Poison Protocol Pauldrons': {'id': 65083, 'agi': 226, 'crit': 171, 'mastery': 191, 'sockets': ['red'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - 'Poison Protocol Pauldrons': {'id': 59120, 'agi': 233, 'crit': 149, 'mastery': 169, 'sockets': ['red'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - "(H)Wind Dancer's Spaulders": {'id': 65243, 'agi': 266, 'crit': 171, 'haste': 191, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 10, 'gear_buff': 'tier_11'}, # Tier 11 - "Wind Dancer's Spaulders": {'id': 60302, 'agi': 233, 'crit': 149, 'haste': 169, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 10, 'gear_buff': 'tier_11'}, # Tier 11 - '(H)Caridean Epaulettes': {'id': 56273, 'agi': 205, 'exp': 150, 'haste': 130, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10}, - 'Clandestine Spaulders': {'id': 66905, 'agi': 199, 'crit': 142, 'haste': 116}, - 'Embrace of the Night': {'id': 58134, 'agi': 205, 'crit': 150, 'hit': 130, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Thieving Spaulders': {'id': 63449, 'agi': 205, 'crit': 130, 'haste': 150, 'sockets': ['yellow'], 'bonus_stat': 'haste', 'bonus_value': 20}, -} -back = { - # ----- 4.2 ----- - 'Dreadfire Drape': {'id': 70992, 'agi': 212, 'hit': 138, 'mastery': 95, 'sockets': ['red', 'red'], 'bonus_stat': 'agi', 'bonus_value': 20}, - '(H)Dreadfire Drape': {'id': 71415, 'agi': 241, 'hit': 158, 'mastery': 113, 'sockets': ['red', 'red'], 'bonus_stat': 'agi', 'bonus_value': 20}, - 'Sleek Flamewrath Cloak': {'id': 71228, 'agi': 227, 'hit': 169, 'crit': 122}, - '(H)Sleek Flamewrath Cloak': {'id': 71388, 'agi': 256, 'hit': 190, 'crit': 138}, - 'Mantle of Doubt': {'id': 71268, 'agi': 201, 'hit': 134, 'mastery': 134}, # 'elemental bonds' quest reward - # ----- 4.1 ----- - 'Recovered Cloak of Frostheim': {'id': 69584, 'agi': 180, 'hit': 117, 'haste': 122}, - "The Frost Lord's War Cloak": {'id': 69766, 'agi': 180, 'crit': 137, 'haste': 91}, # seasonal - # ----- 4.0 ----- - '(H)Cloak of Biting Chill': {'id': 65035, 'agi': 215, 'crit': 143, 'mastery': 143}, - 'Cloak of Biting Chill': {'id': 59348, 'agi': 190, 'crit': 127, 'mastery': 127}, - 'Viewless Wings': {'id': 58191, 'agi': 190, 'crit': 127, 'hit': 127}, - '(H)Cape of the Brotherhood': {'id': 65177, 'agi': 168, 'hit': 112, 'haste': 112}, - 'Cloak of Beasts': {'id': 56518, 'agi': 149, 'hit': 114, 'mastery': 76}, - '(H)Cloak of Thredd': {'id': 63473, 'agi': 168, 'crit': 112, 'mastery': 112}, - '(H)Kaleki Cloak': {'id': 56379, 'agi': 168, 'hit': 85, 'mastery': 128}, - 'Kaleki Cloak': {'id': 55858, 'agi': 149, 'hit': 76, 'mastery': 114}, - 'Razor-Edged Cloak': {'id': 56548, 'agi': 168, 'crit': 125, 'mastery': 90}, - 'Softwind Cape': {'id': 62361, 'agi': 168, 'hit': 112, 'haste': 112}, - '(H)Twitching Shadows': {'id': 56315, 'agi': 168, 'crit': 112, 'haste': 112}, -} -chest = { - # ----- 4.2 ----- - 'Dark Phoenix Tunic': {'id': 71045, 'agi': 368, 'crit': 230, 'exp': 263, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_12'}, # Tier 12 - '(H)Dark Phoenix Tunic': {'id': 71537, 'agi': 420, 'crit': 261, 'exp': 299, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_12'}, # Tier 12 - 'Breastplate of the Incendiary Soul': {'id': 71314, 'agi': 368, 'haste': 231, 'mastery': 267, 'sockets': ['red', 'red'], 'bonus_stat': 'agi', 'bonus_value': 20}, - '(H)Breastplate of the Incendiary Soul': {'id': 71455, 'agi': 420, 'haste': 264, 'mastery': 303, 'sockets': ['red', 'red'], 'bonus_stat': 'agi', 'bonus_value': 20}, - # ----- 4.1 ----- - 'Shadowtooth Trollskin Breastplate': {'id': 69569, 'agi': 283, 'crit': 164, 'haste': 216, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - # ----- 4.0 ----- - "Assassin's Chestplate": {'id': 56562, 'agi': 341, 'crit': 253, 'hit': 183, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - "Morrie's Waywalker Wrap": {'id': 67135, 'agi': 301, 'crit': 198, 'mastery': 218, 'sockets': ['red', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - '(H)Sark of the Unwatched': {'id': 65060, 'agi': 345, 'crit': 227, 'mastery': 247,'sockets': ['red', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - 'Sark of the Unwatched': {'id': 59318, 'agi': 301, 'crit': 198, 'mastery': 218, 'sockets': ['red', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - "(H)Wind Dancer's Tunic": {'id': 65239, 'agi': 345, 'exp': 217, 'haste': 257, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_11'}, # Tier 11 - "Wind Dancer's Tunic": {'id': 60301, 'agi': 301, 'exp': 188, 'haste': 228, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_11'}, # Tier 11 - '(H)Defias Brotherhood Vest': {'id': 63468, 'agi': 262, 'haste': 182, 'mastery': 182, 'sockets': ['red', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - '(H)Hieroglyphic Vest': {'id': 57874, 'agi': 262, 'crit': 182, 'haste': 182, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - 'Hieroglyphic Vest': {'id': 57863, 'agi': 228, 'crit': 158, 'haste': 158, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - 'Sly Fox Jerkin': {'id': 62374, 'agi': 228, 'crit': 178, 'mastery': 138, 'sockets': ['red', 'blue'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - 'Tunic of Sinking Envy': {'id': 58131, 'agi': 262, 'crit': 202, 'hit': 162, 'sockets': ['red', 'blue'], 'bonus_stat': 'crit', 'bonus_value': 20}, - '(H)Vest of Misshapen Hides': {'id': 56455, 'agi': 262, 'crit': 162, 'mastery': 202, 'sockets': ['red', 'blue'], 'bonus_stat': 'mastery', 'bonus_value': 20}, - 'Vest of Misshapen Hides': {'id': 56128, 'agi': 268, 'crit': 178, 'mastery': 178}, -} -wrist = { - # ----- 4.2 ----- - 'Flamebinder Bracers': {'id': 71130, 'agi': 227, 'crit': 148, 'exp': 154}, - '(H)Flamebinder Bracers': {'id': 71569, 'agi': 256, 'crit': 166, 'exp': 173}, - # ----- 4.1 ----- - "Amani'shi Bracers": {'id': 69559, 'agi': 180, 'haste': 120, 'exp': 120}, - # ----- 4.0 ----- - '(H)Parasitic Bands': {'id': 65050, 'agi': 215, 'crit': 143, 'mastery': 143}, - 'Parasitic Bands': {'id': 59329, 'agi': 190, 'crit': 127, 'mastery': 127}, - '(H)Double Dealing Bracers': {'id': 63454, 'agi': 168, 'crit': 112, 'mastery': 112}, - '(H)Poison Fang Bracers': {'id': 56409, 'agi': 168, 'hit': 112, 'haste': 112}, - 'Poison Fang Bracers': {'id': 55886, 'agi': 149, 'hit': 100, 'haste': 100}, -} -hands = { - # ----- 4.2 ----- - 'Gloves of Dissolving Smoke': {'id': 71020, 'agi': 282, 'crit': 172, 'mastery': 208, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Gloves of Dissolving Smoke': {'id': 71440, 'agi': 322, 'crit': 197, 'mastery': 235, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Dark Phoenix Gloves': {'id': 71046, 'agi': 282, 'crit': 133, 'haste': 230, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10, 'gear_buff': 'tier_12'}, # Tier 12 - '(H)Dark Phoenix Gloves': {'id': 71538, 'agi': 322, 'crit': 153, 'haste': 260, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10, 'gear_buff': 'tier_12'}, # Tier 12 - 'Clutches of Evil': {'id': 69942, 'agi': 282, 'haste': 198, 'mastery': 186, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - "Aviana's Grips": {'id': 70122, 'agi': 248, 'haste': 203, 'mastery': 117, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 10}, - # ----- 4.1 ----- - 'Knotted Handwraps': {'id': 69798, 'agi': 240, 'haste': 182, 'exp': 121}, - # ----- 4.0 ----- - '(H)Double Attack Handguards': {'id': 65073, 'agi': 266, 'exp': 171, 'mastery': 191, 'sockets': ['red'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - 'Double Attack Handguards': {'id': 59223, 'agi': 233, 'exp': 149, 'mastery': 169, 'sockets': ['red'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - "Liar's Handwraps": {'id': 62417, 'agi': 233, 'crit': 149, 'haste': 169, 'sockets': ['yellow'], 'bonus_stat': 'haste', 'bonus_value': 10}, - 'Stormbolt Gloves': {'id': 62433, 'agi': 233, 'crit': 149, 'haste': 169, 'sockets': ['yellow'], 'bonus_stat': 'haste', 'bonus_value': 10}, - "(H)Wind Dancer's Gloves": {'id': 65240, 'agi': 266, 'hit': 171, 'haste': 191, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10, 'gear_buff': 'tier_11'}, # Tier 11 - "Wind Dancer's Gloves": {'id': 60298, 'agi': 233, 'hit': 149, 'haste': 169, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10, 'gear_buff': 'tier_11'}, # Tier 11 - '(H)Gloves of Haze': {'id': 56368, 'agi': 205, 'crit': 150, 'mastery': 130, 'sockets': ['blue'], 'bonus_stat': 'crit', 'bonus_value': 10}, - 'Sticky Fingers': {'id': 58138, 'agi': 205, 'haste': 130, 'mastery': 150, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 10}, -} -waist = { - # ----- 4.2 ----- - 'Flamebinding Girdle': {'id': 71131, 'agi': 282, 'hit': 167, 'haste': 211, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Flamebinding Girdle': {'id': 71394, 'agi': 322, 'hit': 191, 'haste': 238, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - "Riplimb's Lost Collar": {'id': 71640, 'agi': 282, 'crit': 216, 'exp': 157, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - "(H)Riplimb's Lost Collar": {'id': 71641, 'agi': 322, 'crit': 244, 'exp': 180, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - # ----- 4.1 ----- - 'Belt of Slithering Serpents': {'id': 69600, 'agi': 220, 'haste': 142, 'mastery': 155, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'exp', 'bonus_value': 10}, - # ----- 4.0 ----- - 'Belt of Nefarious Whispers': {'id': 56537, 'agi': 253, 'hit': 184, 'mastery': 144, 'sockets': ['red', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Dispersing Belt': {'id': 65122, 'agi': 266, 'crit': 171, 'haste': 191, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Dispersing Belt': {'id': 59502, 'agi': 233, 'crit': 149, 'haste': 169, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Belt of a Thousand Mouths': {'id': 67240, 'agi': 225, 'crit': 150, 'haste': 150, 'sockets': ['prismatic']}, - 'Quicksand Belt': {'id': 62446, 'agi': 205, 'crit': 130, 'hit': 150, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Red Beam Cord': {'id': 56429, 'agi': 205, 'crit': 130, 'hast': 150, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'haste', 'bonus_value': 10}, - 'Red Beam Cord': {'id': 56098, 'agi': 199, 'crit': 133, 'haste': 133, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'haste', 'bonus_value': 10}, - 'Sash of Musing': {'id': 57918, 'agi': 205, 'exp': 130, 'mastery': 150, 'sockets': ['red', 'prismatic'], 'bonus_stat': 'mastery', 'bonus_value': 10}, -} -legs = { - # ----- 4.2 ----- - 'Cinderweb Leggings': {'id': 71031, 'agi': 368, 'haste': 212, 'mastery': 284, 'sockets': ['red', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - '(H)Cinderweb Leggings': {'id': 71402, 'agi': 420, 'haste': 244, 'mastery': 320, 'sockets': ['red', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - 'Dark Phoenix Legguards': {'id': 71048, 'agi': 368, 'hit': 280, 'crit': 218, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_12'}, # Tier 12 - '(H)Dark Phoenix Legguards': {'id': 71540, 'agi': 420, 'hit': 316, 'crit': 251, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_12'}, # Tier 12 - # ----- 4.1 ----- - 'Leggings of Dancing Blades': {'id': 69589, 'agi': 283, 'crit': 174, 'exp': 206, 'sockets': ['red', 'blue'], 'bonus_stat': 'exp', 'bonus_value': 20}, - # ----- 4.0 ----- - "(H)Aberration's Leggings": {'id': 65039, 'agi': 345, 'crit': 257, 'haste': 217, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - "Aberration's Leggings": {'id': 59343, 'agi': 301, 'crit': 228, 'haste': 188, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 20}, - "(H)Wind Dancer's Legguards": {'id': 65242, 'agi': 345, 'crit': 217, 'mastery': 257, 'sockets': ['yellow', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_11'}, # Tier 11 - "Wind Dancer's Legguards": {'id': 60300, 'agi': 301, 'crit': 188, 'mastery': 228, 'sockets': ['yellow', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20, 'gear_buff': 'tier_11'}, # Tier 11 - "(H)Beauty's Chew Toy": {'id': 56309, 'agi': 262, 'hit': 162, 'haste': 202, 'sockets': ['red', 'blue'], 'bonus_stat': 'haste', 'bonus_value': 20}, - "Garona's Finest Leggings": {'id': 63703, 'agi': 268, 'crit': 191, 'exp': 157}, - 'Leggings of the Burrowing Mole': {'id': 58132, 'agi': 262, 'exp': 162, 'mastery': 202, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value': 20}, - 'Leggings of the Impenitent': {'id': 62405, 'agi': 228, 'crit': 168, 'haste': 148, 'sockets': ['red', 'yellow'], 'bonus_stat': 'crit', 'bonus_value': 20}, - "Shaw's Finest Leggings": {'id': 63707, 'agi': 268, 'crit': 191, 'exp': 157}, - 'Swiftflight Leggings': {'id': 62425, 'agi': 228, 'crit': 168, 'haste': 148, 'sockets': ['red', 'yellow'], 'bonus_stat': 'crit', 'bonus_value': 20}, -} -feet = { - # ----- 4.2 ----- - 'Sandals of Leaping Coals': {'id': 71313, 'agi': 282, 'crit': 133, 'mastery': 230, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - '(H)Sandals of Leaping Coals': {'id': 71467, 'agi': 322, 'crit': 153, 'mastery': 260, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Treads of the Craft': {'id': 69951, 'agi': 282, 'haste': 197, 'mastery': 187, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - # ----- 4.1 ----- - "Fasc's Preserved Boots": {'id': 69634, 'agi': 220, 'exp': 157, 'mastery': 135, 'sockets': ['red'], 'bonus_stat': 'crit', 'bonus_value': 10}, - # ----- 4.0 ----- - "(H)Storm Rider's Boots": {'id': 65144, 'agi': 266, 'haste': 171, 'mastery': 191, 'sockets': ['yellow'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - "Storm Rider's Boots": {'id': 59469, 'agi': 233, 'haste': 149, 'mastery': 169, 'sockets': ['yellow'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - 'Treads of Fleeting Joy': {'id': 58482, 'agi': 233, 'crit': 149, 'haste': 169, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Boots of the Hard Way': {'id': 66914, 'agi': 199, 'crit': 116, 'haste': 142}, - '(H)Boots of the Predator': {'id': 63435, 'agi': 205, 'crit': 150, 'hit': 130, 'sockets': ['yellow'], 'bonus_stat': 'crit', 'bonus_value': 10}, - "(H)Crafty's Gaiters": {'id': 56395, 'agi': 205, 'haste': 130, 'mastery': 150, 'sockets': ['blue'], 'bonus_stat': 'mastery', 'bonus_value': 10}, - "Crafty's Gaiters": {'id': 55871, 'agi': 199, 'haste': 133, 'mastery': 133}, - "(H)VanCleef's Boots": {'id': 65178, 'agi': 205, 'haste': 150, 'mastery': 130, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 10}, -} -rings = { - # ----- 4.2 ----- - 'Viridian Signet of the Avengers': {'id': 71216, 'agi': 236, 'haste': 181, 'mastery': 134, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 10}, - 'Splintered Brimstone Seal': {'id': 71209, 'agi': 227, 'crit': 140, 'mastery': 158}, - '(H)Splintered Brimstone Seal': {'id': 71566, 'agi': 256, 'crit': 158, 'mastery': 178}, - "Widow's Kiss": {'id': 71032, 'agi': 227, 'haste': 167, 'mastery': 126}, - "(H)Widow's Kiss": {'id': 71401, 'agi': 256, 'haste': 188, 'mastery': 142}, - 'Band of Glittering Lights': {'id': 70110, 'agi': 201, 'crit': 131, 'haste': 136}, - "Matoclaw's Band": {'id': 70105, 'agi': 201, 'hit': 121, 'crit': 142}, - 'Band of Ghoulish Glee': {'id': 71327, 'agi': 201, 'hit': 118, 'crit': 144}, # seasonal - # ----- 4.1 ----- - "Arlokk's Signet": {'id': 69610, 'agi': 180, 'crit': 122, 'mastery': 117}, - 'Quickfinger Ring': {'id': 69799, 'agi': 180, 'haste': 135, 'exp': 94}, - # ----- 4.0 ----- - 'Gilnean Ring of Ruination': {'id': 67136, 'agi': 190, 'hit': 108, 'haste': 138}, - '(H)Lightning Conductor Band': {'id': 65082, 'agi': 215, 'crit': 143, 'hit': 143}, - 'Lightning Conductor Band': {'id': 59121, 'agi': 190, 'crit': 127, 'hit': 127}, - 'Signet of the Elder Council': {'id': 62362, 'agi': 190, 'haste': 127, 'mastery': 127}, - 'Band of Blades': {'id': 52318, 'agi': 138, 'crit': 116, 'hit': 97, 'sockets': ['yellow'], 'bonus_stat': 'hit', 'bonus_value': 10}, - "Elementium Destroyer's Ring": {'id': 52348, 'agi': 148, 'crit': 89, 'mastery': 114, 'sockets': ['red'], 'bonus_stat': 'haste', 'bonus_value': 10}, - '(H)Mirage Ring': {'id': 56404, 'agi': 168, 'hit': 85, 'haste': 128}, - 'Mirage Ring': {'id': 55884, 'agi': 149, 'hit': 76, 'haste': 114}, - '(H)Nautilus Ring': {'id': 56282, 'agi': 168, 'crit': 112, 'haste': 112}, - '(H)Ring of Blinding Stars': {'id': 56412, 'agi': 168, 'haste': 112, 'mastery': 112}, - 'Ring of Blinding Stars': {'id': 55994, 'agi': 149, 'haste': 100, 'mastery': 100}, - '(H)Ring of Dun Algaz': {'id': 56445, 'agi': 168, 'crit': 120, 'hit': 98}, - 'Ring of Dun Algaz': {'id': 56120, 'agi': 149, 'crit': 107, 'hit': 87}, - '(H)Skullcracker Ring': {'id': 58186, 'agi': 168, 'crit': 112, 'haste': 112}, - '(H)Skullcracker Ring': {'id': 56310, 'agi': 168, 'crit': 112, 'mastery': 112}, - "Terrath's Signet of Balance": {'id': 62348, 'agi': 168, 'hit': 112, 'mastery': 112}, -} -trinkets = { - # ----- 4.2 ----- - # "Aella's Bottle": {'id': 71633, 'agi': 340, 'gear_buff': 'rickets_magnetic_fireball'}, # unobtainable - "Ricket's Magnetic Fireball": {'id': 70144, 'agi': 340, 'gear_buff': 'rickets_magnetic_fireball', 'proc': 'rickets_magnetic_fireball_proc'}, - '(H)Matrix Restabilizer': {'id': 69150, 'agi': 433, 'proc': 'heroic_matrix_restabilizer'}, - 'Matrix Restabilizer': {'id': 68994, 'agi': 406, 'proc': 'matrix_restabilizer'}, - '(H)The Hungerer': {'id': 69112, 'agi': 458, 'proc': 'heroic_the_hungerer'}, - 'The Hungerer': {'id': 68927, 'agi': 383, 'proc': 'the_hungerer'}, - '(H)Ancient Petrified Seed': {'id': 69199, 'mastery': 433, 'gear_buff': 'heroic_ancient_petrified_seed'}, - 'Ancient Petrified Seed': {'id': 69001, 'mastery': 383, 'gear_buff': 'ancient_petrified_seed'}, - "Coren's Chilled Chromium Coaster": {'id': 71335, 'crit': 340, 'proc': 'corens_chilled_chromium_coaster'}, # seasonal - # ----- 4.1 ----- - # ----- 4.0 ----- - '(H)Grace of the Herald': {'id': 56295, 'agi': 285, 'proc': 'heroic_grace_of_the_herald'}, - 'Grace of the Herald': {'id': 55266, 'agi': 153, 'proc': 'grace_of_the_herald'}, - '(H)Key to the Endless Chamber': {'id': 56328, 'hit': 285, 'proc': 'heroic_key_to_the_endless_chamber'}, - 'Key to the Endless Chamber': {'id': 55795, 'hit': 215, 'proc': 'key_to_the_endless_chamber'}, - '(H)Left Eye of Rajh': {'id': 56427, 'exp': 285, 'proc': 'heroic_left_eye_of_rajh'}, - 'Left Eye of Rajh': {'id': 56102, 'exp': 252, 'proc': 'left_eye_of_rajh'}, - "(H)Prestor's Talisman of Machination": {'id': 65026, 'agi': 363, 'proc': 'heroic_prestors_talisman_of_machination'}, - "Prestor's Talisman of Machination": {'id': 59441, 'agi': 321, 'proc': 'prestors_talisman_of_machination'}, - "(H)Tia's Grace": {'id': 56394, 'mastery': 285, 'proc': 'heroic_tias_grace'}, - "Tia's Grace": {'id': 55874, 'mastery': 252, 'proc': 'tias_grace'}, - 'Darkmoon Card: Hurricane ': {'id': 62051, 'agi': 321, 'proc': 'darkmoon_card_hurricane'}, - 'Essence of the Cyclone': {'id': 59473, 'agi': 321, 'proc': 'essence_of_the_cyclone'}, - 'Fluid Death ': {'id': 58181, 'hit': 321, 'proc': 'fluid_death'}, - 'Heart of the Vile': {'id': 66969, 'agi': 234, 'proc': 'heart_of_the_vile'}, - 'Unheeded Warning ': {'id': 59520, 'agi': 321, 'proc': 'unheeded_warning'}, - 'Unsolvable Riddle': {'id': 62463, 'mastery': 321, 'gear_buff': 'unsolvable_riddle'}, - 'Figurine - Demon Panther ': {'id': 52199, 'hit': 285, 'gear_buff': 'demon_panther '}, -} -weapons = { - # ----- 4.2 ----- - # "1.8d Avool's Incendiary Shanker": {'id': 71779, 'agi': 175, 'hit': 125, 'crit': 102, 'damage': 993.5, 'speed': 1.8, 'type': 'dagger'}, # throws error when included - # "1.8d (H)Avool's Incendiary Shanker": {'id': 71778, 'agi': 197, 'hit': 141, 'crit': 115, 'damage': 1121, 'speed': 1.8, 'type': 'dagger'}, # throws error when included - # '1.4d Entrail Disgorger': {'id': 71787, 'agi': 175, 'crit': 97, 'mastery': 128, 'damage': 773, 'speed': 1.4, 'type': 'dagger'}, # throws error when included - # '1.4d (H) Entrail Disgorger': {'id': 71786, 'agi': 197, 'crit': 109, 'mastery': 145, 'damage': 872, 'speed': 1.4, 'type': 'dagger'}, # throws error when included - "1.4d Alysra's Razor": {'id': 70733, 'agi': 155, 'haste': 113, 'exp': 98, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 10, 'damage': 772.5, 'speed': 1.4, 'type': 'dagger'}, - # "1.4d (H)Alysra's Razor": {'id': 71427, 'agi': 177, 'haste': 128, 'exp': 113, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 10, 'damage': 872, 'speed': 1.4, 'type': 'dagger'}, # throws error when included - '1.8d Feeding Frenzy': {'id': 71013, 'agi': 175, 'crit': 88, 'haste': 133, 'damage': 993, 'speed': 1.8, 'type': 'dagger'}, - #'1.8d (H)Feeding Frenzy': {'id': 71441, 'agi': 197, 'crit': 100, 'haste': 150, 'damage': 1121, 'speed': 1.8, 'type': 'dagger'}, # throws error when included - '1.8d Brainsplinter': {'id': 70155, 'agi': 155, 'hit': 103, 'haste': 103, 'damage': 880.5, 'speed': 1.8, 'type': 'dagger'}, - # "2.0d Direbrew's Bloodied Shanker": {'id': 71331, 'agi': 155, 'hit': 90, 'crit': 111, 'damage': 978, 'speed': 2.0, 'type': 'dagger'}, # seasonal - throws error when included - # '2.6a Gatecrasher': {'id': 71312, 'agi': 152, 'crit': 120, 'exp': 111, 'damage': 1435, 'speed': 2.6, 'type': 'axe'}, # throws error when included - # '2.6a (H)Gatecrasher': {'id': 71454, 'agi': 197, 'crit': 135, 'exp': 125, 'damage': 1619.5, 'speed': 2.6, 'type': 'axe'}, # throws error when included - # '2.6m Shatterskull Bonecrusher': {'id': 71782, 'agi': 175, 'crit': 123, 'haste': 105, 'damage': 1435, 'speed': 2.6, 'type': 'mace'}, # throws error when included - # '2.6m (H)Shatterskull Bonecrusher': {'id': 71783, 'agi': 197, 'crit': 139, 'haste': 119, 'damage': 1619.5, 'speed': 2.6, 'type': 'mace'}, # throws error when included - # "2.6m Tremendous Tankard O' Terror": {'id': 71332, 'agi': 153, 'crit': 99, 'haste': 97, 'damage': 1271, 'speed': 2.6, 'type': 'mace'}, # seasonal, throws error when included - '2.6s Pyrium Spellward': {'id': 70162, 'agi': 155, 'hit': 103, 'mastery': 103, 'damage': 1271, 'speed': 2.6, 'type': 'sword'}, - # "2.6s The Horseman's Sinister Saber": {'id': 71325, 'agi': 103, 'hit': 103, 'exp': 103, 'damage': 1271, 'speed': 2.6, 'type': 'sword'}, # main hand - seasonal, not modeled gear_buff, throws error when included - # ----- 4.1 ----- - # '1.8d Twinblade of the Hakkari': {'id': 69621, 'agi': 138, 'crit': 92, 'haste': 92, 'damage': 786.5, 'speed': 1.8, 'type': 'dagger'}, # throws error when included - # '1.4d Twinblade of the Hakkari': {'id': 69620, 'agi': 138, 'hit': 92, 'exp': 92, 'damage': 612, 'speed': 1.4, 'type': 'dagger'}, # throws error when included - # "2.6f Thekal's Claws": {'id': 69636, 'agi': 138, 'crit': 96, 'mastery': 85, 'damage': 1136.5, 'speed': 2.6, 'type': 'fist'}, # main hand, throws error when included - # "2.6f Arlokk's Claws": {'id': 69638, 'agi': 138, 'hit': 94, 'haste': 90, 'damage': 1136.5, 'speed': 2.6, 'type': 'fist'}, # off hand, throws error when included - # '2.6m Mace of the Sacrificed': {'id': 69575, 'agi': 138, 'hit': 85, 'haste': 96, 'damage': 1136.5, 'speed': 2.6, 'type': 'mace'}, # throws error when included - # ----- 4.0 ----- - '1.8d (H)Organic Lifeform Inverter': {'id': 65081, 'agi': 165, 'exp': 110, 'mastery': 110, 'damage': 939.5, 'speed': 1.8, 'type': 'dagger'}, - '1.8d Organic Lifeform Inverter': {'id': 59122, 'agi': 146, 'exp': 97, 'mastery': 97, 'damage': 832, 'speed': 1.8, 'type': 'dagger'}, - '1.4d Scaleslicer': {'id': 68601, 'agi': 146, 'hit': 97, 'exp': 97, 'damage': 647.5, 'speed': 1.4, 'type': 'dagger'}, - '1.8d The Twilight Blade': {'id': 68163, 'proc': 'the_twilight_blade', 'damage': 832, 'speed': 1.8, 'type': 'dagger'}, - "1.4d (H)Uhn'agh Fash, the Darkest Betrayal": {'id': 68600, 'agi': 165, 'crit': 110, 'haste': 110, 'damage': 750.5, 'speed': 1.4, 'type': 'dagger'}, - "1.4d Uhn'agh Fash, the Darkest Betrayal": {'id': 59494, 'agi': 146, 'crit': 97, 'haste': 97, 'damage': 647.5, 'speed': 1.4, 'type': 'dagger'}, - "1.4d (H)Barim's Main Gauche": {'id': 56390, 'agi': 129, 'crit': 86, 'mastery': 86, 'damage': 573.5, 'speed': 1.4, 'type': 'dagger'}, - "1.4d Barim's Main Gauche": {'id': 55870, 'agi': 115, 'crit': 76, 'mastery': 76, 'damage': 508, 'speed': 1.4, 'type': 'dagger'}, - '1.4d (H)Buzzer Blade': {'id': 65163, 'agi': 129, 'crit': 86, 'haste': 86, 'damage': 573.5, 'speed': 1.4, 'type': 'dagger'}, - '1.8d Dagger of Restless Nights': {'id': 62456, 'agi': 129, 'crit': 86, 'hit': 86, 'damage': 737, 'speed': 1.8, 'type': 'dagger'}, - '1.8d Elementium Shank': {'id': 55068, 'agi': 129, 'hit': 86, 'haste': 86, 'damage': 737.5, 'speed': 1.8, 'type': 'dagger'}, - '1.8d Laquered Lung-Leak Longknife': {'id': 63792, 'agi': 115, 'crit': 75, 'mastery': 78, 'damage': 653.5, 'speed': 1.8, 'type': 'dagger'}, - #'1.8d (H)Meteor Shard': {'id': 63456, 'agi': 129, 'damage': 737.5, 'speed': 1.8, 'type': 'dagger'}, # not modeled proc - '1.4d (H)Quicksilver Blade': {'id': 56335, 'agi': 129, 'haste': 86, 'mastery': 86, 'damage': 573.5, 'speed': 1.4, 'type': 'dagger'}, - "1.8d (H)Steelbender's Masterpiece": {'id': 56302, 'agi': 129, 'hit': 93, 'mastery': 76, 'damage': 737, 'speed': 1.8, 'type': 'dagger'}, - '1.4d Throat Slasher': {'id': 57927, 'agi': 129, 'crit': 86, 'hit': 86, 'damage': 573.5, 'speed': 1.4, 'type': 'dagger'}, # off hand - '1.4d (H)Toxidunk Dagger': {'id': 56326, 'agi': 129, 'hit': 86, 'exp': 86, 'damage': 573.5, 'speed': 1.4, 'type': 'dagger'}, - '1.8d (H)Wicked Dagger': {'id': 63477, 'agi': 129, 'crit': 86, 'exp': 86, 'damage': 737.5, 'speed': 1.8, 'type': 'dagger'}, - '1.8d (H)Windwalker Blade': {'id': 56454, 'agi': 129, 'crit': 86, 'exp': 86, 'damage': 737, 'speed': 1.8, 'type': 'dagger'}, - '1.8d Windwalker Blade': {'id': 56127, 'agi': 115, 'crit': 76, 'exp': 76, 'damage': 653, 'speed': 1.8, 'type': 'dagger'}, - '2.6f (H)Claws of Torment': {'id': 65006, 'agi': 165, 'crit': 110, 'haste': 110, 'damage': 1356.5, 'speed': 2.6, 'type': 'fist'}, # main hand - '2.6f Claws of Torment': {'id': 63537, 'agi': 146, 'crit': 97, 'haste': 97, 'damage': 1202, 'speed': 2.6, 'type': 'fist'}, # main hand - '2.6f Crystalline Geoknife': {'id': 66972, 'agi': 115, 'crit': 76, 'haste': 76, 'damage': 943.5, 'speed': 2.6, 'type': 'fist'}, # main hand - '2.6f (H)Fist of Pained Senses': {'id': 56329, 'agi': 129, 'crit': 86, 'haste': 86, 'damage': 1065, 'speed': 2.6, 'type': 'fist'}, # main hand - '2.6f The Perforator': {'id': 52493, 'agi': 95, 'crit': 87, 'mastery': 38, 'sockets': ['red'], 'bonus_stat': 'mastery', 'bonus_value': 10, 'damage': 943.5, 'speed': 2.6, 'type': 'fist'}, - "2.6a (H)Crul'korak, the Lightning's Arc": {'id': 65024, 'agi': 165, 'crit': 110, 'haste': 110, 'damage': 1356.5, 'speed': 2.6, 'type': 'axe'}, - "2.6a Crul'korak, the Lightning's Arc": {'id': 59443, 'agi': 146, 'crit': 97, 'haste': 97, 'damage': 1202, 'speed': 2.6, 'type': 'axe'}, - # "2.6a (H)Maimgor's Bite": {'id': 65014, 'agi': 165, 'hit': 110, 'mastery': 110, 'damage': 1356.5, 'speed': 2.6, 'type': 'axe'}, # off hand - # "2.6a Maimgor's Bite": {'id': 59462, 'agi': 146, 'hit': 97, 'mastery': 97, 'damage': 1202, 'speed': 2.6, 'type': 'axe'}, # off hand - "2.6a Calder's Coated Carrion Carver": {'id': 63788, 'agi': 115, 'crit': 84, 'haste': 63, 'damage': 943.5, 'speed': 2.6, 'type': 'axe'}, - '2.6a Elementium Gutslicer': {'id': 67602, 'agi': 129, 'hit': 86, 'mastery': 86, 'damage': 1065, 'speed': 2.6, 'type': 'axe'}, - '2.6a (H)Lightning Whelk Axe': {'id': 56266, 'agi': 129, 'crit': 86, 'hit': 86, 'damage': 1065, 'speed': 2.6, 'type': 'axe'}, - '2.6a Ravening Slicer': {'id': 62457, 'agi': 129, 'haste': 86, 'mastery': 86, 'damage': 1065, 'speed': 2.6, 'type': 'axe'}, - # '2.6a Windslicer': {'id': Windslicer, 'agi': 129, 'crit': 86, 'mastery': 86, 'damage': 1065, 'speed': 2.6, 'type': 'axe'}, # off hand - '2.6m (H)Hammer of Sparks': {'id': 56396, 'agi': 129, 'crit': 86, 'hit': 86, 'damage': 1065, 'speed': 2.6, 'type': 'mace'}, - '2.6m Hammer of Sparks': {'id': 55875, 'agi': 115, 'crit': 76, 'hit': 76, 'damage': 943.5, 'speed': 2.6, 'type': 'mace'}, - '2.6m (H)Heavy Geode Mace': {'id': 56353, 'agi': 129, 'hit': 86, 'exp': 86, 'damage': 1065, 'speed': 2.6, 'type': 'mace'}, - '2.6s (H)Fang of Twilight': {'id': 65094, 'agi': 165, 'crit': 110, 'mastery': 110, 'damage': 1356.5, 'speed': 2.6, 'type': 'sword'}, - '2.6s Fang of Twilight': {'id': 63533, 'agi': 146, 'crit': 97, 'mastery': 97, 'damage': 1202, 'speed': 2.6, 'type': 'sword'}, - '2.6s Krol Decapitator': {'id': 68161, 'agi': 146, 'hit': 86, 'haste': 105, 'damage': 1202, 'speed': 2.6, 'type': 'sword'}, - '2.7s (H)Cruel Barb': {'id': 65164, 'agi': 129, 'crit': 86, 'hit': 86, 'damage': 1106, 'speed': 2.7, 'type': 'sword'}, - "2.6s (H)Thief's Blade": {'id': 65173, 'agi': 129, 'haste': 86, 'mastery': 86, 'damage': 1065, 'speed': 2.6, 'type': 'sword'}, -} \ No newline at end of file diff --git a/test_ui/testing_ui.py b/test_ui/testing_ui.py deleted file mode 100644 index a22ff94..0000000 --- a/test_ui/testing_ui.py +++ /dev/null @@ -1,712 +0,0 @@ -from __future__ import print_function -# All the imports here are either base python or shadowcraft files with the exception of wx, -# which can be downloaded from http://www.wxpython.org/download.php (I worked with windows 2.6/64) -from builtins import str -from builtins import range -from os import path -import sys -sys.path.append(path.abspath(path.join(path.dirname(__file__), '..'))) - -from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator -from shadowcraft.calcs.rogue.Aldriana import settings - -from shadowcraft.core import exceptions -from shadowcraft.core import i18n - -from shadowcraft.objects import buffs -from shadowcraft.objects import race -from shadowcraft.objects import stats -from shadowcraft.objects import procs -from shadowcraft.objects import talents -from shadowcraft.objects import glyphs - -import ui_data -import os -import string -import wx - -class GearPage(wx.Panel): - gear_slots = [ - "head", - "neck", - "shoulders", - "back", - "chest", - "wrists", - "hands", - "waist", - "legs", - "feet", - "ring1", - "ring2", - "trinket1", - "trinket2", - "mainhand", - "offhand", - ] - current_gear = { - "head": 0, - "neck": 0, - "shoulders": 0, - "back": 0, - "chest": 0, - "wrists": 0, - "hands": 0, - "waist": 0, - "legs": 0, - "feet": 0, - "ring1": 0, - "ring2": 0, - "trinket1": 0, - "trinket2": 0, - "mainhand": 0, - "offhand": 0, - } - stats = [ - 'str', - 'agi', - 'ap', - 'crit', - 'hit', - 'exp', - 'haste', - 'mastery', - ] - enchants = {} - gems = {} - reforges = {} - - def __init__(self, parent, calculator): - wx.Panel.__init__(self, parent) - self.calculator = calculator - - grid_sizer = wx.FlexGridSizer(cols = 6) - for slot in self.gear_slots: - self.create_ui_for_slot(grid_sizer, slot) - self.SetSizer(grid_sizer) - self.Fit() - - def create_ui_for_slot(self, sizer, slot): - label = wx.StaticText(self, -1, label = string.capwords(slot)) - sizer.Add(label, flag = wx.ALIGN_RIGHT) - item_cb = self.create_item_ui_for_slot(slot) - sizer.Add(item_cb) - if not slot in ('trinket1', 'trinket2', 'neck', 'waist'): - ench_label = wx.StaticText(self, -1, label = "Enchant") - sizer.Add(ench_label, flag = wx.ALIGN_RIGHT) - enchant_cb = self.create_enchant_ui_for_slot(self, slot) - if not enchant_cb == None: - sizer.Add(enchant_cb) - else: - sizer.Add((10, 10)) - else: - sizer.Add((2, 2)) - sizer.Add((2, 2)) - - gem_selecter = self.create_gem_ui_for_slot(slot) - #In order to get gems set for initial items, have to do update here - self.update_item_for_slot(self.get_items_for_slot(slot)[0], slot) - self.update_gems_for_slot(slot) - sizer.Add(gem_selecter) - - reforging_panel = self.create_reforging_ui_for_slot(slot) - sizer.Add(reforging_panel) - - def create_item_ui_for_slot(self, slot): - cb = None - cb = wx.ComboBox(self, -1, style = wx.CB_READONLY, name = slot) - cb.SetItems(self.get_items_for_slot(slot)) - cb.SetSelection(0) - cb.Bind(wx.EVT_COMBOBOX, lambda evt, slot=slot:self.on_item_selected(evt, slot)) - return cb - - def create_gem_ui_for_slot(self, slot): - vbox = wx.BoxSizer(wx.VERTICAL) - self.gems[slot] = {} - for color in ('meta', 'red', 'yellow', 'blue', 'prismatic'): - panel = wx.Panel(self, -1) - gem_sizer = wx.BoxSizer(wx.HORIZONTAL) - panel.SetSizer(gem_sizer) - color_block = wx.StaticText(panel, -1, label = " ") - gem_sizer.Add(color_block) - if color == 'meta': - color_block.SetBackgroundColour('WHITE') - elif color == 'prismatic': - color_block.SetBackgroundColour('GREY') - else: - color_block.SetBackgroundColour(color.upper()) - cb = wx.ComboBox(panel, -1, style = wx.CB_READONLY) - cb.Bind(wx.EVT_COMBOBOX, self.on_gem_selected) - cb.SetItems([''] + list(ui_data.gems.keys())) - cb.SetSelection(0) - panel.Hide() - - gem_sizer.Add(cb, 0, wx.EXPAND) - self.gems[slot][color] = cb - vbox.Add(panel, 0, wx.EXPAND) - return vbox - - def create_enchant_ui_for_slot(self, master, slot): - cb = None - enchants = [''] + list(self.get_enchants_for_slot(slot).keys()) - cb = wx.ComboBox(master, -1, style = wx.CB_READONLY) - cb.SetItems(enchants) - cb.Bind(wx.EVT_COMBOBOX, self.on_enchant_selected) - self.enchants[slot] = cb - return cb - - def create_reforging_ui_for_slot(self, slot): - sizer = wx.BoxSizer(wx.HORIZONTAL) - reforge_from = self.current_gear[slot].reforgable_from() - reforge_to = self.current_gear[slot].reforgable_to() - if len(reforge_from) > 0: - cb_from = wx.ComboBox(self, -1, style = wx.CB_READONLY) - cb_from.SetItems([''] + reforge_from) - sizer.Add(cb_from, 2, wx.EXPAND) - cb_to = wx.ComboBox(self, -1, style = wx.CB_READONLY) - cb_to.SetItems([''] + reforge_to) - sizer.Add(cb_to, 2, wx.EXPAND) - btn_reforge = wx.Button(self, -1, label = "Reforge") - btn_reforge.Bind(wx.EVT_BUTTON, lambda evt, slot=slot: self.on_reforge(evt, slot)) - sizer.Add(btn_reforge, 2, wx.EXPAND) - btn_restore = wx.Button(self, -1, label = "Restore") - btn_restore.Bind(wx.EVT_BUTTON, lambda evt, slot=slot: self.on_restore(evt, slot)) - btn_restore.Hide() - sizer.Add(btn_restore, 2, wx.EXPAND) - self.reforges[slot] = {'from': cb_from, 'to': cb_to, 'reforge': btn_reforge, 'restore': btn_restore} - return sizer - - def populate_combobox_for_slot(self, combobox, slot): - options = self.get_items_for_slot(slot) - combobox.SetItems(options) - combobox.SetStringSelection(options[0]) - - def get_items_for_slot(self, slot): - item_names = [] - items_dict = getattr(ui_data, slot) - item_names = list(items_dict.keys()) - return item_names - - def get_gems(self): - return list(ui_data.gems.keys()) - - def get_enchants_for_slot(self, slot): - enchants = [] - if slot in ('mainhand', 'offhand'): - enchants = ui_data.enchants['melee_weapons'] - elif slot in ('ring1', 'ring2'): - enchants = ui_data.enchants['rings'] - else: - enchants = ui_data.enchants[slot] - return enchants - - def update_gems_for_slot(self, slot): - for color in list(self.gems[slot].keys()): - self.gems[slot][color].SetSelection(0) - self.gems[slot][color].GetParent().Hide() - item = self.current_gear[slot] - for color in item.sockets: - self.gems[slot][color].GetParent().Show() - self.Layout() - - def update_item_for_slot(self, item_name, slot): - items_dict = getattr(ui_data, slot) - item = None - if slot in ('mainhand', 'offhand'): - item = ui_data.Weapon(item_name, **items_dict[item_name]) - else: - item = ui_data.Item(item_name, **items_dict[item_name]) - self.current_gear[slot] = item - - #Event handler for selecting a combo box entry - def on_item_selected(self, e, slot): - item_name = e.GetString() - self.update_item_for_slot(item_name, slot) - self.update_gems_for_slot(slot) - self.calculator.calculate() - #Clear the (no longer true) reforge settings - self.reset_reforging_ui_for_slot(slot) - - def on_reforge(self, e, slot): - reforge_from = self.reforges[slot]['from'].GetValue() - reforge_to = self.reforges[slot]['to'].GetValue() - if len(reforge_from) > 0 and len(reforge_to) > 0: - self.current_gear[slot].reforge(reforge_from, reforge_to) - self.reforges[slot]['reforge'].Hide() - self.reforges[slot]['restore'].Show() - self.Layout() - self.calculator.calculate() - - def on_restore(self, e, slot): - #Restoring the item to its dictionary definition - print(self.current_gear[slot].name) - self.update_item_for_slot(self.current_gear[slot].name, slot) - self.reset_reforging_ui_for_slot(slot) - self.calculator.calculate() - - def reset_reforging_ui_for_slot(self, slot): - reforge_from = self.current_gear[slot].reforgable_from() - reforge_to = self.current_gear[slot].reforgable_to() - if len(reforge_from) > 0: - self.reforges[slot]['from'].SetItems([''] + reforge_from) - self.reforges[slot]['from'].SetSelection(0) - self.reforges[slot]['to'].SetItems([''] + reforge_to) - self.reforges[slot]['to'].SetSelection(0) - self.reforges[slot]['restore'].Hide() - self.reforges[slot]['reforge'].Show() - self.Layout() - - def on_gem_selected(self, e): - self.calculator.calculate() - - def on_enchant_selected(self, e): - self.calculator.calculate() - - def get_stats(self): - current_stats = {'str': 0, 'agi': 0, 'ap': 0, 'crit': 0, 'hit': 0, 'exp': 0, 'haste': 0, 'mastery': 0, 'procs': [], 'gear_buffs': []} - current_stats['procs'] = [] - current_stats['gear_buffs'] = ['leather_specialization'] #Assuming this rather than give equipment an armor type - enchant_slots = list(self.enchants.keys()) - - tier11_count = 0 - tier12_count = 0 - tier14_count = 0 - for slot in self.gear_slots: - for stat in self.stats: - current_stats[stat] += getattr(self.current_gear[slot], stat) - gear_buff = self.current_gear[slot].gear_buff - if 'tier_11' == gear_buff: - tier11_count += 1 - elif 'tier_12' == gear_buff: - tier12_count += 1 - elif 'tier_14' == gear_buff: - tier14_count += 1 - elif len(gear_buff) > 0: - current_stats['gear_buffs'].append(gear_buff) - if len(self.current_gear[slot].proc) > 0: - current_stats['procs'].append(self.current_gear[slot].proc) - get_bonus = True - for slot_color in self.current_gear[slot].sockets: - gem_name = self.gems[slot][slot_color].GetValue() - if len(gem_name) > 0: - gem = ui_data.gems[gem_name] - for stat in gem[1]: - if stat == 'proc': - current_stats['procs'] += gem[1][stat] - elif stat == 'gear_buff': - current_stats['gear_buffs'] += gem[1][stat] - else: - current_stats[stat] += gem[1][stat] - if not slot_color in gem[0] and slot_color != 'prismatic': - get_bonus = False - if get_bonus and len(self.current_gear[slot].bonus_stat) > 0: - current_stats[self.current_gear[slot].bonus_stat] += self.current_gear[slot].bonus_value - if slot in enchant_slots and slot not in ('mainhand', 'offhand'): - #bugged - enchant_name = self.enchants[slot].GetValue() - if len(enchant_name) > 0: - enchant_data = ui_data.enchants[slot][enchant_name] - for stat in list(enchant_data.keys()): - current_stats[stat] += enchant_data[stat] - if tier11_count >= 2: - current_stats['gear_buffs'].append('rogue_t11_2pc') - if tier11_count >= 4: - current_stats['procs'].append('rogue_t11_4pc') - if tier12_count >= 2: - current_stats['gear_buffs'].append('rogue_t12_2pc') - if tier12_count >= 4: - current_stats['gear_buffs'].append('rogue_t12_4pc') - if tier14_count >= 2: - current_stats['gear_buffs'].append('rogue_t14_2pc') - if tier14_count >= 4: - current_stats['gear_buffs'].append('rogue_t14_4pc') - - mh = self.current_gear['mainhand'] - enchant = None - #bugged - if len(self.enchants['mainhand'].GetValue()) > 0: - enchant = ui_data.enchants['melee_weapons'][self.enchants['mainhand'].GetValue()] - mainhand = stats.Weapon(mh.damage, mh.speed, mh.type, enchant) - current_stats['mh'] = mainhand - - oh = self.current_gear['offhand'] - enchant = None - if len(self.enchants['offhand'].GetValue()) > 0: - enchant = ui_data.enchants['melee_weapons'][self.enchants['offhand'].GetValue()] - offhand = stats.Weapon(oh.damage, oh.speed, oh.type, enchant) - current_stats['oh'] = offhand - - current_stats['procs'] = procs.ProcsList(*set(current_stats['procs'])) - - current_stats['gear_buffs'] = stats.GearBuffs(*set(current_stats['gear_buffs'])) - - return current_stats - -class TalentsPage(wx.Panel): - rogue_talents = [ - ['nightstalker', 'subterfuge', 'shadow_focus'], - ['deadly_throw', 'nerve_strike', 'combat_readiness'], - ['cheat_death', 'leeching_poison', 'elusiveness'], - ['preparation', 'shadowstep', 'burst_of_speed'], - ['prey_on_the_weak', 'paralytic_poison', 'dirty_tricks'], - ['shuriken_toss', 'versatility', 'anticipation'] - ] - major_glyphs = [ - 'ambush', - 'blade_flurry', - 'blind', - 'cloak_of_shadows', - 'crippling_poison', - 'deadly_throw', - 'evasion', - 'expose_armor', - 'fan_of_knives', - 'feint', - 'garrote', - 'gouge', - 'kick', - 'preparation', - 'sap', - 'sprint', - 'tricks_of_the_trade', - 'vanish', - ] - - minor_glyphs = [ - 'blurred_speed', - 'distract', - 'pick_lock', - 'pick_pocket', - 'poisons', - 'safe_fall' - ] - - current_glyphs = [] - talents = {} - MAX_TALENTS_PER_TIER = 4 - - def __init__(self, parent, calculator): - wx.Panel.__init__(self, parent) - self.calculator = calculator - - hbox = wx.BoxSizer(wx.HORIZONTAL) - hbox.Add(self.add_talents(), 0, wx.EXPAND) - hbox.Add(self.add_glyphs()) - self.SetSizer(hbox) - - def add_talents(self): - talents_box = wx.BoxSizer(wx.VERTICAL) - talents_box.Add(wx.StaticText(self, -1, label = "Talents")) - talents_box.Add(wx.StaticLine(self), 0, wx.ALL|wx.EXPAND, 5) - talents_box.Add(wx.StaticText(self, -1, label = "Assassination")) - talents_box.Add(self.add_talents_for_spec('assass'), 0, wx.EXPAND) - talents_box.Add(wx.StaticLine(self), 0, wx.ALL|wx.EXPAND, 5) - talents_box.Add(wx.StaticText(self, -1, label = "Combat")) - talents_box.Add(self.add_talents_for_spec('combat'), 0, wx.EXPAND) - talents_box.Add(wx.StaticLine(self), 0, wx.ALL|wx.EXPAND, 5) - talents_box.Add(wx.StaticText(self, -1, label = "Subtlety")) - talents_box.Add(self.add_talents_for_spec('subtlety'), 0, wx.EXPAND) - - return talents_box - - def add_talents_for_spec(self, spec): - spec_box = wx.FlexGridSizer(cols = 2 * self.MAX_TALENTS_PER_TIER) - talents = [] - talent_data = {} - current_tier = 1 - talents_this_tier = 0 - - for talent in talents: - #Funky ui stuff to get reasonable columns - if talent_data[talent][1] != current_tier: - while(talents_this_tier < self.MAX_TALENTS_PER_TIER): - spec_box.Add((10, 10)) - spec_box.Add((10, 10)) - talents_this_tier += 1 - current_tier += 1 - talents_this_tier = 0 - talents_this_tier += 1 - spec_box.Add(wx.StaticText(self, -1, label = talent), flag = wx.ALIGN_RIGHT) - combo = self.create_combo_with_max(talent_data[talent][0]) - if talent in ui_data.default_talents: - combo.SetSelection(ui_data.default_talents[talent]) - self.talents[talent] = combo - spec_box.Add(combo) - - return spec_box - - def create_combo_with_max(self, max_value): - cb = wx.ComboBox(self, -1, style = wx.CB_READONLY) - cb.Bind(wx.EVT_COMBOBOX, self.on_selection) - for value in range(0, max_value + 1): - cb.Append(str(value)) - cb.SetSelection(0) - return cb - - def add_glyphs(self): - vbox = wx.BoxSizer(wx.VERTICAL) - vbox.Add(wx.StaticText(self, -1, label = "Glyphs")) - vbox.Add(wx.StaticLine(self), 0, wx.ALL|wx.EXPAND, 5) - sizer = wx.FlexGridSizer(cols = 4) - vbox.Add(sizer) - - sizer.Add(wx.StaticText(self, -1, label = "Major: ")) - sizer.Add(self.add_glyph(self.major_glyphs)) - sizer.Add(self.add_glyph(self.major_glyphs)) - sizer.Add(self.add_glyph(self.major_glyphs)) - - sizer.Add(wx.StaticText(self, -1, label = "Minor: ")) - sizer.Add(self.add_glyph(self.minor_glyphs)) - sizer.Add(self.add_glyph(self.minor_glyphs)) - sizer.Add(self.add_glyph(self.minor_glyphs)) - - return vbox - - def add_glyph(self, options): - cb = wx.ComboBox(self, -1, style = wx.CB_READONLY) - cb.Bind(wx.EVT_COMBOBOX, self.on_selection) - cb.SetItems([''] + options) - self.current_glyphs.append(cb) - return cb - - def get_talents(self): - # TODO - talents = '000000' - #for each row - - return (talents) - - def get_glyphs(self): - glyphs_list = [] - for glyph in self.current_glyphs: - glyph_name = glyph.GetValue() - if len(glyph_name) > 0 and glyph_name not in glyphs_list: - glyphs_list.append(glyph_name) - return glyphs_list - - def on_selection(self, e): - self.calculator.calculate() - -class BuffsPage(wx.Panel): - current_buffs = [] - - def __init__(self, parent, calculator): - wx.Panel.__init__(self, parent) - self.calculator = calculator - - self.create_buff_checkboxes() - - def create_buff_checkboxes(self): - vbox = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(vbox) - - for buff in buffs.Buffs.allowed_buffs: - chk_box = wx.CheckBox(self, -1, buff, name = buff) - chk_box.SetValue(False) - vbox.Add(chk_box, 2, wx.BOTTOM) - chk_box.Bind(wx.EVT_CHECKBOX, lambda event, name = buff: self.on_check_changed(event, name)) - - self.current_buffs = list(buffs.Buffs.allowed_buffs) - - def on_check_changed(self, e, name): - chk_box = e.GetEventObject() - if (name in self.current_buffs) and (not chk_box.GetValue()): - self.current_buffs.remove(name) - elif not (name in self.current_buffs) and (chk_box.GetValue()): - self.current_buffs.append(name) - self.calculator.calculate() - - - def get_buff_string(self): - return ", ".join(self.current_buffs) - -class SettingsPage(wx.Panel): - def __init__(self, parent, calculator): - wx.Panel.__init__(self, parent) - self.calculator = calculator - sizer = wx.FlexGridSizer(cols = 2) - - sizer.Add(wx.StaticText(self, -1, label = "Race: ")) - sizer.Add(self.create_race_selector()) - sizer.Add(wx.StaticText(self, -1, label = "Cycle: ")) - sizer.Add(self.create_cycle_selector()) - sizer.Add(wx.StaticText(self, -1, label = "Response Time: ")) - sizer.Add(self.create_response_time_entry()) - - self.SetSizer(sizer) - - def create_race_selector(self): - races = list(race.Race.racial_stat_offset.keys()) - cb = self.create_combobox_with_options(races) - self.race = cb - return cb - - def create_combobox_with_options(self, options): - cb = wx.ComboBox(self, -1, style = wx.CB_READONLY) - cb.Bind(wx.EVT_COMBOBOX, self.on_selection) - cb.SetItems(options) - cb.SetSelection(0) - return cb - - def create_cycle_selector(self): - cycles = ["Assassination", "Combat", "Subtlety"] - cb = self.create_combobox_with_options(cycles) - self.cycle = cb - test_cycle = settings.AssassinationCycle() - test_settings = settings.Settings(test_cycle, response_time=1) - return cb - - def create_response_time_entry(self): - tc = wx.TextCtrl(self, -1, value = '1') - self.response_time = tc - return tc - - def get_race(self): - return self.race.GetValue() - - def get_cycle(self): - cycle = '' - cur_cycle = self.cycle.GetValue() - if cur_cycle == "Assassination": - cycle = settings.AssassinationCycle() - elif cur_cycle == "Combat": - cycle = settings.OutlawCycle() - elif cur_cycle == "Subtlety": - cycle = settings.SubtletyCycle() - return cycle - - def get_response_time(self): - response_time = 1 - if len(self.response_time.GetValue()) > 0: - reponse_time = float(self.response_time.GetValue()) - return response_time - - def on_selection(self, e): - self.calculator.calculate() - -class TestGUI(wx.Frame): - ep_stats = [ - 'white_hit', - 'yellow_hit', - 'str', - 'agi', - 'haste', - 'crit', - 'mastery', - 'dodge_exp', - 'parry_exp' - ] - - def __init__(self): - wx.Frame.__init__(self, None, title = "ShadowCraft") - self.initializing = True - vbox = wx.BoxSizer(wx.VERTICAL) - nb = wx.Notebook(self) - - self.gear_page = GearPage(nb, self) - self.talents_page = TalentsPage(nb, self) - self.buffs_page = BuffsPage(nb, self) - self.settings_page = SettingsPage(nb, self) - - nb.AddPage(self.gear_page, "Gear") - nb.AddPage(self.talents_page, "Talents") - nb.AddPage(self.buffs_page, "Buffs") - nb.AddPage(self.settings_page, "Settings") - vbox.Add(nb, 0, wx.EXPAND) - - self.error_area = wx.StaticText(self, -1) - self.error_area.SetForegroundColour("Red") - error_font = self.error_area.GetFont() - error_font.SetPointSize(16) - self.error_area.SetFont(error_font) - vbox.Add(self.error_area) - - results_area = self.create_results_area() - vbox.Add(results_area, 2, wx.EXPAND | wx.BOTTOM) - - self.SetSizer(vbox) - self.Fit() - self.initializing = False - self.calculate() - - def no_edit_text_box(self): - panel = wx.Panel(self, -1) - tb = wx.TextCtrl(self, -1, style = wx.TE_READONLY) - tb.SetBackgroundColour(panel.GetBackgroundColour()) - return tb - - def create_multiline_with_label(self, label, key): - vbox = wx.BoxSizer(wx.VERTICAL) - vbox.Add(wx.StaticText(self, -1, label = label)) - multi = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE | wx.TE_READONLY) - vbox.Add(multi, 2, wx.EXPAND | wx.ALL) - setattr(self, key, multi) - return vbox - - def create_results_area(self): - hbox = wx.BoxSizer(wx.HORIZONTAL) - - dps_box = wx.FlexGridSizer(cols = 2) - dps_box.Add(wx.StaticText(self, -1, style = wx.ALIGN_RIGHT, label = "DPS: ")) - self.dps = self.no_edit_text_box() - dps_box.Add(self.dps, 2, wx.BOTTOM) - hbox.Add(dps_box, 2, wx.BOTTOM | wx.EXPAND) - - sizer = wx.FlexGridSizer(cols = 2) - for stat in GearPage.stats: - sizer.Add(wx.StaticText(self, -1, label = stat)) - stat_box = wx.TextCtrl(self, -1, style = wx.TE_READONLY) - setattr(self, stat, stat_box) - sizer.Add(stat_box) - hbox.Add(sizer, 2, wx.EXPAND) - - hbox.Add(self.create_multiline_with_label("EP Values", 'ep_box'), 2, wx.EXPAND | wx.ALL) - #TODO: add talents comparions here - hbox.Add(self.create_multiline_with_label("DPS Breakdown", 'dps_breakdown'), 2, wx.EXPAND | wx.ALL) - - return hbox - - def calculate(self): - # bugged - if not self.initializing: - gear_stats = self.gear_page.get_stats() - my_stats = stats.Stats(**gear_stats) - my_talents = talents.Talents(talent_string='311113') #(*self.talents_page.get_talents() ) - #my_talents = '311113' - my_glyphs = glyphs.Glyphs('rogue', *self.talents_page.get_glyphs()) #.RogueGlyphs(*self.talents_page.get_glyphs()) - my_buffs = buffs.Buffs(*self.buffs_page.current_buffs) - my_race = race.Race(self.settings_page.get_race()) - test_settings = settings.Settings(self.settings_page.get_cycle(), response_time = self.settings_page.get_response_time()) - - self.error_area.SetLabel("") - try: - calculator = AldrianasRogueDamageCalculator(my_stats, my_talents, my_glyphs, my_buffs, my_race, test_settings) - dps = calculator.get_dps() - ep_values = calculator.get_ep() - dps_breakdown = calculator.get_dps_breakdown() - - except exceptions.InvalidInputException as e: - self.error_area.SetLabel(str(e)) - - self.dps.SetValue(str(dps)) - self.ep_box.SetValue(self.pretty_print(ep_values)) - self.dps_breakdown.SetValue(self.pretty_print(dps_breakdown)) - for stat in GearPage.stats: - tc = getattr(self, stat) - tc.SetValue(str(gear_stats[stat])) - - def pretty_print(self, my_dict): - ret_str = '' - max_len = max(len(entry[0]) for entry in list(my_dict.items())) - dict_values = list(my_dict.items()) - dict_values.sort(key=lambda entry: entry[1], reverse=True) - for value in dict_values: - ret_str += value[0] + ':' + ' ' * (max_len - len(value[0])) + str(value[1]) + os.linesep - - return ret_str - -if __name__ == "__main__": - app = wx.App(False) - gui = TestGUI(); - gui.Show() - app.MainLoop() diff --git a/test_ui/ui_data.py b/test_ui/ui_data.py deleted file mode 100644 index 1b59069..0000000 --- a/test_ui/ui_data.py +++ /dev/null @@ -1,322 +0,0 @@ -from __future__ import print_function -# the following items had incorrect stats and should be corrected now -# necklace of strife -# wind dancer tunic -# dispersing belt -# storm rider's boots -# wind dancer gloves -# Uhn'agh Fash -# Poison Protocol Pauldrons -# Wind Dancer's Spaulders -# lots of necks/heads - -from builtins import str -from builtins import object -import math - -class Item(object): - reforgable_stats = frozenset([ - 'crit', - 'hit', - 'exp', - 'haste', - 'mastery' - ]) - - def __init__(self, name, id=0, str=0, agi=0, ap=0, crit=0, hit=0, exp=0, haste=0, mastery=0, sockets=[], bonus_stat='', bonus_value=0, proc='', gear_buff=''): - self.name = name - self.id = id - self.str = str - self.agi = agi - self.ap = ap - self.crit = crit - self.hit = hit - self.exp = exp - self.haste = haste - self.mastery = mastery - self.sockets = sockets - self.bonus_stat = bonus_stat - self.bonus_value = bonus_value - self.proc = proc - self.gear_buff = gear_buff - - def reforgable_from(self): - reforgable = [] - for stat in self.reforgable_stats: - if getattr(self, stat) > 0: - reforgable.append(stat) - return reforgable - - def reforgable_to(self): - reforgable = [] - for stat in self.reforgable_stats: - if getattr(self, stat) == 0: - reforgable.append(stat) - return reforgable - - def reforge(self, from_stat, to_stat): - print("before: " + from_stat + " = " + str(getattr(self, from_stat))) - print(" " + to_stat + " = " + str(getattr(self, to_stat))) - reforged_value = math.floor(getattr(self, from_stat) * 0.4) - setattr(self, from_stat, getattr(self, from_stat) - reforged_value) - setattr(self, to_stat, reforged_value) - print("after: " + from_stat + " = " + str(getattr(self, from_stat))) - print(" " + to_stat + " = " + str(getattr(self, to_stat))) - -class Weapon(Item): - def __init__(self, name, id=0, str=0, agi=0, ap=0, crit=0, hit=0, exp=0, haste=0, mastery=0, sockets=[], bonus_stat='', bonus_value=0, proc='', gear_buff='', damage=0, speed=0, type=''): - super(Weapon, self).__init__(name, id, str, agi, ap, crit, hit, exp, haste, mastery, sockets, bonus_stat, bonus_value, proc, gear_buff) - self.damage = damage - self.speed = speed - self.type = type - -head = { - # ----- 5.0 ----- - '(H)Helmet of the Thousandfold Blades': {'id': 87126, 'agi': 1140, 'exp': 755, 'mastery': 828, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180, 'gear_buff': 'tier_14'}, - '(H)Crown of Opportunistic Strikes': {'id': 87070, 'agi': 1054, 'crit': 702, 'haste': 782, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, - 'Helmet of the Thousandfold Blades': {'id': 85301, 'agi': 983, 'exp': 655, 'mastery': 720, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180, 'gear_buff': 'tier_14'}, - 'Crown of Opportunistic Strikes': {'id': 86146, 'agi': 906, 'crit': 604, 'haste': 684, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, - 'Red Smoke Bandana': {'id': 89300, 'agi': 906, 'hit': 665, 'mastery': 616, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, - '(L)Helmet of the Thousandfold Blades': {'id': 86641, 'agi': 844, 'exp': 567, 'mastery': 624, 'sockets': ['blue', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180, 'gear_buff': 'tier_14'}, - '(L)Crown of Opportunistic Strikes': {'id': 86804, 'agi': 775, 'crit': 517, 'haste': 597, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, - 'Windblast Helm': {'id': 81283, 'agi': 659, 'haste': 520, 'mastery': 440, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, - 'Soulburner Crown': {'id': 82853, 'agi': 659, 'hit': 537, 'crit': 410, 'sockets': ['red', 'meta'], 'bonus_stat': 'agi', 'bonus_value': 180}, -} -neck = { - # ----- 5.0 ----- - '(H)Choker of the Unleashed Storm': {'id': 86953, 'agi': 769, 'crit': 564, 'mastery': 426}, - '(H)Amulet of the Hidden Kings': {'id': 87045, 'agi': 720, 'haste': 502, 'exp': 445}, - 'Choker of the Unleashed Storm': {'id': 86166, 'agi': 682, 'crit': 500, 'mastery': 377}, - 'Amulet of the Hidden Kings': {'id': 86047, 'agi': 638, 'haste': 444, 'exp': 394}, - 'Choker of the Klaxxi\'va': {'id': 89065, 'agi': 638, 'hit': 363, 'crit': 463}, - 'Delicate Necklace of the Golden Lotus': {'id': 90593, 'agi': 638, 'crit': 426, 'mastery': 426}, - '(L)Choker of the Unleashed Storm': {'id': 86824, 'agi': 604, 'crit': 443, 'mastery': 334}, - '(L)Amulet of the Hidden Kings': {'id': 86776, 'agi': 566, 'haste': 394, 'exp': 349}, - 'Don Guerrero\'s Glorious Choker': {'id': 90583, 'agi': 566, 'haste': 287, 'mastery': 430}, - 'Scorched Scarlet Key': {'id': 81564, 'agi': 501, 'hit': 334, 'exp': 334}, - 'Engraved Amber Pendant': {'id': 81271, 'agi': 501, 'crit': 344, 'haste': 318}, -} -shoulders = { - # ----- 5.0 ----- - '(H)Spaulders of the Thousandfold Blades': {'id': 87128, 'agi': 946, 'haste': 520, 'exp': 733, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - '(H)Netherrealm Shoulderpads': {'id': 87033, 'agi': 881, 'exp': 690, 'mastery': 447, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60}, - 'Spaulders of the Thousandfold Blades': {'id': 85299, 'agi': 829, 'haste': 452, 'exp': 650, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - 'Netherrealm Shoulderpads': {'id': 85995, 'agi': 771, 'exp': 607, 'mastery': 391, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60}, - 'Imperion Spaulders': {'id': 89341, 'agi': 771, 'hit': 513, 'crit': 536, 'sockets': ['yellow'], 'bonus_stat': 'agi', 'bonus_value': 60}, - '(L)Spaulders of the Thousandfold Blades': {'id': 86639, 'agi': 725, 'haste': 391, 'exp': 576, 'sockets': ['blue'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - '(L)Netherrealm Shoulderpads': {'id': 86763, 'agi': 674, 'exp': 533, 'mastery': 342, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60}, - 'Doubtridden Shoulderpads': {'id': 81071, 'agi': 668, 'hit': 434, 'exp': 452}, - 'Fizzy Spaulders': {'id': 81068, 'agi': 668, 'crit': 412, 'haste': 465}, -} -back = { - # ----- 5.0 ----- - '(H)Legbreaker Greatcloak': {'id': 86963, 'agi': 769, 'crit': 513, 'mastery': 513}, - '(H)Arrow Breaking Windcloak': {'id': 87044, 'agi': 720, 'haste': 399, 'exp': 529}, - 'Legbreaker Greatcloak': {'id': 86173, 'agi': 682, 'crit': 454, 'mastery': 454}, - 'Arrow Breaking Windcloak': {'id': 86082, 'agi': 638, 'haste': 353, 'exp': 468}, - 'Blackguard Cape': {'id': 89076, 'agi': 638, 'hit': 444, 'mastery': 394}, - '(L)Legbreaker Greatcloak': {'id': 86831, 'agi': 604, 'crit': 402, 'mastery': 402}, - '(L)Arrow Breaking Windcloak': {'id': 86782, 'agi': 566, 'haste': 313, 'exp': 415}, - 'Dory\'s Pageantry': {'id': 86782, 'agi': 566, 'hit': 377, 'crit': 377}, - 'Aerial Bombardment Cloak': {'id': 81282, 'agi': 501, 'hit': 254, 'crit': 381}, - 'Wind-soaked Drape': {'id': 81123, 'agi': 501, 'crit': 358, 'mastery': 293}, -} -chest = { - # ----- 5.0 ----- - '(H)Tunic of the Thousandfold Blades': {'id': 87124, 'agi': 1220, 'crit': 880, 'mastery': 800, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 120, 'gear_buff': 'tier_14'}, - '(H)Chestguard of Total Annihilation': {'id': 87058, 'agi': 1134, 'crit': 698, 'haste': 833, 'sockets': ['red', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 120}, - 'Tunic of the Thousandfold Blades': {'id': 85303, 'agi': 1063, 'crit': 775, 'mastery': 695, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 120, 'gear_buff': 'tier_14'}, - 'Chestguard of Total Annihilation': {'id': 86136, 'agi': 986, 'crit': 609, 'haste': 729, 'sockets': ['red', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 120}, - 'Softfoot Silentwrap': {'id': 89431, 'agi': 986, 'hit': 554, 'exp': 761, 'sockets': ['blue', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 120}, - 'Chestguard of Nemeses': {'id': 85788, 'agi': 1223, 'crit': 755, 'mastery': 852}, - '(L)Tunic of the Thousandfold Blades': {'id': 86643, 'agi': 924, 'crit': 683, 'mastery': 603, 'sockets': ['yellow', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 120, 'gear_buff': 'tier_14'}, - 'Greyshadow Chestguard': {'id': 85823, 'agi': 1015, 'crit': 660, 'mastery': 687}, - 'Vulajin\'s Vicious Breastplate': {'id': 90585, 'agi': 855, 'crit': 557, 'mastery': 637, 'sockets': ['red', 'yellow'], 'bonus_stat': 'mastery', 'bonus_value': 20}, #bug? seems like bonus should be 120 - '(L)Chestguard of Total Annihilation': {'id': 86795, 'agi': 855, 'crit': 530, 'haste': 636, 'sockets': ['red', 'yellow'], 'bonus_stat': 'agi', 'bonus_value': 120}, - 'Korloff\'s Raiment': {'id': 81573, 'agi': 899, 'hit': 456, 'crit': 683}, - 'Nimbletoe Chestguard': {'id': 81080, 'agi': 899, 'crit': 609, 'haste': 584}, - 'Delicate Chestguard of the Golden Lotus': {'id': 90597, 'agi': 899, 'crit': 660, 'mastery': 497}, - 'Refurbished Zandalari Vestment': {'id': 89667, 'agi': 797, 'haste': 555, 'exp': 492}, -} -wrists = { - # ----- 5.0 ----- - '(H)Bracers of Unseen Strikes': {'id': 86954, 'agi': 769, 'crit': 487, 'haste': 528}, - '(H)Smooth Bettle Wristbands': {'id': 86995, 'agi': 769, 'exp': 414, 'mastery': 571}, - 'Bracers of Unseen Strikes': {'id': 86163, 'agi': 682, 'crit': 432, 'haste': 468}, - 'Smooth Bettle Wristbands': {'id': 86185, 'agi': 682, 'exp': 366, 'mastery': 506}, - 'Quillpaw Family Bracers': {'id': 88884, 'agi': 638, 'hit': 426, 'crit': 426}, - '(L)Bracers of Unseen Strikes': {'id': 86821, 'agi': 604, 'crit': 383, 'haste': 414}, - '(L)Smooth Bettle Wristbands': {'id': 86843, 'agi': 604, 'exp': 325, 'mastery': 448}, - 'Lightblade Bracer': {'id': 81700, 'agi': 501, 'crit': 363, 'mastery': 285}, - 'Saboteur\'s Stabilizing Bracers': {'id': 81090, 'agi': 501, 'haste': 339, 'exp': 326}, -} -hands = { - # ----- 5.0 ----- - '(H)Gloves of the Thousandfold Blades': {'id': 87125, 'agi': 1026, 'hit': 724, 'crit': 616, 'gear_buff': 'tier_14'}, - '(H)Bonebreaker Gauntlets': {'id': 86964, 'agi': 946, 'hit': 495, 'haste': 730, 'sockets': ['blue'], 'bonus_stat': 'haste', 'bonus_value': 60}, - 'Murderer\'s Gloves': {'id': 85828, 'agi': 909, 'crit': 615, 'mastery': 591}, - 'Gloves of the Thousandfold Blades': {'id': 85302, 'agi': 909, 'hit': 641, 'crit': 546, 'gear_buff': 'tier_14'}, - 'Bonebreaker Gauntlets': {'id': 86176, 'agi': 829, 'hit': 434, 'haste': 643, 'sockets': ['blue'], 'bonus_stat': 'haste', 'bonus_value': 60}, - 'Fingers of the Loneliest Monk': {'id': 88744, 'agi': 851, 'exp': 593, 'mastery': 526}, - '(L)Bonebreaker Gauntlets': {'id': 86834, 'agi': 725, 'hit': 380, 'haste': 565, 'sockets': ['blue'], 'bonus_stat': 'haste', 'bonus_value': 60}, - '(L)Gloves of the Thousandfold Blades': {'id': 86642, 'agi': 805, 'hit': 568, 'crit': 484, 'gear_buff': 'tier_14'}, - 'Greyshadow Gloves': {'id': 85824, 'agi': 754, 'crit': 490, 'mastery': 510}, - 'Tombstone Gauntlets': {'id': 82858, 'agi': 668, 'hit': 349, 'haste': 502}, - 'Hound Trainer\'s Gloves': {'id': 81695, 'agi': 668, 'exp': 391, 'mastery': 478}, -} -waist = { - # ----- 5.0 ----- - '(H)Stalker\'s Cord of Eternal Autumn': {'id': 87180, 'agi': 946, 'hit': 593, 'crit': 674, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'crit', 'bonus_value': 60}, - '(H)Tomb Raider\'s Girdle': {'id': 87022, 'agi': 801, 'haste': 598, 'exp': 498, 'sockets': ['yellow', 'blue', 'prismatic'], 'bonus_stat': 'exp', 'bonus_value': 120}, - 'Stalker\'s Cord of Eternal Autumn': {'id': 86341, 'agi': 829, 'hit': 521, 'crit': 593, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'crit', 'bonus_value': 60}, - 'Tomb Raider\'s Girdle': {'id': 85982, 'agi': 691, 'haste': 521, 'exp': 432, 'sockets': ['yellow', 'blue', 'prismatic'], 'bonus_stat': 'exp', 'bonus_value': 120}, - 'Klaxxi Lash of the Borrower': {'id': 89060, 'agi': 771, 'crit': 391, 'mastery': 607, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'crit', 'bonus_value': 60}, - '(L)Stalker\'s Cord of Eternal Autumn': {'id': 86899, 'agi': 725, 'hit': 457, 'crit': 520, 'sockets': ['blue', 'prismatic'], 'bonus_stat': 'crit', 'bonus_value': 60}, - '(L)Tomb Raider\'s Girdle': {'id': 86750, 'agi': 594, 'haste': 452, 'exp': 373, 'sockets': ['yellow', 'blue', 'prismatic'], 'bonus_stat': 'exp', 'bonus_value': 120}, - 'Icewrath Belt': {'id': 82823, 'agi': 668, 'crit': 401, 'mastery': 471}, - 'Belt of Brazen Inebriation': {'id': 81135, 'agi': 668, 'hit': 412, 'exp': 465}, -} -legs = { - # ----- 5.0 ----- - '(H)Legguards of the Thousandfold Blades': {'id': 87127, 'agi': 1300, 'hit': 659, 'haste': 1009, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - '(H)Stoneflesh Leggings': {'id': 87013, 'agi': 1134, 'crit': 845, 'exp': 677, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value':120}, - 'Legguards of the Thousandfold Blades': {'id': 85300, 'agi': 1143, 'hit': 580, 'haste': 890, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - 'Stoneflesh Leggings': {'id': 85926, 'agi': 986, 'crit': 740, 'exp': 590, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value':120}, - 'Dreadsworn Slayer Legs': {'id': 89090, 'agi': 1066, 'crit': 801, 'mastery': 594, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value':60}, - '(L)Legguards of the Thousandfold Blades': {'id': 86640, 'agi': 1004, 'hit': 509, 'haste': 784, 'sockets': ['red'], 'bonus_stat': 'agi', 'bonus_value': 60, 'gear_buff': 'tier_14'}, - '(L)Stoneflesh Leggings': {'id': 86743, 'agi': 855, 'crit': 646, 'exp': 514, 'sockets': ['red', 'blue'], 'bonus_stat': 'agi', 'bonus_value':120}, - 'Wall-Breaker Legguards': {'id': 81091, 'agi': 899, 'exp': 584, 'mastery': 609}, - 'Ghostwoven Legguards': {'id': 82851, 'agi': 899, 'crit': 497, 'haste': 660}, -} -feet = { - # ----- 5.0 ----- - '(H)Boots of the Still Breath': {'id': 86943, 'agi': 946, 'crit': 551, 'haste': 695, 'sockets': ['yellow'], 'bonus_stat': 'crit', 'bonus_value': 60}, - '(H)Treads of Deadly Secretions': {'id': 86984, 'agi': 946, 'exp': 568, 'mastery': 685, 'sockets': ['yellow'], 'bonus_stat': 'exp', 'bonus_value': 60}, - 'Boots of the Still Breath': {'id': 86153, 'agi': 829, 'crit': 485, 'haste': 610, 'sockets': ['yellow'], 'bonus_stat': 'crit', 'bonus_value': 60}, - 'Treads of Deadly Secretions': {'id': 86984, 'agi': 829, 'exp': 500, 'mastery': 602, 'sockets': ['yellow'], 'bonus_stat': 'exp', 'bonus_value': 60}, - 'Tukka-Tuk\'s Hairy Boots': {'id': 88868, 'agi': 851, 'hit': 601, 'haste': 512}, - '(L)Boots of the Still Breath': {'id': 86811, 'agi': 725, 'crit': 426, 'haste': 535, 'sockets': ['yellow'], 'bonus_stat': 'crit', 'bonus_value': 60}, - '(L)Treads of Deadly Secretions': {'id': 86859, 'agi': 725, 'exp': 439, 'mastery': 528, 'sockets': ['yellow'], 'bonus_stat': 'exp', 'bonus_value': 60}, - 'Dashing Strike Treads': {'id': 81688, 'agi': 668, 'crit': 391, 'mastery': 478}, - 'Boots of Plummeting Death': {'id': 81249, 'agi': 668, 'haste': 434, 'exp': 452}, -} -rings = { - # ----- 5.0 ----- - '(HE)Regail\'s Band of the Endless': {'id': 90503, 'agi': 821, 'crit': 571, 'haste': 507}, - '(H)Painful Thorned Ring': {'id': 86974, 'agi': 769, 'exp': 438, 'mastery': 557}, - '(H)Regail\'s Band of the Endless': {'id': 87144, 'agi': 769, 'crit': 536, 'haste': 475}, - '(E)Regail\'s Band of the Endless': {'id': 90517, 'agi': 727, 'crit': 506, 'haste': 449}, - 'Painful Thorned Ring': {'id': 86200, 'agi': 682, 'exp': 388, 'mastery': 494}, - 'Regail\'s Band of the Endless': {'id': 86231, 'agi': 682, 'crit': 474, 'haste': 421}, - 'Anji\'s Keepsake': {'id': 89070, 'agi': 638, 'hit': 323, 'haste': 485}, - '(L)Painful Thorned Ring': {'id': 86851, 'agi': 604, 'exp': 343, 'mastery': 437}, - '(L)Regail\'s Band of the Endless': {'id': 86869, 'agi': 604, 'crit': 420, 'haste': 373}, - 'Perculia\'s Peculiar Signet': {'id': 90584, 'agi': 566, 'hit': 322, 'haste': 410}, - 'Seal of Ghoulish Glee': {'id': 88168, 'agi': 535, 'hit': 313, 'crit': 382}, - 'Pulled Grenade Pin': {'id': 81191, 'agi': 501, 'crit': 349, 'mastery': 309}, - 'Signet of Dancing Jade': {'id': 81128, 'agi': 501, 'crit': 309, 'exp': 349}, - 'Seal of Hateful Meditation': {'id': 81186, 'agi': 501, 'hit': 254, 'haste': 381}, -} -ring1 = rings -ring2 = rings -trinkets = { - # ----- 5.0 ----- - '(H)Terror in the Mists': {'id': 87167, 'agi': 1300, 'proc': 'heroic_terror_in_the_mists'}, - '(H)Jade Bandit Figurine': {'id': 87079, 'agi': 1218, 'proc': 'heroic_jade_bandit_figurine'}, - '(H)Bottle of Infinite Stars': {'id': 87057, 'mastery': 1218, 'proc': 'heroic_bottle_of_infinite_stars'}, - 'Terror in the Mists': {'id': 86332, 'agi': 1152, 'proc': 'terror_in_the_mists'}, - 'Jade Bandit Figurine': {'id': 86043, 'agi': 1079, 'proc': 'jade_bandit_figurine'}, - 'Bottle of Infinite Stars': {'id': 86132, 'mastery': 1079, 'proc': 'bottle_of_infinite_stars'}, - "Hawkmaster's Talon": {'id': 89082, 'agi': 1079, 'proc': 'hawkmasters_talon'}, - '(L)Terror in the Mists': {'id': 86890, 'agi': 1021, 'proc': 'lfr_terror_in_the_mists'}, - '(L)Jade Bandit Figurine': {'id': 87079, 'agi': 956, 'proc': 'lfr_jade_bandit_figurine'}, - '(L)Bottle of Infinite Stars': {'id': 87057, 'mastery': 956, 'proc': 'lfr_bottle_of_infinite_stars'}, - 'Relic of Xuen': {'id': 79328, 'agi': 956, 'proc': 'relic_of_xuen'}, - "Coren's Cold Chromium Coaster": {'id': 87574, 'crit': 904, 'proc': 'corens_cold_chromium_coaster'}, - 'Flashing Steel Talisman' : {'id': 81265, 'hit': 847, 'proc': 'flashing_steel_talisman'}, - 'Searing Words' : {'id': 81267, 'crit': 847, 'proc': 'searing_words'}, - 'Windswept Pages' : {'id': 81125, 'agi': 847, 'proc': 'windswept_pages'}, - #'Shadow-Pan Dragon Gun' : {'id': 88995, 'proc': 'shadow_pan_dragon_gun'}, #worth modelling? - 'Zen Alchemist Stone': {'id': 75274, 'mastery': 751, 'proc': 'zen_alchemist_stone'}, -} -trinket1 = trinkets -trinket2 = trinkets -melee_weapons = { - # ----- 5.0 ----- - #daggers - 'd-(H)Spiritsever': {'id': 87166, 'agi': 592, 'exp': 337, 'mastery': 429, 'damage': 6733, 'speed': 1.8, 'type': 'dagger','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - 'd-(H)Dagger of the Seven Stars': {'id': 87012, 'agi': 554, 'hit': 324, 'haste': 396, 'damage': 6308, 'speed': 1.8, 'type': 'dagger'}, - 'd-Spiritsever': {'id': 86391, 'agi': 524, 'exp': 298, 'mastery': 380, 'damage': 5965, 'speed': 1.8, 'type': 'dagger','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - 'd-Dagger of the Seven Stars': {'id': 85924, 'agi': 491, 'hit': 287, 'haste': 351, 'damage': 5588, 'speed': 1.8, 'type': 'dagger'}, - 'd-(L)Spiritsever': {'id': 86910, 'agi': 464, 'exp': 264, 'mastery': 336, 'damage': 5284.5, 'speed': 1.8, 'type': 'dagger','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - 'd-(L)Dagger of the Seven Stars': {'id': 86741, 'agi': 435, 'hit': 254, 'haste': 311, 'damage': 4951, 'speed': 1.8, 'type': 'dagger'}, - 'd-Tolakesh, Horn of the Black Ox': {'id': 87547, 'agi': 435, 'hit': 294, 'exp': 283, 'damage': 4951, 'speed': 1.8, 'type': 'dagger'}, - "d-Amber Slicer of Klaxxi'vess": {'id': 89393, 'agi': 385, 'haste': 261, 'mastery': 251, 'damage': 4386, 'speed': 1.8, 'type': 'dagger'}, - "d-Koegler's Ritual Knife": {'id': 82813, 'agi': 385, 'hit': 201, 'mastery': 290, 'damage': 4386, 'speed': 1.8, 'type': 'dagger'}, - 'd-Mantid Trochanter': {'id': 81088, 'agi': 385, 'crit': 251, 'haste': 261, 'damage': 4386, 'speed': 1.8, 'type': 'dagger'}, - 'd-Masterwork Ghost Shard': {'id': 82974, 'agi': 385, 'crit': 283, 'mastery': 213, 'damage': 4386, 'speed': 1.8, 'type': 'dagger'}, - #swords, maces, fists, axes - "f-(H)Claws of Shek'zeer": {'id': 86988, 'agi': 592, 'crit': 444, 'exp': 309, 'damage': 9725.5, 'speed': 2.6, 'type': 'fist','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - "f-(H)Gara'kal, Fist of the Spiritbinder": {'id': 87032, 'agi': 554, 'haste': 391, 'mastery': 333, 'damage': 9111.5, 'speed': 2.6, 'type': 'fist'}, - "f-Claws of Shek'zeer": {'id': 86226, 'agi': 524, 'crit': 394, 'exp': 274, 'damage': 8616, 'speed': 2.6, 'type': 'fist','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - "f-Gara'kal, Fist of the Spiritbinder": {'id': 85994, 'agi': 491, 'haste': 347, 'mastery': 295, 'damage': 8072, 'speed': 2.6, 'type': 'fist'}, - "f-(L)Claws of Shek'zeer": {'id': 86864, 'agi': 464, 'crit': 349, 'exp': 242, 'damage': 7633.5, 'speed': 2.6, 'type': 'fist','sockets': ['sha'], 'bonus_stat': 'agi', 'bonus_value': 0}, - "f-(L)Gara'kal, Fist of the Spiritbinder": {'id': 86762, 'agi': 435, 'haste': 307, 'mastery': 261, 'damage': 7151, 'speed': 2.6, 'type': 'fist'}, - "f-Ka'eng, Breath of the Shadow": {'id': 87543, 'agi': 355, 'crit': 287, 'exp': 187, 'damage': 7151, 'speed': 2.6, 'type': 'fist', 'sockets': ['blue'], 'bonus_stat': 'exp', 'bonus_value': 60}, - "f-Claws of Gekkan": {'id': 81245, 'agi': 385, 'crit': 272, 'haste': 232, 'damage': 6335, 'speed': 2.6, 'type': 'fist'}, - "f-Ner'onok's Razor Katar": {'id': 81286, 'agi': 385, 'hit': 257, 'exp': 257, 'damage': 6335, 'speed': 2.6, 'type': 'fist'}, -} -mainhand = melee_weapons -offhand = melee_weapons - -default_talents = { - # -} - -enchants = { - 'head': { - #'Arcanum of the Ramkahen':{'agi': 60, 'haste': 35} - }, - 'shoulders': { - #'Greater Inscription of Shattered Crystal': {'agi': 50, 'mastery': 25}, - #'Lesser Inscription of Shattered Crystal': {'agi': 30, 'mastery': 20} - }, - 'back': { - 'Greater Critical Strike': {'crit': 65}, - 'Major Agility': {'agi': 22} - }, - 'chest': {'Peerless Stats': {'agi': 20, 'str': 20}}, - 'wrists': { - 'Greater Speed': {'haste': 50}, - 'Greater Expertise': {'exp': 50}, - 'Precision': {'hit': 50}, - '(LW)Draconic Embossment':{'agi': 130} - }, - 'hands': { - 'Greater Mastery':{'mastery': 65}, - 'Greater Expertise': {'exp': 50}, - 'Haste': {'haste': 50} - }, - 'legs': {'Dragonbone': {'ap': 190, 'crit': 55}}, - 'feet': { - 'Major Agility': {'agi': 35}, - 'Mastery':{'mastery': 50}, - 'Precision': {'hit': 50}, - 'Haste': {'haste': 50} - }, - 'rings': {'dummy1': {}}, - 'melee_weapons': { - 'Landslide': 'landslide', - 'Hurricane': 'hurricane' - } -} - -gems = { - "Fleet Mists Metagem": (['meta'], {'mastery': 432}), - "Agile Mists Metagem": (['meta'], {'agi': 216, 'gear_buff': ['chaotic_metagem']}), - "Delicate Mists Gem": (['red'], {'agi': 160}), - "Adept Mists Gem": (['red', 'yellow'], {'agi': 80, 'mastery': 160}), - "Deft Mists Gem": (['red', 'yellow'], {'agi': 80, 'haste': 160}), - "Glinting Mists Gem": (['red', 'blue'], {'agi': 80, 'hit': 160}), - "Rigid Mists Gem": (['blue'], {'hit': 320}) -} \ No newline at end of file From 5a1681bd0cb371034a4c5827fb1b5ecae1f78eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 10 Mar 2017 17:44:37 +0100 Subject: [PATCH 172/265] Update README and code style guidelines --- README | 41 --------------------------- README.md | 59 ++++++++++++++++++++++++++++++++++++++ style.md | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.txt | 63 ----------------------------------------- 4 files changed, 143 insertions(+), 104 deletions(-) delete mode 100644 README create mode 100644 README.md create mode 100644 style.md delete mode 100644 style.txt diff --git a/README b/README deleted file mode 100644 index 754db24..0000000 --- a/README +++ /dev/null @@ -1,41 +0,0 @@ -# ShadowCraft - Engine - -This repository contains the calculations piece of ShadowCraft, a WoW -theorycraft project. Initially, this is focused on rogues (hence the name), -but the framework is designed such that if other classes wish to make use of it -in the future, they can do so in a sensible and reasonable way. All rogue -specific functionality is currently contained in directories named "rogue" - -for instance, the objects/ directory contains objects of general use for -theorycrafting calculations, while objects/rogue contains objects specifically -for use in rogue theorycraft. - -If you would like to contribute to this project, either to add your own -calculations module (for rogues or otherwise) or to improve what's already here -(bugfixes, new features, etc.) by all means do so; however, I will be -maintaining reasonably tight control over the architecture and *extremely* -tight control over my calculations module (currently located in -calcs/rogue/Aldriana). This doesn't mean you can't contribute stuff; it just -means that you should be aware that I may not accept your changes. - -If you have any questions/comments/suggestions, you can email me at aldriana at -elitistjerks dot com. Additionally, if your question is of a more general -nature, there is a discussion thread for this project on the EJ forums. - -NOTE: Please read style.txt if you intend to submit code to this project. - - -- Aldriana - Oct 28 2010 - -## Tests - -Running basic tests: - - nosetests --rednose tests - -With code coverage: - - nosetests --rednose --with-coverage --cover-html tests - -With profiling: - - nosetests --rednose -s --with-profile tests \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a509a23 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +ShadowCraft-Engine +================== + +This repository contains the calculations engine behind the ShadowCraft +theorycrafting webapp for the Rogue class in World of Warcraft. For the +web application including the UI see +[shadowcraft-ui](https://github.com/cheald/shadowcraft-ui). + +ShadowCraft-Engine is written in Python and supports both Python 2 and +3. The calculation modules can be found in shadowcraft/calcs. Objects +used for those calculations are defined in shadowcraft/objects. + + +How To +------ + +In order to support both Python 2 and 3, ShadowCraft-Engine depends on +the *future* library. You can install it by running: + +``` +pip install future +``` + +To run a simple calculation for the rogue spec of your choice you can +look at the examples in the scripts folder. Feel free to play around +and edit those files as you see fit, when testing. E.g. to run a DPS +calculation for Subtlety, type: + +``` +python scripts/subtlety.py +``` + + +Tests +----- + +Although the tests currently do not provide good number testing for the +different specialization models, they can be used to ensure that nothing +major is broken. Run the tests using the following command: + +``` +python tests/runtests.py +``` + +Of course, we appreciate any help in extending the test coverage for the +engine. + + +Contributing +------------ + +The ShadowCraft team is always looking for help. If you would like to +contribute to the engine, you can always contact the active developers +or open a pull request on GitHub. There is also a #shadowcraft channel +on the [Ravenholdt Discord Server](https://discord.gg/DdPahJ9) that can +be used for discussion about the project. + +Before writing code and submitting for review, please have a look at the +code style guidelines in style.md. diff --git a/style.md b/style.md new file mode 100644 index 0000000..0961544 --- /dev/null +++ b/style.md @@ -0,0 +1,84 @@ +Style Guideines for ShadowCraft-Engine +====================================== + +These are the code style guidelines that were defined by Aldriana when the +project started. Although, they may or may not have been heeded consistently +throughout the code base, try to keep them in mind when writing new code. + +1. Indents are 4 spaces. Tabs are strictly forbidden. + +2. Avoid trailing whitespace in all cases. And I do mean all cases. + +3. Line length: Try to keep comments to 80 characters. For general code I'm + not going to enforce a strict limit, but if you're going over 120 characters + or so you should think about whether there's a natural way to break it. If + there's not, that's fine, but if there is, that's better. + +4. List comprehensions, lambda functions, map(), reduce(), filter(), etc. are + all fine if they're simple and generally aid code clarity. If you're doing + some hairy nested thing, it's probably better to split it up. + +5. For binary operators (+, -, *, /, %, etc.) put a space around the operator: + + Correct: `a = 1 + 2 * 3` + + Wrong: `a=1+2*3` + + Exception: When assigning a default value for a function parameter, do not + use spaces: + + Correct: `def foo(bar=1):` + + Wrong: `def foo(bar = 1):` + +6. Imports: With the exception of importing something that's in `__init__`, + import the module, not the class. + + Correct: `from calcs import gylphs` + + Slightly Wrong: `import calcs.glyphs` + + Wrong: `from calcs.glyphs import Glyph` + + Very Wrong: `from calcs.glyph import *` + + Imports should also generally be done in alphabetical order. + +7. Try to keep module names distinct to the extent that it's possible to do so + and still have them make sense. It helps if you use descriptive module + names. + +8. Modules names should be lowercase_and_underscores. + +9. Function names should be lowercase_and_underscores. + +10. Class names should be CamelCase. + +11. If a module primarily consists of a single class definition, the module + name and the class name should match. + +12. Any string where there is even the slightest chance it will be shown to an + external user should use named introspection for variables. This is to + make translation, um, possible. + + Correct: `"%(character_name)s is level %(character_level)d" % {'character_name': name, 'character_level': level}` + + Wrong: `"%s is level %d" % (name, level)` + + Very Wrong: `name + ' is level ' + str(level)` + + To explain: in various languages the sentence syntax may require the + variables to be in a different order. Giving them good descriptive names + lets the translators properly rearrange them as needed to convey the proper + meaning. + +13. Comments are a good thing. If what you're doing isn't immediately obvious + from a quick readthrough, add a comment to explain it. Recommended practise + are comments without a space after the #. + +In general: please try to write code that's as readable and maintainable as +possible. You only write the code once, but it will be read many many times. +Hence it's worth spending an extra couple of minutes writing it if it saves the +readers even a few seconds in understanding it. As the saying goes: always +write code as though the person who has to maintain it is a dangerous +psychopath that knows where you live. diff --git a/style.txt b/style.txt deleted file mode 100644 index 5ffd676..0000000 --- a/style.txt +++ /dev/null @@ -1,63 +0,0 @@ -Style guideines for ShadowCraft-Engine - -While we're getting started I'm going to be somewhat lenient about these, as -its pretty important to get the framework roughed out so people can start -building off it; however, once the initial flurry settles down, I will start -enforcing these a bit more strenuously. - -0) Assume for the moment that we're using Python 2.6. If there's some 2.7 - feature that you feel would be a real asset for something you're doing, - let me know and we can discuss it. We are not using 3.x. -1) Indents are 4 spaces. Tabs are strictly forbidden. -2) Avoid trailing whitespace in all cases. And I do mean all cases. -3) Line length: Try to keep comments to 80 characters. For general code I'm - not going to enforce a strict limit, but if you're going over 120 characters - or so you should think about whether there's a natural way to break it. If - there's not, that's fine, but if there is, that's better. -4) List comprehensions, lambda functions, map(), reduce(), filter(), etc. are - all fine if they're simple and generally aid code clarity. If you're doing - some hairy nested thing, it's probably better to split it up. -5) For binary operators (+, -, *, /, %, etc.) put a space around the operator: - Correct: a = 1 + 2 * 3 - Wrong: a=1+2*3 - - Exception: When assigning a default value for a function parameter, do not - use spaces: - Correct: def foo(bar=1): - Wrong: def foo(bar = 1): -6) Imports: With the exception of importing something that's in __init__, - import the module, not the class. - Correct: from calcs import gylphs - Slightly Wrong: import calcs.glyphs - Wrong: from calcs.glyphs import Glyph - Very Wrong: from calcs.glyph import * - - Imports should also generally be done in alphabetical order. -7) Try to keep module names distinct to the extent that it's possible to do so - and still have them make sense. It helps if you use descriptive module - names -8) Modules names should be lowercase_and_underscores. -9) Function names should be lowercase_and_underscores. -10) Class names should be CamelCase. -11) If a module primarily consists of a single class definition, the module - name and the class name should match. -12) Any string where there is even the slightest chance it will be shown to an - external user should use named introspection for variables. This is to - make translation, um, possible. - Correct: "%(character_name)s is level %(character_level)d" % {'character_name': name, 'character_level': level} - Wrong: "%s is level %d" % (name, level) - Very Wrong: name + ' is level ' + str(level). - - To explain: in various languages the sentence syntax may require the - variables to be in a different order. Giving them good descriptive names - lets the translators properly rearrange them as needed to convey the proper - meaning. -13) Comments are a good thing. If what you're doing isn't immediately obvious - from a quick readthrough, add a comment to explain it. - -In general: please try to write code that's as readable and maintainable as -possible. You only write the code once, but it will be read many many times. -Hence its worth spending an extra couple of minutes writing it if it saves the -readers even a few seconds in understanding it. As the saying goes: always -write code as though the person who has to maintain it is a dangerous -psychopath that knows where you live. From f972b6585bad3bf084c5a20872c2c417ac17cb02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 14 Mar 2017 16:24:05 +0100 Subject: [PATCH 173/265] Implement Old War and Prolonged Power potions --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 ++++ shadowcraft/calcs/rogue/__init__.py | 17 +++++-- shadowcraft/objects/proc_data.py | 50 ++++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a9f50ec..30f1c2d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -223,6 +223,14 @@ def set_constants(self): getattr(self.stats.procs, 'draenic_agi_pot').icd = self.settings.duration if self.stats.procs.draenic_agi_prepot: getattr(self.stats.procs, 'draenic_agi_prepot').icd = self.settings.duration + if self.stats.procs.prolonged_power_pot: + self.stats.procs.prolonged_power_pot.icd = self.settings.duration + if self.stats.procs.prolonged_power_prepot: + self.stats.procs.prolonged_power_prepot.icd = self.settings.duration + if self.stats.procs.old_war_pot: + self.stats.procs.old_war_pot.icd = self.settings.duration + if self.stats.procs.old_war_prepot: + self.stats.procs.old_war_prepot.icd = self.settings.duration self.relentless_strikes_energy_return_per_cp = 5 #.20 * 25 @@ -543,6 +551,7 @@ def get_average_alacrity(self, attacks_per_second): def determine_stats(self, attack_counts_function): current_stats = { 'str': self.base_stats['str'] * self.stat_multipliers['str'], + 'int': self.base_stats['int'] * self.stat_multipliers['int'], 'agi': self.base_stats['agi'] * self.stat_multipliers['agi'], 'ap': self.base_stats['ap'] * self.stat_multipliers['ap'], 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], @@ -655,6 +664,7 @@ def determine_stats(self, attack_counts_function): while (need_converge or self.spec_needs_converge): current_stats = { 'str': self.base_stats['str'] * self.stat_multipliers['str'], + 'int': self.base_stats['int'] * self.stat_multipliers['int'], 'agi': self.base_stats['agi'] * self.stat_multipliers['agi'], 'ap': self.base_stats['ap'] * self.stat_multipliers['ap'], 'crit': self.base_stats['crit'] * self.stat_multipliers['crit'], diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 15c0193..a1eaa26 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -589,10 +589,17 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates damage_per_use = self.get_proc_damage_contribution(oozeling, stacks_per_use, current_stats, ap, modifier_dict) damage_breakdown[oozeling.proc_name] = damage_per_use / oozeling.icd - # Tirathon's Betrayal and Faulty Countermeasure - for proc in [self.stats.procs.tirathons_betrayal, self.stats.procs.faulty_countermeasure]: + # Potions: Potion of the Old War + # Trinkets: Tirathon's Betrayal and Faulty Countermeasure + for proc in [self.stats.procs.old_war_pot, self.stats.procs.old_war_prepot, + self.stats.procs.tirathons_betrayal, self.stats.procs.faulty_countermeasure]: if proc: - # both 20 RPPM with haste mod - procs_per_use = proc.duration * 20 * 1.1307 * self.get_haste_multiplier(current_stats) / 60 + # all 20 RPPM with haste mod + rppm = 20 + haste_mod = self.get_haste_multiplier(current_stats) if proc.haste_scales else 1 + procs_per_use = proc.duration * rppm * 1.1307 * haste_mod / 60 damage_per_use = self.get_proc_damage_contribution(proc, procs_per_use, current_stats, ap, modifier_dict) - damage_breakdown[proc.proc_name] = damage_per_use / proc.icd + if proc.proc_name in damage_breakdown: + damage_breakdown[proc.proc_name] += damage_per_use / proc.icd + else: + damage_breakdown[proc.proc_name] = damage_per_use / proc.icd diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 00b977f..ea8b3c9 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -17,6 +17,56 @@ 'trigger': 'all_attacks' }, #potions + 'old_war_pot': { + 'stat': 'special_model', #rppm, modeled in add_special_procs_damage + 'value': 169900, #level 110 assumed for simplicity + 'dmg_school': 'physical', + 'duration': 25, + 'proc_name': 'Potion of the Old War', + 'type': 'icd', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1.0, + 'can_crit': True, + 'haste_scales': True, + 'trigger': 'all_attacks' + }, + 'old_war_prepot': { + 'stat': 'special_model', #rppm, modeled in add_special_procs_damage + 'value': 169900, #level 110 assumed for simplicity + 'dmg_school': 'physical', + 'duration': 25, + 'proc_name': 'Potion of the Old War', + 'type': 'icd', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1.0, + 'can_crit': True, + 'haste_scales': True, + 'trigger': 'all_attacks' + }, + 'prolonged_power_pot': { + 'stat': 'stats', + 'value': {'agi': 2500, 'str': 2500, 'int': 2500}, + 'duration': 60, + 'proc_name': 'Potion of Prolonged Power', + 'type': 'icd', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, + 'prolonged_power_prepot': { + 'stat': 'stats', + 'value': {'agi': 2500, 'str': 2500, 'int': 2500}, + 'duration': 60, + 'proc_name': 'Potion of Prolonged Power', + 'type': 'icd', + 'source': 'unique', + 'icd': 0, + 'proc_rate': 1.0, + 'trigger': 'all_attacks' + }, 'draenic_agi_pot': { 'stat': 'stats', 'value': {'agi':1000}, From 3edd3ede182ee55a933b419d48afec500d68bdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 14 Mar 2017 16:53:18 +0100 Subject: [PATCH 174/265] Minor Shadow Techniques change --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 30f1c2d..a199a3a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2032,8 +2032,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance attacks_per_second['oh_autoattack_hits'] = attacks_per_second['oh_autoattacks'] * self.dw_oh_hit_chance + # Shadow Techniques have a 50% chance to proc on fourth autohit and are guaranteed on fifth shadow_techniques_cps_per_proc = 1 + (0.05 * self.traits.fortunes_bite) - shadow_techniques_procs = self.settings.duration * (attacks_per_second['mh_autoattack_hits'] + attacks_per_second['oh_autoattack_hits']) / 4 + shadow_techniques_procs = self.settings.duration * (attacks_per_second['mh_autoattack_hits'] + attacks_per_second['oh_autoattack_hits']) / 4.5 shadow_techniques_cps = shadow_techniques_procs * shadow_techniques_cps_per_proc self.cp_budget += shadow_techniques_cps From 668e34cd6cc3ef2369f7f05b8d2b2527ebf73d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 16 Mar 2017 22:41:48 +0100 Subject: [PATCH 175/265] Fix Rocket Barrage for Goblins --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a199a3a..64f5018 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -501,7 +501,7 @@ def get_shadow_focus_multiplier(self): def setup_unique_procs(self, current_stats, average_ap): if self.stats.procs.rocket_barrage: - getattr(self.stats.procs, 'rocket_barrage').value = 0.42900 * self.base_intellect + .5 * average_ap + 1 + self.level * 2 #need to update + getattr(self.stats.procs, 'rocket_barrage').value = 0.42900 * current_stats['int'] + .5 * average_ap + 1 + self.level * 2 #need to update if self.stats.procs.touch_of_the_grave: getattr(self.stats.procs, 'touch_of_the_grave').value = 8 * self.tools.get_constant_scaling_point(self.level) # +/- 15% spread From 05d21d35c38d78497d9b212be4d5c82bee04c69b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 17 Mar 2017 13:41:38 +0100 Subject: [PATCH 176/265] Denial of the Half-Giants --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 35 +++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 64f5018..bc23004 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2006,7 +2006,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.goremaws_bite: goremaws_bite_cd = self.get_spell_cd('goremaws_bite') + self.settings.response_time attacks_per_second['goremaws_bite'] = 1 / goremaws_bite_cd - self.cp_budget += 3 * (self.settings.duration / goremaws_bite_cd) + self.cp_budget += (3 + self.shadow_blades_uptime) * (self.settings.duration / goremaws_bite_cd) self.energy_budget += 30 * (self.settings.duration / goremaws_bite_cd) if self.talents.death_from_above: @@ -2100,6 +2100,31 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second[self.cp_builder] = extra_builders / self.settings.duration attacks_per_second['eviscerate'][self.settings.finisher_threshold] += extra_evis + #Calculate Shadow Blades extension so far + if self.stats.gear_buffs.denial_of_the_half_giants: + sb_uptime = self.shadow_blades_uptime + sb_extension_converged = False + sb_extension = 0 + counter = 0 + while not sb_extension_converged: + if counter > 50: + raise ConvergenceErrorException(_('Denial Shadow Blades extension failed to converge.')) + finisher_cps = 0 + for i in range(0, 7): + finisher_cps += attacks_per_second['eviscerate'][i] * i + finisher_cps += attacks_per_second['nightblade'][i] * i + new_sb_extension = finisher_cps * sb_uptime * 0.3 + sb_extension_converged = (new_sb_extension - sb_extension) < 10 ** -5 + sb_uptime = self.shadow_blades_uptime + new_sb_extension + sb_extension = new_sb_extension + counter += 1 + #Account for extra cps + generators = ['shadowstrike', 'shuriken_storm', 'backstab', 'goremaws_bite', 'gloomblade', 'shuriken_toss'] + denial_extra_cps = sb_extension * sum((attacks_per_second[gen] if gen in attacks_per_second else 0) for gen in generators) + self.cp_budget += denial_extra_cps * self.settings.duration + self.shadow_blades_uptime = sb_uptime + cp_per_builder += sb_extension + #Hopefully energy budget here isn't negative, if it is we're in trouble #Now we convert all the energy we have left into mini-cycles #Each mini-cycle contains enough 1 dance and generators+finishers for one dance @@ -2144,6 +2169,14 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): new_alacrity_regen = self.energy_regen * (1 + (new_alacrity_stacks *0.02)) self.energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks + #Update Shadow Blades extension from Denial + if self.stats.gear_buffs.denial_of_the_half_giants: + new_sb_extension = mini_cycle_count * finishers_per_minicycle * self.settings.finisher_threshold * self.shadow_blades_uptime * 0.3 / self.settings.duration + generators = ['shadowstrike', 'shuriken_storm', 'backstab', 'goremaws_bite', 'gloomblade', 'shuriken_toss'] + denial_extra_cps = new_sb_extension * sum((attacks_per_second[gen] if gen in attacks_per_second else 0) for gen in generators) + self.shadow_blades_uptime += new_sb_extension + self.cp_budget += denial_extra_cps * self.settings.duration + cp_per_builder += new_sb_extension #Now fixup attacks_per_second #convert nightblade casts into nightblade ticks From 5185644989dff7b3c38a49d8aba4e72670e89c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 17 Mar 2017 14:08:50 +0100 Subject: [PATCH 177/265] Fix CPs from Shadow Blades during Dance --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index bc23004..fa581c6 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2311,11 +2311,11 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): net_energy -= attack_counts[cp_builder] * cp_builder_cost if cp_builder == 'shadowstrike': - net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation) + self.shadow_blades_uptime + net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation + self.shadow_blades_uptime) if self.stats.gear_buffs.rogue_t19_4pc: net_cps += attack_counts['shadowstrike'] * 0.3 elif cp_builder == 'shuriken_storm': - net_cps += min(1 + self.settings.num_boss_adds, self.max_store_cps) + self.shadow_blades_uptime + net_cps += min(1 + self.settings.num_boss_adds + self.shadow_blades_uptime, self.max_store_cps) return net_energy, net_cps, spent_cps, attack_counts From 235c28d09ad7782ca5117b1f8473c4a0ae7ed05a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 18 Mar 2017 01:35:02 +0100 Subject: [PATCH 178/265] Duskwalker's Footpads Includes assassination model fixes like modifier applications and Kingsbane dot increase --- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 81 ++++++++++++-------- shadowcraft/calcs/rogue/__init__.py | 2 +- shadowcraft/objects/stats.py | 2 +- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 4ef4cf6..6d34d86 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -700,4 +700,4 @@ def target_armor(self, armor=None): # Passes base armor reduced by armor debuffs or overridden armor if armor is None: armor = self.target_base_armor - return armor #* self.buffs.armor_reduction_multiplier() \ No newline at end of file + return armor #* self.buffs.armor_reduction_multiplier() diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index fa581c6..203793b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -786,18 +786,11 @@ def compute_symbiote_strike_damage(self, damage_breakdown): #T2:SF #Artifact: - # 'poison_knives', - # 'bag_of_tricks', - # 'from_the_shadows', + # 'poison_knives' #Items: - #Class hall set bonus - #Tier bonus - #Trinkets #Legendaries - #Rotation details: - def assassination_dps_estimate(self): return sum(self.assassination_dps_breakdown().values()) @@ -810,9 +803,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers = modifiers.ModifierList(self.assassination_damage_sources + ['autoattacks']) self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', - 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks', 't19_2pc'], dmg_schools=['physical'])) + 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', - 'deadly_instant_poison', 'envenom', 'poison_bomb'])) + 'deadly_instant_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, ['death_from_above_pulse', 'death_from_above_strike', @@ -820,7 +813,7 @@ def assassination_dps_breakdown(self): 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'])) #time averaged vendetta modifier used for most things - self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, [], all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['rupture_ticks', 'kingsbane', 'kingsbane_ticks'], blacklist=True, all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) @@ -836,6 +829,8 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) #trait specific modifiers + if self.traits.kingsbane: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('kingsbane_tick_increase', None, ['kingsbane_ticks'])) if self.traits.blood_of_the_assassinated: self.damage_modifiers.register_modifier(modifiers.DamageModifier('blood_of_the_assassinated', None, ['rupture_ticks'])) if self.traits.surge_of_toxins: @@ -853,39 +848,38 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.zoldyck_family_training_shackles: #Assume spend 30% of the time sub 30% health, imperfect but good enough self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', - 'garrote_ticks', 'kingsbane_ticks', 'rupture_ticks'], dmg_schools=['poison', 'bleed'])) + 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb', 't19_2pc'], dmg_schools=['poison', 'bleed'])) + + #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.2, ['envenom'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.3, ['envenom'])) self.set_constants() - self.vendetta_cd = self.get_spell_cd('vendetta') - self.vendetta_multiplier = 0.3 * (20 / self.vendetta_cd) + stats, aps, crits, procs, additional_info = self.determine_stats(self.assassination_attack_counts) - #cd stacking handlers - if self.settings.cycle.kingsbane_with_vendetta == 'only': - self.kingsbane_cd = min(self.vendetta_cd, self.get_spell_cd('kingsbane')) + self.vendetta_multiplier = 0.3 * (20 / self.vendetta_cd) + if self.settings.cycle.kingsbane_with_vendetta == 'just': + kb_venn_uptime = min(1, self.kingsbane_cd / self.vendetta_cd) + else: kb_venn_uptime = 1.0 + if self.settings.cycle.exsang_with_vendetta == 'just': + exsang_venn_uptime = min(1, self.exsang_cd / self.vendetta_cd) else: - self.kingsbane_cd = self.get_spell_cd('kingsbane') - kb_venn_uptime = self.kingsbane_cd / self.vendetta_cd - - if self.settings.cycle.exsang_with_vendetta == 'only': - self.exsang_cd = min(self.vendetta_cd), self.get_spell_cd('exsanguinate') exsang_venn_uptime = 1.0 - else: - self.exsang_cd = self.get_spell_cd('exsanguinate') - exsang_venn_uptime = self.exsang_cd / self.vendetta_cd - self.damage_modifiers.update_modifier_value('vendetta_time_average', 1 + self.vendetta_multiplier) self.damage_modifiers.update_modifier_value('vendetta_exsang', 1 + (self.vendetta_multiplier * exsang_venn_uptime)) self.damage_modifiers.update_modifier_value('vendetta_kb', 1 + (self.vendetta_multiplier * kb_venn_uptime)) - stats, aps, crits, procs, additional_info = self.determine_stats(self.assassination_attack_counts) - self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) self.damage_modifiers.update_modifier_value('potent_poisons', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) + #Lethal poison applications increase kingsbane damage by 15% each, KB ticks 7 times every 2 sec + if self.traits.kingsbane: + applications_per_tick = 2 * (aps['agonizing_poison'] if self.talents.agonizing_poison else aps['deadly_instant_poison']) + average_kb_stacks = (applications_per_tick + applications_per_tick * 7) / 2 + self.damage_modifiers.update_modifier_value('kingsbane_tick_increase', 1 + (average_kb_stacks * 0.15)) + if self.traits.blood_of_the_assassinated: bota_uptime = 0.35 * sum(aps['rupture']) * 10 # procs/ability * ability/second * seconds/proc gives unit-less uptime bota_multiplier = 1 + 2 * bota_uptime @@ -957,6 +951,18 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) + #Vendetta cd, modified by Duskwalker Legendary, used for damage modifier + self.vendetta_cd = self.get_spell_cd('vendetta') + + #cd stacking handlers, FIXME: in 'only' mode, we don't respect vendetta cdr with duskwalkers atm + self.kingsbane_cd = self.get_spell_cd('kingsbane') + if self.settings.cycle.kingsbane_with_vendetta == 'only': + self.kingsbane_cd = max(self.vendetta_cd, self.get_spell_cd('kingsbane')) + + self.exsang_cd = self.get_spell_cd('exsanguinate') + if self.settings.cycle.exsang_with_vendetta == 'only': + self.exsang_cd = max(self.vendetta_cd, self.get_spell_cd('exsanguinate')) + # set up our finisher distributions #unlike outlaw these depend on gear (crit) so they cannot be precomputed self.cp_builder = self.settings.cycle.cp_builder @@ -1058,7 +1064,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): vw_regen_per_second = vw_energy_per_tick * (sum(attacks_per_second['rupture_ticks']) + attacks_per_second['garrote_ticks']) net_energy_per_second = energy_regen + vw_regen_per_second - net_energy_per_second -= rupture_cost_per_second - garrote_cost_per_second + net_energy_per_second -= rupture_cost_per_second + garrote_cost_per_second + duskwalker_expended_energy = rupture_cost_per_second + garrote_cost_per_second #compute cooldowned talents: mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) @@ -1069,6 +1076,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['fan_of_knives'] = fok_interval cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) net_energy_per_second -= fok_interval * 35 + duskwalker_expended_energy += fok_interval * 35 if self.traits.kingsbane: attacks_per_second['kingsbane'] = 1 / self.kingsbane_cd @@ -1080,6 +1088,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): avg_cp_per_kb = sum([cp * cpg_cps[cp] for cp in cpg_cps]) cp_budget += avg_cp_per_kb * attacks_per_second['kingsbane'] * self.settings.duration net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] + duskwalker_expended_energy += self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] if self.talents.hemorrhage: hemos_per_second = 1 / 20 @@ -1087,6 +1096,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) cp_budget += hemo_cps net_energy_per_second -= self.get_spell_cost('hemorrhage') * hemos_per_second + duskwalker_expended_energy += self.get_spell_cost('hemorrhage') * hemos_per_second if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time @@ -1100,8 +1110,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): dfa_cost_per_second = self.get_spell_cost('death_from_above') * dfa_per_second dfa_cost_per_second += cp_builder_energy_per_finisher * dfa_per_second net_energy_per_second -= dfa_cost_per_second + duskwalker_expended_energy += dfa_cost_per_second #form whats left into a budget + duskwalker_expended_energy *= self.settings.duration energy_budget = self.settings.duration * net_energy_per_second max_energy = 120 if self.talents.vigor: @@ -1116,6 +1128,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if cp_budget > 0: extra_envenom = cp_budget / avg_finisher_size energy_budget -= self.get_spell_cost('envenom') * extra_envenom + duskwalker_expended_energy += self.get_spell_cost('envenom') * extra_envenom extra_envenom_per_second = extra_envenom / self.settings.duration for cp in range(7): attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] @@ -1136,6 +1149,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): for cp in range(7): attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second energy_budget -= total_minicycles * mini_cycle_energy + duskwalker_expended_energy += total_minicycles * mini_cycle_energy if self.talents.alacrity: old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.02)) @@ -1151,6 +1165,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (1 / sum(attacks_per_second['envenom'])) / 60 attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) + if self.stats.gear_buffs.duskwalkers_footpads: + self.vendetta_cd /= 1 + (duskwalker_expended_energy / 65) / self.settings.duration + if self.traits.from_the_shadows: attacks_per_second['from_the_shadows'] = 1 / self.vendetta_cd @@ -1182,9 +1199,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): # 'blurred_time', #Items: - #Class hall set bonus #Tier bonus - #Trinkets #Legendaries #Rotation details: @@ -1763,8 +1778,6 @@ def get_max_energy(self): # 'flickering_shadows', #Items: - #Class hall set bonus - #Trinkets #Legendaries #Rotation details: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index a1eaa26..120d21d 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -376,7 +376,7 @@ def poisoned_knife_damage(self, ap): #Lumping 6 ticks together for simplicity def poison_bomb_damage(self, ap): - return 6 * 1.2 * ap + return 6 * 1.2 * ap * (1 + (0.3 * self.talents.master_poisoner)) def rupture_tick_damage(self, ap, cp): return 1.5 * ap * (1 + (0.0333 * self.traits.gushing_wounds)) diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 94cec68..2de693f 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -214,7 +214,7 @@ class GearBuffs(object): 'rogue_orderhall_8pc', # Your finishing moves have a chance to increase your Haste by 2000 for 12 sec. #Legendaries 'the_dreadlords_deceit', #fok/ssk damage increased by 35% per 2 seconds up to 1 minute - 'duskwalker_footpads', #Vendetta CD reduced by 1 second for each 50 energy spent + 'duskwalkers_footpads', #Vendetta CD reduced by 1 second for each 65 energy spent 'thraxis_tricksy_treads', # 'shadow_satyrs_walk', #3+1/3yd energy refund on ssk 'insignia_of_ravenholdt', #15% damage as shadow on cp generators From d44ccc266ca17737e8d518ee75ddb695cc48f3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 19 Mar 2017 13:05:50 +0100 Subject: [PATCH 179/265] Mantle of the Master Assassin --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 43 +++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 203793b..d2d17c1 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -268,6 +268,8 @@ def get_proc_damage_contribution(self, proc, proc_count, current_stats, average_ if proc.can_crit == False: crit_rate = 0 + elif self.stats.gear_buffs.mantle_of_the_master_assassin: + crit_rate = min(crit_rate * (1. - self.mantle_uptime) + self.mantle_uptime, 1) proc_value = proc.value #280+75% AP @@ -720,6 +722,20 @@ def determine_stats(self, attack_counts_function): for proc in weapon_damage_procs: self.set_uptime(proc, attacks_per_second, crit_rates) + + #Mantle of the Master Assassin Legendary + if self.stats.gear_buffs.mantle_of_the_master_assassin: + mantle_triggers = 1 #Opener + if attacks_per_second['vanish']: + mantle_triggers += attacks_per_second['vanish'] * self.settings.duration + mantle_seconds = mantle_triggers * 6 + #Note: As of 7.1 subterfuge keeps the buff, this is not true on 7.2 PTR (2018/03/18) + if self.spec in ['assassination', 'subtlety'] and self.talents.subterfuge: + mantle_seconds += mantle_triggers * 3 + self.mantle_uptime = mantle_seconds / self.settings.duration + for attack in crit_rates: + crit_rates[attack] = min(crit_rates[attack] * (1. - self.mantle_uptime) + self.mantle_uptime, 1) + return current_stats, attacks_per_second, crit_rates, damage_procs, additional_info def add_special_aps_penalties(self, attacks_per_second): @@ -788,9 +804,6 @@ def compute_symbiote_strike_damage(self, damage_breakdown): #Artifact: # 'poison_knives' - #Items: - #Legendaries - def assassination_dps_estimate(self): return sum(self.assassination_dps_breakdown().values()) @@ -963,6 +976,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.settings.cycle.exsang_with_vendetta == 'only': self.exsang_cd = max(self.vendetta_cd, self.get_spell_cd('exsanguinate')) + #Vanish on cooldown + attacks_per_second['vanish'] = 1 / self.get_spell_cd('vanish') + # set up our finisher distributions #unlike outlaw these depend on gear (crit) so they cannot be precomputed self.cp_builder = self.settings.cycle.cp_builder @@ -1158,7 +1174,12 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration alacrity_stacks = new_alacrity_stacks - attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01))) / self.stats.mh.speed + #swing timer + white_swing_downtime = 0 + self.swing_reset_spacing = self.get_spell_cd('vanish') + if self.swing_reset_spacing is not None: + white_swing_downtime += self.settings.response_time / self.swing_reset_spacing + attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01))) / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] if self.traits.bag_of_tricks: @@ -1367,6 +1388,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer self.white_swing_downtime = self.settings.response_time / self.get_spell_cd('vanish') + # Compute dps phases each non-rerolling RtB buff combo AR and not phases = {} ar_phases = {} @@ -1511,6 +1533,10 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): #old_ar_cd = new_ar_cd ar_uptime = self.ar_duration / ar_cd + + #Vanish on cooldown + attacks_per_second['vanish'] = 1 / self.get_spell_cd('vanish') + # Add in Cannonball and Killing Spree if self.talents.killing_spree: ksp_cd = self.get_spell_cd('killing_spree') / (1. + tb_seconds_per_second) @@ -1525,7 +1551,7 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) - swing_timer = self.stats.mh.speed / attack_speed_multiplier + swing_timer = self.stats.mh.speed / (attack_speed_multiplier * (1 - self.white_swing_downtime)) attacks_per_second['mh_autoattacks'] = 1 / swing_timer attacks_per_second['oh_autoattacks'] = 1 / swing_timer attacks_per_second['main_gauche'] = self.main_gauche_proc_rate * attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() @@ -1775,10 +1801,7 @@ def get_max_energy(self): #Legion TODO: #Artifact: - # 'flickering_shadows', - - #Items: - #Legendaries + # 'flickering_shadows' #Rotation details: #Combo Point loss @@ -1946,7 +1969,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): white_swing_downtime = 0 self.swing_reset_spacing = self.get_spell_cd('vanish') if self.swing_reset_spacing is not None: - white_swing_downtime += .5 / self.swing_reset_spacing + white_swing_downtime += self.settings.response_time / self.swing_reset_spacing attacks_per_second['mh_autoattacks'] = haste_multiplier / self.stats.mh.speed * (1 - white_swing_downtime) attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) From e3cd4b649c593b28b4827309e4db2d2b271df64b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 19 Mar 2017 14:52:18 +0100 Subject: [PATCH 180/265] Extended stealth mantle opener --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d2d17c1..399af4d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -732,6 +732,9 @@ def determine_stats(self, attack_counts_function): #Note: As of 7.1 subterfuge keeps the buff, this is not true on 7.2 PTR (2018/03/18) if self.spec in ['assassination', 'subtlety'] and self.talents.subterfuge: mantle_seconds += mantle_triggers * 3 + #Assume extended stealth bug opener for subtlety (shadow dance -> stealth, stealth wears off after dance) + if self.spec == 'subtlety': + mantle_seconds += 5 if self.talents.subterfuge else 3 #One full dance mantle uptime self.mantle_uptime = mantle_seconds / self.settings.duration for attack in crit_rates: crit_rates[attack] = min(crit_rates[attack] * (1. - self.mantle_uptime) + self.mantle_uptime, 1) From 81b87dc207086d84e400e1937fbea5b0f5c7c417 Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Sun, 19 Mar 2017 19:59:33 -0700 Subject: [PATCH 181/265] Restore setup.py --- setup.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2cecae1 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +from distutils.core import setup + +setup( + name='ShadowCraft-Engine', + url='http://github.com/ShadowCraft/ShadowCraft-Engine/', + version='7.1.5', + packages=[ + 'shadowcraft', + 'shadowcraft.calcs', + 'shadowcraft.calcs.rogue', + 'shadowcraft.calcs.rogue.Aldriana', + 'shadowcraft.core', + 'shadowcraft.objects' + ], + license='LGPL', + long_description=open('README').read(), +) From 33ba8c06dfc5af584aa98a0b25205149cc198c41 Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Sun, 19 Mar 2017 20:00:27 -0700 Subject: [PATCH 182/265] Setup.py readme fix --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2cecae1..27f0ea7 100644 --- a/setup.py +++ b/setup.py @@ -13,5 +13,5 @@ 'shadowcraft.objects' ], license='LGPL', - long_description=open('README').read(), + long_description=open('README.md').read(), ) From 642f3eaceaa323206381b13ef45fa4057b8b3238 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Mar 2017 14:29:42 +0100 Subject: [PATCH 183/265] Add new trait data and implement Uncrowned damage trait --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 9 ++++++ shadowcraft/objects/artifact_data.py | 31 ++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 399af4d..2fb9595 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -857,6 +857,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', 1.05 + (0.005 * (self.traits.slayers_precision - 1)), [], all_damage=True)) + if self.traits.silence_of_the_uncrowned: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('silence_of_the_uncrowned', 1.1, [], all_damage=True)) + #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['fan_of_knives'])) @@ -1332,6 +1335,9 @@ def outlaw_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('cursed_steel', 1.05 + (0.005 * (self.traits.legionblade - 1)), [], all_damage=True)) + if self.traits.bravado_of_the_uncrowned: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('bravado_of_the_uncrowned', 1.1, [], all_damage=True)) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) @@ -1881,6 +1887,9 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', 1.05 + (0.005 * (self.traits.legionblade - 1)), [], all_damage=True)) + if self.traits.shadows_of_the_uncrowned: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadows_of_the_uncrowned', 1.1, [], all_damage=True)) + #gear specific modifiers if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['shuriken_storm'])) diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 1bc714f..7abe7ae 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -18,6 +18,11 @@ 'from_the_shadows', 'blood_of_the_assassinated', 'slayers_precision', + 'silence_of_the_uncrowned', + 'strangler', + 'dense_concoction', + 'sinister_circulation', + 'concordance_of_the_legionfall', ), ('rogue', 'outlaw'): ( 'curse_of_the_dreadblades', @@ -38,6 +43,11 @@ 'blademaster', 'blunderbuss', 'cursed_steel', + 'bravado_of_the_uncrowned', + 'sabermetrics', + 'dreadblades_vigor', + 'loaded_dice', + 'concordance_of_the_legionfall', ), ('rogue', 'subtlety'): ( 'goremaws_bite', @@ -57,7 +67,12 @@ 'akarris_soul', 'soul_shadows', 'shadow_nova', - 'legionblade' + 'legionblade', + 'shadows_of_the_uncrowned', + 'etched_in_shadow', + 'shadows_whisper', + 'feeding_frenzy', + 'concordance_of_the_legionfall', ), } @@ -73,6 +88,10 @@ 'bag_of_tricks', 'from_the_shadows', 'blood_of_the_assassinated', + 'slayers_precision', + 'silence_of_the_uncrowned', + 'dense_concoction', + 'sinister_circulation', ), ('rogue', 'outlaw'): ( 'curse_of_the_dreadblades', @@ -83,6 +102,10 @@ 'blurred_time', 'blademaster', 'blunderbuss', + 'cursed_steel', + 'bravado_of_the_uncrowned', + 'dreadblades_vigor', + 'loaded_dice', ), ('rogue', 'subtlety'): ( 'goremaws_bite', @@ -93,5 +116,9 @@ 'finality', 'akarris_soul', 'shadow_nova', + 'legionblade', + 'shadows_of_the_uncrowned', + 'shadows_whisper', + 'feeding_frenzy', ), -} \ No newline at end of file +} From 5af7a14f86369f107873f379b7c33061d208a488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Mar 2017 14:34:20 +0100 Subject: [PATCH 184/265] New Urge to Kill --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2fb9595..9d472a1 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1141,9 +1141,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): if self.talents.vigor: max_energy += 50 energy_budget += max_energy - #assume you get 50% of max energy back each time + #As of Patch 7.2 we get 60 energy + 60 over 2s, assume no loss if self.traits.urge_to_kill: - energy_budget += (self.settings.duration / self.vendetta_cd) * 0.5 * max_energy + energy_budget += (self.settings.duration / self.vendetta_cd) * 120 attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] #spend those extra cps From a1fec632a09ac651af852130598b761204f5998b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Mar 2017 14:35:58 +0100 Subject: [PATCH 185/265] No more subterfuge mantle uptime --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9d472a1..e9c157e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -729,9 +729,6 @@ def determine_stats(self, attack_counts_function): if attacks_per_second['vanish']: mantle_triggers += attacks_per_second['vanish'] * self.settings.duration mantle_seconds = mantle_triggers * 6 - #Note: As of 7.1 subterfuge keeps the buff, this is not true on 7.2 PTR (2018/03/18) - if self.spec in ['assassination', 'subtlety'] and self.talents.subterfuge: - mantle_seconds += mantle_triggers * 3 #Assume extended stealth bug opener for subtlety (shadow dance -> stealth, stealth wears off after dance) if self.spec == 'subtlety': mantle_seconds += 5 if self.talents.subterfuge else 3 #One full dance mantle uptime From 61b3f7fcbc6ea35469320b95fa0e96e71616c753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Mar 2017 14:49:15 +0100 Subject: [PATCH 186/265] Poison Bomb is no longer RPPM --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e9c157e..3bd4e4d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1186,8 +1186,11 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] if self.traits.bag_of_tricks: - bag_of_tricks_proc_chance = (haste_multiplier + (0.1 * alacrity_stacks)) * (1 / sum(attacks_per_second['envenom'])) / 60 - attacks_per_second['poison_bomb'] = bag_of_tricks_proc_chance * sum(attacks_per_second['envenom']) + #2.5% chance per cp on envenom and rupture + attacks_per_second['poison_bomb'] = 0 + for i in range(7): + attacks_per_second['poison_bomb'] += attacks_per_second['envenom'][i] * i * 0.025 + attacks_per_second['poison_bomb'] += attacks_per_second['rupture'][i] * i * 0.025 if self.stats.gear_buffs.duskwalkers_footpads: self.vendetta_cd /= 1 + (duskwalker_expended_energy / 65) / self.settings.duration From 74ee2420486227373c0eefbaaea95dfa01e88157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 23 Mar 2017 15:07:01 +0100 Subject: [PATCH 187/265] Concordance of the Legionfall --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++++ shadowcraft/objects/proc_data.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 3bd4e4d..c2ff5ef 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -593,6 +593,10 @@ def determine_stats(self, attack_counts_function): self.stats.procs.toe_knees_promise.update_proc_value() self.stats.procs.toe_knees_promise.value *= 1.3 + if self.traits.concordance_of_the_legionfall: + self.stats.procs.set_proc('concordance_of_the_legionfall') + self.stats.procs.concordance_of_the_legionfall.value['agi'] = 2000 + (self.traits.concordance_of_the_legionfall - 1) * 200 + #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): if (proc.stat == 'stats'): diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index ea8b3c9..3eca7d9 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -757,6 +757,18 @@ 'trigger': 'all_attacks' #should be only finishing moves, but since it's rppm that doesn't matter }, + 'concordance_of_the_legionfall': { + 'stat': 'stats', + 'value': {}, #set depending on traits in determine_stats + 'duration': 10, + 'proc_name': 'Concordance of the Legionfall', + 'type': 'rppm', + 'source': 'trait', + 'proc_rate': 1.37, + 'icd': 10, + 'trigger': 'all_attacks' + }, + #6.2.3 procs 'infallible_tracking_charm': { 'stat':'spell_damage', From 1d1fed15190b8210987f382f7ba885a4a320b4d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 13:47:40 +0100 Subject: [PATCH 188/265] Nerf Fatebringer --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c2ff5ef..af330d3 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1388,15 +1388,15 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): cost_reducer = self.main_gauche_proc_rate * self.combat_potency_from_mg # Compute Main Gauche lumped ability costs - self.run_through_energy_cost = self.get_spell_cost('run_through') - (3 * self.traits.fatebringer) - cost_reducer - self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (3 * self.traits.fatebringer) - cost_reducer - self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (3 * self.traits.fatebringer) - cost_reducer + self.run_through_energy_cost = self.get_spell_cost('run_through') - (1 * self.traits.fatebringer) - cost_reducer + self.between_the_eyes_energy_cost = self.get_spell_cost('between_the_eyes') - (1 * self.traits.fatebringer) - cost_reducer + self.pistol_shot_energy_cost = self.get_spell_cost('run_through') - (1 * self.traits.fatebringer) - cost_reducer self.saber_slash_energy_cost = self.get_spell_cost('saber_slash') - cost_reducer - self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (3 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) + self.death_from_above_energy_cost = max(0, self.get_spell_cost('death_from_above') - (1 * self.traits.fatebringer) - cost_reducer * (1 + self.settings.num_boss_adds)) if self.talents.slice_and_dice: - self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (3 * self.traits.fatebringer) + self.slice_and_dice_cost = self.get_spell_cost('slice_and_dice') - (1 * self.traits.fatebringer) else: - self.roll_the_bones_cost = self.get_spell_cost('roll_the_bones') - (3 * self.traits.fatebringer) + self.roll_the_bones_cost = self.get_spell_cost('roll_the_bones') - (1 * self.traits.fatebringer) if self.talents.ghostly_strike: self.ghostly_strike_cost = self.get_spell_cost('ghostly_strike') - cost_reducer From 6e3362d9f1114d92d07707555b0541b9dd464e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 15:34:17 +0100 Subject: [PATCH 189/265] Assumed probabilites for Loaded Dice rolls (NYI) --- shadowcraft/calcs/rogue/__init__.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 120d21d..0d16ce0 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -59,17 +59,29 @@ class RogueDamageCalculator(DamageCalculator): #probability of getting X buffs with rtb rtb_probabilities = { - 1: 0.5923, - 2: 0.3537, - 3: 0.0386, - 6: 0.0154, + 1: 0.5923, + 2: 0.3537, + 3: 0.0386, + 6: 0.0154, } + + #probabilities of getting X buffs from RtB with loaded dice + #assume we reroll/blacklist one buff rolls instead of adding a second buff to one rolls + #so far this assumption could neither be confirmed nor disproved + #TODO: actually plug these into the model :/ + rtb_loaded_dice_probabilities = { + 1: 0, + 2: 0.8675, + 3: 0.0946, + 6: 0.0379, + } + #number of unique rtb buffs of each amount rtb_buff_count = { - 1: 6, - 2: 15, - 3: 20, - 6: 1, + 1: 6, + 2: 15, + 3: 20, + 6: 1, } assassination_mastery_conversion = .04 From 8987f10eac965e7278f4d25fdd13b056d6676a16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 15:54:35 +0100 Subject: [PATCH 190/265] Update Slice and Dice modifiers (incl. Loaded Dice) --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index af330d3..edacb6a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -164,7 +164,7 @@ def get_haste_multiplier(self, current_stats): return self.stats.get_haste_multiplier_from_rating(current_stats['haste']) * self.true_haste_mod - def get_energy_regen(self, current_stats, buried=False, ar=False, alacrity_stacks=0): + def get_energy_regen(self, current_stats, buried=False, ar=False, alacrity_stacks=0, snd=False): regen = 10. if self.spec == "outlaw": regen = 12. @@ -174,6 +174,8 @@ def get_energy_regen(self, current_stats, buried=False, ar=False, alacrity_stack regen *= 1.25 if ar: regen *= 2.0 + if snd: + regen *= 1.195 if ar and self.traits.loaded_dice else 1.15 else: alacrity_stacks = 0 if self.talents.vigor: @@ -187,7 +189,7 @@ def get_attack_speed_multiplier(self, current_stats, snd=False, melee=False, ar= if melee: attack_speed_multiplier *= 1.5 elif snd: - attack_speed_multiplier *= 1.9 + attack_speed_multiplier *= 2.3 if ar and self.traits.loaded_dice else 2 if ar: attack_speed_multiplier *= 1.2 return attack_speed_multiplier @@ -1564,6 +1566,9 @@ def outlaw_attack_counts(self, current_stats, crit_rates=None): attack_speed_multiplier *= (1 + (0.2 * ar_uptime)) if not self.talents.slice_and_dice: attack_speed_multiplier *= (1 + (0.5 * gm_uptime)) + elif self.talents.slice_and_dice and self.traits.loaded_dice: + buffed_snd_uptime = (self.settings.finisher_threshold + 1) * 6 / self.ar_cd + attack_speed_multiplier *= 1 + (0.3 * buffed_snd_uptime) swing_timer = self.stats.mh.speed / (attack_speed_multiplier * (1 - self.white_swing_downtime)) attacks_per_second['mh_autoattacks'] = 1 / swing_timer attacks_per_second['oh_autoattacks'] = 1 / swing_timer @@ -1599,7 +1604,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll maintainence_buff = 'slice_and_dice' if snd else 'roll_the_bones' attack_speed_multiplier = self.get_attack_speed_multiplier(current_stats, snd, melee, ar) - energy_regen = self.get_energy_regen(current_stats, buried, ar) + energy_regen = self.get_energy_regen(current_stats, buried, ar, snd) gcd_size = 1.0 + self.settings.latency if ar: From ef0eaefc3e1493ba859d62fb1a507ae7105ae76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 16:28:47 +0100 Subject: [PATCH 191/265] [Outlaw] Sabermetrics --- shadowcraft/calcs/rogue/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 0d16ce0..77cd942 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -311,6 +311,8 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da #http://beta.askmrrobot.com/wow/simulator/docs/critdamage if ability == 'between_the_eyes': crit_mod = self.crit_damage_modifiers(3) + if ability == 'saber_slash' and self.traits.sabermetrics: + crit_mod = self.crit_damage_modifiers(1 + self.traits.sabermetrics * 0.05) damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) if self.spec == 'subtlety': From 4af06b77a2b4039782829a8f9a5fef7061df6c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 16:37:41 +0100 Subject: [PATCH 192/265] [Outlaw] Dreadblade's Vigor --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index edacb6a..4d55e42 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1344,10 +1344,16 @@ def outlaw_dps_breakdown(self): if self.traits.bravado_of_the_uncrowned: self.damage_modifiers.register_modifier(modifiers.DamageModifier('bravado_of_the_uncrowned', 1.1, [], all_damage=True)) + if self.traits.dreadblades_vigor: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dreadblades_vigor', None, [], all_damage=True)) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) + if self.traits.dreadblades_vigor: + self.damage_modifiers.update_modifier_value('dreadblades_vigor', 1 + (0.1 * 12 / self.cotd_cd)) + damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: From 514db10f0af1e098249a71ab5c4a2d534918e871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 16:50:24 +0100 Subject: [PATCH 193/265] [Subtlety] Etched in Shadows, Shadow's Whisper and Feeding Frenzy --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4d55e42..14be7d2 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1865,7 +1865,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2 + 0.01 * self.traits.etched_in_shadow, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) @@ -2071,6 +2071,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['goremaws_bite'] = 1 / goremaws_bite_cd self.cp_budget += (3 + self.shadow_blades_uptime) * (self.settings.duration / goremaws_bite_cd) self.energy_budget += 30 * (self.settings.duration / goremaws_bite_cd) + if self.traits.feeding_frenzy: + #assume we time it so we can get three free eviscerates + self.energy_budget += self.get_spell_cost('eviscerate') * (self.settings.duration / goremaws_bite_cd) if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time @@ -2100,6 +2103,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): shadow_techniques_procs = self.settings.duration * (attacks_per_second['mh_autoattack_hits'] + attacks_per_second['oh_autoattack_hits']) / 4.5 shadow_techniques_cps = shadow_techniques_procs * shadow_techniques_cps_per_proc self.cp_budget += shadow_techniques_cps + if self.traits.shadows_whisper: + self.energy_budget += 5 * shadow_techniques_procs #vanish handling vanish_count = self.settings.duration / self.get_spell_cd('vanish') From 379a7fdfdad88666206cc7a5fa4fe7c0e4c92515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 17:48:19 +0100 Subject: [PATCH 194/265] [Assassination] Strangler --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 +++-- shadowcraft/calcs/rogue/__init__.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 14be7d2..91f3390 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1228,8 +1228,9 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #T3:Anticipation #Artifact: - # 'hidden_blade', (ambush proc weirdness) - # 'blurred_time', + # 'hidden_blade' (ambush proc weirdness) + # 'blurred_time' + # 'loaded_dice' (for RtB) #Items: #Tier bonus diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 77cd942..8169ed1 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -368,7 +368,7 @@ def from_the_shadows_damage(self, ap): return 40 * 0.35 * ap def garrote_tick_damage(self, ap): - return .9 * ap + return .9 * ap * (1 + 0.04 * self.traits.strangler) def hemorrhage_damage(self, ap): return 1 * self.get_weapon_damage('mh', ap) From bc18db43b7c861584ba73149a23e279adae50e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Mar 2017 20:21:59 +0100 Subject: [PATCH 195/265] Update test scripts --- scripts/assassination.py | 85 ++++++++++++++++++++++---------------- scripts/outlaw.py | 88 +++++++++++++++++++++++++++++----------- scripts/subtlety.py | 31 +++++++++----- 3 files changed, 134 insertions(+), 70 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 67dc7b3..7fc67d5 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -25,46 +25,53 @@ # Set up level/class/race test_level = 110 -test_race = race.Race('none') +test_race = race.Race('blood_elf', 'rogue', 110) test_class = 'rogue' test_spec = 'assassination' # Set up buffs. test_buffs = buffs.Buffs( - 'short_term_haste_buff', - 'flask_wod_agi', - 'food_wod_versatility' - ) + 'short_term_haste_buff', + 'flask_legion_agi', + 'food_legion_mastery_375' +) # Set up weapons. -test_mh = stats.Weapon(4821.0, 1.8, 'dagger', None) -test_oh = stats.Weapon(4821.0, 1.8, 'dagger', None) +test_mh = stats.Weapon(7063.0, 1.8, 'dagger', None) +test_oh = stats.Weapon(7063.0, 1.8, 'dagger', None) # Set up procs. #test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), # 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') -test_procs = procs.ProcsList() +test_procs = procs.ProcsList('old_war_pot', 'old_war_prepot', + #'convergence_of_fates', + ('draught_of_souls', 910), + #('chaos_talisman', 890), + ('nightblooming_frond', 910), +) # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization', #'the_dreadlords_deceit', -#'rogue_t19_2pc', -#'rogue_t19_4pc', -#'zoldyck_family_training_shackles', +'rogue_t19_2pc', +'rogue_t19_4pc', +'zoldyck_family_training_shackles', +'mantle_of_the_master_assassin', +#'duskwalkers_footpads', #'cinidaria_the_symbiote', ) # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=25583, - stam=28368, - crit=7804, - haste=1521, - mastery=8230, - versatility=3085,) + agi=21964, + stam=32801, + crit=8749, + haste=1935, + mastery=9729, + versatility=4382,) # Initialize talents.. #test_talents = talents.Talents('2110031', test_spec, test_class, level=test_level) -test_talents = talents.Talents('3110011', test_spec, test_class, level=test_level) +test_talents = talents.Talents('1230011', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ @@ -73,36 +80,45 @@ 'toxic_blades': 3, 'poison_knives': 3, 'urge_to_kill': 1, - 'balanced_blades ': 2, + 'balanced_blades ': 3, 'surge_of_toxins': 1, - 'shadow_walker': 0, - 'master_assassin': 3+3, - 'shadow_swiftness': 0, + 'shadow_walker': 3, + 'master_assassin': 3+2, + 'shadow_swiftness': 1, 'serrated_edge': 3, 'bag_of_tricks': 1, 'master_alchemist': 3, - 'gushing_wounds': 3, - 'fade_into_shadows': 0, + 'gushing_wounds': 3+1, + 'fade_into_shadows': 3, 'from_the_shadows': 1, 'blood_of_the_assassinated': 1, - 'slayers_precision': 8, + 'slayers_precision': 1, + 'silence_of_the_uncrowned': 1, + 'strangler': 4, + 'dense_concoction': 0, + 'sinister_circulation': 0, + 'concordance_of_the_legionfall': 0, }) # Set up settings. -test_cycle = settings.AssassinationCycle() -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, - finisher_threshold=5) +test_cycle = settings.AssassinationCycle('just', 'just') +test_settings = settings.Settings(test_cycle, response_time=.5, duration=300, + finisher_threshold=4) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) +print(str(calculator.stats.get_character_stats(calculator.race))) + # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) # Compute EP values. -#ep_values = calculator.get_ep(baseline_dps=total_dps) -tier_ep_values = calculator.get_other_ep(['the_dreadlords_deceit', 'zoldyck_family_training_shackles']) +ep_values = calculator.get_ep(baseline_dps=total_dps) +tier_ep_values = calculator.get_other_ep(['rogue_t19_4pc', 'rogue_t19_2pc', +'duskwalkers_footpads', 'zoldyck_family_training_shackles', 'mantle_of_the_master_assassin' +]) #talent_ranks = calculator.get_talents_ranking() @@ -117,22 +133,21 @@ def max_length(dict_list): return max_len -def pretty_print(dict_list, total_sum = 1., show_percent=False): +def pretty_print(dict_list): max_len = max_length(dict_list) for i in dict_list: dict_values = list(i.items()) dict_values.sort(key=lambda entry: entry[1], reverse=True) for value in dict_values: - #print value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) - if show_percent and ("{0:.2f}".format(float(value[1]) / total_sum)) != '0.00': - print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_sum) )+'%)') + if ("{0:.2f}".format(value[1] / total_dps)) != '0.00': + print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1]) + ' ('+str( "{0:.2f}".format(100*float(value[1])/total_dps) )+'%)') else: print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) print('-' * (max_len + 15)) dicts_for_pretty_print = [ - #ep_values, + ep_values, tier_ep_values, #talent_ranks, #trinkets_ep_value, diff --git a/scripts/outlaw.py b/scripts/outlaw.py index 8b61f92..f250a1b 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -25,14 +25,16 @@ # Set up level/class/race test_level = 110 -test_race = race.Race('pandaren') +test_race = race.Race('pandaren', 'rogue', 110) test_class = 'rogue' test_spec = 'outlaw' # Set up buffs. -test_buffs = buffs.Buffs('short_term_haste_buff', - 'flask_wod_agi', - 'food_wod_versatility') +test_buffs = buffs.Buffs( + 'short_term_haste_buff', + 'flask_legion_agi', + 'food_legion_versatility_375' +) # Set up weapons. mark_of_the_frostwolf mark_of_the_shattered_hand test_mh = stats.Weapon(4821.0, 2.6, 'sword', None) @@ -42,50 +44,88 @@ #test_procs = procs.ProcsList(('assurance_of_consequence', 588), #('draenic_philosophers_stone', 620), 'virmens_bite', 'virmens_bite_prepot', #'archmages_incandescence') #trinkets, other things (legendary procs) -test_procs = procs.ProcsList() +test_procs = procs.ProcsList( + 'mark_of_the_hidden_satyr', + 'old_war_pot', + 'old_war_prepot', + ('nightblooming_frond', 895), + ('memento_of_angerboda', 885) +) # Set up gear buffs. -test_gear_buffs = stats.GearBuffs('gear_specialization') #tier buffs located here +test_gear_buffs = stats.GearBuffs( + 'gear_specialization', + 'rogue_t19_2pc', + 'rogue_t19_4pc', + 'mantle_of_the_master_assassin', + 'greenskins_waterlogged_wristcuffs' +) # Set up a calcs object.. test_stats = stats.Stats(test_mh, test_oh, test_procs, test_gear_buffs, - agi=21122, + agi=round(35872 * 0.95238 - test_race.racial_agi), stam=28367, - crit=6306, - haste=3260, - mastery=3706, - versatility=3486,) + crit=9070, + haste=2476, + mastery=6254, + versatility=5511,) # Initialize talents.. -test_talents = talents.Talents('1010022', test_spec, test_class, level=test_level) +test_talents = talents.Talents('3213122', test_spec, test_class, level=test_level) #initialize artifact traits.. -test_traits = artifact.Artifact(test_spec, test_class, '100000000000000000') +test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ + 'curse_of_the_dreadblades': 1, + 'cursed_edges': 1, + 'fates_thirst': 4, + 'blade_dancer': 3, + 'fatebringer': 4, + 'gunslinger': 3, + 'hidden_blade': 1, + 'fortune_strikes': 3, + 'ghostly_shell': 3, + 'deception': 1, + 'black_powder': 4, + 'greed': 1, + 'blurred_time': 1, + 'fortunes_boon': 3, + 'fortunes_strike': 3, + 'blademaster': 1, + 'blunderbuss': 1, + 'cursed_steel': 1, + 'bravado_of_the_uncrowned': 1, + 'sabermetrics': 0, + 'dreadblades_vigor': 0, + 'loaded_dice': 0, + 'concordance_of_the_legionfall': 0, +}) # Set up settings. test_cycle = settings.OutlawCycle(blade_flurry=False, - jolly_roger_reroll=1, - grand_melee_reroll=1, - shark_reroll=1, - true_bearing_reroll=1, - buried_treasure_reroll=1, - broadsides_reroll=1, + jolly_roger_reroll=2, + grand_melee_reroll=2, + shark_reroll=2, + true_bearing_reroll=0, + buried_treasure_reroll=2, + broadsides_reroll=2, between_the_eyes_policy='never' ) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=360, +test_settings = settings.Settings(test_cycle, response_time=.5, duration=300, adv_params="", is_demon=True, num_boss_adds=0, finisher_threshold=5) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) +print(str(test_stats.get_character_stats(test_race))) + # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) # Compute EP values. -#ep_values = calculator.get_ep(baseline_dps=total_dps) -#tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc']) +ep_values = calculator.get_ep(baseline_dps=total_dps) +tier_ep_values = calculator.get_other_ep(['rogue_t16_2pc', 'rogue_t16_4pc', 'mantle_of_the_master_assassin']) #mh_enchants_and_dps_ep_values, oh_enchants_and_dps_ep_values = #calculator.get_weapon_ep(dps=True, enchants=True) @@ -116,8 +156,8 @@ def pretty_print(dict_list, total_sum=1., show_percent=False): print(value[0] + ':' + ' ' * (max_len - len(value[0])), str(value[1])) print('-' * (max_len + 15)) -dicts_for_pretty_print = [#ep_values, - #tier_ep_values, +dicts_for_pretty_print = [ep_values, + tier_ep_values, #talent_ranks, #trinkets_ep_value, dps_breakdown, diff --git a/scripts/subtlety.py b/scripts/subtlety.py index b794924..1e22c32 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -31,6 +31,7 @@ # Set up buffs. test_buffs = buffs.Buffs( + 'short_term_haste_buff', 'flask_legion_agi', 'food_legion_mastery_375', #'food_legion_feast_150' @@ -41,16 +42,15 @@ test_oh = stats.Weapon(5442.0, 1.8, 'dagger', None) # Set up procs. - trinkets, other things (legendary procs) -#test_procs = procs.ProcsList(('scales_of_doom', 691), ('beating_heart_of_the_mountain', 701), ('infallible_tracking_charm', 715), -# 'draenic_agi_pot', 'draenic_agi_prepot', 'archmages_greater_incandescence') test_procs = procs.ProcsList( 'mark_of_the_hidden_satyr', - #'mark_of_the_distant_army', ('convergence_of_fates', 890), ('nightblooming_frond', 905), - #('tirathons_betrayal', 840), - #('faulty_countermeasure', 840), #('kiljaedens_burning_wish', 940) + #'old_war_pot', + #'old_war_prepot', + 'prolonged_power_pot', + 'prolonged_power_prepot', ) """ @@ -72,7 +72,8 @@ 'denial_of_the_half_giants', 'rogue_t19_2pc', 'rogue_t19_4pc', -'insignia_of_ravenholdt', +#'insignia_of_ravenholdt', +'mantle_of_the_master_assassin' ) #tier buffs located here # Set up a calcs object.. @@ -106,7 +107,12 @@ 'akarris_soul': 1, 'soul_shadows': 3, 'shadow_nova': 1, - 'legionblade': 20, + 'legionblade': 1, + 'shadows_of_the_uncrowned': 1, + 'etched_in_shadow': 4, + 'shadows_whisper': 1, + 'feeding_frenzy': 1, + 'concordance_of_the_legionfall': 12, }) # Set up settings. @@ -126,10 +132,13 @@ dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) +print(str(calculator.shadow_blades_uptime)) + # Compute EP values. ep_values = calculator.get_ep(baseline_dps=total_dps) #ep_values = calculator.get_ep() -tier_ep_values = calculator.get_other_ep(['rogue_t19_2pc', 'rogue_t19_4pc', 'denial_of_the_half_giants', 'insignia_of_ravenholdt']) +tier_ep_values = calculator.get_other_ep(['rogue_t19_2pc', 'rogue_t19_4pc', 'denial_of_the_half_giants', 'insignia_of_ravenholdt', +'shadow_satyrs_walk', 'convergence_of_fates', 'mantle_of_the_master_assassin']) #talent_ranks = calculator.get_talents_ranking() #trait_ranks = calculator.get_trait_ranking() @@ -167,12 +176,12 @@ def pretty_print(dict_list): print(' ' * (max_length(dicts_for_pretty_print) + 1), total_dps, ("total damage per second.")) """ -for value in aps.items(): +for value in list(aps.items()): if type(value[1]) is float: val = value[1] * 300. else: val = sum(value[1]) * 300. - print str(value[0]) + ' - ' + str(val) + print(str(value[0]) + ' - ' + str(val)) """ -#pprint(talent_ranks) \ No newline at end of file +#pprint(talent_ranks) From fc6f5c50bcb96a27615abfc987f82b01284a24d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Mar 2017 15:25:13 +0200 Subject: [PATCH 196/265] Update non-linear CDR for 8 possible traits --- shadowcraft/calcs/rogue/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8169ed1..8766ece 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -157,6 +157,8 @@ class RogueDamageCalculator(DamageCalculator): 4: 38, 5: 44, 6: 48, + 7: 52, + 8: 56 } # Adrenaline Rush CDR for number of points in trait @@ -168,6 +170,8 @@ class RogueDamageCalculator(DamageCalculator): 4: 31, 5: 37, 6: 42, + 7: 46, + 8: 49 } def __setattr__(self, name, value): From 8d0b40db1197ed0ab8d24aa8eeea7c5b827c166b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Mar 2017 16:21:37 +0200 Subject: [PATCH 197/265] [Assa] Sinister Circulation --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 469 ++++++++++--------- 1 file changed, 248 insertions(+), 221 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 91f3390..4336222 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -976,238 +976,265 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #Vendetta cd, modified by Duskwalker Legendary, used for damage modifier self.vendetta_cd = self.get_spell_cd('vendetta') - #cd stacking handlers, FIXME: in 'only' mode, we don't respect vendetta cdr with duskwalkers atm self.kingsbane_cd = self.get_spell_cd('kingsbane') - if self.settings.cycle.kingsbane_with_vendetta == 'only': - self.kingsbane_cd = max(self.vendetta_cd, self.get_spell_cd('kingsbane')) - self.exsang_cd = self.get_spell_cd('exsanguinate') - if self.settings.cycle.exsang_with_vendetta == 'only': - self.exsang_cd = max(self.vendetta_cd, self.get_spell_cd('exsanguinate')) - - #Vanish on cooldown - attacks_per_second['vanish'] = 1 / self.get_spell_cd('vanish') - # set up our finisher distributions - #unlike outlaw these depend on gear (crit) so they cannot be precomputed - self.cp_builder = self.settings.cycle.cp_builder - cp_builder_crit = crit_rates[self.cp_builder] - if self.cp_builder == 'mutilate': - cpg_cps = {2: (1 - cp_builder_crit) ** 2, - 3: 2 * (1 - cp_builder_crit) * cp_builder_crit, - 4: cp_builder_crit ** 2} - elif self.cp_builder == 'fan_of_knives': - raise InputNotModeledException(_('Fan of Knives cp builder unimplemented')) - else: - raise InputNotModeledException(_('Cp builder must be \'mutilate\' or \'fan_of_knives\'')) - - #if anticipation we can just assume no waste - if self.talents.anticipation: - avg_cp_per_builder = sum([cp * cpg_cps[cp] for cp in cpg_cps]) - builders_per_finisher = self.settings.finisher_threshold / avg_cp_per_builder - avg_finisher_size = self.settings.finisher_threshold - finisher_list = [0, 0, 0, 0, 0, 0, 0] - finisher_list[self.settings.finisher_threshold] = 1.0 - #otherwise we need to enumerate paths to determine amount of waste given cp threshold - else: - #TODO: Super hackish, do this right - finisher_list = [0, 0, 0, 0, 0, 0, 0] - if self.settings.finisher_threshold == 4: - paths = [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4,)] - elif self.settings.finisher_threshold == 5: - paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] - elif self.settings.finisher_threshold == 6: - paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 2), (2, 3, 3), (2, 3, 4), (2, 4), - (3, 2, 2), (3, 2, 3), (3, 2, 4), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + #convergence loop + old_aps = {} + for assa_loop in range(6): + if assa_loop >= 5: + raise ConvergenceErrorException(_('Assassination aps failed to converge.')) + + #cd stacking handlers + if self.settings.cycle.kingsbane_with_vendetta == 'only': + self.kingsbane_cd = max(self.vendetta_cd, self.kingsbane_cd) + if self.settings.cycle.exsang_with_vendetta == 'only': + self.exsang_cd = max(self.vendetta_cd, self.exsang_cd) + + #Vanish on cooldown + attacks_per_second['vanish'] = 1 / self.get_spell_cd('vanish') + + # set up our finisher distributions + #unlike outlaw these depend on gear (crit) so they cannot be precomputed + self.cp_builder = self.settings.cycle.cp_builder + cp_builder_crit = crit_rates[self.cp_builder] + if self.cp_builder == 'mutilate': + cpg_cps = {2: (1 - cp_builder_crit) ** 2, + 3: 2 * (1 - cp_builder_crit) * cp_builder_crit, + 4: cp_builder_crit ** 2} + elif self.cp_builder == 'fan_of_knives': + raise InputNotModeledException(_('Fan of Knives cp builder unimplemented')) else: - raise InputNotModeledException(_('Finisher thresholds less than 4 unimplemented')) - max_cps = 5 - if self.talents.deeper_strategem: - max_cps = 6 - builders_per_finisher = 0.0 - avg_finisher_size = 0.0 - finisher_list = [0., 0., 0., 0., 0., 0., 0.] - - for path in paths: - chance = 1.0 - for step in path: - chance *= cpg_cps[step] - builders_per_finisher += chance * len(path) - size = min(max_cps, sum(path)) - avg_finisher_size += chance * size - finisher_list[size] += chance - - cp_builder_energy_per_finisher = builders_per_finisher * self.get_spell_cost(self.cp_builder) - - #set up our energy budget - haste_multiplier = self.get_haste_multiplier(current_stats) - energy_regen = self.get_energy_regen(current_stats) - - #set up rupture - attacks_per_second['rupture'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['rupture_ticks'] = [0, 0, 0, 0, 0, 0, 0] - base_rupture_duration = 4 * (1 + avg_finisher_size) - if self.talents.exsanguinate: - #assume full pandemic on exsanged ruptures - exsang_rupture_duration = (1.3 * base_rupture_duration) / 2 - #rupture we're pandemicing from - exsang_from_duration = 0.7 * base_rupture_duration - normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration) / base_rupture_duration - ruptures_per_second = (2. + normal_ruptures_per_exsang_cd) / self.exsang_cd - rupture_ticks_per_second = 1. * float(exsang_rupture_duration)/ self.exsang_cd + \ - 0.5 * float(self.exsang_cd - exsang_rupture_duration)/self.exsang_cd - else: - ruptures_per_second = 1 / base_rupture_duration - rupture_ticks_per_second = 0.5 - - for cp in range(7): - attacks_per_second['rupture'][cp] = ruptures_per_second * finisher_list[cp] - attacks_per_second['rupture_ticks'][cp] = rupture_ticks_per_second * finisher_list[cp] - rupture_cost_per_second = self.get_spell_cost('rupture') * ruptures_per_second - rupture_cost_per_second += cp_builder_energy_per_finisher * ruptures_per_second - attacks_per_second[self.cp_builder] = ruptures_per_second * builders_per_finisher - - #set up garrote: - base_garrote_duration = 18. - garrote_cooldown = self.get_spell_cd('garrote') - if self.talents.exsanguinate: - exsang_garrote_duration = base_garrote_duration / 2 - exsang_downtime = garrote_cooldown - exsang_garrote_duration - normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration - attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd - attacks_per_second['garrote_ticks'] = 2/3 * float(exsang_garrote_duration) / self.exsang_cd + \ - 1/3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd - else: - attacks_per_second['garrote'] = 1 / base_garrote_duration - attacks_per_second['garrote_ticks'] = 1 / 3 - - cp_budget = attacks_per_second['garrote'] * self.settings.duration - garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] - - #Now that ticks are done, we can compute VW regen - vw_energy_per_tick = 7 + 3 * self.talents.venom_rush - vw_regen_per_second = vw_energy_per_tick * (sum(attacks_per_second['rupture_ticks']) + attacks_per_second['garrote_ticks']) - - net_energy_per_second = energy_regen + vw_regen_per_second - net_energy_per_second -= rupture_cost_per_second + garrote_cost_per_second - duskwalker_expended_energy = rupture_cost_per_second + garrote_cost_per_second - - #compute cooldowned talents: - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) - cp_budget += mfd_cps - - if self.stats.gear_buffs.the_dreadlords_deceit: - fok_interval = 1 / 60 - attacks_per_second['fan_of_knives'] = fok_interval - cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) - net_energy_per_second -= fok_interval * 35 - duskwalker_expended_energy += fok_interval * 35 - - if self.traits.kingsbane: - attacks_per_second['kingsbane'] = 1 / self.kingsbane_cd - attacks_per_second['kingsbane_ticks'] = 7 / self.kingsbane_cd - kb_crit = crit_rates['kingsbane'] - cpg_cps = {1: (1 - kb_crit) ** 2, - 2: 2 * (1 - kb_crit) * kb_crit, - 3: kb_crit ** 2} - avg_cp_per_kb = sum([cp * cpg_cps[cp] for cp in cpg_cps]) - cp_budget += avg_cp_per_kb * attacks_per_second['kingsbane'] * self.settings.duration - net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] - duskwalker_expended_energy += self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] - - if self.talents.hemorrhage: - hemos_per_second = 1 / 20 - attacks_per_second['hemorrhage'] = hemos_per_second - hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) - cp_budget += hemo_cps - net_energy_per_second -= self.get_spell_cost('hemorrhage') * hemos_per_second - duskwalker_expended_energy += self.get_spell_cost('hemorrhage') * hemos_per_second - - if self.talents.death_from_above: - dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - dfa_per_second = 1 / dfa_cd - attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - for cp in range(7): - attacks_per_second['death_from_above_pulse'][cp] = dfa_per_second * finisher_list[cp] - attacks_per_second['death_from_above_strike'][cp] = dfa_per_second * finisher_list[cp] - attacks_per_second[self.cp_builder] += dfa_per_second * builders_per_finisher - dfa_cost_per_second = self.get_spell_cost('death_from_above') * dfa_per_second - dfa_cost_per_second += cp_builder_energy_per_finisher * dfa_per_second - net_energy_per_second -= dfa_cost_per_second - duskwalker_expended_energy += dfa_cost_per_second - - #form whats left into a budget - duskwalker_expended_energy *= self.settings.duration - energy_budget = self.settings.duration * net_energy_per_second - max_energy = 120 - if self.talents.vigor: - max_energy += 50 - energy_budget += max_energy - #As of Patch 7.2 we get 60 energy + 60 over 2s, assume no loss - if self.traits.urge_to_kill: - energy_budget += (self.settings.duration / self.vendetta_cd) * 120 - - attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] - #spend those extra cps - if cp_budget > 0: - extra_envenom = cp_budget / avg_finisher_size - energy_budget -= self.get_spell_cost('envenom') * extra_envenom - duskwalker_expended_energy += self.get_spell_cost('envenom') * extra_envenom - extra_envenom_per_second = extra_envenom / self.settings.duration - for cp in range(7): - attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] + raise InputNotModeledException(_('Cp builder must be \'mutilate\' or \'fan_of_knives\'')) - #now burn whats left in a minicycle - mini_cycle_energy = self.get_spell_cost('envenom') + cp_builder_energy_per_finisher - loop_counter = 0 - - alacrity_stacks = 0 - while energy_budget > 0.1: - if loop_counter > 20: - raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) - loop_counter += 1 + #if anticipation we can just assume no waste + if self.talents.anticipation: + avg_cp_per_builder = sum([cp * cpg_cps[cp] for cp in cpg_cps]) + builders_per_finisher = self.settings.finisher_threshold / avg_cp_per_builder + avg_finisher_size = self.settings.finisher_threshold + finisher_list = [0, 0, 0, 0, 0, 0, 0] + finisher_list[self.settings.finisher_threshold] = 1.0 + #otherwise we need to enumerate paths to determine amount of waste given cp threshold + else: + #TODO: Super hackish, do this right + finisher_list = [0, 0, 0, 0, 0, 0, 0] + if self.settings.finisher_threshold == 4: + paths = [(2, 2), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4,)] + elif self.settings.finisher_threshold == 5: + paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3), (2, 4), (3, 2), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + elif self.settings.finisher_threshold == 6: + paths = [(2, 2, 2), (2, 2, 3), (2, 2, 4), (2, 3, 2), (2, 3, 3), (2, 3, 4), (2, 4), + (3, 2, 2), (3, 2, 3), (3, 2, 4), (3, 3), (3, 4), (4, 2), (4, 3), (4, 4)] + else: + raise InputNotModeledException(_('Finisher thresholds less than 4 unimplemented')) + max_cps = 5 + if self.talents.deeper_strategem: + max_cps = 6 + builders_per_finisher = 0.0 + avg_finisher_size = 0.0 + finisher_list = [0., 0., 0., 0., 0., 0., 0.] + + for path in paths: + chance = 1.0 + for step in path: + chance *= cpg_cps[step] + builders_per_finisher += chance * len(path) + size = min(max_cps, sum(path)) + avg_finisher_size += chance * size + finisher_list[size] += chance + + cp_builder_energy_per_finisher = builders_per_finisher * self.get_spell_cost(self.cp_builder) + + #set up our energy budget + haste_multiplier = self.get_haste_multiplier(current_stats) + energy_regen = self.get_energy_regen(current_stats) + + #set up rupture + attacks_per_second['rupture'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['rupture_ticks'] = [0, 0, 0, 0, 0, 0, 0] + base_rupture_duration = 4 * (1 + avg_finisher_size) + if self.talents.exsanguinate: + #assume full pandemic on exsanged ruptures + exsang_rupture_duration = (1.3 * base_rupture_duration) / 2 + #rupture we're pandemicing from + exsang_from_duration = 0.7 * base_rupture_duration + normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration) / base_rupture_duration + ruptures_per_second = (2. + normal_ruptures_per_exsang_cd) / self.exsang_cd + rupture_ticks_per_second = 1. * float(exsang_rupture_duration)/ self.exsang_cd + \ + 0.5 * float(self.exsang_cd - exsang_rupture_duration)/self.exsang_cd + else: + ruptures_per_second = 1 / base_rupture_duration + rupture_ticks_per_second = 0.5 - total_minicycles = energy_budget / mini_cycle_energy - attacks_per_second[self.cp_builder] += total_minicycles * builders_per_finisher / self.settings.duration - finishers_per_second = total_minicycles / self.settings.duration for cp in range(7): - attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second - energy_budget -= total_minicycles * mini_cycle_energy - duskwalker_expended_energy += total_minicycles * mini_cycle_energy - - if self.talents.alacrity: - old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.02)) - new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) - new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.02)) - energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration - alacrity_stacks = new_alacrity_stacks - - #swing timer - white_swing_downtime = 0 - self.swing_reset_spacing = self.get_spell_cd('vanish') - if self.swing_reset_spacing is not None: - white_swing_downtime += self.settings.response_time / self.swing_reset_spacing - attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01))) / self.stats.mh.speed * (1 - white_swing_downtime) - attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] + attacks_per_second['rupture'][cp] = ruptures_per_second * finisher_list[cp] + attacks_per_second['rupture_ticks'][cp] = rupture_ticks_per_second * finisher_list[cp] + rupture_cost_per_second = self.get_spell_cost('rupture') * ruptures_per_second + rupture_cost_per_second += cp_builder_energy_per_finisher * ruptures_per_second + attacks_per_second[self.cp_builder] = ruptures_per_second * builders_per_finisher + + #set up garrote: + base_garrote_duration = 18. + garrote_cooldown = self.get_spell_cd('garrote') + if self.talents.exsanguinate: + exsang_garrote_duration = base_garrote_duration / 2 + exsang_downtime = garrote_cooldown - exsang_garrote_duration + normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration + attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd + attacks_per_second['garrote_ticks'] = 2/3 * float(exsang_garrote_duration) / self.exsang_cd + \ + 1/3 * float(self.exsang_cd - exsang_garrote_duration - exsang_downtime) / self.exsang_cd + else: + attacks_per_second['garrote'] = 1 / base_garrote_duration + attacks_per_second['garrote_ticks'] = 1 / 3 + + cp_budget = attacks_per_second['garrote'] * self.settings.duration + garrote_cost_per_second = self.get_spell_cost('garrote') * attacks_per_second['garrote'] + + #Now that ticks are done, we can compute VW regen + vw_energy_per_tick = 7 + 3 * self.talents.venom_rush + vw_regen_per_second = vw_energy_per_tick * (sum(attacks_per_second['rupture_ticks']) + attacks_per_second['garrote_ticks']) + + net_energy_per_second = energy_regen + vw_regen_per_second + net_energy_per_second -= rupture_cost_per_second + garrote_cost_per_second + duskwalker_expended_energy = rupture_cost_per_second + garrote_cost_per_second + + #compute cooldowned talents: + mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) + cp_budget += mfd_cps + + if self.stats.gear_buffs.the_dreadlords_deceit: + fok_interval = 1 / 60 + attacks_per_second['fan_of_knives'] = fok_interval + cp_budget += self.settings.duration * fok_interval * (1 + crit_rates['fan_of_knives']) + net_energy_per_second -= fok_interval * 35 + duskwalker_expended_energy += fok_interval * 35 + + if self.traits.kingsbane: + attacks_per_second['kingsbane'] = 1 / self.kingsbane_cd + attacks_per_second['kingsbane_ticks'] = 7 / self.kingsbane_cd + kb_crit = crit_rates['kingsbane'] + cpg_cps = {1: (1 - kb_crit) ** 2, + 2: 2 * (1 - kb_crit) * kb_crit, + 3: kb_crit ** 2} + avg_cp_per_kb = sum([cp * cpg_cps[cp] for cp in cpg_cps]) + cp_budget += avg_cp_per_kb * attacks_per_second['kingsbane'] * self.settings.duration + net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] + duskwalker_expended_energy += self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] + + if self.talents.hemorrhage: + hemos_per_second = 1 / 20 + attacks_per_second['hemorrhage'] = hemos_per_second + hemo_cps = (1 + crit_rates['hemorrhage']) * (self.settings.duration * hemos_per_second) + cp_budget += hemo_cps + net_energy_per_second -= self.get_spell_cost('hemorrhage') * hemos_per_second + duskwalker_expended_energy += self.get_spell_cost('hemorrhage') * hemos_per_second + + if self.talents.death_from_above: + dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + dfa_per_second = 1 / dfa_cd + attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] + attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] + for cp in range(7): + attacks_per_second['death_from_above_pulse'][cp] = dfa_per_second * finisher_list[cp] + attacks_per_second['death_from_above_strike'][cp] = dfa_per_second * finisher_list[cp] + attacks_per_second[self.cp_builder] += dfa_per_second * builders_per_finisher + dfa_cost_per_second = self.get_spell_cost('death_from_above') * dfa_per_second + dfa_cost_per_second += cp_builder_energy_per_finisher * dfa_per_second + net_energy_per_second -= dfa_cost_per_second + duskwalker_expended_energy += dfa_cost_per_second + + #form whats left into a budget + duskwalker_expended_energy *= self.settings.duration + energy_budget = self.settings.duration * net_energy_per_second + max_energy = 120 + if self.talents.vigor: + max_energy += 50 + energy_budget += max_energy + #As of Patch 7.2 we get 60 energy + 60 over 2s, assume no loss + if self.traits.urge_to_kill: + energy_budget += (self.settings.duration / self.vendetta_cd) * 120 + + attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] + #spend those extra cps + if cp_budget > 0: + extra_envenom = cp_budget / avg_finisher_size + energy_budget -= self.get_spell_cost('envenom') * extra_envenom + duskwalker_expended_energy += self.get_spell_cost('envenom') * extra_envenom + extra_envenom_per_second = extra_envenom / self.settings.duration + for cp in range(7): + attacks_per_second['envenom'][cp] = extra_envenom_per_second * finisher_list[cp] - if self.traits.bag_of_tricks: - #2.5% chance per cp on envenom and rupture - attacks_per_second['poison_bomb'] = 0 - for i in range(7): - attacks_per_second['poison_bomb'] += attacks_per_second['envenom'][i] * i * 0.025 - attacks_per_second['poison_bomb'] += attacks_per_second['rupture'][i] * i * 0.025 + #now burn whats left in a minicycle + mini_cycle_energy = self.get_spell_cost('envenom') + cp_builder_energy_per_finisher + loop_counter = 0 - if self.stats.gear_buffs.duskwalkers_footpads: - self.vendetta_cd /= 1 + (duskwalker_expended_energy / 65) / self.settings.duration + alacrity_stacks = 0 + while energy_budget > 0.1: + if loop_counter > 20: + raise ConvergenceErrorException(_('Mini-cycles failed to converge.')) + loop_counter += 1 - if self.traits.from_the_shadows: - attacks_per_second['from_the_shadows'] = 1 / self.vendetta_cd + total_minicycles = energy_budget / mini_cycle_energy + attacks_per_second[self.cp_builder] += total_minicycles * builders_per_finisher / self.settings.duration + finishers_per_second = total_minicycles / self.settings.duration + for cp in range(7): + attacks_per_second['envenom'][cp] += finisher_list[cp] * finishers_per_second + energy_budget -= total_minicycles * mini_cycle_energy + duskwalker_expended_energy += total_minicycles * mini_cycle_energy + + if self.talents.alacrity: + old_alacrity_regen = energy_regen * (1 + (alacrity_stacks *0.02)) + new_alacrity_stacks = self.get_average_alacrity(attacks_per_second) + new_alacrity_regen = energy_regen * (1 + (new_alacrity_stacks *0.02)) + energy_budget += (new_alacrity_regen - old_alacrity_regen) * self.settings.duration + alacrity_stacks = new_alacrity_stacks + + #swing timer + white_swing_downtime = 0 + self.swing_reset_spacing = self.get_spell_cd('vanish') + if self.swing_reset_spacing is not None: + white_swing_downtime += self.settings.response_time / self.swing_reset_spacing + attacks_per_second['mh_autoattacks'] = (haste_multiplier * (1 + (alacrity_stacks * 0.01))) / self.stats.mh.speed * (1 - white_swing_downtime) + attacks_per_second['oh_autoattacks'] = attacks_per_second['mh_autoattacks'] + + if self.traits.bag_of_tricks: + #2.5% chance per cp on envenom and rupture + attacks_per_second['poison_bomb'] = 0 + for i in range(7): + attacks_per_second['poison_bomb'] += attacks_per_second['envenom'][i] * i * 0.025 + attacks_per_second['poison_bomb'] += attacks_per_second['rupture'][i] * i * 0.025 + + if self.stats.gear_buffs.duskwalkers_footpads: + #Recalculate Vendetta cooldown + self.vendetta_cd = self.get_spell_cd('vendetta') / (1 + (duskwalker_expended_energy / 65) / self.settings.duration) + + #poison computations, use old function for now + self.get_poison_counts(attacks_per_second, current_stats) + if self.stats.gear_buffs.rogue_t19_2pc: + attacks_per_second['t19_2pc'] = attacks_per_second['mutilate'] + + #Sinister Circulation + if self.traits.sinister_circulation: + if self.talents.agonizing_poison: + kb_cdr_per_sec = attacks_per_second['agonizing_poison'] * 0.5 + else: + kb_cdr_per_sec = attacks_per_second['deadly_instant_poison'] * 0.5 + #Recalculate KB cooldown + self.kingsbane_cd = self.get_spell_cd('kingsbane') + if self.settings.cycle.kingsbane_with_vendetta == 'only': + self.kingsbane_cd = max(self.vendetta_cd, self.kingsbane_cd) + self.kingsbane_cd /= 1 + kb_cdr_per_sec + + if self.traits.from_the_shadows: + attacks_per_second['from_the_shadows'] = 1 / self.vendetta_cd + + #Break convergence loop when it's not needed + if not self.traits.sinister_circulation and not self.stats.gear_buffs.duskwalkers_footpads: + break + if self.are_close_enough(old_aps, attacks_per_second): + break - #poison computations, use old function for now - self.get_poison_counts(attacks_per_second, current_stats) - if self.stats.gear_buffs.rogue_t19_2pc: - attacks_per_second['t19_2pc'] = attacks_per_second['mutilate'] + old_aps = attacks_per_second # for a in attacks_per_second: # if isinstance(attacks_per_second[a], list): From 0ce464ea96d9544000ec07ceaf82f68b84654b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Mar 2017 17:32:59 +0200 Subject: [PATCH 198/265] [Assa] T2 Talent implementations --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4336222..d30dcec 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -802,11 +802,6 @@ def compute_symbiote_strike_damage(self, damage_breakdown): #Legion TODO: - #Talents: - #T2:Nightstalker - #T2:Subter - #T2:SF - #Artifact: # 'poison_knives' @@ -842,6 +837,10 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], all_damage=True)) if self.talents.hemorrhage: self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks', 't19_2pc'])) + if self.talents.nightstalker: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker', None, ['rupture_ticks'])) + if self.talents.subterfuge: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('subterfuge_garrote', None, ['garrote_ticks'])) if self.talents.agonizing_poison: self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], all_damage=True)) if self.talents.deeper_strategem: @@ -927,6 +926,15 @@ def assassination_dps_breakdown(self): ep_uptime = finisher_aps * 5 #attacks/second * seconds/attack self.damage_modifiers.update_modifier_value('elaborate_planning', 1 + (0.12 * ep_uptime)) + if self.talents.nightstalker: + #Assume we use nightstalker for snapshotting Rupture + ns_rupture_uptime = aps['vanish'] / sum(aps['rupture']) + self.damage_modifiers.update_modifier_value('nightstalker', 1 + (0.5 * ns_rupture_uptime)) + + if self.talents.subterfuge: + #Get modifier for buffed garrotes from Subterfuge, including opener + subterfuge_garrote_uptime = (1 / self.settings.duration + aps['vanish']) / aps['garrote'] + self.damage_modifiers.update_modifier_value('subterfuge_garrote', 1 + (1.25 * subterfuge_garrote_uptime)) if self.talents.agonizing_poison: stack_time = 5 / aps['agonizing_poison'] @@ -1153,6 +1161,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #As of Patch 7.2 we get 60 energy + 60 over 2s, assume no loss if self.traits.urge_to_kill: energy_budget += (self.settings.duration / self.vendetta_cd) * 120 + #If we have Shadow Focus, use it as a builder cost reducer after vanish + if self.talents.shadow_focus: + energy_budget += 0.75 * self.get_spell_cost('garrote') #Opener + energy_budget += 0.75 * self.get_spell_cost(self.cp_builder) * self.settings.duration / self.get_spell_cd('vanish') attacks_per_second['envenom'] = [0, 0, 0, 0, 0, 0, 0] #spend those extra cps From 23c15c814791decafea37006b21bf43c62a7bc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 26 Mar 2017 17:43:19 +0200 Subject: [PATCH 199/265] [Outlaw] 7.2 Spec Aura Buff --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d30dcec..70f1274 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1368,7 +1368,7 @@ def outlaw_dps_breakdown(self): 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'], dmg_schools=['physical'])) # Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.16, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.20, ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'killing_spree', 'pistol_shot', 'run_through', 'saber_slash'])) From 4f657275449aeda37ee1e9177f066790d40e06ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 27 Mar 2017 17:31:30 +0200 Subject: [PATCH 200/265] Splinters of Agronax Trinket --- shadowcraft/objects/proc_data.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 3eca7d9..a0dcc2b 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -586,6 +586,22 @@ 'proc_rate': .92, }, + 'splinters_of_agronax': { #Equip: Your attacks have a chance to imbed Fel Barbs into your target, dealing X Fire damage over 6 sec. + 'stat': 'spell_dot', + 'dmg_school': 'fire', + 'dot_ticks': 6, + 'can_crit': True, + 'value': 0, #rpp-scaled + 'duration': 6, + 'proc_name': 'Fel Barbs', + 'scaling': 5.075319, + 'item_level': 845, + 'type': 'rppm', + 'haste_scales': True, + 'proc_rate': 3.5, + 'source': 'trinket', + }, + 'spontaneous_appendages': { #Equip: Your melee attacks have a chance to generate extra appendages for 12 sec that attack nearby enemies for X Physical damage every 0.75 sec. 'stat':'physical_dot', 'value': 0, #rpp-scaled From 71aec2ed828dc8f89f865f99fb78c7c596cefb6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 27 Mar 2017 21:06:37 +0200 Subject: [PATCH 201/265] Add everyone's favorite ring legendary --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 ++ shadowcraft/objects/stats.py | 1 + 2 files changed, 3 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 70f1274..041d1b9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -242,6 +242,8 @@ def set_constants(self): self.true_haste_mod *= 1 + self.race.get_racial_haste() #doesn't include Berserking if self.stats.gear_buffs.rogue_t14_4pc: self.true_haste_mod *= 1.05 + if self.stats.gear_buffs.sephuzs_secret: + self.true_haste_mod *= 1.02 #hit chances self.dw_mh_hit_chance = self.dual_wield_mh_hit_chance() diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 2de693f..acd0b0c 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -224,6 +224,7 @@ class GearBuffs(object): 'shivarran_symmetry', # 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after 'cinidaria_the_symbiote', #30% additional damage to enemies above 90% health + 'sephuzs_secret', #2% haste ] allowed_buffs = frozenset(other_gear_buffs) From 693f1ea09e7c96db9094fe31f59199f0dfb6139c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Mar 2017 11:24:54 +0200 Subject: [PATCH 202/265] Feasts stat increase --- shadowcraft/objects/buffs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/shadowcraft/objects/buffs.py b/shadowcraft/objects/buffs.py index c4408ae..ba0ea81 100644 --- a/shadowcraft/objects/buffs.py +++ b/shadowcraft/objects/buffs.py @@ -58,8 +58,8 @@ class Buffs(object): 'food_legion_damage_1', # Spiced Rib Roast 'food_legion_damage_2', # Drogbar-Style Salmon 'food_legion_damage_3', # Fishbrul Special - 'food_legion_feast_150', - 'food_legion_feast_200', + 'food_legion_feast_400', + 'food_legion_feast_500', ]) buffs_debuffs = frozenset([ @@ -108,8 +108,8 @@ def buff_agi(self, race=False): bonus_agi += 200 * self.flask_wod_agi_200 bonus_agi += 250 * self.flask_wod_agi bonus_agi += 1300 * self.flask_legion_agi - bonus_agi += 150 * self.food_legion_feast_150 * [1, 2][race] - bonus_agi += 200 * self.food_legion_feast_200 * [1, 2][race] + bonus_agi += 400 * self.food_legion_feast_400 * [1, 2][race] + bonus_agi += 500 * self.food_legion_feast_500 * [1, 2][race] return bonus_agi def buff_haste(self, race=False): From 0e28a18691bce1ae0756a3fafa615d4a63ead204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 28 Mar 2017 17:51:54 +0200 Subject: [PATCH 203/265] Bump target version number --- shadowcraft/calcs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 6d34d86..698774b 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -35,7 +35,7 @@ class DamageCalculator(object): normalize_ep_stat = None def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '7.1.5' # should reflect the game patch being targetted + self.WOW_BUILD_TARGET = '7.2.0' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = self.get_version_string() self.tools = class_data.Util() self.stats = stats From d981a3965540fa01f84b5fba4a00e06b0f48abdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 1 Apr 2017 16:51:39 +0200 Subject: [PATCH 204/265] [Assa] Agonizing Poison update --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 041d1b9..498b164 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -521,10 +521,7 @@ def get_poison_counts(self, attacks_per_second, current_stats): if not poison: return - if self.talents.agonizing_poison: - poison_base_proc_rate = 0.2 - else: - poison_base_proc_rate = 0.5 + poison_base_proc_rate = 0.3 poison_envenom_proc_rate = poison_base_proc_rate + 0.3 aps_envenom = attacks_per_second['envenom'] if self.talents.death_from_above: @@ -915,6 +912,7 @@ def assassination_dps_breakdown(self): #actually 2% per cp up to max of 5 surge_of_toxins_multiplier = 1. + surge_of_toxins_ap_multiplier = 1 if self.traits.surge_of_toxins: finisher_cpps = 0.0 #finisher cps per second for ability in aps: @@ -922,6 +920,7 @@ def assassination_dps_breakdown(self): finisher_cpps += sum([min(cp, 5) * aps[ability][cp] for cp in range(len(aps[ability]))]) surge_uptime = finisher_aps * 5 #attacks/second * seconds/attack surge_of_toxins_multiplier = 1. + ((0.02 * finisher_cpps) * surge_uptime) + surge_of_toxins_ap_multiplier = 1. + ((0.01 * finisher_cpps) * surge_uptime) self.damage_modifiers.update_modifier_value('surge_of_toxins', surge_of_toxins_multiplier) if self.talents.elaborate_planning: @@ -943,19 +942,18 @@ def assassination_dps_breakdown(self): max_time = self.settings.duration - stack_time agonizing_poison_stacks = (max_time / self.settings.duration) * 5 + (stack_time / self.settings.duration) * 2.5 - agonizing_poison_adder = 0.0 + 0.01 * self.traits.master_alchemist + 0.02 * self.traits.poison_knives - agonizing_poison_adder += 1 + (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 + agonizing_poison_additive_mod = 1 + 0.01 * self.traits.master_alchemist + agonizing_poison_additive_mod += 0.02 * self.traits.poison_knives + agonizing_poison_additive_mod += (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 - #12% reduction from 4% per stack - agonizing_poison_mod_per_stack= 0.0352 * agonizing_poison_adder + agonizing_poison_mod = 0.04 * agonizing_poison_stacks + agonizing_poison_mod *= agonizing_poison_additive_mod if self.talents.master_poisoner: - agonizing_poison_mod_per_stack *= 1.2 - + agonizing_poison_mod *= 1.2 if self.traits.surge_of_toxins: - agonizing_poison_mod_per_stack *= surge_of_toxins_multiplier + agonizing_poison_mod *= surge_of_toxins_ap_multiplier - agonizing_poison_mod = 1 + (agonizing_poison_mod_per_stack * agonizing_poison_stacks) - self.damage_modifiers.update_modifier_value('agonizing_poison', agonizing_poison_mod) + self.damage_modifiers.update_modifier_value('agonizing_poison', 1 + agonizing_poison_mod) if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5 / aps['fan_of_knives'] @@ -1230,10 +1228,11 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #Sinister Circulation if self.traits.sinister_circulation: if self.talents.agonizing_poison: - kb_cdr_per_sec = attacks_per_second['agonizing_poison'] * 0.5 + poisons_per_second = attacks_per_second['agonizing_poison'] else: - kb_cdr_per_sec = attacks_per_second['deadly_instant_poison'] * 0.5 - #Recalculate KB cooldown + poisons_per_second = attacks_per_second['deadly_instant_poison'] + #Recalculate KB cooldown, Sinister Circulation has a 0.5s icd + kb_cdr_per_sec = min(poisons_per_second, 2) * 0.5 self.kingsbane_cd = self.get_spell_cd('kingsbane') if self.settings.cycle.kingsbane_with_vendetta == 'only': self.kingsbane_cd = max(self.vendetta_cd, self.kingsbane_cd) From 288203cc02defa8f79fd1b1f3bf41d8cd70410c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 3 Apr 2017 12:54:23 +0200 Subject: [PATCH 205/265] [Assa] Improved Poisons Add proc increase back in and clarify where it comes from --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 498b164..9e0577a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -522,6 +522,8 @@ def get_poison_counts(self, attacks_per_second, current_stats): return poison_base_proc_rate = 0.3 + if not self.talents.agonizing_poison: + poison_base_proc_rate += 0.2 #Improved Poisons passive for Deadly and Wound Poison poison_envenom_proc_rate = poison_base_proc_rate + 0.3 aps_envenom = attacks_per_second['envenom'] if self.talents.death_from_above: From be5bec49ee9371c2ad04dd0f39815740f3aea4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 3 Apr 2017 18:27:51 +0200 Subject: [PATCH 206/265] [Assa] T19 2pc rework --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 15 +++++++++++---- shadowcraft/calcs/rogue/__init__.py | 4 ---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9e0577a..e61e2f0 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -837,7 +837,7 @@ def assassination_dps_breakdown(self): if self.talents.elaborate_planning: self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], all_damage=True)) if self.talents.hemorrhage: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks', 't19_2pc'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('hemorrhage', 1.25, ['rupture_ticks', 'garrote_ticks'])) if self.talents.nightstalker: self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker', None, ['rupture_ticks'])) if self.talents.subterfuge: @@ -870,7 +870,7 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.zoldyck_family_training_shackles: #Assume spend 30% of the time sub 30% health, imperfect but good enough self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', - 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb', 't19_2pc'], dmg_schools=['poison', 'bleed'])) + 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb'], dmg_schools=['poison', 'bleed'])) #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: @@ -968,6 +968,15 @@ def assassination_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) + if self.stats.gear_buffs.rogue_t19_2pc: + # To prevent double dipping this is based on actual Mutilate damage. + # There's no pandemic and it does not respect other modifiers. + # Remaining damage is added on refresh. + damage_breakdown['t19_2pc'] = damage_breakdown['mutilate'] * 0.3 + # This does double dip off Agonizing poison though + if self.talents.agonizing_poison: + damage_breakdown['t19_2pc'] *= 1 + agonizing_poison_mod + if self.stats.gear_buffs.insignia_of_ravenholdt: damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) @@ -1224,8 +1233,6 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #poison computations, use old function for now self.get_poison_counts(attacks_per_second, current_stats) - if self.stats.gear_buffs.rogue_t19_2pc: - attacks_per_second['t19_2pc'] = attacks_per_second['mutilate'] #Sinister Circulation if self.traits.sinister_circulation: diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8766ece..364c592 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -399,9 +399,6 @@ def poison_bomb_damage(self, ap): def rupture_tick_damage(self, ap, cp): return 1.5 * ap * (1 + (0.0333 * self.traits.gushing_wounds)) - def assn_t19_2pc_damage(self, ap): - return 0.3 * (self.mh_mutilate_damage(ap) + self.oh_mutilate_damage(ap)) - #outlaw def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) @@ -512,7 +509,6 @@ def get_formula(self, name): 'poisoned_knife': self.poisoned_knife_damage, 'poison_bomb': self.poison_bomb_damage, 'rupture_ticks': self.rupture_tick_damage, - 't19_2pc': self.assn_t19_2pc_damage, #outlaw 'ambush': self.ambush_damage, 'between_the_eyes': self.between_the_eyes_damage, From 7752a00505d9ad3559897aef078609c56ef8900f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 3 Apr 2017 18:28:20 +0200 Subject: [PATCH 207/265] Update several damage formulas --- shadowcraft/calcs/rogue/__init__.py | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 364c592..cac2453 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -25,8 +25,7 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', - 't19_2pc'] + 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -362,10 +361,10 @@ def deadly_instant_poison_damage(self, ap): #Maybe add better handling for 'rule of three' for artifact traits def envenom_damage(self, ap, cp): - return .5 * cp * ap * (1 + (0.0333 * self.traits.toxic_blades)) + return .6 * cp * ap * (1 + (0.0333 * self.traits.toxic_blades)) def fan_of_knives_damage(self, ap): - return .8316 * ap + return 1.079 * ap #Lumping 40 ticks together for simplicity def from_the_shadows_damage(self, ap): @@ -404,18 +403,18 @@ def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) def between_the_eyes_damage(self, ap, cp): - return .75 * cp * ap * (1 + (0.06 * self.traits.black_powder)) + return .85 * cp * ap * (1 + (0.06 * self.traits.black_powder)) - #7*55% AP + #7*121% AP def blunderbuss_damage(self, ap): - return 3.85 * ap + return 8.47 * ap - #Ignoring that this behaves as a dot for simplicity + #Ignoring that this behaves as a dot for simplicity, 6*150% def cannonball_barrage_damage(self, ap): - return 7.2 * ap + return 9 * ap def ghostly_strike_damage(self, ap): - return 1.76 * self.get_weapon_damage('mh', ap) + return 1.94 * self.get_weapon_damage('mh', ap) def mh_greed_damage(self, ap): return 3.5 * self.get_weapon_damage('mh', ap) @@ -425,28 +424,29 @@ def oh_greed_damage(self, ap): #For KsP treat each hit individually def mh_killing_spree_damage(self, ap): - return 2.108 * self.get_weapon_damage('mh', ap) + return 2.6 * self.get_weapon_damage('mh', ap) def oh_killing_spree_damage(self, ap): - return 2.018 * self.oh_penalty() * self.get_weapon_damage('oh', ap) + return 2.6 * self.oh_penalty() * self.get_weapon_damage('oh', ap) def main_gauche_damage(self, ap): return 2.1 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.1 * self.traits.fortunes_strike)) def pistol_shot_damage(self, ap): - return 1.5 * ap + return 1.65 * ap def run_through_damage(self, ap, cp): - return 1.5 * ap * cp * (1 + (0.06 * self.traits.fates_thirst)) + return 1.42 * ap * cp * (1 + (0.04 * self.traits.fates_thirst)) def saber_slash_damage(self, ap): - return 2.6 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) + return 3.02 * self.get_weapon_damage('mh', ap) * (1 + (0.15 * self.traits.cursed_edges)) #subtlety #Ignore positional modifier for now def backstab_damage(self, ap): return 3.7 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) + #has two ranks def eviscerate_damage(self, ap, cp): return 1.472 * cp * ap From 1f3f2e45d428d4f28248f90570f3d4d2297b2c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 10 Apr 2017 22:26:06 +0200 Subject: [PATCH 208/265] [Assa] Assassin's Resolve is hidden but active --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e61e2f0..90f1a3c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -821,6 +821,7 @@ def assassination_dps_breakdown(self): 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, ['death_from_above_pulse', 'death_from_above_strike', From b5883645455e48d96252bbb1ff96fa40b030ddff Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Sun, 19 Mar 2017 19:36:20 -0700 Subject: [PATCH 209/265] Temporary changes for the new UI --- shadowcraft/calcs/__init__.py | 24 ++++------ shadowcraft/calcs/rogue/Aldriana/__init__.py | 19 -------- shadowcraft/calcs/rogue/Aldriana/settings.py | 12 ++--- shadowcraft/objects/proc_data.py | 48 -------------------- shadowcraft/objects/procs.py | 3 +- 5 files changed, 17 insertions(+), 89 deletions(-) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 698774b..565d7bd 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -621,7 +621,7 @@ def armor_mitigate(self, damage, armor): # damage value. return damage * self.armor_mitigation_multiplier(armor) - def melee_hit_chance(self, base_miss_chance, dodgeable, parryable, weapon_type, blockable=False): + def melee_hit_chance(self, base_miss_chance, dodgeable, parryable, blockable=False): miss_chance = base_miss_chance if dodgeable: @@ -642,45 +642,41 @@ def melee_hit_chance(self, base_miss_chance, dodgeable, parryable, weapon_type, return (1 - (miss_chance + dodge_chance + parry_chance)) * (1 - block_chance) def melee_spells_hit_chance(self, bonus_hit=0): - hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable=False, parryable=False, weapon_type=None) + hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable=False, parryable=False) return hit_chance - def one_hand_melee_hit_chance(self, dodgeable=False, parryable=False, weapon=None, blockable=False): + def one_hand_melee_hit_chance(self, dodgeable=False, parryable=False, blockable=False): # Most attacks by DPS aren't parryable due to positional negation. But # if you ever want to attacking from the front, you can just set that # to True. - if weapon == None: - weapon = self.stats.mh - hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable, weapon.type, blockable) + hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable, blockable) return hit_chance - def off_hand_melee_hit_chance(self, dodgeable=False, parryable=False, weapon=None, bonus_hit=0): + def off_hand_melee_hit_chance(self, dodgeable=False, parryable=False, bonus_hit=0): # Most attacks by DPS aren't parryable due to positional negation. But # if you ever want to attacking from the front, you can just set that # to True. - if weapon == None: - weapon = self.stats.oh - hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable, weapon.type) + hit_chance = self.melee_hit_chance(self.base_one_hand_miss_rate, dodgeable, parryable) return hit_chance def dual_wield_mh_hit_chance(self, dodgeable=False, parryable=False, dw_miss=None): # Most attacks by DPS aren't parryable due to positional negation. But # if you ever want to attacking from the front, you can just set that # to True. - hit_chance = self.dual_wield_hit_chance(dodgeable, parryable, self.stats.mh.type, dw_miss=dw_miss) + hit_chance = self.dual_wield_hit_chance(dodgeable, parryable, dw_miss=dw_miss) return hit_chance def dual_wield_oh_hit_chance(self, dodgeable=False, parryable=False, dw_miss=None): # Most attacks by DPS aren't parryable due to positional negation. But # if you ever want to attacking from the front, you can just set that # to True. - hit_chance = self.dual_wield_hit_chance(dodgeable, parryable, self.stats.oh.type, dw_miss=dw_miss) + hit_chance = self.dual_wield_hit_chance(dodgeable, parryable, dw_miss=dw_miss) return hit_chance - def dual_wield_hit_chance(self, dodgeable, parryable, weapon_type, dw_miss=None): + def dual_wield_hit_chance(self, dodgeable, parryable, dw_miss=None): if not dw_miss: dw_miss = self.base_dw_miss_rate - hit_chance = self.melee_hit_chance(dw_miss, dodgeable, parryable, weapon_type) + hit_chance = self.melee_hit_chance(dw_miss, dodgeable, parryable) return hit_chance def buff_melee_crit(self): diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 90f1a3c..a5c1f98 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -617,25 +617,6 @@ def determine_stats(self, attack_counts_function): elif proc.stat == 'extra_weapon_damage': weapon_damage_procs.append(proc) - #calculate weapon procs - weapon_enchants = set([]) - for hand, enchant in [(x, y) for x in ('mh', 'oh') for y in ('dancing_steel', 'mark_of_the_frostwolf', - 'mark_of_the_shattered_hand', 'mark_of_the_thunderlord', - 'mark_of_the_bleeding_hollow', 'mark_of_warsong')]: - proc = getattr(getattr(self.stats, hand), enchant) - if proc: - setattr(proc, '_'.join((hand, 'only')), True) - if (proc.stat in current_stats or proc.stat == 'stats'): - if proc.is_real_ppm(): - active_procs_rppm.append(proc) - else: - if proc.icd: - active_procs_icd.append(proc) - else: - active_procs_no_icd.append(proc) - elif enchant in ('mark_of_the_shattered_hand', ): - damage_procs.append(proc) - static_proc_stats = { 'str': 0, 'agi': 0, diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index b39f687..914d44d 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -5,8 +5,8 @@ class Settings(object): # Settings object for AldrianasRogueDamageCalculator. def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_params=None, - merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', is_day=False, is_demon=False, - marked_for_death_resets=0, finisher_threshold=5): + merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', + is_day=False, is_demon=False, marked_for_death_resets=0, finisher_threshold=5): self.cycle = cycle self.response_time = response_time self.latency = latency @@ -63,13 +63,13 @@ class Cycle(object): class AssassinationCycle(Cycle): _cycle_type = 'assassination' - def __init__(self, kingsbane_with_vendetta ='just', exsang_with_vendetta='just', cp_builder='mutilate'): + def __init__(self, kingsbane='just', exsang='just', cp_builder='mutilate', lethal_poison=''): self.cp_builder = cp_builder #Allowed values: 'mutilate', 'fan_of_knives' #Cooldown scheduling and usage settings #Allowed values: 'just': Use cooldown if it aligns with vendetta but don't delay usages # 'only': Only use cooldown with vendetta - self.kingsbane_with_vendetta = kingsbane_with_vendetta - self.exsang_with_vendetta = exsang_with_vendetta + self.kingsbane_with_vendetta = kingsbane + self.exsang_with_vendetta = exsang class OutlawCycle(Cycle): _cycle_type = 'outlaw' @@ -153,4 +153,4 @@ def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy= #Allow finishers to be scheduled during dance self.dance_finishers_allowed= dance_finishers_allowed #EXPERIMENTAL CP Waste Test - self.compute_cp_waste = compute_cp_waste \ No newline at end of file + self.compute_cp_waste = compute_cp_waste diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index a0dcc2b..f653237 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -67,54 +67,6 @@ 'proc_rate': 1.0, 'trigger': 'all_attacks' }, - 'draenic_agi_pot': { - 'stat': 'stats', - 'value': {'agi':1000}, - 'duration': 25, - 'proc_name': 'Draenic Agi Potion', - 'item_level': 100, - 'type': 'icd', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1.0, - 'trigger': 'all_attacks' - }, - 'draenic_agi_prepot': { - 'stat': 'stats', - 'value': {'agi':1000}, - 'duration': 23, - 'proc_name': 'Draenic Agi Prepot', - 'item_level': 100, - 'type': 'icd', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1.0, - 'trigger': 'all_attacks' - }, - 'virmens_bite': { - 'stat': 'stats', - 'value': {'agi':456}, - 'duration': 25, - 'proc_name': 'Virmens Bite', - 'item_level': 90, - 'type': 'icd', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1.0, - 'trigger': 'all_attacks' - }, - 'virmens_bite_prepot': { - 'stat': 'stats', - 'value': {'agi':456}, - 'duration': 23, - 'proc_name': 'Virmens Bite', - 'item_level': 90, - 'type': 'icd', - 'source': 'unique', - 'icd': 0, - 'proc_rate': 1.0, - 'trigger': 'all_attacks' - }, #racials 'touch_of_the_grave': { 'stat': 'spell_damage', diff --git a/shadowcraft/objects/procs.py b/shadowcraft/objects/procs.py index 5d6ea55..f03b9b5 100755 --- a/shadowcraft/objects/procs.py +++ b/shadowcraft/objects/procs.py @@ -216,7 +216,6 @@ def get_all_procs_for_stat(self, stat=None): procs.append(proc) elif proc.stat in ('stats', 'highest', 'random') and stat in proc.value: procs.append(proc) - return procs def get_all_damage_procs(self): @@ -227,4 +226,4 @@ def get_all_damage_procs(self): if proc.stat in ('spell_damage', 'physical_damage', 'physical_dot', 'spell_dot'): procs.append(proc) - return procs \ No newline at end of file + return procs From 2492d3ffefb00d95e77d958b157da3df2fcc7580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 13 Apr 2017 13:42:02 +0200 Subject: [PATCH 210/265] Use settings_data for defaults and UI integration --- scripts/assassination.py | 2 +- scripts/subtlety.py | 11 +- setup.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 8 - shadowcraft/calcs/rogue/Aldriana/settings.py | 94 ++-- .../calcs/rogue/Aldriana/settings_data.py | 423 ++++++++++++++++++ 6 files changed, 474 insertions(+), 66 deletions(-) create mode 100644 shadowcraft/calcs/rogue/Aldriana/settings_data.py diff --git a/scripts/assassination.py b/scripts/assassination.py index 7fc67d5..963f63a 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -101,7 +101,7 @@ }) # Set up settings. -test_cycle = settings.AssassinationCycle('just', 'just') +test_cycle = settings.AssassinationCycle() test_settings = settings.Settings(test_cycle, response_time=.5, duration=300, finisher_threshold=4) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index 1e22c32..e41665e 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -9,6 +9,7 @@ from shadowcraft.calcs.rogue.Aldriana import AldrianasRogueDamageCalculator from shadowcraft.calcs.rogue.Aldriana import settings +from shadowcraft.calcs.rogue.Aldriana import settings_data from shadowcraft.objects import buffs from shadowcraft.objects import race @@ -116,12 +117,8 @@ }) # Set up settings. -test_cycle = settings.SubtletyCycle(cp_builder='backstab', - dance_finishers_allowed=True, - positional_uptime=1. - ) -test_settings = settings.Settings(test_cycle, response_time=.5, duration=300, - adv_params="", is_demon=False, num_boss_adds=0, marked_for_death_resets=0.0) +test_cycle = settings.SubtletyCycle() +test_settings = settings.Settings(test_cycle) # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) @@ -132,8 +129,6 @@ dps_breakdown = calculator.get_dps_breakdown() total_dps = sum(entry[1] for entry in list(dps_breakdown.items())) -print(str(calculator.shadow_blades_uptime)) - # Compute EP values. ep_values = calculator.get_ep(baseline_dps=total_dps) #ep_values = calculator.get_ep() diff --git a/setup.py b/setup.py index 27f0ea7..8381bcb 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ShadowCraft-Engine', url='http://github.com/ShadowCraft/ShadowCraft-Engine/', - version='7.1.5', + version='7.2.0', packages=[ 'shadowcraft', 'shadowcraft.calcs', diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a5c1f98..f4894c1 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -217,14 +217,6 @@ def set_constants(self): if boost['stat'] in self.base_stats: self.base_stats[boost['stat']] += boost['value'] * boost['duration'] * 1.0 / (boost['cooldown'] + self.settings.response_time) - if self.stats.procs.virmens_bite: - getattr(self.stats.procs, 'virmens_bite').icd = self.settings.duration - if self.stats.procs.virmens_bite_prepot: - getattr(self.stats.procs, 'virmens_bite_prepot').icd = self.settings.duration - if self.stats.procs.draenic_agi_pot: - getattr(self.stats.procs, 'draenic_agi_pot').icd = self.settings.duration - if self.stats.procs.draenic_agi_prepot: - getattr(self.stats.procs, 'draenic_agi_prepot').icd = self.settings.duration if self.stats.procs.prolonged_power_pot: self.stats.procs.prolonged_power_pot.icd = self.settings.duration if self.stats.procs.prolonged_power_prepot: diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 914d44d..d6d88e1 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -1,27 +1,28 @@ from builtins import object from shadowcraft.core import exceptions +from shadowcraft.calcs.rogue.Aldriana import settings_data class Settings(object): # Settings object for AldrianasRogueDamageCalculator. - def __init__(self, cycle, response_time=.5, latency=.03, duration=300, adv_params=None, - merge_damage=True, num_boss_adds=0, feint_interval=0, default_ep_stat='ap', - is_day=False, is_demon=False, marked_for_death_resets=0, finisher_threshold=5): + def __init__(self, cycle, **kwargs): self.cycle = cycle - self.response_time = response_time - self.latency = latency - self.duration = duration - self.feint_interval = feint_interval - self.is_day = is_day - self.is_demon = is_demon - self.num_boss_adds = max(num_boss_adds, 0) - self.adv_params = self.interpret_adv_params(adv_params) - self.default_ep_stat = default_ep_stat - #per minute - self.marked_for_death_resets=marked_for_death_resets - - #TODO: can be overridden by spec specific finisher thresholds - self.finisher_threshold = finisher_threshold + self.default_ep_stat = kwargs.get('default_ep_stat', 'ap') + self.feint_interval = int(kwargs.get('feint_interval', 0)) + + #Get defaults from settings_data + defaults = settings_data.get_default_settings('general.settings', self.cycle._cycle_type, settings_data.rogue_settings) + defaults.update(settings_data.get_default_settings('other', self.cycle._cycle_type, settings_data.rogue_settings)) + + self.response_time = float(kwargs.get('response_time', defaults['response_time'])) + self.latency = float(kwargs.get('latency', defaults['latency'])) + self.duration = int(kwargs.get('duration', defaults['duration'])) + self.is_day = kwargs.get('is_day', defaults['is_day']) + self.is_demon = kwargs.get('is_demon', defaults['is_demon']) + self.num_boss_adds = max(int(kwargs.get('num_boss_adds', defaults['num_boss_adds'])), 0) + self.adv_params = self.interpret_adv_params(kwargs.get('adv_params', defaults['adv_params'])) + self.marked_for_death_resets = int(kwargs.get('marked_for_death_resets', defaults['marked_for_death_resets'])) + self.finisher_threshold = int(kwargs.get('finisher_threshold', defaults['finisher_threshold'])) def interpret_adv_params(self, s=""): data = {} @@ -55,7 +56,7 @@ class Cycle(object): # respect. # When subclassing, define _cycle_type to be one of 'assassination', - # 'combat', or 'subtlety' - this is how the damage calculator makes sure + # 'outlaw', or 'subtlety' - this is how the damage calculator makes sure # you have an appropriate cycle object to go with your talent trees, etc. _cycle_type = '' @@ -63,13 +64,12 @@ class Cycle(object): class AssassinationCycle(Cycle): _cycle_type = 'assassination' - def __init__(self, kingsbane='just', exsang='just', cp_builder='mutilate', lethal_poison=''): - self.cp_builder = cp_builder #Allowed values: 'mutilate', 'fan_of_knives' - #Cooldown scheduling and usage settings - #Allowed values: 'just': Use cooldown if it aligns with vendetta but don't delay usages - # 'only': Only use cooldown with vendetta - self.kingsbane_with_vendetta = kingsbane - self.exsang_with_vendetta = exsang + def __init__(self, **kwargs): + defaults = settings_data.get_default_settings('rotation.assassination', 'assassination', settings_data.rogue_settings) + self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) + self.kingsbane_with_vendetta = kwargs.get('kingsbane', defaults['kingsbane']) + self.exsang_with_vendetta = kwargs.get('exsang', defaults['exsang']) + self.lethal_poison = kwargs.get('lethal_poison', defaults['lethal_poison']) class OutlawCycle(Cycle): _cycle_type = 'outlaw' @@ -98,23 +98,22 @@ class OutlawCycle(Cycle): #single buffs ('jr',), ('gm',), ('s',), ('tb',), ('bt',), ('b',)] - def __init__(self, blade_flurry=False, between_the_eyes_policy='shark', - jolly_roger_reroll=0, grand_melee_reroll=0, shark_reroll=0, - true_bearing_reroll=0, buried_treasure_reroll=0, broadsides_reroll=0): - self.blade_flurry = bool(blade_flurry) - self.between_the_eyes_policy = between_the_eyes_policy #Allowed values: 'shark', 'always', 'never' + def __init__(self, **kwargs): + defaults = settings_data.get_default_settings('rotation.outlaw', 'outlaw', settings_data.rogue_settings) + self.blade_flurry = kwargs.get('blade_flurry', defaults['blade_flurry']) + self.between_the_eyes_policy = kwargs.get('between_the_eyes_policy', defaults['between_the_eyes_policy']) + self.jolly_roger_reroll = int(kwargs.get('jolly_roger_reroll', defaults['jolly_roger_reroll'])) + self.grand_melee_reroll = int(kwargs.get('grand_melee_reroll', defaults['grand_melee_reroll'])) + self.shark_reroll = int(kwargs.get('shark_reroll', defaults['shark_reroll'])) + self.true_bearing_reroll = int(kwargs.get('true_bearing_reroll', defaults['true_bearing_reroll'])) + self.buried_treasure_reroll = int(kwargs.get('buried_treasure_reroll', defaults['buried_treasure_reroll'])) + self.broadsides_reroll = int(kwargs.get('broadsides_reroll', defaults['broadsides_reroll'])) + # RtB reroll thresholds, 0, 1, 2, 3 # 0 means never reroll combos with this buff # 1 means reroll singles of buff # 2 means reroll doubles containing this buff # 3 means reroll triples containing this buff - self.jolly_roger_reroll = jolly_roger_reroll - self.grand_melee_reroll = grand_melee_reroll - self.shark_reroll = shark_reroll - self.true_bearing_reroll = true_bearing_reroll - self.buried_treasure_reroll = buried_treasure_reroll - self.broadsides_reroll = broadsides_reroll - #build reroll lists here self.reroll_list = [] self.keep_list = [] @@ -143,14 +142,13 @@ def __init__(self, blade_flurry=False, between_the_eyes_policy='shark', class SubtletyCycle(Cycle): _cycle_type = 'subtlety' - def __init__(self, cp_builder='backstab', positional_uptime=1.0, symbols_policy='just', - dance_finishers_allowed=True, compute_cp_waste=False): - self.cp_builder = cp_builder #Allowed values: 'shuriken_storm', 'backstab' (implies gloomblade if selected and ssk during dance) - self.positional_uptime = positional_uptime #Range 0.0 to 1.0, time behind target - self.symbols_policy = symbols_policy #Allowed values: - #'always' - use SoD every dance (macro) - #'just' - Only use SoD when needed to refresh - #Allow finishers to be scheduled during dance - self.dance_finishers_allowed= dance_finishers_allowed - #EXPERIMENTAL CP Waste Test - self.compute_cp_waste = compute_cp_waste + def __init__(self, **kwargs): + defaults = settings_data.get_default_settings('rotation.subtlety', 'subtlety', settings_data.rogue_settings) + self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) + self.symbols_policy = kwargs.get('symbols_policy', defaults['symbols_policy']) + self.dance_finishers_allowed = kwargs.get('dance_finishers_allowed', defaults['dance_finishers_allowed']) + self.compute_cp_waste = kwargs.get('compute_cp_waste', defaults['compute_cp_waste']) + + #Handle percent vs float automatically + self.positional_uptime = int(kwargs.get('positional_uptime_percent', defaults['positional_uptime_percent'])) / 100 + self.positional_uptime = float(kwargs.get('positional_uptime', self.positional_uptime)) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py new file mode 100644 index 0000000..d65e480 --- /dev/null +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -0,0 +1,423 @@ +# This file contains all rogue settings that are advertised to the react UI and later passed +# into the settings object before calculation. + +def get_default_settings(block_name, spec, settings_data): + defaults = {} + for category in settings_data: + if category['name'] == block_name: + for option in category['items']: + if isinstance(option['default'], dict): + defaults[option['name']] = option['default'][spec] + else: + defaults[option['name']] = option['default'] + break + return defaults + +rogue_settings = [ + { + 'spec': 'a', + 'heading': 'Assassination Rotation Settings', + 'name': 'rotation.assassination', + 'items': [ + { + 'name': 'kingsbane', + 'label': 'Kingsbane w/ Vendetta', + 'description': '', + 'type': 'dropdown', + 'default': 'just', + 'options': { + 'just': "Use cooldown if it aligns, but don't delay usage", + 'only': 'Only use cooldown with Vendetta' + } + }, + { + 'name': 'exsang', + 'label': 'Exsang w/ Vendetta', + 'description': '', + 'type': 'dropdown', + 'default': 'just', + 'options': { + 'just': "Use cooldown if it aligns, but don't delay usage", + 'only': 'Only use cooldown with Vendetta' + } + }, + { + 'name': 'cp_builder', + 'label': 'CP Builder', + 'description': '', + 'type': 'dropdown', + 'default': 'mutilate', + 'options': { + 'mutilate': 'Mutilate', + 'fan_of_knives': 'Fan of Knives' + } + }, + { + 'name': 'lethal_poison', + 'label': 'Lethal Poison', + 'description': '', + 'type': 'dropdown', + 'default': 'dp', + 'options': { + 'dp': 'Deadly Poison', + 'wp': 'Wound Poison' + } + }, + ] + }, + { + 'spec': 'Z', + 'heading': 'Outlaw Rotation Settings', + 'name': 'rotation.outlaw', + 'items': [ + { + 'name': 'blade_flurry', + 'label': 'Blade Flurry', + 'description': 'Use Blade Flurry', + 'type': 'checkbox', + 'default': False + }, + { + 'name': 'between_the_eyes_policy', + 'label': 'BtE Policy', + 'description': '', + 'type': 'dropdown', + 'default': 'never', + 'options': { + 'shark': 'Only use with Shark', + 'always': 'Use BtE on cooldown', + 'never': 'Never use BtE', + } + }, + { + 'name': 'reroll_policy', + 'label': 'RtB Reroll Policy', + 'description': '', + 'type': 'dropdown', + 'default': 'custom', + 'options': { + '1': 'Reroll single buffs', + '2': 'Reroll two or fewer buffs', + '3': 'Reroll three or fewer buffs', + 'custom': 'Custom setup per buff (see below)', + } + }, + { + 'name': 'jolly_roger_reroll', + 'label': 'Jolly Roger', + 'description': '', + 'type': 'dropdown', + 'default': '2', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + }, + { + 'name': 'grand_melee_reroll', + 'label': 'Grand Melee', + 'description': '', + 'type': 'dropdown', + 'default': '2', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + }, + { + 'name': 'shark_reroll', + 'label': 'Shark-Infested Waters', + 'description': '', + 'type': 'dropdown', + 'default': '2', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + }, + { + 'name': 'true_bearing_reroll', + 'label': 'True Bearing', + 'description': '', + 'type': 'dropdown', + 'default': '0', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + }, + { + 'name': 'buried_treasure_reroll', + 'label': 'Buried Treasure', + 'description': '', + 'type': 'dropdown', + 'default': '2', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + }, + { + 'name': 'broadsides_reroll', + 'label': 'Broadsides', + 'description': '', + 'type': 'dropdown', + 'default': '2', + 'options': { + '0': '0 - Never reroll combos with this buff', + '1': '1 - Reroll single buff rolls of this buff', + '2': '2 - Reroll double-buff rolls containing this buff', + '3': '3 - Reroll triple-buff rolls containing this buff' + } + } + ] + }, + { + 'spec': 'b', + 'heading': 'Subtlety Rotation Settings', + 'name': 'rotation.subtlety', + 'items': [ + { + 'name': 'cp_builder', + 'label': 'CP Builder', + 'description': '', + 'type': 'dropdown', + 'default': 'backstab', + 'options': { + 'backstab': 'Backstab', + 'shuriken_storm': 'Shuriken Storm', + } + }, + { + 'name': 'symbols_policy', + 'label': 'SoD Policy', + 'description': '', + 'type': 'dropdown', + 'default': 'just', + 'options': { + 'always': 'Use on cooldown', + 'just': 'Only use SoD when needed to refresh', + } + }, + { + 'name': 'dance_finishers_allowed', + 'label': 'Use Finishers during Dance', + 'description': '', + 'type': 'checkbox', + 'default': True + }, + { + 'name': 'positional_uptime_percent', + 'label': 'Backstab uptime', + 'description': 'Percentage of the fight you are behind the target (0-100). This has no effect if Gloomblade is selected as a talent.', + 'type': 'text', + 'default': '100' + }, + { + 'name': 'compute_cp_waste', + 'label': 'Compute CP Waste', + 'description': 'EXPERIMENTAL FEATURE: Compute combo point waste', + 'type': 'checkbox', + 'default': False + } + ] + }, + { + 'spec': 'All', + 'heading': 'Raid Buffs', + 'name': 'buffs', + 'items': [ + { + 'name': 'flask_legion_agi', + 'label': 'Legion Agility Flask', + 'description': 'Flask of the Seventh Demon (1300 Agility)', + 'type': 'checkbox', + 'default': False + }, + { + 'name': 'short_term_haste_buff', + 'label': '+30% Haste/40 sec', + 'description': 'Heroism/Bloodlust/Time Warp', + 'type': 'checkbox', + 'default': False + }, + { + 'name': 'food_buff', + 'label': 'Food', + 'description': '', + 'type': 'dropdown', + 'default': 'food_legion_feast_500', + 'options': { + 'food_legion_crit_375': 'The Hungry Magister (375 Crit)', + 'food_legion_haste_375': 'Azshari Salad (375 Haste)', + 'food_legion_mastery_375': 'Nightborne Delicacy Platter (375 Mastery)', + 'food_legion_versatility_375': 'Seed-Battered Fish Plate (375 Versatility)', + 'food_legion_feast_500': 'Lavish Suramar Feast (500 Agility)', + 'food_legion_damage_3': 'Fishbrul Special (High Fire Proc)', + } + }, + { + 'name': 'prepot', + 'label': 'Pre-pot', + 'description': '', + 'type': 'dropdown', + 'default': 'old_war_pot', + 'options': { + 'old_war_pot': 'Potion of the Old War', + 'prolonged_power_pot': 'Potion of Prolonged Power', + 'potion_none': 'None', + } + }, + { + 'name': 'potion', + 'label': 'Combat Potion', + 'description': '', + 'type': 'dropdown', + 'default': 'old_war_prepot', + 'options': { + 'old_war_prepot': 'Potion of the Old War', + 'prolonged_power_prepot': 'Potion of Prolonged Power', + 'potion_none': 'None', + } + } + ] + }, + { + 'spec': 'All', + 'heading': 'General Settings', + 'name': 'general.settings', + 'items': [ + { + 'name': 'is_demon', + 'label': 'Enemy is Demon', + 'description': 'Enables damage buff from heirloom trinket against demons', + 'type': 'checkbox', + 'default': False + }, + { + 'name': 'patch', + 'label': 'Patch/Engine', + 'description': '', + 'type': 'dropdown', + 'default': '7.0', + 'options': { + '7.0': '7.0', + 'fierys_strange_voodoo': 'fierys strange voodoo', + } + }, + { + 'name': 'race', + 'label': 'Race', + 'description': '', + 'type': 'dropdown', + 'default': 'human', + 'options': { + 'human': 'Human', + 'dwarf': 'Dwarf', + 'orc': 'Orc', + 'blood_elf': 'Blood Elf', + 'gnome': 'Gnome', + 'worgen': 'Worgen', + 'troll': 'Troll', + 'night_elf': 'Night Elf', + 'undead': 'Undead', + 'goblin': 'Goblin', + 'pandren': 'Pandaren', + } + }, + { + 'name': 'is_day', + 'label': 'Night Elf Racial', + 'description': '', + 'type': 'dropdown', + 'default': False, + 'options': { + False: 'Night', + True: 'Day', + } + }, + { + 'name': 'finisher_threshold', + 'label': 'Finisher Threshold', + 'description': 'Minimum CPs to use finisher', + 'type': 'dropdown', + 'default': { + 'assassination': '4', + 'outlaw': '5', + 'subtlety': '5' + }, + 'options': { + '4': '4', + '5': '5', + '6': '6' + } + }, + { + 'name': 'level', + 'label': 'Level', + 'description': '', + 'type': 'text', + 'default': '110' + }, + { + 'name': 'duration', + 'label': 'Fight Duration', + 'description': '', + 'type': 'text', + 'default': '300' + }, + { + 'name': 'response_time', + 'label': 'Response Time', + 'description': '', + 'type': 'text', + 'default': '0.5', + }, + { + 'name': 'num_boss_adds', + 'label': 'Number of Boss Adds', + 'description': '', + 'type': 'text', + 'default': '0', + }, + { + 'name': 'marked_for_death_resets', + 'label': 'MfD Resets Per Minute', + 'description': '', + 'type': 'text', + 'default': '0', + } + ] + }, + { + 'spec': 'All', + 'heading': 'Other', + 'name': 'other', + 'items': [ + { + 'name': 'latency', + 'label': 'Latency', + 'description': '', + 'type': 'text', + 'default': '0.03' + }, + { + 'name': 'adv_params', + 'label': 'Advanced Parameters', + 'description': '', + 'type': 'text', + 'default': '' + } + ] + } +] From 2734f1ee1769cb975ce3de5de4511575cf3064d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 27 Apr 2017 15:28:23 +0200 Subject: [PATCH 211/265] [Settings] Use spec overrides instead of dicts --- shadowcraft/calcs/rogue/Aldriana/settings.py | 21 ++++-- .../calcs/rogue/Aldriana/settings_data.py | 74 ++++++++++++------- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index d6d88e1..c20c94b 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -11,8 +11,19 @@ def __init__(self, cycle, **kwargs): self.feint_interval = int(kwargs.get('feint_interval', 0)) #Get defaults from settings_data - defaults = settings_data.get_default_settings('general.settings', self.cycle._cycle_type, settings_data.rogue_settings) - defaults.update(settings_data.get_default_settings('other', self.cycle._cycle_type, settings_data.rogue_settings)) + defaults = settings_data.get_default_settings(settings_data.rogue_settings) + + suffix = '_' + self.cycle._cycle_type + #Spec overrides from defaults + for setting in list(defaults.keys()): + if setting.endswith(suffix): + override_key = setting.replace(suffix, '') + defaults[override_key] = defaults[setting] + #Spec overrides from params + for setting in kwargs: + if setting.endswith(suffix): + override_key = setting.replace(suffix, '') + defaults[override_key] = defaults[setting] self.response_time = float(kwargs.get('response_time', defaults['response_time'])) self.latency = float(kwargs.get('latency', defaults['latency'])) @@ -65,7 +76,7 @@ class AssassinationCycle(Cycle): _cycle_type = 'assassination' def __init__(self, **kwargs): - defaults = settings_data.get_default_settings('rotation.assassination', 'assassination', settings_data.rogue_settings) + defaults = settings_data.get_default_settings(settings_data.rogue_settings) self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) self.kingsbane_with_vendetta = kwargs.get('kingsbane', defaults['kingsbane']) self.exsang_with_vendetta = kwargs.get('exsang', defaults['exsang']) @@ -99,7 +110,7 @@ class OutlawCycle(Cycle): ('jr',), ('gm',), ('s',), ('tb',), ('bt',), ('b',)] def __init__(self, **kwargs): - defaults = settings_data.get_default_settings('rotation.outlaw', 'outlaw', settings_data.rogue_settings) + defaults = settings_data.get_default_settings(settings_data.rogue_settings) self.blade_flurry = kwargs.get('blade_flurry', defaults['blade_flurry']) self.between_the_eyes_policy = kwargs.get('between_the_eyes_policy', defaults['between_the_eyes_policy']) self.jolly_roger_reroll = int(kwargs.get('jolly_roger_reroll', defaults['jolly_roger_reroll'])) @@ -143,7 +154,7 @@ class SubtletyCycle(Cycle): _cycle_type = 'subtlety' def __init__(self, **kwargs): - defaults = settings_data.get_default_settings('rotation.subtlety', 'subtlety', settings_data.rogue_settings) + defaults = settings_data.get_default_settings(settings_data.rogue_settings) self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) self.symbols_policy = kwargs.get('symbols_policy', defaults['symbols_policy']) self.dance_finishers_allowed = kwargs.get('dance_finishers_allowed', defaults['dance_finishers_allowed']) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index d65e480..0a2d81d 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -1,16 +1,14 @@ -# This file contains all rogue settings that are advertised to the react UI and later passed -# into the settings object before calculation. +#This file contains all rogue settings that are advertised to the react UI and later passed +#into the settings object before calculation. +#Variable names are supposed to be unique and currently passed into the settings and cycle +#constructors all together. +#Options ending in "_spec" will set shared settings with the spec stripped. -def get_default_settings(block_name, spec, settings_data): +def get_default_settings(settings_data): defaults = {} for category in settings_data: - if category['name'] == block_name: - for option in category['items']: - if isinstance(option['default'], dict): - defaults[option['name']] = option['default'][spec] - else: - defaults[option['name']] = option['default'] - break + for option in category['items']: + defaults[option['name']] = option['default'] return defaults rogue_settings = [ @@ -63,6 +61,18 @@ def get_default_settings(block_name, spec, settings_data): 'wp': 'Wound Poison' } }, + { + 'name': 'finisher_threshold_assassination', + 'label': 'Finisher Threshold', + 'description': 'Minimum CPs to use finisher', + 'type': 'dropdown', + 'default': '4', + 'options': { + '4': '4', + '5': '5', + '6': '6' + } + }, ] }, { @@ -179,7 +189,19 @@ def get_default_settings(block_name, spec, settings_data): '2': '2 - Reroll double-buff rolls containing this buff', '3': '3 - Reroll triple-buff rolls containing this buff' } - } + }, + { + 'name': 'finisher_threshold_outlaw', + 'label': 'Finisher Threshold', + 'description': 'Minimum CPs to use finisher', + 'type': 'dropdown', + 'default': '5', + 'options': { + '4': '4', + '5': '5', + '6': '6' + } + }, ] }, { @@ -229,7 +251,19 @@ def get_default_settings(block_name, spec, settings_data): 'description': 'EXPERIMENTAL FEATURE: Compute combo point waste', 'type': 'checkbox', 'default': False - } + }, + { + 'name': 'finisher_threshold_subtlety', + 'label': 'Finisher Threshold', + 'description': 'Minimum CPs to use finisher', + 'type': 'dropdown', + 'default': '5', + 'options': { + '4': '4', + '5': '5', + '6': '6' + } + }, ] }, { @@ -346,22 +380,6 @@ def get_default_settings(block_name, spec, settings_data): True: 'Day', } }, - { - 'name': 'finisher_threshold', - 'label': 'Finisher Threshold', - 'description': 'Minimum CPs to use finisher', - 'type': 'dropdown', - 'default': { - 'assassination': '4', - 'outlaw': '5', - 'subtlety': '5' - }, - 'options': { - '4': '4', - '5': '5', - '6': '6' - } - }, { 'name': 'level', 'label': 'Level', From 1ceb83f2c2152e0c1eaa46ae31d59ff6ba92eb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 28 Apr 2017 11:25:17 +0200 Subject: [PATCH 212/265] Fix copy pasta --- shadowcraft/calcs/rogue/Aldriana/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index c20c94b..7662eb5 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -23,7 +23,7 @@ def __init__(self, cycle, **kwargs): for setting in kwargs: if setting.endswith(suffix): override_key = setting.replace(suffix, '') - defaults[override_key] = defaults[setting] + defaults[override_key] = kwargs[setting] self.response_time = float(kwargs.get('response_time', defaults['response_time'])) self.latency = float(kwargs.get('latency', defaults['latency'])) From 8cbef3a8d919bd4d44d57b3e0ff7cda5cfe0cb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 1 May 2017 15:57:56 +0200 Subject: [PATCH 213/265] Add Wound Poison and a settings fix --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 30 ++++++++++++++----- shadowcraft/calcs/rogue/Aldriana/settings.py | 19 +++++------- .../calcs/rogue/Aldriana/settings_data.py | 17 +++++++++-- shadowcraft/calcs/rogue/__init__.py | 7 ++++- 4 files changed, 51 insertions(+), 22 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f4894c1..040cc10 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -527,8 +527,11 @@ def get_poison_counts(self, attacks_per_second, current_stats): attacks_per_second['agonizing_poison'] = total_hits_per_second * avg_poison_proc_rate else: poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration - attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = 1 / 3 + if self.settings.cycle.lethal_poison == 'dp': + attacks_per_second['deadly_instant_poison'] = poison_procs + attacks_per_second['deadly_poison'] = 1 / 3 + elif self.settings.cycle.lethal_poison == 'wp': + attacks_per_second['wound_poison'] = poison_procs def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 @@ -793,7 +796,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', - 'deadly_instant_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) + 'deadly_instant_poison', 'wound_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) + # Wound poison is affected by Mastery twice, probably to offset that DP profits twice as well (direct + dot part) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons_2', None, ['wound_poison'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura @@ -828,7 +833,7 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('blood_of_the_assassinated', None, ['rupture_ticks'])) if self.traits.surge_of_toxins: self.damage_modifiers.register_modifier(modifiers.DamageModifier('surge_of_toxins', None, ['deadly_poison', - 'deadly_instant_poison', 'envenom', 'poison_bomb'], dmg_schools=['poison'])) + 'deadly_instant_poison', 'wound_poison', 'envenom', 'poison_bomb'], dmg_schools=['poison'])) if self.traits.slayers_precision: self.damage_modifiers.register_modifier(modifiers.DamageModifier('slayers_precision', @@ -844,7 +849,7 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.zoldyck_family_training_shackles: #Assume spend 30% of the time sub 30% health, imperfect but good enough self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', - 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb'], dmg_schools=['poison', 'bleed'])) + 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb', 'wound_poison'], dmg_schools=['poison', 'bleed'])) #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: @@ -869,10 +874,18 @@ def assassination_dps_breakdown(self): self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) self.damage_modifiers.update_modifier_value('potent_poisons', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) + self.damage_modifiers.update_modifier_value('potent_poisons_2', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) #Lethal poison applications increase kingsbane damage by 15% each, KB ticks 7 times every 2 sec if self.traits.kingsbane: - applications_per_tick = 2 * (aps['agonizing_poison'] if self.talents.agonizing_poison else aps['deadly_instant_poison']) + poison_aps = 0 + if self.talents.agonizing_poison: + poison_aps = aps['agonizing_poison'] + elif self.settings.cycle.lethal_poison == 'dp': + poison_aps = aps['deadly_instant_poison'] + elif self.settings.cycle.lethal_poison == 'wp': + poison_aps = aps['wound_poison'] + applications_per_tick = 2 * poison_aps average_kb_stacks = (applications_per_tick + applications_per_tick * 7) / 2 self.damage_modifiers.update_modifier_value('kingsbane_tick_increase', 1 + (average_kb_stacks * 0.15)) @@ -1210,10 +1223,13 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #Sinister Circulation if self.traits.sinister_circulation: + poisons_per_second = 0 if self.talents.agonizing_poison: poisons_per_second = attacks_per_second['agonizing_poison'] - else: + elif self.settings.cycle.lethal_poison == 'dp': poisons_per_second = attacks_per_second['deadly_instant_poison'] + elif self.settings.cycle.lethal_poison == 'wp': + poisons_per_second = attacks_per_second['wound_poison'] #Recalculate KB cooldown, Sinister Circulation has a 0.5s icd kb_cdr_per_sec = min(poisons_per_second, 2) * 0.5 self.kingsbane_cd = self.get_spell_cd('kingsbane') diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 7662eb5..1d4f6c2 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -12,18 +12,7 @@ def __init__(self, cycle, **kwargs): #Get defaults from settings_data defaults = settings_data.get_default_settings(settings_data.rogue_settings) - - suffix = '_' + self.cycle._cycle_type - #Spec overrides from defaults - for setting in list(defaults.keys()): - if setting.endswith(suffix): - override_key = setting.replace(suffix, '') - defaults[override_key] = defaults[setting] - #Spec overrides from params - for setting in kwargs: - if setting.endswith(suffix): - override_key = setting.replace(suffix, '') - defaults[override_key] = kwargs[setting] + settings_data.process_overrides(defaults, kwargs, self.cycle._cycle_type) self.response_time = float(kwargs.get('response_time', defaults['response_time'])) self.latency = float(kwargs.get('latency', defaults['latency'])) @@ -77,6 +66,8 @@ class AssassinationCycle(Cycle): def __init__(self, **kwargs): defaults = settings_data.get_default_settings(settings_data.rogue_settings) + settings_data.process_overrides(defaults, kwargs, self._cycle_type) + self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) self.kingsbane_with_vendetta = kwargs.get('kingsbane', defaults['kingsbane']) self.exsang_with_vendetta = kwargs.get('exsang', defaults['exsang']) @@ -111,6 +102,8 @@ class OutlawCycle(Cycle): def __init__(self, **kwargs): defaults = settings_data.get_default_settings(settings_data.rogue_settings) + settings_data.process_overrides(defaults, kwargs, self._cycle_type) + self.blade_flurry = kwargs.get('blade_flurry', defaults['blade_flurry']) self.between_the_eyes_policy = kwargs.get('between_the_eyes_policy', defaults['between_the_eyes_policy']) self.jolly_roger_reroll = int(kwargs.get('jolly_roger_reroll', defaults['jolly_roger_reroll'])) @@ -155,6 +148,8 @@ class SubtletyCycle(Cycle): def __init__(self, **kwargs): defaults = settings_data.get_default_settings(settings_data.rogue_settings) + settings_data.process_overrides(defaults, kwargs, self._cycle_type) + self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) self.symbols_policy = kwargs.get('symbols_policy', defaults['symbols_policy']) self.dance_finishers_allowed = kwargs.get('dance_finishers_allowed', defaults['dance_finishers_allowed']) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index 0a2d81d..b96bc97 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -11,6 +11,19 @@ def get_default_settings(settings_data): defaults[option['name']] = option['default'] return defaults +def process_overrides(defaults_dict, params_dict, spec): + suffix = '_' + spec + #Spec overrides from defaults + for setting in list(defaults_dict.keys()): + if setting.endswith(suffix): + override_key = setting.replace(suffix, '') + defaults_dict[override_key] = defaults_dict[setting] + #Spec overrides from params + for setting in params_dict: + if setting.endswith(suffix): + override_key = setting.replace(suffix, '') + defaults_dict[override_key] = params_dict[setting] + rogue_settings = [ { 'spec': 'a', @@ -40,7 +53,7 @@ def get_default_settings(settings_data): } }, { - 'name': 'cp_builder', + 'name': 'cp_builder_assassination', 'label': 'CP Builder', 'description': '', 'type': 'dropdown', @@ -210,7 +223,7 @@ def get_default_settings(settings_data): 'name': 'rotation.subtlety', 'items': [ { - 'name': 'cp_builder', + 'name': 'cp_builder_subtlety', 'label': 'CP Builder', 'description': '', 'type': 'dropdown', diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index cac2453..86b9ed9 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -25,7 +25,8 @@ class RogueDamageCalculator(DamageCalculator): 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', - 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows'] + 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', + 'wound_poison'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -398,6 +399,9 @@ def poison_bomb_damage(self, ap): def rupture_tick_damage(self, ap, cp): return 1.5 * ap * (1 + (0.0333 * self.traits.gushing_wounds)) + def wound_poison_damage(self, ap): + return 0.13 * ap * (1 + (0.05 * self.traits.master_alchemist)) * (1 + (0.3 * self.talents.master_poisoner)) + #outlaw def ambush_damage(self, ap): return 4.5 * self.get_weapon_damage('mh', ap) @@ -509,6 +513,7 @@ def get_formula(self, name): 'poisoned_knife': self.poisoned_knife_damage, 'poison_bomb': self.poison_bomb_damage, 'rupture_ticks': self.rupture_tick_damage, + 'wound_poison': self.wound_poison_damage, #outlaw 'ambush': self.ambush_damage, 'between_the_eyes': self.between_the_eyes_damage, From bf923a7d15f060a0223db4f47a3585cd33cbaacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 2 May 2017 19:34:21 +0200 Subject: [PATCH 214/265] Jeweled Signet of Melandrus --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 10 ++++++++++ shadowcraft/objects/stats.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 040cc10..1330c03 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -851,6 +851,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('zoldyck_family_training_shackles', 1.09, ['deadly_poison', 'deadly_instant_poison', 'garrote_ticks', 'kingsbane', 'kingsbane_ticks', 'rupture_ticks', 'poison_bomb', 'wound_poison'], dmg_schools=['poison', 'bleed'])) + if self.stats.gear_buffs.jeweled_signet_of_melandrus: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks'])) + #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.3, ['envenom'])) @@ -1387,6 +1390,10 @@ def outlaw_dps_breakdown(self): if self.traits.dreadblades_vigor: self.damage_modifiers.register_modifier(modifiers.DamageModifier('dreadblades_vigor', None, [], all_damage=True)) + #Gear specific + if self.stats.gear_buffs.jeweled_signet_of_melandrus: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks'])) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) @@ -1949,6 +1956,9 @@ def subtlety_dps_breakdown(self): if self.stats.gear_buffs.the_dreadlords_deceit: self.damage_modifiers.register_modifier(modifiers.DamageModifier('the_dreadlords_deceit', None, ['shuriken_storm'])) + if self.stats.gear_buffs.jeweled_signet_of_melandrus: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks', 'shadow_blades'])) + stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) self.damage_modifiers.update_modifier_value('executioner', (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index acd0b0c..861c5fd 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -225,6 +225,9 @@ class GearBuffs(object): 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after 'cinidaria_the_symbiote', #30% additional damage to enemies above 90% health 'sephuzs_secret', #2% haste + #Other + 'jeweled_signet_of_melandrus', #Increases your autoattack damage by 10%. + 'gnawed_thumb_ring', #Use: Have a nibble, increasing your healing and magic damage done by 5% for 12 sec. (3 Min Cooldown) ] allowed_buffs = frozenset(other_gear_buffs) From f23d91bffc100ecc42ab68dafb71c7418eb0eebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 2 May 2017 20:43:45 +0200 Subject: [PATCH 215/265] Gnawed Thumb Ring --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1330c03..577f9b8 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -854,6 +854,13 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.jeweled_signet_of_melandrus: self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks'])) + if self.stats.gear_buffs.gnawed_thumb_ring: + gtr_mod = 1 + 0.05 * 12 / 180 + self.damage_modifiers.register_modifier(modifiers.DamageModifier('gnawed_thumb_ring', gtr_mod, + ['deadly_poison', 'deadly_instant_poison', 'envenom', 'kingsbane', 'kingsbane_ticks', + 'poison_bomb', 'from_the_shadows', 'wound_poison'], + dmg_schools=['arcane', 'fire', 'frost', 'holy', 'nature', 'shadow'])) + #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.3, ['envenom'])) @@ -1394,6 +1401,11 @@ def outlaw_dps_breakdown(self): if self.stats.gear_buffs.jeweled_signet_of_melandrus: self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks'])) + if self.stats.gear_buffs.gnawed_thumb_ring: + gtr_mod = 1 + 0.05 * 12 / 180 + self.damage_modifiers.register_modifier(modifiers.DamageModifier('gnawed_thumb_ring', gtr_mod, [], + dmg_schools=['arcane', 'fire', 'frost', 'holy', 'nature', 'shadow'])) + stats, aps, crits, procs, additional_info = self.determine_stats(self.outlaw_attack_counts) self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) @@ -1959,6 +1971,12 @@ def subtlety_dps_breakdown(self): if self.stats.gear_buffs.jeweled_signet_of_melandrus: self.damage_modifiers.register_modifier(modifiers.DamageModifier('jeweled_signet_of_melandrus', 1.1, ['autoattacks', 'shadow_blades'])) + if self.stats.gear_buffs.gnawed_thumb_ring: + gtr_mod = 1 + 0.05 * 12 / 180 + self.damage_modifiers.register_modifier(modifiers.DamageModifier('gnawed_thumb_ring', gtr_mod, + ['gloomblade', 'goremaws_bite', 'shadow_blades', 'nightblade_ticks', 'soul_rip', 'shadow_nova'], + dmg_schools=['arcane', 'fire', 'frost', 'holy', 'nature', 'shadow'])) + stats, aps, crits, procs, additional_info = self.determine_stats(self.subtlety_attack_counts) self.damage_modifiers.update_modifier_value('executioner', (1 + self.subtlety_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) From 7e32a78314e2929a89a9cd0fa7f99b5e6244849d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 19 May 2017 20:51:58 +0200 Subject: [PATCH 216/265] Fix for Nightblooming Frond --- shadowcraft/calcs/rogue/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 86b9ed9..25e0798 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -593,10 +593,10 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates stack_list = [] for second in range(1, frond.duration + 1): stack_list.append(min(second * autoattacks_per_second, frond.max_stacks)) - stack_damage = self.get_proc_damage_contribution(frond, 1, current_stats, ap, modifier_dict) + base_damage = self.get_proc_damage_contribution(frond, 1, current_stats, ap, modifier_dict) proc_damage = 0 for stack_count in stack_list: - proc_damage += stack_count * stack_damage * autoattacks_per_second + proc_damage += base_damage * (1 + stack_count * 0.5) * autoattacks_per_second damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) * 1.1307 #BLP From c60d03124fe63459ce6af19b00110d189d4fd598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 23 May 2017 16:02:50 +0200 Subject: [PATCH 217/265] Revert "Fix for Nightblooming Frond" This reverts commit 7e32a78314e2929a89a9cd0fa7f99b5e6244849d. --- shadowcraft/calcs/rogue/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 25e0798..86b9ed9 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -593,10 +593,10 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates stack_list = [] for second in range(1, frond.duration + 1): stack_list.append(min(second * autoattacks_per_second, frond.max_stacks)) - base_damage = self.get_proc_damage_contribution(frond, 1, current_stats, ap, modifier_dict) + stack_damage = self.get_proc_damage_contribution(frond, 1, current_stats, ap, modifier_dict) proc_damage = 0 for stack_count in stack_list: - proc_damage += base_damage * (1 + stack_count * 0.5) * autoattacks_per_second + proc_damage += stack_count * stack_damage * autoattacks_per_second damage_breakdown[frond.proc_name] = proc_damage * frond.get_proc_rate(spec=self.spec) * 1.1307 #BLP From 159db71bcebb07af6d61a458f85d4c1ee3456ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:43:45 +0200 Subject: [PATCH 218/265] Update talent and trait data --- shadowcraft/objects/artifact_data.py | 2 +- shadowcraft/objects/talents_data.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 7abe7ae..6f6b850 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -69,7 +69,7 @@ 'shadow_nova', 'legionblade', 'shadows_of_the_uncrowned', - 'etched_in_shadow', + 'weak_point', 'shadows_whisper', 'feeding_frenzy', 'concordance_of_the_legionfall', diff --git a/shadowcraft/objects/talents_data.py b/shadowcraft/objects/talents_data.py index 9900159..2d1f3d3 100644 --- a/shadowcraft/objects/talents_data.py +++ b/shadowcraft/objects/talents_data.py @@ -76,7 +76,7 @@ ('deeper_strategem', 'anticipation', 'vigor'), ('leeching_poison', 'elusiveness', 'cheat_death'), ('thuggee', 'prey_on_the_weak', 'internal_bleeding'), - ('agonizing_poison', 'alacrity', 'exsanguinate'), + ('toxic_blade', 'alacrity', 'exsanguinate'), ('venom_rush', 'marked_for_death', 'death_from_above') ), ('rogue', 'outlaw'): ( @@ -94,7 +94,7 @@ ('deeper_strategem', 'anticipation', 'vigor'), ('soothing_darkness', 'elusiveness', 'cheat_death'), ('strike_from_the_shadows', 'prey_on_the_weak', 'tangled_shadow'), - ('premeditation', 'alacrity', 'enveloping_shadows'), + ('dark_shadow', 'alacrity', 'enveloping_shadows'), ('master_of_shadows', 'marked_for_death', 'death_from_above') ), ('shaman', 'elemental'): ( From a12db2a688d06f2ace17195b9ae8fb4c3110236b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:44:17 +0200 Subject: [PATCH 219/265] Vendetta affects only certain abilities --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 577f9b8..ce15145 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -807,7 +807,8 @@ def assassination_dps_breakdown(self): 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'])) #time averaged vendetta modifier used for most things - self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['rupture_ticks', 'kingsbane', 'kingsbane_ticks'], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['garrote_ticks', 'mutilate', 'deadly_poison', + 'wound_poison', 'hemorrhage', 'envenom', 'fan_of_knives', 'death_from_above_pulse', 'poisoned_knife', 'from_the_shadows', 'poison_bomb', 'toxic_blade'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) From 12b542ef847135b483cb65650e0b3bc9e571698c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:44:53 +0200 Subject: [PATCH 220/265] [Assa] Remove Agonizing Poison --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 50 ++++---------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ce15145..4fb2a41 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -513,9 +513,7 @@ def get_poison_counts(self, attacks_per_second, current_stats): if not poison: return - poison_base_proc_rate = 0.3 - if not self.talents.agonizing_poison: - poison_base_proc_rate += 0.2 #Improved Poisons passive for Deadly and Wound Poison + poison_base_proc_rate = 0.5 #Improved Poisons passive for Deadly and Wound Poison poison_envenom_proc_rate = poison_base_proc_rate + 0.3 aps_envenom = attacks_per_second['envenom'] if self.talents.death_from_above: @@ -523,15 +521,12 @@ def get_poison_counts(self, attacks_per_second, current_stats): envenom_uptime = min(sum([(1 + cps) * aps_envenom[cps] for cps in range(1, 6)]), 1) avg_poison_proc_rate = poison_base_proc_rate * (1 - envenom_uptime) + poison_envenom_proc_rate * envenom_uptime - if self.talents.agonizing_poison: - attacks_per_second['agonizing_poison'] = total_hits_per_second * avg_poison_proc_rate - else: - poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration - if self.settings.cycle.lethal_poison == 'dp': - attacks_per_second['deadly_instant_poison'] = poison_procs - attacks_per_second['deadly_poison'] = 1 / 3 - elif self.settings.cycle.lethal_poison == 'wp': - attacks_per_second['wound_poison'] = poison_procs + poison_procs = avg_poison_proc_rate * total_hits_per_second - 1 / self.settings.duration + if self.settings.cycle.lethal_poison == 'dp': + attacks_per_second['deadly_instant_poison'] = poison_procs + attacks_per_second['deadly_poison'] = 1 / 3 + elif self.settings.cycle.lethal_poison == 'wp': + attacks_per_second['wound_poison'] = poison_procs def get_average_alacrity(self, attacks_per_second): stacks_per_second = 0.0 @@ -822,8 +817,6 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker', None, ['rupture_ticks'])) if self.talents.subterfuge: self.damage_modifiers.register_modifier(modifiers.DamageModifier('subterfuge_garrote', None, ['garrote_ticks'])) - if self.talents.agonizing_poison: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('agonizing_poison', None, [], all_damage=True)) if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) @@ -890,9 +883,7 @@ def assassination_dps_breakdown(self): #Lethal poison applications increase kingsbane damage by 15% each, KB ticks 7 times every 2 sec if self.traits.kingsbane: poison_aps = 0 - if self.talents.agonizing_poison: - poison_aps = aps['agonizing_poison'] - elif self.settings.cycle.lethal_poison == 'dp': + if self.settings.cycle.lethal_poison == 'dp': poison_aps = aps['deadly_instant_poison'] elif self.settings.cycle.lethal_poison == 'wp': poison_aps = aps['wound_poison'] @@ -937,24 +928,6 @@ def assassination_dps_breakdown(self): subterfuge_garrote_uptime = (1 / self.settings.duration + aps['vanish']) / aps['garrote'] self.damage_modifiers.update_modifier_value('subterfuge_garrote', 1 + (1.25 * subterfuge_garrote_uptime)) - if self.talents.agonizing_poison: - stack_time = 5 / aps['agonizing_poison'] - max_time = self.settings.duration - stack_time - agonizing_poison_stacks = (max_time / self.settings.duration) * 5 + (stack_time / self.settings.duration) * 2.5 - - agonizing_poison_additive_mod = 1 + 0.01 * self.traits.master_alchemist - agonizing_poison_additive_mod += 0.02 * self.traits.poison_knives - agonizing_poison_additive_mod += (self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery'])) / 2 - - agonizing_poison_mod = 0.04 * agonizing_poison_stacks - agonizing_poison_mod *= agonizing_poison_additive_mod - if self.talents.master_poisoner: - agonizing_poison_mod *= 1.2 - if self.traits.surge_of_toxins: - agonizing_poison_mod *= surge_of_toxins_ap_multiplier - - self.damage_modifiers.update_modifier_value('agonizing_poison', 1 + agonizing_poison_mod) - if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5 / aps['fan_of_knives'] self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) @@ -971,9 +944,6 @@ def assassination_dps_breakdown(self): # There's no pandemic and it does not respect other modifiers. # Remaining damage is added on refresh. damage_breakdown['t19_2pc'] = damage_breakdown['mutilate'] * 0.3 - # This does double dip off Agonizing poison though - if self.talents.agonizing_poison: - damage_breakdown['t19_2pc'] *= 1 + agonizing_poison_mod if self.stats.gear_buffs.insignia_of_ravenholdt: damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) @@ -1235,9 +1205,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #Sinister Circulation if self.traits.sinister_circulation: poisons_per_second = 0 - if self.talents.agonizing_poison: - poisons_per_second = attacks_per_second['agonizing_poison'] - elif self.settings.cycle.lethal_poison == 'dp': + if self.settings.cycle.lethal_poison == 'dp': poisons_per_second = attacks_per_second['deadly_instant_poison'] elif self.settings.cycle.lethal_poison == 'wp': poisons_per_second = attacks_per_second['wound_poison'] From ff21375d67a62a707ffafdbaa62ab856693b0851 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:49:44 +0200 Subject: [PATCH 221/265] [Sub] Remove Etched in Shadow --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 4fb2a41..c51413e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1893,7 +1893,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2 + 0.01 * self.traits.etched_in_shadow, [], all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) From 4eaacd272a18b476cd7749ed88f9e947430721af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:50:40 +0200 Subject: [PATCH 222/265] [Sub] Remove Premeditation --- scripts/subtlety.py | 6 +++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/subtlety.py b/scripts/subtlety.py index e41665e..ec7b797 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -70,11 +70,11 @@ # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization', -'denial_of_the_half_giants', +'shadow_satyrs_walk', 'rogue_t19_2pc', 'rogue_t19_4pc', #'insignia_of_ravenholdt', -'mantle_of_the_master_assassin' +'mantle_of_the_master_assassin', ) #tier buffs located here # Set up a calcs object.. @@ -110,7 +110,7 @@ 'shadow_nova': 1, 'legionblade': 1, 'shadows_of_the_uncrowned': 1, - 'etched_in_shadow': 4, + 'weak_point': 4, 'shadows_whisper': 1, 'feeding_frenzy': 1, 'concordance_of_the_legionfall': 12, diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c51413e..18a572a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2416,7 +2416,7 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): net_energy -= attack_counts[cp_builder] * cp_builder_cost if cp_builder == 'shadowstrike': - net_cps += attack_counts['shadowstrike'] * (1 + self.talents.premeditation + self.shadow_blades_uptime) + net_cps += attack_counts['shadowstrike'] * (2 + self.shadow_blades_uptime) if self.stats.gear_buffs.rogue_t19_4pc: net_cps += attack_counts['shadowstrike'] * 0.3 elif cp_builder == 'shuriken_storm': From b8af54b9a477075ae7e6e36c31f3b632bd62a054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 13:58:30 +0200 Subject: [PATCH 223/265] [Assa] Fix Wound Poison Mastery Double Dip --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 18a572a..d35da69 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -792,8 +792,6 @@ def assassination_dps_breakdown(self): 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', 'deadly_instant_poison', 'wound_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) - # Wound poison is affected by Mastery twice, probably to offset that DP profits twice as well (direct + dot part) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons_2', None, ['wound_poison'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura From 6d9c56fb88fc8340ac198c1749b1e4b4d4a1d2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 14:09:52 +0200 Subject: [PATCH 224/265] [Assa] Add base functions for Toxic Blade --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 +++++-- shadowcraft/calcs/rogue/__init__.py | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index d35da69..ee117d9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -791,13 +791,13 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'fan_of_knives', 'hemorrhage', 'mutilate', 'poisoned_knife', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('potent_poisons', None, ['deadly_poison', - 'deadly_instant_poison', 'wound_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks'])) + 'deadly_instant_poison', 'wound_poison', 'envenom', 'poison_bomb', 'kingsbane', 'kingsbane_ticks', 'toxic_blade'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, ['death_from_above_pulse', 'death_from_above_strike', 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', - 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks'])) + 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks', 'toxic_blade'])) #time averaged vendetta modifier used for most things self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['garrote_ticks', 'mutilate', 'deadly_poison', @@ -806,6 +806,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('toxic_blade', None, ['deadly_poison', 'wound_poison', 'envenom', + 'from_the_shadows', 'poison_bomb', 'kingsbane', 'kingsbane_ticks', 'toxic_blade'])) + #talent specific modifiers if self.talents.elaborate_planning: self.damage_modifiers.register_modifier(modifiers.DamageModifier('elaborate_planning', None, [], all_damage=True)) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 86b9ed9..1ce1a6b 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -26,7 +26,7 @@ class RogueDamageCalculator(DamageCalculator): 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'poison_bomb', 'rupture_ticks', 'from_the_shadows', - 'wound_poison'] + 'wound_poison', 'toxic_blade'] outlaw_damage_sources = ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'greed', 'killing_spree', 'main_gauche', @@ -103,6 +103,7 @@ class RogueDamageCalculator(DamageCalculator): 'mutilate': (55., 'strike'), 'poisoned_knife': (40., 'strike'), 'rupture': (25., 'strike'), + 'toxic_blade': (20., 'strike'), #outlaw 'ambush': (60., 'strike'), 'between_the_eyes': (35., 'strike'), @@ -137,6 +138,7 @@ class RogueDamageCalculator(DamageCalculator): 'garrote': 15., 'kingsbane': 45., 'vendetta': 120., + 'toxic_blade': 25., #outlaw 'adrenaline_rush': 180., 'cannonball_barrage': 60., @@ -378,7 +380,7 @@ def hemorrhage_damage(self, ap): return 1 * self.get_weapon_damage('mh', ap) def mh_kingsbane_damage(self, ap): - return 2.4 * self.get_weapon_damage('mh', ap) * (1 + (0.3 * self.talents.master_poisoner)) + return 2.4 * self.get_weapon_damage('mh', ap) * (1 + (0.3 * self.talents.master_poisoner)) def oh_kingsbane_damage(self, ap): return 2.4 * self.oh_penalty() * self.get_weapon_damage('oh', ap) * (1 + (0.3 * self.talents.master_poisoner)) def kingsbane_tick_damage(self, ap): @@ -392,6 +394,9 @@ def oh_mutilate_damage(self, ap): def poisoned_knife_damage(self, ap): return 0.6 * ap + def toxic_blade_damage(self, ap): + return 6 * self.get_weapon_damage('mh', ap) + #Lumping 6 ticks together for simplicity def poison_bomb_damage(self, ap): return 6 * 1.2 * ap * (1 + (0.3 * self.talents.master_poisoner)) @@ -514,6 +519,7 @@ def get_formula(self, name): 'poison_bomb': self.poison_bomb_damage, 'rupture_ticks': self.rupture_tick_damage, 'wound_poison': self.wound_poison_damage, + 'toxic_blade': self.toxic_blade_damage, #outlaw 'ambush': self.ambush_damage, 'between_the_eyes': self.between_the_eyes_damage, From 0ceec3747fe18de4a6b7a007b4cb1acd48574cac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 14:14:54 +0200 Subject: [PATCH 225/265] [Assa] Forgot DP Instant modifier --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ee117d9..9019094 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -800,13 +800,13 @@ def assassination_dps_breakdown(self): 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks', 'toxic_blade'])) #time averaged vendetta modifier used for most things - self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['garrote_ticks', 'mutilate', 'deadly_poison', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_time_average', None, ['garrote_ticks', 'mutilate', 'deadly_poison', 'deadly_instant_poison', 'wound_poison', 'hemorrhage', 'envenom', 'fan_of_knives', 'death_from_above_pulse', 'poisoned_knife', 'from_the_shadows', 'poison_bomb', 'toxic_blade'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('toxic_blade', None, ['deadly_poison', 'wound_poison', 'envenom', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('toxic_blade', None, ['deadly_poison', 'deadly_instant_poison','wound_poison', 'envenom', 'from_the_shadows', 'poison_bomb', 'kingsbane', 'kingsbane_ticks', 'toxic_blade'])) #talent specific modifiers From 7bca8b8d39445aafdd934838687687ad3ad67fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 14:35:49 +0200 Subject: [PATCH 226/265] [Assa] Add Toxic Blade --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9019094..9b2e8f9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -877,9 +877,12 @@ def assassination_dps_breakdown(self): self.damage_modifiers.update_modifier_value('vendetta_exsang', 1 + (self.vendetta_multiplier * exsang_venn_uptime)) self.damage_modifiers.update_modifier_value('vendetta_kb', 1 + (self.vendetta_multiplier * kb_venn_uptime)) + if self.talents.toxic_blade: + tb_debuff_multiplier = 0.35 * (9 / self.get_spell_cd('toxic_blade')) + self.damage_modifiers.update_modifier_value('toxic_blade', 1 + tb_debuff_multiplier) + self.damage_modifiers.update_modifier_value('versatility', self.stats.get_versatility_multiplier_from_rating(rating=stats['versatility'])) self.damage_modifiers.update_modifier_value('potent_poisons', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) - self.damage_modifiers.update_modifier_value('potent_poisons_2', (1 + self.assassination_mastery_conversion * self.stats.get_mastery_from_rating(stats['mastery']))) #Lethal poison applications increase kingsbane damage by 15% each, KB ticks 7 times every 2 sec if self.traits.kingsbane: @@ -1131,6 +1134,14 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): net_energy_per_second -= dfa_cost_per_second duskwalker_expended_energy += dfa_cost_per_second + if self.talents.toxic_blade: + attacks_per_second['toxic_blade'] = 1 / self.get_spell_cd('toxic_blade') + tb_cps = (1 + crit_rates['toxic_blade']) * (self.settings.duration * attacks_per_second['toxic_blade']) + cp_budget += tb_cps + tb_cost_per_second = self.get_spell_cost('toxic_blade') * attacks_per_second['toxic_blade'] + net_energy_per_second -= tb_cost_per_second + duskwalker_expended_energy += tb_cost_per_second + #form whats left into a budget duskwalker_expended_energy *= self.settings.duration energy_budget = self.settings.duration * net_energy_per_second From 4c3fb4e2351c7140327cd04e5048a892167c4e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 14:50:30 +0200 Subject: [PATCH 227/265] [Assa] Update 7.2.5 tuned numbers --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 23 ++++++++++++-------- shadowcraft/calcs/rogue/__init__.py | 3 ++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9b2e8f9..a8c611a 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -795,7 +795,7 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.11, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.22, ['death_from_above_pulse', 'death_from_above_strike', 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks', 'toxic_blade'])) @@ -858,7 +858,7 @@ def assassination_dps_breakdown(self): #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.3, ['envenom'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.21, ['envenom'])) self.set_constants() @@ -934,12 +934,12 @@ def assassination_dps_breakdown(self): if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5 / aps['fan_of_knives'] - self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) + self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.25 * avg_dreadlord_stacks)) if self.stats.gear_buffs.rogue_t19_4pc: if aps['mutilate'] < 0.125: - t19_4pc_multiplier = 0.1 * (aps['mutilate'] / 0.125) - self.damage_modifiers.update_modifier_value('t19_4pc', 1.2 + t19_4pc_multiplier) + t19_4pc_multiplier = 0.07 * (aps['mutilate'] / 0.125) + self.damage_modifiers.update_modifier_value('t19_4pc', 1.14 + t19_4pc_multiplier) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) @@ -947,7 +947,7 @@ def assassination_dps_breakdown(self): # To prevent double dipping this is based on actual Mutilate damage. # There's no pandemic and it does not respect other modifiers. # Remaining damage is added on refresh. - damage_breakdown['t19_2pc'] = damage_breakdown['mutilate'] * 0.3 + damage_breakdown['t19_2pc'] = damage_breakdown['mutilate'] * 0.2 if self.stats.gear_buffs.insignia_of_ravenholdt: damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) @@ -1046,7 +1046,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_rupture_duration = 4 * (1 + avg_finisher_size) if self.talents.exsanguinate: #assume full pandemic on exsanged ruptures - exsang_rupture_duration = (1.3 * base_rupture_duration) / 2 + exsang_rupture_duration = (1.3 * base_rupture_duration) / 2.5 #rupture we're pandemicing from exsang_from_duration = 0.7 * base_rupture_duration normal_ruptures_per_exsang_cd = (self.exsang_cd - exsang_from_duration - exsang_rupture_duration) / base_rupture_duration @@ -1068,7 +1068,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): base_garrote_duration = 18. garrote_cooldown = self.get_spell_cd('garrote') if self.talents.exsanguinate: - exsang_garrote_duration = base_garrote_duration / 2 + exsang_garrote_duration = base_garrote_duration / 2.5 exsang_downtime = garrote_cooldown - exsang_garrote_duration normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd @@ -1142,6 +1142,11 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): net_energy_per_second -= tb_cost_per_second duskwalker_expended_energy += tb_cost_per_second + if self.talents.exsanguinate: + exsg_cost_per_second = self.get_spell_cost('exsanguinate') / self.exsang_cd + net_energy_per_second -= exsg_cost_per_second + duskwalker_expended_energy += exsg_cost_per_second + #form whats left into a budget duskwalker_expended_energy *= self.settings.duration energy_budget = self.settings.duration * net_energy_per_second @@ -1995,7 +2000,7 @@ def subtlety_dps_breakdown(self): if self.stats.gear_buffs.the_dreadlords_deceit: avg_dreadlord_stacks = 0.5 / aps['shuriken_storm'] - self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.35 * avg_dreadlord_stacks)) + self.damage_modifiers.update_modifier_value('the_dreadlords_deceit', 1 + (0.25 * avg_dreadlord_stacks)) damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 1ce1a6b..a674ba0 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -104,6 +104,7 @@ class RogueDamageCalculator(DamageCalculator): 'poisoned_knife': (40., 'strike'), 'rupture': (25., 'strike'), 'toxic_blade': (20., 'strike'), + 'exsanguinate': (25., 'buff'), #outlaw 'ambush': (60., 'strike'), 'between_the_eyes': (35., 'strike'), @@ -367,7 +368,7 @@ def envenom_damage(self, ap, cp): return .6 * cp * ap * (1 + (0.0333 * self.traits.toxic_blades)) def fan_of_knives_damage(self, ap): - return 1.079 * ap + return 1.5 * ap #Lumping 40 ticks together for simplicity def from_the_shadows_damage(self, ap): From 1ca82c976745d3a2154bbf9182f4b9e17d4c5acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 17 Jun 2017 14:54:17 +0200 Subject: [PATCH 228/265] [Assa] Fix TB modifier without the talent --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a8c611a..0c60198 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -806,8 +806,9 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_exsang', None, ['rupture_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('vendetta_kb', None, ['kingsbane', 'kingsbane_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('toxic_blade', None, ['deadly_poison', 'deadly_instant_poison','wound_poison', 'envenom', - 'from_the_shadows', 'poison_bomb', 'kingsbane', 'kingsbane_ticks', 'toxic_blade'])) + if self.talents.toxic_blade: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('toxic_blade', None, ['deadly_poison', 'deadly_instant_poison','wound_poison', 'envenom', + 'from_the_shadows', 'poison_bomb', 'kingsbane', 'kingsbane_ticks', 'toxic_blade'])) #talent specific modifiers if self.talents.elaborate_planning: From 571aa18798ec9bab7583259fe13f73191b3f7f84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 15:18:01 +0200 Subject: [PATCH 229/265] [Sub] General 7.2.5 tuning updates --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 16 ++++------------ shadowcraft/calcs/rogue/__init__.py | 18 ++++++++++++------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0c60198..082499f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -226,7 +226,7 @@ def set_constants(self): if self.stats.procs.old_war_prepot: self.stats.procs.old_war_prepot.icd = self.settings.duration - self.relentless_strikes_energy_return_per_cp = 5 #.20 * 25 + self.relentless_strikes_energy_return_per_cp = 6 #should only include bloodlust if the spec can average it in, deal with this later if self.race.berserking: @@ -1913,10 +1913,10 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.3 * self.settings.cycle.positional_uptime, ['backstab'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.2 * self.settings.cycle.positional_uptime, ['backstab'])) #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.09, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks'])) #talent specific modifiers @@ -2059,14 +2059,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) self.cp_budget = mfd_cps - - #Enveloping Shadows generates 1 bonus cp per 6 seconds regardless of cps - #2 net energy per 6 seconds from relentless strikes - if self.talents.enveloping_shadows: - self.cp_budget += self.settings.duration / 6 - self.energy_budget += (2 / 6) * self.settings.duration - self.dance_budget += (0.5 * self.settings.duration) / 60 - #setup timelines sod_duration = 35 nightblade_duration = 6 + (2 * self.settings.finisher_threshold) @@ -2159,7 +2151,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): shadow_techniques_cps = shadow_techniques_procs * shadow_techniques_cps_per_proc self.cp_budget += shadow_techniques_cps if self.traits.shadows_whisper: - self.energy_budget += 5 * shadow_techniques_procs + self.energy_budget += 8 * shadow_techniques_procs #vanish handling vanish_count = self.settings.duration / self.get_spell_cd('vanish') diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index a674ba0..139f383 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -454,14 +454,14 @@ def saber_slash_damage(self, ap): #subtlety #Ignore positional modifier for now def backstab_damage(self, ap): - return 3.7 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) + return 5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.the_quiet_knife)) #has two ranks def eviscerate_damage(self, ap, cp): return 1.472 * cp * ap def gloomblade_damage(self, ap): - return 5.25 * self.get_weapon_damage('mh', ap) * (1 + (0.0333 * self.traits.the_quiet_knife)) + return 5.75 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.the_quiet_knife)) def mh_goremaws_bite_damage(self, ap): return 10 * self.get_weapon_damage('mh', ap) @@ -471,7 +471,7 @@ def oh_goremaws_bite_damage(self, ap): #Nightblade doesn't actually scale with cps but passing cps for simplicity def nightblade_tick_damage(self, ap, cp): - return 1.38 * ap * (1 + (0.05 * self.traits.demons_kiss)) + return 0.9 * ap * (1 + (0.05 * self.traits.demons_kiss)) def shadowstrike_damage(self, ap): return 8.5 * self.get_weapon_damage('mh', ap) * (1 + (0.05 * self.traits.precision_strike)) @@ -483,7 +483,7 @@ def oh_shadow_blades_damage(self, ap): return self.oh_penalty() * self.get_weapon_damage('oh', ap, is_normalized=False) def second_shuriken_damage(self, ap): - return 0.338 * ap + return 1.0 * ap def shuriken_storm_damage(self, ap): return 0.7215 * ap @@ -495,7 +495,7 @@ def soul_rip_damage(self, ap): return 1.5 * ap def shadow_nova_damage(self, ap): - return 1.5 * ap + return 2.5 * ap def get_formula(self, name): formulas = { @@ -556,10 +556,12 @@ def get_formula(self, name): def get_spell_cost(self, ability, cost_mod=1.0): cost = self.ability_info[ability][0] * cost_mod if ability == 'shadowstrike': - cost -= 0.25 * (5 * self.traits.energetic_stabbing) + cost -= 0.5 * (2 * self.traits.energetic_stabbing) #Assume 5 yards away so 3 + 5/3 if self.stats.gear_buffs.shadow_satyrs_walk: cost -= 4.67 + elif ability == 'backstab': + cost -= 0.5 * (2 * self.traits.energetic_stabbing) return cost def get_spell_cd(self, ability): @@ -568,6 +570,10 @@ def get_spell_cd(self, ability): cd -= self.fortunes_boon_cdr[self.traits.fortunes_boon] elif ability == 'vendetta': cd -= self.master_assassin_cdr[self.traits.master_assassin] + elif ability == 'vanish' and self.traits.flickering_shadows: + cd -= 30 + elif ability == 'marked_for_death': + cd = 40 #Convergence of Fates Trinket cof = self.stats.procs.convergence_of_fates From 3805e5ee728cc032835e382a839174abba56a2f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 15:37:07 +0200 Subject: [PATCH 230/265] [Sub] Add Weak Point --- shadowcraft/calcs/rogue/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 139f383..8e4d438 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -340,6 +340,8 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability == 'death_from_above_strike': modifier *= 1.5 ability = 'eviscerate' + if ability in ['shadowstrike', 'backstab']: + crit_mod *= 1.0 + (0.08 * self.traits.weak_point) damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) From e72a6793e972306348686f978e900ebad8ef102f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 15:57:06 +0200 Subject: [PATCH 231/265] [Sub] New Symbols of Death --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 69 ++++--------------- shadowcraft/calcs/rogue/Aldriana/settings.py | 1 - .../calcs/rogue/Aldriana/settings_data.py | 11 --- shadowcraft/calcs/rogue/__init__.py | 2 +- 4 files changed, 15 insertions(+), 68 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 082499f..da605d0 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1911,7 +1911,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'], dmg_schools=['physical'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', 1.2, [], all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.2 * self.settings.cycle.positional_uptime, ['backstab'])) @@ -1977,6 +1977,10 @@ def subtlety_dps_breakdown(self): self.set_rppm_uptime(ift) infallible_trinket_mod = 1+(ift.uptime *0.10) + #Symbols of Death + sod_uptime = 10 / self.get_spell_cd('symbols_of_death') + self.damage_modifiers.update_modifier_value('symbols_of_death', 1 + 0.15 * sod_uptime) + #nightstalker if self.talents.nightstalker: ns_full_multiplier = 0.12 @@ -2028,10 +2032,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if crit_rates == None: crit_rates = self.get_crit_rates(current_stats) - use_sod = False - if self.settings.cycle.symbols_policy == 'always': - use_sod = True - #Set up initial energy budget haste_multiplier = self.get_haste_multiplier(current_stats) self.energy_regen = self.get_energy_regen(current_stats) @@ -2041,6 +2041,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.max_energy += 50 self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy + #Symbols of Death + self.energy_budget += 40 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) + #set initial dance budget self.dance_budget = 3 + self.settings.duration / 60 @@ -2060,51 +2063,19 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget = mfd_cps #setup timelines - sod_duration = 35 nightblade_duration = 6 + (2 * self.settings.finisher_threshold) if self.stats.gear_buffs.rogue_t19_2pc: nightblade_duration = 6 + (4 * self.settings.finisher_threshold) #Add attacks that could occur during first pass to aps attacks_per_second[self.dance_cp_builder] = 0 - attacks_per_second['symbols_of_death'] = 0 attacks_per_second['shadow_dance'] = 0 attacks_per_second['vanish'] = 0 - #Leaving space for opener handling for the first cast - sod_timeline = list(range(0, self.settings.duration, sod_duration)) nightblade_timeline = list(range(nightblade_duration, self.settings.duration, nightblade_duration)) - - dance_nb_uptime = 0.0 for finisher in ['nightblade', 'eviscerate']: attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] - #Timeline match of ruptures, fill in rest with eviscerate - if self.settings.cycle.dance_finishers_allowed: - dance_count = 0 - if finisher == 'nightblade': - joint, sod_timeline, nightblade_timeline = self.timeline_overlap(sod_timeline, nightblade_timeline, -0.3 * sod_duration) - dance_count = len(joint) - dance_nb_uptime = dance_count / len(nightblade_timeline) - elif finisher == 'eviscerate': - dance_count = len(sod_timeline) - sod_timeline = [] - - #Not using finishers during dance - else: - finisher=None - dance_count = len(sod_timeline) - sod_timeline = [] - - if dance_count: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=True, finisher=finisher) - self.energy_budget += dance_count * net_energy - self.cp_budget += dance_count * net_cps - self.dance_budget += ((3 * spent_cps * dance_count) / 60) - dance_count - #merge attack counts into attacks_per_second - self.rotation_merge(attacks_per_second, attack_counts, dance_count) - - #Add in ruptures not previously covered nightblade_count = len(nightblade_timeline) attacks_per_second['nightblade'][self.settings.finisher_threshold] += nightblade_count / self.settings.duration self.cp_budget -= self.settings.finisher_threshold * nightblade_count @@ -2157,9 +2128,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): vanish_count = self.settings.duration / self.get_spell_cd('vanish') #Treat subterfuge as a mini-dance if self.talents.subterfuge: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate', vanish=True) + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher='eviscerate', vanish=True) else: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None, vanish=True) + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy self.cp_budget += vanish_count * net_cps self.dance_budget += (3. * spent_cps* vanish_count) / 60 @@ -2167,9 +2138,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Generate one final dance templates if self.settings.cycle.dance_finishers_allowed: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher='eviscerate') + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher='eviscerate') else: - net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(use_sod=use_sod, finisher=None) + net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None) #Now lets make sure all our budgets are positive cp_per_builder = 1 + self.shadow_blades_uptime @@ -2334,9 +2305,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): stealth_time = 10. * attacks_per_second['shadow_dance'] + 8 * attacks_per_second['vanish'] self.mos_time = stealth_time / self.settings.duration - if self.talents.nightstalker: - self.dance_nb_uptime = dance_nb_uptime - for ability in list(attacks_per_second.keys()): if not attacks_per_second[ability]: del attacks_per_second[ability] @@ -2355,11 +2323,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.second_shuriken and 'shuriken_toss' in attacks_per_second: attacks_per_second['second_shuriken'] = 0.1 * attacks_per_second['shuriken_toss'] - #add SoD auto crits - if 'shadowstrike' in attacks_per_second: - sod_shadowstrikes = attacks_per_second['symbols_of_death'] / attacks_per_second['shadowstrike'] - crit_rates['shadowstrike'] = crit_rates['shadowstrike'] * (1. - sod_shadowstrikes) + sod_shadowstrikes - if self.talents.weaponmaster: for ability in attacks_per_second: if isinstance(attacks_per_second[ability], list): @@ -2377,7 +2340,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Computes the net energy and combo points from a shadow dance rotation #Returns net_energy, net_cps, spent_cps, dict of attack counts - def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): + def get_dance_resources(self, finisher=None, vanish=False): net_energy = 0 net_cps = 0 spent_cps = 0 @@ -2389,11 +2352,7 @@ def get_dance_resources(self, use_sod=False, finisher=None, vanish=False): cost_mod = 1.0 if self.talents.shadow_focus: - cost_mod = 0.8 - - if use_sod: - net_energy -= self.get_spell_cost('symbols_of_death', cost_mod=cost_mod) - attack_counts['symbols_of_death'] = 1 + cost_mod = 0.75 dance_gcds = 3 if self.talents.subterfuge: diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 1d4f6c2..36a5b1a 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -151,7 +151,6 @@ def __init__(self, **kwargs): settings_data.process_overrides(defaults, kwargs, self._cycle_type) self.cp_builder = kwargs.get('cp_builder', defaults['cp_builder']) - self.symbols_policy = kwargs.get('symbols_policy', defaults['symbols_policy']) self.dance_finishers_allowed = kwargs.get('dance_finishers_allowed', defaults['dance_finishers_allowed']) self.compute_cp_waste = kwargs.get('compute_cp_waste', defaults['compute_cp_waste']) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index b96bc97..ac6b835 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -233,17 +233,6 @@ def process_overrides(defaults_dict, params_dict, spec): 'shuriken_storm': 'Shuriken Storm', } }, - { - 'name': 'symbols_policy', - 'label': 'SoD Policy', - 'description': '', - 'type': 'dropdown', - 'default': 'just', - 'options': { - 'always': 'Use on cooldown', - 'just': 'Only use SoD when needed to refresh', - } - }, { 'name': 'dance_finishers_allowed', 'label': 'Use Finishers during Dance', diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 8e4d438..93c66c7 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -123,7 +123,6 @@ class RogueDamageCalculator(DamageCalculator): 'shadowstrike': (40., 'strike'), 'shuriken_storm': (35., 'strike'), 'shuriken_toss': (40., 'strike'), - 'symbols_of_death': (35., 'buff'), } ability_cds = { #general @@ -149,6 +148,7 @@ class RogueDamageCalculator(DamageCalculator): 'goremaws_bite': 60., 'shadow_dance': 60., 'shadow_blades': 180., + 'symbols_of_death': 30., } # Vendetta CDR for number of points in trait From c1f0e36951762cf08501cf8481c12ba1a64e129a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 16:13:05 +0200 Subject: [PATCH 232/265] [Sub] Update Subterfuge --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index da605d0..7fa0552 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1986,7 +1986,7 @@ def subtlety_dps_breakdown(self): ns_full_multiplier = 0.12 self.damage_modifiers.update_modifier_value('nightstalker_ssk', 1 + ns_full_multiplier) self.damage_modifiers.update_modifier_value('nightstalker_shuriken_storm', 1 + (0.12 * self.stealth_shuriken_uptime)) - self.damage_modifiers.update_modifier_value('nightstalker_nightblade', 1 + (0.12 * self.dance_nb_uptime)) + #Re-add? self.damage_modifiers.update_modifier_value('nightstalker_nightblade', 1 + (0.12 * self.dance_nb_uptime)) self.damage_modifiers.update_modifier_value('nightstalker_evis', 1 + (0.12 * self.stealth_evis_uptime)) #master of subtlety @@ -2127,7 +2127,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #vanish handling vanish_count = self.settings.duration / self.get_spell_cd('vanish') #Treat subterfuge as a mini-dance - if self.talents.subterfuge: + if self.talents.subterfuge or self.talents.nightstalker: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher='eviscerate', vanish=True) else: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None, vanish=True) @@ -2354,14 +2354,13 @@ def get_dance_resources(self, finisher=None, vanish=False): if self.talents.shadow_focus: cost_mod = 0.75 - dance_gcds = 3 - if self.talents.subterfuge: - if vanish: + dance_gcds = 1 + if vanish and self.talents.subterfuge: + dance_gcds += 3 + elif not vanish: + dance_gcds = 4 + if self.talents.subterfuge: dance_gcds += 1 - else: - dance_gcds += 2 - elif vanish: - dance_gcds = 1 max_dance_energy = dance_gcds * self.energy_regen + self.max_energy From 52ebd39d4f79776dab28e63d970e11d6ad3a1fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 16:48:41 +0200 Subject: [PATCH 233/265] [Sub] Relentless Strikes and Enveloping Shadows --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 28 +++++++++++--------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 7fa0552..1ee45c8 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2045,7 +2045,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_budget += 40 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) #set initial dance budget - self.dance_budget = 3 + self.settings.duration / 60 + self.dance_budget = 2 + self.settings.duration / 60 + if self.talents.enveloping_shadows: + self.dance_budget += 1 + deepening_shadows_cdr_per_cp = 2.5 if self.talents.enveloping_shadows else 1.5 shadow_blades_duration = 15. + (3.3333 * self.traits.soul_shadows) self.shadow_blades_uptime = shadow_blades_duration / self.get_spell_cd('shadow_blades') @@ -2079,8 +2082,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): nightblade_count = len(nightblade_timeline) attacks_per_second['nightblade'][self.settings.finisher_threshold] += nightblade_count / self.settings.duration self.cp_budget -= self.settings.finisher_threshold * nightblade_count - self.energy_budget += (40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost('nightblade')) * nightblade_count - self.dance_budget += (3 * self.settings.finisher_threshold * nightblade_count) / 60 + self.energy_budget += (self.relentless_strikes_energy_return_per_cp * self.settings.finisher_threshold - self.get_spell_cost('nightblade')) * nightblade_count + self.dance_budget += (deepening_shadows_cdr_per_cp * self.settings.finisher_threshold * nightblade_count) / self.get_spell_cd('shadow_dance') #Add in various cooldown abilities #This could be made better with timelining but for now simple time average will do @@ -2109,8 +2112,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += 1 / dfa_cd self.cp_budget -= self.max_spend_cps * dfa_count - self.energy_budget += (40 * (0.2 * self.max_spend_cps) - self.get_spell_cost('death_from_above')) * dfa_count - self.dance_budget += (3 * self.max_spend_cps * dfa_count) / 60 + self.energy_budget += (self.relentless_strikes_energy_return_per_cp * self.max_spend_cps - self.get_spell_cost('death_from_above')) * dfa_count + self.dance_budget += (deepening_shadows_cdr_per_cp * self.max_spend_cps * dfa_count) / self.get_spell_cd('shadow_dance') #Need to handle shadow techniques now to account for swing timer loss attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance @@ -2133,7 +2136,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy self.cp_budget += vanish_count * net_cps - self.dance_budget += (3. * spent_cps* vanish_count) / 60 + self.dance_budget += (deepening_shadows_cdr_per_cp * spent_cps * vanish_count) / self.get_spell_cd('shadow_dance') self.rotation_merge(attacks_per_second, attack_counts, vanish_count) #Generate one final dance templates @@ -2151,10 +2154,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): extra_evis = 0 extra_builders = 0 #Not enough dances, generate some more + cps_per_dance = self.get_spell_cd('shadow_dance') / deepening_shadows_cdr_per_cp + net_evis_cost = self.relentless_strikes_energy_return_per_cp * self.settings.finisher_threshold - self.get_spell_cost('eviscerate') if self.dance_budget<0: - cps_required = abs(self.dance_budget) * 20 + cps_required = abs(self.dance_budget) * cps_per_dance extra_evis += cps_required / self.settings.finisher_threshold - self.energy_budget += self.net_evis_cost + self.energy_budget += net_evis_cost #just subtract the cps because we'll fix those next self.cp_budget -= cps_required self.dance_budget = 0 @@ -2168,7 +2173,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): dance_count = abs(self.dance_budget) self.energy_budget += dance_count * net_energy self.cp_budget += dance_count * net_cps - self.dance_budget += (3 * spent_cps * dance_count / 60) - dance_count + self.dance_budget += (deepening_shadows_cdr_per_cp * spent_cps * dance_count / self.get_spell_cd('shadow_dance')) - dance_count #merge attack counts into attacks_per_second self.rotation_merge(attacks_per_second, attack_counts, dance_count) loop_counter += 1 @@ -2214,7 +2219,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Hopefully energy budget here isn't negative, if it is we're in trouble #Now we convert all the energy we have left into mini-cycles #Each mini-cycle contains enough 1 dance and generators+finishers for one dance - cps_per_dance = 20 finishers_per_minicycle = cps_per_dance / self.settings.finisher_threshold attack_counts_mini_cycle = attack_counts @@ -2228,7 +2232,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): loop_counter += 1 cps_to_generate = max(cps_per_dance - self.cp_budget, 0) builders_per_minicycle = cps_to_generate / cp_per_builder - mini_cycle_energy = 5 * finishers_per_minicycle - (cps_to_generate * energy_per_cp) + mini_cycle_energy = net_evis_cost * finishers_per_minicycle - (cps_to_generate * energy_per_cp) #add in dance energy mini_cycle_energy += net_energy if cps_to_generate: @@ -2365,7 +2369,7 @@ def get_dance_resources(self, finisher=None, vanish=False): max_dance_energy = dance_gcds * self.energy_regen + self.max_energy if finisher: - net_energy += 40 * (0.2 * self.settings.finisher_threshold) - self.get_spell_cost(finisher) + net_energy += self.relentless_strikes_energy_return_per_cp * self.settings.finisher_threshold - self.get_spell_cost(finisher) dance_gcds -= 1 net_cps -= self.settings.finisher_threshold attack_counts[finisher] = [0, 0, 0, 0, 0, 0, 0] From 5cfe70455e87867f1696331821bee8e9d2763a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 16:57:50 +0200 Subject: [PATCH 234/265] [Sub] Nightblade modifier --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 1ee45c8..e4d584b 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1915,6 +1915,9 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.2 * self.settings.cycle.positional_uptime, ['backstab'])) + #Assume 100% Nightblade uptime, TODO: AoE handling + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightblade', 1.15, [], all_damage=True)) + #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks'])) @@ -1932,11 +1935,9 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate'], blacklist=True, all_damage=True)) - if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) - #trait specific modifiers if self.traits.shadow_fangs: self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True, dmg_schools=['physical', 'shadow'])) From d98f829ff736c4e89371f0f1103bbbf0bf202d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 19 Jun 2017 17:15:53 +0200 Subject: [PATCH 235/265] Slightly rework and update Marked for Death --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 18 ++++++++++++------ .../calcs/rogue/Aldriana/settings_data.py | 2 +- shadowcraft/calcs/rogue/__init__.py | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e4d584b..058df4c 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1091,8 +1091,10 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): duskwalker_expended_energy = rupture_cost_per_second + garrote_cost_per_second #compute cooldowned talents: - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) - cp_budget += mfd_cps + if self.talents.marked_for_death: + mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') + mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + cp_budget += mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: fok_interval = 1 / 60 @@ -1735,8 +1737,9 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #consider MfD if self.talents.marked_for_death: - mfd_count = (1 + self.settings.marked_for_death_resets) / duration - bonus_cps += 5 * (1 + self.settings.marked_for_death_resets) * mfd_count + mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') + mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + bonus_cps += mfd_cps #consider Curse of the Dreadblades if self.traits.curse_of_the_dreadblades: @@ -2063,8 +2066,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] = haste_multiplier / self.stats.oh.speed * (1 - white_swing_downtime) #Set up initial combo point budget - mfd_cps = self.talents.marked_for_death * (self.settings.duration/60. * (5. + self.talents.deeper_strategem) * (1. + self.settings.marked_for_death_resets)) - self.cp_budget = mfd_cps + self.cp_budget = 0 + if self.talents.marked_for_death: + mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') + mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + self.cp_budget += mfd_cps #setup timelines nightblade_duration = 6 + (2 * self.settings.finisher_threshold) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index ac6b835..382a205 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -412,7 +412,7 @@ def process_overrides(defaults_dict, params_dict, spec): }, { 'name': 'marked_for_death_resets', - 'label': 'MfD Resets Per Minute', + 'label': 'Total number of additional MfD Resets', 'description': '', 'type': 'text', 'default': '0', diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 93c66c7..42a1f98 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -574,7 +574,7 @@ def get_spell_cd(self, ability): cd -= self.master_assassin_cdr[self.traits.master_assassin] elif ability == 'vanish' and self.traits.flickering_shadows: cd -= 30 - elif ability == 'marked_for_death': + elif self.spec == 'subtlety' and ability == 'marked_for_death': cd = 40 #Convergence of Fates Trinket From ead1276117a9adc110250db51b96b64cf3eee9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 12:14:32 +0200 Subject: [PATCH 236/265] [Sub] Add missing spells to spec aura --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 058df4c..a312389 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1923,7 +1923,8 @@ def subtlety_dps_breakdown(self): #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', - 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks'])) + 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks', 'shadow_blades', + 'second_shuriken', 'shadow_nova', 'goremaws_bite', 'soul_rip'])) #talent specific modifiers if self.talents.nightstalker: From a219d3b557876c119a8b744d2541814fd5aaaf1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 12:14:49 +0200 Subject: [PATCH 237/265] [Sub] Shadowstrike (Rank 2) --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index a312389..f8f73ec 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1921,6 +1921,9 @@ def subtlety_dps_breakdown(self): #Assume 100% Nightblade uptime, TODO: AoE handling self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightblade', 1.15, [], all_damage=True)) + #Shadowstrike (Rank 2) deals 25% more damage from stealth + self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadowstrike_rank_2', None, ['shadowstrike'])) + #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks', 'shadow_blades', @@ -1986,6 +1989,14 @@ def subtlety_dps_breakdown(self): sod_uptime = 10 / self.get_spell_cd('symbols_of_death') self.damage_modifiers.update_modifier_value('symbols_of_death', 1 + 0.15 * sod_uptime) + #Shadowstrike (Rank 2) deals 25% more damage from stealth + #Atm, we assume one Shadowstrike per Vanish + Opener unless Nightstalker was chosen + if aps['shadowstrike']: + buffed_shadowstrikes = 1 / self.settings.duration + if not self.talents.nightstalker and aps['vanish']: + buffed_shadowstrikes += aps['vanish'] / aps['shadowstrike'] + self.damage_modifiers.update_modifier_value('shadowstrike_rank_2', 1 + (0.25 * buffed_shadowstrikes)) + #nightstalker if self.talents.nightstalker: ns_full_multiplier = 0.12 From 97eb03d754a15ff3e0bf72852577b712f1fa8a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 13:00:32 +0200 Subject: [PATCH 238/265] [Sub] Dark Shadow --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 33 ++++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f8f73ec..ee3c026 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1933,18 +1933,25 @@ def subtlety_dps_breakdown(self): if self.talents.nightstalker: self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_shuriken_storm', None, ['shuriken_storm'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_nightblade', None, ['nightblade_ticks'])) + #self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_nightblade', None, ['nightblade_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_evis', None, ['eviscerate'])) if self.talents.master_of_subtlety: self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_shuriken_storm', None, ['shuriken_storm'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate'], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate', 'shuriken_storm'], blacklist=True, all_damage=True)) if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) + if self.talents.dark_shadow: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_ssk', None, ['shadowstrike'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_storm', None, ['shuriken_storm'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_evis', None, ['eviscerate'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_other', None, ['shadowstrike', 'shuriken_storm', 'eviscerate', + 'backstab', 'goremaws_bite'], blacklist=True, all_damage=True)) + #trait specific modifiers if self.traits.shadow_fangs: self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True, dmg_schools=['physical', 'shadow'])) @@ -2014,6 +2021,22 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.update_modifier_value('mos_evis', 1 + (0.1 * self.stealth_evis_uptime)) self.damage_modifiers.update_modifier_value('mos_other', mos_uptime_multipler) + if self.talents.dark_shadow: + dsh_uptime = aps['shadow_dance'] * (5 if self.talents.subterfuge else 4) + dsh_ssk_uptime = 0 + dsh_storm_uptime = 0 + dsh_evis_uptime = 0 + if 'shadowstrike' in self.dark_shadow_attacks_per_dance and 'shadowstrike' in aps: + dsh_ssk_uptime = self.dark_shadow_attacks_per_dance['shadowstrike'] * aps['shadow_dance'] / aps['shadowstrike'] + if 'shuriken_storm' in self.dark_shadow_attacks_per_dance and 'shuriken_storm' in aps: + dsh_storm_uptime = self.dark_shadow_attacks_per_dance['shuriken_storm'] * aps['shadow_dance'] / aps['shuriken_storm'] + if 'eviscerate' in self.dark_shadow_attacks_per_dance and 'eviscerate' in aps: + dsh_evis_uptime = sum(self.dark_shadow_attacks_per_dance['eviscerate']) * aps['shadow_dance'] / sum(aps['eviscerate']) + self.damage_modifiers.update_modifier_value('dark_shadow_ssk', 1 + (0.3 * dsh_ssk_uptime)) + self.damage_modifiers.update_modifier_value('dark_shadow_storm', 1 + (0.3 * dsh_storm_uptime)) + self.damage_modifiers.update_modifier_value('dark_shadow_evis', 1 + (0.3 * dsh_evis_uptime)) + self.damage_modifiers.update_modifier_value('dark_shadow_other', 1 + (0.3 * dsh_uptime)) + if self.traits.finality: #4% increase per cp applied every to every other finality_damage_boost = 1 + 0.02 * self.settings.finisher_threshold @@ -2094,7 +2117,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['shadow_dance'] = 0 attacks_per_second['vanish'] = 0 - nightblade_timeline = list(range(nightblade_duration, self.settings.duration, nightblade_duration)) for finisher in ['nightblade', 'eviscerate']: attacks_per_second[finisher] = [0, 0, 0, 0, 0, 0, 0] @@ -2164,6 +2186,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): else: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None) + #Remember dark shadow buffed abilties per one dance + self.dark_shadow_attacks_per_dance = {} + if self.talents.dark_shadow: + self.dark_shadow_attacks_per_dance = dict(attack_counts) + #Now lets make sure all our budgets are positive cp_per_builder = 1 + self.shadow_blades_uptime if self.cp_builder == 'shuriken_storm': From 3eade963895176cb6c18acb799a4f011f9cc8bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 13:28:12 +0200 Subject: [PATCH 239/265] [Sub] Shuriken Combo --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index ee3c026..851c352 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1924,6 +1924,9 @@ def subtlety_dps_breakdown(self): #Shadowstrike (Rank 2) deals 25% more damage from stealth self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadowstrike_rank_2', None, ['shadowstrike'])) + #Shuriken Combo + self.damage_modifiers.register_modifier(modifiers.DamageModifier('focused_shurikens', None, ['eviscerate'])) + #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks', 'shadow_blades', @@ -1998,11 +2001,23 @@ def subtlety_dps_breakdown(self): #Shadowstrike (Rank 2) deals 25% more damage from stealth #Atm, we assume one Shadowstrike per Vanish + Opener unless Nightstalker was chosen - if aps['shadowstrike']: + if 'shadowstrike' in aps: buffed_shadowstrikes = 1 / self.settings.duration if not self.talents.nightstalker and aps['vanish']: buffed_shadowstrikes += aps['vanish'] / aps['shadowstrike'] self.damage_modifiers.update_modifier_value('shadowstrike_rank_2', 1 + (0.25 * buffed_shadowstrikes)) + else: + self.damage_modifiers.update_modifier_value('shadowstrike_rank_2', 1) + + #Focused Shurikens gets one stack up to 5 per additional enemy hit and increases Evi dmg by 10% per stack + if 'shuriken_storm' in aps: + storms_per_evis = aps['shuriken_storm'] / sum(aps['eviscerate']) + stacks_per_evis = min(5, storms_per_evis * self.settings.num_boss_adds) + print(stacks_per_evis) + self.damage_modifiers.update_modifier_value('focused_shurikens', 1 + (0.1 * stacks_per_evis)) + else: + self.damage_modifiers.update_modifier_value('focused_shurikens', 1) + #nightstalker if self.talents.nightstalker: From 310fce735b751b9349fa09f86f1cbee3ea0aa04c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 13:37:21 +0200 Subject: [PATCH 240/265] [Assa] T20 Bonuses --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 ++++- shadowcraft/calcs/rogue/__init__.py | 4 ++++ shadowcraft/objects/stats.py | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 851c352..2223344 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -857,6 +857,9 @@ def assassination_dps_breakdown(self): 'poison_bomb', 'from_the_shadows', 'wound_poison'], dmg_schools=['arcane', 'fire', 'frost', 'holy', 'nature', 'shadow'])) + if self.stats.gear_buffs.rogue_t20_2pc: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('t20_2pc', 1.4, ['garrote_ticks'])) + #Assume 100% uptime of Rupture, Garrote and Mutilated Flesh (2pc bleed) if self.stats.gear_buffs.rogue_t19_4pc: self.damage_modifiers.register_modifier(modifiers.DamageModifier('t19_4pc', 1.21, ['envenom'])) @@ -1070,7 +1073,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): garrote_cooldown = self.get_spell_cd('garrote') if self.talents.exsanguinate: exsang_garrote_duration = base_garrote_duration / 2.5 - exsang_downtime = garrote_cooldown - exsang_garrote_duration + exsang_downtime = max(0, garrote_cooldown - exsang_garrote_duration) normal_garrote_per_exsang = (self.exsang_cd - garrote_cooldown) / base_garrote_duration attacks_per_second['garrote'] = (1 + normal_garrote_per_exsang) / self.exsang_cd attacks_per_second['garrote_ticks'] = 2/3 * float(exsang_garrote_duration) / self.exsang_cd + \ diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 42a1f98..9f839b2 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -564,6 +564,8 @@ def get_spell_cost(self, ability, cost_mod=1.0): cost -= 4.67 elif ability == 'backstab': cost -= 0.5 * (2 * self.traits.energetic_stabbing) + elif ability == 'garrote' and self.stats.gear_buffs.rogue_t20_4pc: + cost -= 25 return cost def get_spell_cd(self, ability): @@ -576,6 +578,8 @@ def get_spell_cd(self, ability): cd -= 30 elif self.spec == 'subtlety' and ability == 'marked_for_death': cd = 40 + elif ability == 'garrote' and self.stats.gear_buffs.rogue_t20_4pc: + cd -= 12 #Convergence of Fates Trinket cof = self.stats.procs.convergence_of_fates diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 861c5fd..88b7746 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -206,6 +206,8 @@ class GearBuffs(object): 'rogue_t18_4pc_lfr', # Energy increased by 20, 5% increase in energy regen 'rogue_t19_2pc', # Mutilate causes 30% bleed over 8 seconds, Nightblades lasts additional 2 seconds per CP 'rogue_t19_4pc', # 10% envenom damage per bleed, 30% SSk generates additional CP if nightblade up + 'rogue_t20_2pc', # Garrote deals 40% increased damage, Symbols of Death increases your damage done by an additional 10%. + 'rogue_t20_4pc', # Garrote's cost is reduced by 25 Energy and cooldown is reduced by 12 sec, Symbols of Death has 5 sec reduced cooldown and generates 2 Energy per sec while active. 'jacins_ruse_2pc', # Proc 3000 mastery for 15s, 1 rppm 'march_of_the_legion_2pc', # Proc 35K damage when fighting demons, 6+Haste RPPM 'journey_through_time_2pc', # The effect from Chrono Shard now increases your movement speed by 30%, and grants an additional 1000 Haste. @@ -225,6 +227,10 @@ class GearBuffs(object): 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after 'cinidaria_the_symbiote', #30% additional damage to enemies above 90% health 'sephuzs_secret', #2% haste + 'the_empty_crown', + 'the_first_of_the_dead', + 'the_curse_of_restlessness', #NYI + 'soul_of_the_shadowblade', #Other 'jeweled_signet_of_melandrus', #Increases your autoattack damage by 10%. 'gnawed_thumb_ring', #Use: Have a nibble, increasing your healing and magic damage done by 5% for 12 sec. (3 Min Cooldown) From ff1870150d32afa2a0ff2cda1d8bfdbd35a58d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 13:43:39 +0200 Subject: [PATCH 241/265] [Sub] T20 Bonuses --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 5 ++++- shadowcraft/calcs/rogue/__init__.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2223344..137d9af 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2000,7 +2000,8 @@ def subtlety_dps_breakdown(self): #Symbols of Death sod_uptime = 10 / self.get_spell_cd('symbols_of_death') - self.damage_modifiers.update_modifier_value('symbols_of_death', 1 + 0.15 * sod_uptime) + sod_modifier = 0.25 if self.stats.gear_buffs.rogue_t20_2pc else 0.15 + self.damage_modifiers.update_modifier_value('symbols_of_death', 1 + sod_modifier * sod_uptime) #Shadowstrike (Rank 2) deals 25% more damage from stealth #Atm, we assume one Shadowstrike per Vanish + Opener unless Nightstalker was chosen @@ -2100,6 +2101,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Symbols of Death self.energy_budget += 40 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) + if self.stats.gear_buffs.rogue_t20_4pc: + self.energy_budget += 20 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) #set initial dance budget self.dance_budget = 2 + self.settings.duration / 60 diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 9f839b2..7b0a777 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -580,6 +580,8 @@ def get_spell_cd(self, ability): cd = 40 elif ability == 'garrote' and self.stats.gear_buffs.rogue_t20_4pc: cd -= 12 + elif ability == 'symbols_of_death' and self.stats.gear_buffs.rogue_t20_4pc: + cd -= 5 #Convergence of Fates Trinket cof = self.stats.procs.convergence_of_fates From c6f04a5d2112e4784cdbeab693241f3af6afe239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 14:02:27 +0200 Subject: [PATCH 242/265] New legendaries Soul of the Shadowblade The Empty Crown The First of the Dead --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 19 +++++++++++++------ shadowcraft/objects/stats.py | 6 +++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 137d9af..557c4e2 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -178,7 +178,7 @@ def get_energy_regen(self, current_stats, buried=False, ar=False, alacrity_stack regen *= 1.195 if ar and self.traits.loaded_dice else 1.15 else: alacrity_stacks = 0 - if self.talents.vigor: + if self.talents.vigor or self.stats.gear_buffs.soul_of_the_shadowblade: regen *= 1.1 regen *= self.get_haste_multiplier(current_stats) + 0.01 * alacrity_stacks return regen @@ -1117,6 +1117,8 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): cp_budget += avg_cp_per_kb * attacks_per_second['kingsbane'] * self.settings.duration net_energy_per_second -= self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] duskwalker_expended_energy += self.get_spell_cost('kingsbane') * attacks_per_second['kingsbane'] + if self.stats.gear_buffs.the_empty_crown: + net_energy_per_second += 40 * attacks_per_second['kingsbane'] if self.talents.hemorrhage: hemos_per_second = 1 / 20 @@ -1157,7 +1159,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): duskwalker_expended_energy *= self.settings.duration energy_budget = self.settings.duration * net_energy_per_second max_energy = 120 - if self.talents.vigor: + if self.talents.vigor or self.stats.gear_buffs.soul_of_the_shadowblade: max_energy += 50 energy_budget += max_energy #As of Patch 7.2 we get 60 energy + 60 over 2s, assume no loss @@ -1864,7 +1866,7 @@ def get_mg_cp_regen_from_haste(self, haste_multiplier): def get_max_energy(self): self.max_energy = 100 - if self.talents.vigor: + if self.talents.vigor or self.stats.gear_buffs.soul_of_the_shadowblade: self.max_energy += 50 if self.race.expansive_mind: self.max_energy = round(self.max_energy * 1.05, 0) @@ -2095,14 +2097,15 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.energy_regen = self.get_energy_regen(current_stats) self.max_energy = 100. - if self.talents.vigor: + if self.talents.vigor or self.stats.gear_buffs.soul_of_the_shadowblade: self.max_energy += 50 self.energy_budget = self.settings.duration * self.energy_regen + self.max_energy #Symbols of Death - self.energy_budget += 40 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) + sod_casts = 1 + self.settings.duration / self.get_spell_cd('symbols_of_death') + self.energy_budget += 40 * sod_casts if self.stats.gear_buffs.rogue_t20_4pc: - self.energy_budget += 20 * (1 + self.settings.duration / self.get_spell_cd('symbols_of_death')) + self.energy_budget += 20 * sod_casts #set initial dance budget self.dance_budget = 2 + self.settings.duration / 60 @@ -2128,6 +2131,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) self.cp_budget += mfd_cps + #Very VERY simple implementation for The First of the Dead legendary (this should be handled better) + if self.stats.gear_buffs.the_first_of_the_dead: + self.cp_budget += (6 if self.talents.anticipation else 3) * sod_casts + #setup timelines nightblade_duration = 6 + (2 * self.settings.finisher_threshold) if self.stats.gear_buffs.rogue_t19_2pc: diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 88b7746..18bdb08 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -227,10 +227,10 @@ class GearBuffs(object): 'mantle_of_the_master_assassin', #100% crit during stealth and for 6 seconds after 'cinidaria_the_symbiote', #30% additional damage to enemies above 90% health 'sephuzs_secret', #2% haste - 'the_empty_crown', - 'the_first_of_the_dead', + 'the_empty_crown', #Kingsbane generates 40 Energy over 5 sec. + 'the_first_of_the_dead', #For 2 sec after activating Symbols of Death, Shadowstrike generates 3 additional combo points and Backstab generates 4 additional combo points. 'the_curse_of_restlessness', #NYI - 'soul_of_the_shadowblade', + 'soul_of_the_shadowblade', #Gain the Vigor talent. #Other 'jeweled_signet_of_melandrus', #Increases your autoattack damage by 10%. 'gnawed_thumb_ring', #Use: Have a nibble, increasing your healing and magic damage done by 5% for 12 sec. (3 Min Cooldown) From 8796081585524f7dff950bd00578221590868d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 14:42:06 +0200 Subject: [PATCH 243/265] Add ToS trinkets --- shadowcraft/calcs/rogue/__init__.py | 6 +++ shadowcraft/objects/proc_data.py | 79 +++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 7b0a777..ab9cd2b 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -643,3 +643,9 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates damage_breakdown[proc.proc_name] += damage_per_use / proc.icd else: damage_breakdown[proc.proc_name] = damage_per_use / proc.icd + + # Specter of Betrayal + specter_of_betrayal = self.stats.procs.specter_of_betrayal + if specter_of_betrayal: + num_torrents = (1 + 2 * (self.settings.duration / specter_of_betrayal.icd)) / self.settings.duration + damage_breakdown[specter_of_betrayal.proc_name] = self.get_proc_damage_contribution(specter_of_betrayal, num_torrents, current_stats, ap, modifier_dict) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index f653237..7d2265a 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -311,6 +311,20 @@ 'trigger': 'all_attacks', }, + 'engine_of_eradication': { #Equip: Your auto attacks have a chance to increase your Strength or Agility, based on your specialization, by 5424 for 12 sec, and expel orbs of fel energy. Collecting an orb increases the duration of this effect by 3 sec. + 'stat':'stats', + 'value': {'agi': 0}, + 'duration': 24, # 12 + 4*3 + 'proc_name': 'Demonic Vigor', + 'scaling': 1.314659, + 'crm_scales': False, + 'item_level': 900, + 'source': 'trinket', + 'type': 'rppm', + 'proc_rate': 1, + 'trigger': 'all_attacks', + }, + 'entwined_elemental_foci': { #Equip: Your attacks have a chance to grant you a Fiery, Frost, or Arcane enchants for 20 sec. 'stat':'stats', 'value': {'haste': 0, 'crit': 0, 'mastery': 0}, #TODO: needs special modeling, you get only one stat per proc, but can have multiple at the same time @@ -402,6 +416,22 @@ 'trigger': 'all_attacks' }, + 'infernal_cinders': { #Your melee attacks have a chance to deal an additional 82910 Fire damage. The critical strike chance of this damage is increased by 10% for each ally within 10 yds who bears this item. + 'stat':'spell_damage', + 'value': 0, #rpp-scaled + 'duration': 0, + 'proc_name': 'Infernal Cinders', + 'dmg_school': 'fire', + 'scaling': 20.09453, + 'item_level': 900, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 10, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'kiljaedens_burning_wish': { #Use: Launch a vortex of destruction that seeks your current enemy. When it reaches the target, it explodes, dealing a critical strike to all enemies within 10 yds for X Fire damage. (1 Min, 15 Sec Cooldown) 'stat':'spell_damage', 'dmg_school': 'fire', @@ -526,6 +556,22 @@ 'can_crit': True }, + 'specter_of_betrayal': { #Use: Create a Dread Reflection at your location for 1 min and cause each of your Dread Reflections to unleash a torrent of magic that deals (111484 * 4) Shadow damage over 3 sec, split evenly among nearby enemies. (45 Sec Cooldown) + 'stat':'spell_damage', + 'dmg_school': 'shadow', + 'value': 0, #rpp-scaled + 'duration': 0, #modeled all-in-one + 'proc_name': 'Dread Torrent', + 'scaling': 4 * 24.6155, + 'item_level': 900, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 45, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'spiked_counterweight': { #Your melee attacks have a chance to deal X Physical damage and increase all damage the target takes from you by 15% for 15 sec, up to Y extra damage dealt. 'stat':'physical_damage', 'value': 0, #rpp-scaled @@ -668,6 +714,39 @@ 'trigger': 'all_attacks', }, + 'umbral_moonglaives': { #Use: Conjure a storm of glaives at your location, causing 125220 Arcane damage every 1 sec to nearby enemies. After 8 sec the glaives shatter, causing another 313052 Arcane damage to enemies in the area. (1 Min, 30 Sec Cooldown) + 'stat':'spell_damage', + 'dmg_school': 'arcane', + 'value': 0, #rpp-scaled + 'aoe': True, + 'duration': 0, #modeled all-in-one + 'proc_name': 'Umbral Glaive Storm', + 'scaling': 8 * 30.34914 + 75.87286, + 'item_level': 900, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 90, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + + 'vial_of_ceaseless_toxins': { #Use: Inflict 225700 Shadow damage to an enemy in melee range, plus 366752 damage over 20 sec. If they die while this effect is active, the cooldown of this ability is reduced by 45 sec. (1 Min Cooldown) + 'stat':'spell_damage', + 'dmg_school': 'shadow', + 'value': 0, #rpp-scaled + 'duration': 0, #modeled all-in-one + 'proc_name': 'Ceaseless Toxin', + 'scaling': 10 * 8.888798 + 54.70188, + 'item_level': 900, + 'type': 'icd', + 'source': 'trinket', + 'proc_rate': 1, + 'icd': 60, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting X Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) 'stat':'physical_damage', 'value': 0, #rpp-scaled From 7e2cd635146258f5993bf61267ce1aebe0579717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 20 Jun 2017 14:51:17 +0200 Subject: [PATCH 244/265] Bump versions --- setup.py | 2 +- shadowcraft/calcs/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 8381bcb..3b9b291 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ShadowCraft-Engine', url='http://github.com/ShadowCraft/ShadowCraft-Engine/', - version='7.2.0', + version='7.2.5', packages=[ 'shadowcraft', 'shadowcraft.calcs', diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 565d7bd..72d9bcb 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -35,7 +35,7 @@ class DamageCalculator(object): normalize_ep_stat = None def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '7.2.0' # should reflect the game patch being targetted + self.WOW_BUILD_TARGET = '7.2.5' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = self.get_version_string() self.tools = class_data.Util() self.stats = stats From bf836016531ac2943fc85a117e13035efb126a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 8 Jul 2017 14:22:45 +0200 Subject: [PATCH 245/265] General DfA and Sub AoE fix --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 14 +++++++------- shadowcraft/calcs/rogue/__init__.py | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 557c4e2..2bfdb48 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2078,7 +2078,7 @@ def subtlety_dps_breakdown(self): #add AoE damage sources: if self.settings.num_boss_adds: for key in damage_breakdown: - if key in ['shuriken_toss', 'second_shuriken', 'shadow_nova']: + if key in ['shuriken_storm', 'second_shuriken', 'shadow_nova']: damage_breakdown[key] *= 1 + self.settings.num_boss_adds if self.stats.gear_buffs.cinidaria_the_symbiote: @@ -2176,13 +2176,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd attacks_per_second['death_from_above_strike'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_strike'][self.max_spend_cps] += 1 / dfa_cd + attacks_per_second['death_from_above_strike'][self.settings.finisher_threshold] += 1 / dfa_cd attacks_per_second['death_from_above_pulse'] = [0, 0, 0, 0, 0, 0, 0] - attacks_per_second['death_from_above_pulse'][self.max_spend_cps] += 1 / dfa_cd + attacks_per_second['death_from_above_pulse'][self.settings.finisher_threshold] += 1 / dfa_cd - self.cp_budget -= self.max_spend_cps * dfa_count - self.energy_budget += (self.relentless_strikes_energy_return_per_cp * self.max_spend_cps - self.get_spell_cost('death_from_above')) * dfa_count - self.dance_budget += (deepening_shadows_cdr_per_cp * self.max_spend_cps * dfa_count) / self.get_spell_cd('shadow_dance') + self.cp_budget -= self.settings.finisher_threshold * dfa_count + self.energy_budget += (self.relentless_strikes_energy_return_per_cp * self.settings.finisher_threshold - self.get_spell_cost('death_from_above')) * dfa_count + self.dance_budget += (deepening_shadows_cdr_per_cp * self.settings.finisher_threshold * dfa_count) / self.get_spell_cd('shadow_dance') #Need to handle shadow techniques now to account for swing timer loss attacks_per_second['mh_autoattack_hits'] = attacks_per_second['mh_autoattacks'] * self.dw_mh_hit_chance @@ -2325,7 +2325,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] = finishers_per_minicycle self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) self.energy_budget += mini_cycle_energy * mini_cycle_count - self.cp_budget += net_cps - 20 + cps_to_generate + self.cp_budget += net_cps - cps_per_dance + cps_to_generate #Update energy budget with alacrity and haste procs if self.talents.alacrity: old_alacrity_regen = self.energy_regen * (1 + (alacrity_stacks *0.02)) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index ab9cd2b..27d89a0 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -295,7 +295,10 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability == 'death_from_above_strike': modifier *= 1.5 ability = 'envenom' - damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + if ability in damage_breakdown: + damage_breakdown[ability] += self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + else: + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) if self.spec == 'outlaw': for ability in self.outlaw_damage_sources: @@ -320,7 +323,10 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da crit_mod = self.crit_damage_modifiers(3) if ability == 'saber_slash' and self.traits.sabermetrics: crit_mod = self.crit_damage_modifiers(1 + self.traits.sabermetrics * 0.05) - damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + if ability in damage_breakdown: + damage_breakdown[ability] += self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + else: + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) if self.spec == 'subtlety': @@ -343,7 +349,14 @@ def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, da if ability in ['shadowstrike', 'backstab']: crit_mod *= 1.0 + (0.08 * self.traits.weak_point) - damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + if ability in damage_breakdown: + damage_breakdown[ability] += self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + else: + damage_breakdown[ability] = self.get_ability_dps(average_ap, ability, aps, crits, modifier, crit_mod, both_hands, cps) + + #DfA AoE + if 'death_from_above_pulse' in damage_breakdown: + damage_breakdown['death_from_above_pulse'] *= 1 + self.settings.num_boss_adds return damage_breakdown, additional_info From 75571f853ab5dabdfa4494749a30416644392800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 8 Jul 2017 15:11:10 +0200 Subject: [PATCH 246/265] [Sub] DfA cooldown stacking --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 39 +++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2bfdb48..2ca135f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1686,7 +1686,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll if self.talents.death_from_above and not ar: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time - (10 * true_bearing) dfa_count = duration / dfa_cd - dfa_lost_swings = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / attack_speed_multiplier) + dfa_lost_swings = self.lost_swings_from_swing_delay(1.5, self.stats.mh.speed / attack_speed_multiplier) dfa_energy_lost = dfa_lost_swings * (self.main_gauche_proc_rate * self.combat_potency_from_mg + self.combat_potency_regen_per_oh) energy_budget -= dfa_energy_lost @@ -1918,7 +1918,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('versatility', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('armor', self.armor_mitigation_multiplier(), ['death_from_above_pulse', 'death_from_above_strike', 'shuriken_storm', 'eviscerate', 'backstab', 'shadowstrike', 'shuriken_toss', 'autoattacks'], dmg_schools=['physical'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('executioner', None, ['eviscerate', 'nightblade_ticks', 'death_from_above_strike', 'death_from_above_pulse'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('symbols_of_death', None, [], all_damage=True)) self.damage_modifiers.register_modifier(modifiers.DamageModifier('stealth_shuriken_storm', None, ['shuriken_storm', 'second_shuriken'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('backstab_positional', 1 + 0.2 * self.settings.cycle.positional_uptime, ['backstab'])) @@ -1930,7 +1930,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadowstrike_rank_2', None, ['shadowstrike'])) #Shuriken Combo - self.damage_modifiers.register_modifier(modifiers.DamageModifier('focused_shurikens', None, ['eviscerate'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('focused_shurikens', None, ['eviscerate', 'death_from_above_strike'])) #Generic tuning aura self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', @@ -1948,7 +1948,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_ssk', None, ['shadowstrike'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_shuriken_storm', None, ['shuriken_storm'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) - self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate', 'shuriken_storm'], blacklist=True, all_damage=True)) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate', 'shuriken_storm', 'death_from_above_strike'], blacklist=True, all_damage=True)) if self.talents.deeper_strategem: self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) @@ -1958,7 +1958,10 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_storm', None, ['shuriken_storm'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_evis', None, ['eviscerate'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_other', None, ['shadowstrike', 'shuriken_storm', 'eviscerate', - 'backstab', 'goremaws_bite'], blacklist=True, all_damage=True)) + 'backstab', 'goremaws_bite', 'death_from_above_strike'], blacklist=True, all_damage=True)) + + if self.talents.death_from_above: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('dfa_mods', None, ['death_from_above_strike'])) #trait specific modifiers if self.traits.shadow_fangs: @@ -2017,7 +2020,10 @@ def subtlety_dps_breakdown(self): #Focused Shurikens gets one stack up to 5 per additional enemy hit and increases Evi dmg by 10% per stack if 'shuriken_storm' in aps: - storms_per_evis = aps['shuriken_storm'] / sum(aps['eviscerate']) + if self.talents.death_from_above: + storms_per_evis = aps['shuriken_storm'] / (sum(aps['eviscerate']) + sum(aps['death_from_above_strike'])) + else: + storms_per_evis = aps['shuriken_storm'] / sum(aps['eviscerate']) stacks_per_evis = min(5, storms_per_evis * self.settings.num_boss_adds) print(stacks_per_evis) self.damage_modifiers.update_modifier_value('focused_shurikens', 1 + (0.1 * stacks_per_evis)) @@ -2058,6 +2064,17 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.update_modifier_value('dark_shadow_evis', 1 + (0.3 * dsh_evis_uptime)) self.damage_modifiers.update_modifier_value('dark_shadow_other', 1 + (0.3 * dsh_uptime)) + # Special DfA mod handling + if self.talents.death_from_above: + dfa_mod = 1 + if self.talents.dark_shadow: + dfa_mod *= 1.3 + if self.talents.nightstalker: + dfa_mod *= 1.12 + elif self.talents.master_of_subtlety: + dfa_mod *= mos_uptime_multipler + self.damage_modifiers.update_modifier_value('dfa_mods', dfa_mod) + if self.traits.finality: #4% increase per cp applied every to every other finality_damage_boost = 1 + 0.02 * self.settings.finisher_threshold @@ -2128,7 +2145,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget = 0 if self.talents.marked_for_death: mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') - mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + mfd_cps = (6 if self.talents.deeper_strategem else 5) * (mfd_base_count + self.settings.marked_for_death_resets) self.cp_budget += mfd_cps #Very VERY simple implementation for The First of the Dead legendary (this should be handled better) @@ -2167,10 +2184,12 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time + if self.talents.dark_shadow: + dfa_cd = self.get_spell_cd('symbols_of_death') + self.settings.response_time dfa_count = self.settings.duration / dfa_cd - lost_swings_mh = self.lost_swings_from_swing_delay(1.3, self.stats.mh.speed / haste_multiplier) - lost_swings_oh = self.lost_swings_from_swing_delay(1.3, self.stats.oh.speed / haste_multiplier) + lost_swings_mh = self.lost_swings_from_swing_delay(1.475, self.stats.mh.speed / haste_multiplier) + lost_swings_oh = self.lost_swings_from_swing_delay(1.475, self.stats.oh.speed / haste_multiplier) attacks_per_second['mh_autoattacks'] -= lost_swings_mh / dfa_cd attacks_per_second['oh_autoattacks'] -= lost_swings_oh / dfa_cd @@ -2325,7 +2344,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] = finishers_per_minicycle self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) self.energy_budget += mini_cycle_energy * mini_cycle_count - self.cp_budget += net_cps - cps_per_dance + cps_to_generate + self.cp_budget += (net_cps - cps_per_dance + cps_to_generate) * mini_cycle_count #Update energy budget with alacrity and haste procs if self.talents.alacrity: old_alacrity_regen = self.energy_regen * (1 + (alacrity_stacks *0.02)) From 580f40106b61e7ec1d3eec5c086bf5422e299f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 8 Jul 2017 16:04:34 +0200 Subject: [PATCH 247/265] [Sub] Minicycle fixes --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 2ca135f..8f86973 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2242,6 +2242,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): cp_per_builder = 1 + self.shadow_blades_uptime if self.cp_builder == 'shuriken_storm': cp_per_builder += self.settings.num_boss_adds + cp_per_builder = min(self.max_store_cps, cp_per_builder) energy_per_cp = self.get_spell_cost(self.cp_builder) / cp_per_builder extra_evis = 0 @@ -2295,6 +2296,8 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): raise ConvergenceErrorException(_('Denial Shadow Blades extension failed to converge.')) finisher_cps = 0 for i in range(0, 7): + if self.talents.death_from_above: + finisher_cps += attacks_per_second['death_from_above_strike'][i] * i finisher_cps += attacks_per_second['eviscerate'][i] * i finisher_cps += attacks_per_second['nightblade'][i] * i new_sb_extension = finisher_cps * sb_uptime * 0.3 @@ -2315,7 +2318,10 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): finishers_per_minicycle = cps_per_dance / self.settings.finisher_threshold attack_counts_mini_cycle = attack_counts - attack_counts_mini_cycle['eviscerate'] = [0, 0, 0, 0, 0, 0, 0] + if 'eviscerate' in attack_counts_mini_cycle: + attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] += finishers_per_minicycle + else: + attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] = finishers_per_minicycle loop_counter = 0 alacrity_stacks = 0 @@ -2341,7 +2347,6 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attack_counts_mini_cycle['shuriken_storm-no-dance'] = builders_per_minicycle else: attack_counts_mini_cycle[self.cp_builder] = builders_per_minicycle - attack_counts_mini_cycle['eviscerate'][self.settings.finisher_threshold] = finishers_per_minicycle self.rotation_merge(attacks_per_second, attack_counts_mini_cycle, mini_cycle_count) self.energy_budget += mini_cycle_energy * mini_cycle_count self.cp_budget += (net_cps - cps_per_dance + cps_to_generate) * mini_cycle_count @@ -2354,7 +2359,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = new_alacrity_stacks #Update Shadow Blades extension from Denial if self.stats.gear_buffs.denial_of_the_half_giants: - new_sb_extension = mini_cycle_count * finishers_per_minicycle * self.settings.finisher_threshold * self.shadow_blades_uptime * 0.3 / self.settings.duration + new_sb_extension = mini_cycle_count * sum(attack_counts_mini_cycle['eviscerate']) * self.settings.finisher_threshold * self.shadow_blades_uptime * 0.3 / self.settings.duration generators = ['shadowstrike', 'shuriken_storm', 'backstab', 'goremaws_bite', 'gloomblade', 'shuriken_toss'] denial_extra_cps = new_sb_extension * sum((attacks_per_second[gen] if gen in attacks_per_second else 0) for gen in generators) self.shadow_blades_uptime += new_sb_extension From 484b5813a3074fb730862c20ce41698b9ba467f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 25 Jul 2017 11:51:46 +0200 Subject: [PATCH 248/265] Legendary hotfix nerfs --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 8f86973..c023ed4 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -706,10 +706,7 @@ def determine_stats(self, attack_counts_function): mantle_triggers = 1 #Opener if attacks_per_second['vanish']: mantle_triggers += attacks_per_second['vanish'] * self.settings.duration - mantle_seconds = mantle_triggers * 6 - #Assume extended stealth bug opener for subtlety (shadow dance -> stealth, stealth wears off after dance) - if self.spec == 'subtlety': - mantle_seconds += 5 if self.talents.subterfuge else 3 #One full dance mantle uptime + mantle_seconds = mantle_triggers * 5 self.mantle_uptime = mantle_seconds / self.settings.duration for attack in crit_rates: crit_rates[attack] = min(crit_rates[attack] * (1. - self.mantle_uptime) + self.mantle_uptime, 1) @@ -2300,7 +2297,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): finisher_cps += attacks_per_second['death_from_above_strike'][i] * i finisher_cps += attacks_per_second['eviscerate'][i] * i finisher_cps += attacks_per_second['nightblade'][i] * i - new_sb_extension = finisher_cps * sb_uptime * 0.3 + new_sb_extension = finisher_cps * sb_uptime * 0.2 sb_extension_converged = (new_sb_extension - sb_extension) < 10 ** -5 sb_uptime = self.shadow_blades_uptime + new_sb_extension sb_extension = new_sb_extension @@ -2359,7 +2356,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): alacrity_stacks = new_alacrity_stacks #Update Shadow Blades extension from Denial if self.stats.gear_buffs.denial_of_the_half_giants: - new_sb_extension = mini_cycle_count * sum(attack_counts_mini_cycle['eviscerate']) * self.settings.finisher_threshold * self.shadow_blades_uptime * 0.3 / self.settings.duration + new_sb_extension = mini_cycle_count * sum(attack_counts_mini_cycle['eviscerate']) * self.settings.finisher_threshold * self.shadow_blades_uptime * 0.2 / self.settings.duration generators = ['shadowstrike', 'shuriken_storm', 'backstab', 'goremaws_bite', 'gloomblade', 'shuriken_toss'] denial_extra_cps = new_sb_extension * sum((attacks_per_second[gen] if gen in attacks_per_second else 0) for gen in generators) self.shadow_blades_uptime += new_sb_extension From 3811167621738211660c665bce99418b9bca98d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 28 Jul 2017 20:56:28 +0200 Subject: [PATCH 249/265] Update Insignia of Ravenholdt calculation --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c023ed4..f1b9536 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -740,17 +740,22 @@ def compute_damage(self, attack_counts_function): #damage_breakdown, additional_info = self.get_damage_breakdown(self.determine_stats(attack_counts_function)) return damage_breakdown, additional_info - def compute_insignia_of_ravenholdt_damage(self, stats, attacks_per_second): - # Insignia of Ravenholdt, 30% (Assassination) / 15% base generator damage with crit chance - ap = stats['ap'] + stats['agi'] * self.stat_multipliers['ap'] + def compute_insignia_of_ravenholdt_damage(self, stats, damage_breakdown): + # Insignia of Ravenholdt, 30% (Assassination) / 15% generator damage with crit chance insignia_base_dmg = 0 insignia_dmg_factor = 0.3 if self.spec == 'assassination' else 0.15 - for ability in attacks_per_second: + # Ignores Vendetta and Nightblade modifiers + if self.spec == 'assassination': + insignia_dmg_factor /= 1 + self.vendetta_multiplier + if self.spec == 'subtlety': + insignia_dmg_factor /= 1.15 + for ability in damage_breakdown: if ability in ['mutilate', 'hemorrhage', 'ambush', 'blunderbuss', 'pistol_shot', 'saber_slash', 'backstab', 'gloomblade', 'shadowstrike']: - both_hands = ability in self.dual_wield_damage_sources - insignia_base_dmg += insignia_dmg_factor * self.get_ability_dps(ap, ability, attacks_per_second[ability], 0, 1, 1, both_hands) # base dps wihout modifiers + # For physical generators we assume an additional 4.38% damage. (See https://github.com/Ravenholdt-TC/Rogue/issues/50) + physical_mod = 1.0438 if ability in self.physical_damage_sources else 1 + insignia_base_dmg += insignia_dmg_factor * physical_mod * damage_breakdown[ability] crit_rate = self.crit_rate(crit=stats['crit']) crit_mod = self.crit_damage_modifiers() insignia_dmg = insignia_base_dmg * (1 - crit_rate) + insignia_base_dmg * crit_rate * crit_mod @@ -951,7 +956,7 @@ def assassination_dps_breakdown(self): damage_breakdown['t19_2pc'] = damage_breakdown['mutilate'] * 0.2 if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, damage_breakdown) if self.stats.gear_buffs.cinidaria_the_symbiote: damage_breakdown['symbiote_strike'] = self.compute_symbiote_strike_damage(damage_breakdown) @@ -1407,7 +1412,7 @@ def outlaw_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, damage_breakdown) bf_mod = .35 if self.settings.cycle.blade_flurry: @@ -2084,7 +2089,7 @@ def subtlety_dps_breakdown(self): damage_breakdown, additional_info = self.compute_damage_from_aps(stats, aps, crits, procs, additional_info) if self.stats.gear_buffs.insignia_of_ravenholdt: - damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, aps) + damage_breakdown['insignia_of_ravenholdt'] = self.compute_insignia_of_ravenholdt_damage(stats, damage_breakdown) for key in damage_breakdown: damage_breakdown[key] *= infallible_trinket_mod From d0059556d39f96426a577daccb33480139e9b9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Mon, 14 Aug 2017 16:19:12 +0200 Subject: [PATCH 250/265] Implement Cradle of Anguish --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 4 ++++ shadowcraft/objects/proc_data.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index f1b9536..88ca330 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -629,6 +629,10 @@ def determine_stats(self, attack_counts_function): for e in proc.value: static_proc_stats[ e ] += proc.uptime * proc.value[e] * self.stat_multipliers[e] + # Cradle of Anguish, special hanling + if self.stats.procs.cradle_of_anguish: + static_proc_stats['agi'] += self.stats.procs.cradle_of_anguish.value['agi'] * self.stats.procs.cradle_of_anguish.max_stacks + for k in static_proc_stats: current_stats[k] += static_proc_stats[ k ] diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 7d2265a..bdeef4a 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -280,6 +280,20 @@ 'trigger': 'all_attacks', }, + 'cradle_of_anguish': { #Equip: While you are above 80% health you gain X Strength or Agility per second, based on your specialization, stacking up to 10 times. If you fall below 50% health, this effect is lost. + 'stat': 'special_model', #handled in determine stats, assume 10 stacks all the time + 'value': {'agi':0}, #rpp-scaled + 'proc_name': 'Strength of Will', + 'item_level': 900, + 'scaling': 0.05585, + 'duration': 1, + 'max_stacks': 10, + 'type': 'icd', + 'icd': 1, + 'proc_rate': 1, + 'source': 'trinket', + }, + #removed the ":" not sure which way it should be 'darkmoon_deck_dominion': { #Equip: Increase critical strike by X-Y. The amount of critical strike depends on the topmost card in the deck. Equip: Periodically shuffle the deck while in combat. 'stat': 'stats', From 2d32dc8eabe70f2925abd7fdff26005669928ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 16 Sep 2017 15:42:46 +0200 Subject: [PATCH 251/265] Bump version to 7.3 and add aura hotfixes --- setup.py | 2 +- shadowcraft/calcs/__init__.py | 2 +- shadowcraft/calcs/rogue/Aldriana/__init__.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 3b9b291..291cd8b 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='ShadowCraft-Engine', url='http://github.com/ShadowCraft/ShadowCraft-Engine/', - version='7.2.5', + version='7.3.0', packages=[ 'shadowcraft', 'shadowcraft.calcs', diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index 72d9bcb..b5061ca 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -35,7 +35,7 @@ class DamageCalculator(object): normalize_ep_stat = None def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, level=110, target_level=None, char_class='rogue'): - self.WOW_BUILD_TARGET = '7.2.5' # should reflect the game patch being targetted + self.WOW_BUILD_TARGET = '7.3.0' # should reflect the game patch being targetted self.SHADOWCRAFT_BUILD = self.get_version_string() self.tools = class_data.Util() self.stats = stats diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 88ca330..50162ad 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -801,7 +801,7 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassins_resolve', 1.17, [], all_damage=True)) #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.22, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('assassination_aura', 1.28, ['death_from_above_pulse', 'death_from_above_strike', 'deadly_poison', 'deadly_instant_poison', 'envenom', 'fan_of_knives', 'garrote_ticks', 'hemorrhage', 'kingsbane', 'kingsbane_ticks', 'mutilate', 'poisoned_knife', 'rupture_ticks', 'toxic_blade'])) @@ -1378,7 +1378,7 @@ def outlaw_dps_breakdown(self): 'pistol_shot', 'run_through', 'saber_slash', 'autoattacks'], dmg_schools=['physical'])) # Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.20, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('outlaw_aura', 1.06, ['death_from_above_pulse', 'death_from_above_strike', 'ambush', 'between_the_eyes', 'blunderbuss', 'cannonball_barrage', 'ghostly_strike', 'killing_spree', 'pistol_shot', 'run_through', 'saber_slash'])) @@ -1939,7 +1939,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('focused_shurikens', None, ['eviscerate', 'death_from_above_strike'])) #Generic tuning aura - self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.25, ['death_from_above_pulse', 'death_from_above_strike', + self.damage_modifiers.register_modifier(modifiers.DamageModifier('subtlety_aura', 1.27, ['death_from_above_pulse', 'death_from_above_strike', 'backstab', 'eviscerate', 'gloomblade', 'nightblade', 'shadowstrike', 'shuriken_storm', 'shuriken_toss', 'nightblade_ticks', 'shadow_blades', 'second_shuriken', 'shadow_nova', 'goremaws_bite', 'soul_rip'])) From d5eec71689996c926bee46919b0ec997e1050123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 16 Sep 2017 15:46:03 +0200 Subject: [PATCH 252/265] Raise exception for Outlaw Better throw an exception to say the model is not working correctly than having it return outdated calculation. --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 50162ad..e2bf7f4 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -39,6 +39,7 @@ def get_dps(self): if self.spec == 'assassination': return self.assassination_dps_estimate() elif self.spec == 'outlaw': + raise InputNotModeledException(_('Outlaw model not supported, at the moment.')) return self.outlaw_dps_estimate() elif self.spec == 'subtlety': return self.subtlety_dps_estimate() @@ -49,6 +50,7 @@ def get_dps_breakdown(self): if self.spec == 'assassination': return self.assassination_dps_breakdown() elif self.spec == 'outlaw': + raise InputNotModeledException(_('Outlaw model not supported, at the moment.')) return self.outlaw_dps_breakdown() elif self.spec == 'subtlety': return self.subtlety_dps_breakdown() From 0bd74deddf53b920a196c1d6de43ce92082e0778 Mon Sep 17 00:00:00 2001 From: Tim Wojtulewicz Date: Thu, 28 Sep 2017 19:35:06 -0700 Subject: [PATCH 253/265] Added traits for netherlight crucible --- shadowcraft/objects/artifact.py | 4 ++-- shadowcraft/objects/artifact_data.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/shadowcraft/objects/artifact.py b/shadowcraft/objects/artifact.py index 3b4050d..d35d086 100644 --- a/shadowcraft/objects/artifact.py +++ b/shadowcraft/objects/artifact.py @@ -8,7 +8,7 @@ class InvalidTraitException(exceptions.InvalidInputException): class Artifact(object): def __init__(self, class_spec, game_class, trait_string='', trait_dict= {}): - self.allowed_traits = artifact_data.traits[(game_class, class_spec)] + self.allowed_traits = artifact_data.traits[(game_class, class_spec)]+artifact_data.traits[('all','netherlight')] self.single_rank_traits = artifact_data.single_rank[(game_class, class_spec)] if trait_string: @@ -48,4 +48,4 @@ def get_trait_list(self): return list(self.allowed_traits) def get_single_rank_trait_list(self): - return list(self.single_rank_traits) \ No newline at end of file + return list(self.single_rank_traits) diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index 6f6b850..c3b0308 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -74,6 +74,20 @@ 'feeding_frenzy', 'concordance_of_the_legionfall', ), + ('all','netherlight'): ( + 'chaotic_darkness', + 'dark_sorrows', + 'infusion_of_light', + 'light_speed', + 'lights_embrace' + 'master_of_shadows', + 'murderous_intent', + 'refractive_shell', + 'secure_in_the_light', + 'shadowbind', + 'shocklight', + 'torment_of_the_weak', + ) } #Single Rank Traits for each spec From 922f3eeb2a08bd6b437a6773493b9d4c31cb0e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 5 Oct 2017 15:30:29 +0200 Subject: [PATCH 254/265] Implement Netherlight Crucible T2 traits --- scripts/assassination.py | 2 +- scripts/outlaw.py | 2 +- scripts/subtlety.py | 34 ++++--- shadowcraft/calcs/rogue/Aldriana/__init__.py | 102 +++++++++++++------ shadowcraft/objects/artifact_data.py | 4 +- shadowcraft/objects/proc_data.py | 84 +++++++++++++++ shadowcraft/objects/stats.py | 13 ++- 7 files changed, 188 insertions(+), 53 deletions(-) diff --git a/scripts/assassination.py b/scripts/assassination.py index 963f63a..327a890 100644 --- a/scripts/assassination.py +++ b/scripts/assassination.py @@ -108,7 +108,7 @@ # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) -print(str(calculator.stats.get_character_stats(calculator.race))) +print(str(calculator.stats.get_character_stats(calculator.race, test_traits))) # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() diff --git a/scripts/outlaw.py b/scripts/outlaw.py index f250a1b..385e752 100644 --- a/scripts/outlaw.py +++ b/scripts/outlaw.py @@ -117,7 +117,7 @@ # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) -print(str(test_stats.get_character_stats(test_race))) +print(str(test_stats.get_character_stats(test_race, test_traits))) # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() diff --git a/scripts/subtlety.py b/scripts/subtlety.py index ec7b797..8e63e69 100644 --- a/scripts/subtlety.py +++ b/scripts/subtlety.py @@ -70,9 +70,9 @@ # Set up gear buffs. test_gear_buffs = stats.GearBuffs('gear_specialization', -'shadow_satyrs_walk', -'rogue_t19_2pc', -'rogue_t19_4pc', +'the_first_of_the_dead', +'rogue_t20_2pc', +'rogue_t20_4pc', #'insignia_of_ravenholdt', 'mantle_of_the_master_assassin', ) #tier buffs located here @@ -87,33 +87,35 @@ versatility=5428,) # Initialize talents.. -test_talents = talents.Talents('2223211', test_spec, test_class, level=test_level) +test_talents = talents.Talents('1113213', test_spec, test_class, level=test_level) #initialize artifact traits.. test_traits = artifact.Artifact(test_spec, test_class, trait_dict={ 'goremaws_bite': 1, 'shadow_fangs': 1, - 'gutripper': 3, - 'fortunes_bite': 3, - 'catlike_reflexes': 3, + 'gutripper': 4, + 'fortunes_bite': 4, + 'catlike_reflexes': 4, 'embrace_of_darkness': 1, - 'ghost_armor': 3, - 'precision_strike': 3, - 'energetic_stabbing': 3+3, + 'ghost_armor': 4, + 'precision_strike': 4, + 'energetic_stabbing': 4+3, 'flickering_shadows': 1, 'second_shuriken': 1, - 'demons_kiss': 3, + 'demons_kiss': 4, 'finality': 1, - 'the_quiet_knife': 3, + 'the_quiet_knife': 4, 'akarris_soul': 1, - 'soul_shadows': 3, + 'soul_shadows': 4, 'shadow_nova': 1, 'legionblade': 1, 'shadows_of_the_uncrowned': 1, 'weak_point': 4, 'shadows_whisper': 1, 'feeding_frenzy': 1, - 'concordance_of_the_legionfall': 12, + 'concordance_of_the_legionfall': 24, + #crucible + #'master_of_shadows': 3, }) # Set up settings. @@ -123,7 +125,7 @@ # Build a DPS object. calculator = AldrianasRogueDamageCalculator(test_stats, test_talents, test_traits, test_buffs, test_race, test_spec, test_settings, test_level) -print(str(test_stats.get_character_stats(test_race))) +print(str(test_stats.get_character_stats(test_race, test_traits))) # Compute DPS Breakdown. dps_breakdown = calculator.get_dps_breakdown() @@ -179,4 +181,4 @@ def pretty_print(dict_list): print(str(value[0]) + ' - ' + str(val)) """ -#pprint(talent_ranks) +#pprint(trait_ranks) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index e2bf7f4..c31ab3e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -212,7 +212,7 @@ def set_constants(self): #only include if general multiplier applies to spec calculations self.true_haste_mod *= self.get_heroism_haste_multiplier() - self.base_stats = self.stats.get_character_base_stats(self.race, self.buffs) + self.base_stats = self.stats.get_character_base_stats(self.race, self.traits, self.buffs) self.stat_multipliers = self.stats.get_character_stat_multipliers(self.race) for boost in self.race.get_racial_stat_boosts(): @@ -239,6 +239,74 @@ def set_constants(self): if self.stats.gear_buffs.sephuzs_secret: self.true_haste_mod *= 1.02 + #The procs are set within the stats object, which is global. + #This means they will still be active, even if gear/traits of + #origin changed due to rankings. + #Because of that, remove these manually added procs before setting them. + #For other procs, the specific EP ranking function is responsible. + + #set additional procs + self.stats.procs.del_proc('felmouth_frenzy') + if self.buffs.felmouth_food(): + self.stats.procs.set_proc('felmouth_frenzy') + self.stats.procs.del_proc('jacins_ruse_2pc') + if self.stats.gear_buffs.jacins_ruse_2pc: + self.stats.procs.set_proc('jacins_ruse_2pc') + self.stats.procs.del_proc('march_of_the_legion_2pc') + if self.stats.gear_buffs.march_of_the_legion_2pc and self.settings.is_demon: + self.stats.procs.set_proc('march_of_the_legion_2pc') + self.stats.procs.del_proc('rogue_orderhall_8pc') + if self.stats.gear_buffs.rogue_orderhall_8pc: + self.stats.procs.set_proc('rogue_orderhall_8pc') + if self.stats.gear_buffs.journey_through_time_2pc and self.stats.procs.chrono_shard: + self.stats.procs.chrono_shard.update_proc_value() + self.stats.procs.chrono_shard.value['haste'] += 1000 + if self.stats.gear_buffs.kara_empowered_2pc: + if self.stats.procs.bloodstained_handkerchief: + self.stats.procs.bloodstained_handkerchief.update_proc_value() + self.stats.procs.bloodstained_handkerchief.value *= 1.3 + if self.stats.procs.eye_of_command: + self.stats.procs.eye_of_command.update_proc_value() + self.stats.procs.eye_of_command.value['crit'] *= 1.3 + if self.stats.procs.toe_knees_promise: + self.stats.procs.toe_knees_promise.update_proc_value() + self.stats.procs.toe_knees_promise.value *= 1.3 + + self.stats.procs.del_proc('concordance_of_the_legionfall') + if self.traits.concordance_of_the_legionfall: + self.stats.procs.set_proc('concordance_of_the_legionfall') + self.stats.procs.concordance_of_the_legionfall.value['agi'] = 4000 + (self.traits.concordance_of_the_legionfall - 1) * 300 + if self.traits.murderous_intent: + self.stats.procs.concordance_of_the_legionfall.value['versatility'] = 1500 * self.traits.murderous_intent + if self.traits.shocklight: + self.stats.procs.concordance_of_the_legionfall.value['crit'] = 1500 * self.traits.shocklight + + #netherlight crucible t2 procs + self.stats.procs.del_proc('chaotic_darkness') + if self.traits.chaotic_darkness: + self.stats.procs.set_proc('chaotic_darkness') + self.stats.procs.chaotic_darkness.value *= self.traits.chaotic_darkness + self.stats.procs.del_proc('dark_sorrows') + if self.traits.dark_sorrows: + self.stats.procs.set_proc('dark_sorrows') + self.stats.procs.dark_sorrows.value *= self.traits.dark_sorrows + self.stats.procs.del_proc('infusion_of_light') + if self.traits.infusion_of_light: + self.stats.procs.set_proc('infusion_of_light') + self.stats.procs.infusion_of_light.value *= self.traits.infusion_of_light + self.stats.procs.del_proc('secure_in_the_light') + if self.traits.secure_in_the_light: + self.stats.procs.set_proc('secure_in_the_light') + self.stats.procs.secure_in_the_light.value *= self.traits.secure_in_the_light + self.stats.procs.del_proc('shadowbind') + if self.traits.shadowbind: + self.stats.procs.set_proc('shadowbind') + self.stats.procs.shadowbind.value *= self.traits.shadowbind + self.stats.procs.del_proc('torment_the_weak') + if self.traits.torment_the_weak: + self.stats.procs.set_proc('torment_the_weak') + self.stats.procs.torment_the_weak.value *= self.traits.torment_the_weak + #hit chances self.dw_mh_hit_chance = self.dual_wield_mh_hit_chance() self.dw_oh_hit_chance = self.dual_wield_oh_hit_chance() @@ -566,32 +634,6 @@ def determine_stats(self, attack_counts_function): damage_procs = [] weapon_damage_procs = [] - if self.buffs.felmouth_food(): - self.stats.procs.set_proc('felmouth_frenzy') - if self.stats.gear_buffs.jacins_ruse_2pc: - self.stats.procs.set_proc('jacins_ruse_2pc') - if self.stats.gear_buffs.march_of_the_legion_2pc and self.settings.is_demon: - self.stats.procs.set_proc('march_of_the_legion_2pc') - if self.stats.gear_buffs.rogue_orderhall_8pc: - self.stats.procs.set_proc('rogue_orderhall_8pc') - if self.stats.gear_buffs.journey_through_time_2pc and self.stats.procs.chrono_shard: - self.stats.procs.chrono_shard.update_proc_value() - self.stats.procs.chrono_shard.value['haste'] += 1000 - if self.stats.gear_buffs.kara_empowered_2pc: - if self.stats.procs.bloodstained_handkerchief: - self.stats.procs.bloodstained_handkerchief.update_proc_value() - self.stats.procs.bloodstained_handkerchief.value *= 1.3 - if self.stats.procs.eye_of_command: - self.stats.procs.eye_of_command.update_proc_value() - self.stats.procs.eye_of_command.value['crit'] *= 1.3 - if self.stats.procs.toe_knees_promise: - self.stats.procs.toe_knees_promise.update_proc_value() - self.stats.procs.toe_knees_promise.value *= 1.3 - - if self.traits.concordance_of_the_legionfall: - self.stats.procs.set_proc('concordance_of_the_legionfall') - self.stats.procs.concordance_of_the_legionfall.value['agi'] = 2000 + (self.traits.concordance_of_the_legionfall - 1) * 200 - #sort the procs into groups for proc in self.stats.procs.get_all_procs_for_stat(): if (proc.stat == 'stats'): @@ -2186,9 +2228,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['goremaws_bite'] = 1 / goremaws_bite_cd self.cp_budget += (3 + self.shadow_blades_uptime) * (self.settings.duration / goremaws_bite_cd) self.energy_budget += 30 * (self.settings.duration / goremaws_bite_cd) - if self.traits.feeding_frenzy: - #assume we time it so we can get three free eviscerates - self.energy_budget += self.get_spell_cost('eviscerate') * (self.settings.duration / goremaws_bite_cd) + if self.traits.feeding_frenzy: + #assume we time it so we can get three free eviscerates + self.energy_budget += self.get_spell_cost('eviscerate') * (self.settings.duration / goremaws_bite_cd) if self.talents.death_from_above: dfa_cd = self.get_spell_cd('death_from_above') + self.settings.response_time diff --git a/shadowcraft/objects/artifact_data.py b/shadowcraft/objects/artifact_data.py index c3b0308..4c520a9 100644 --- a/shadowcraft/objects/artifact_data.py +++ b/shadowcraft/objects/artifact_data.py @@ -79,14 +79,14 @@ 'dark_sorrows', 'infusion_of_light', 'light_speed', - 'lights_embrace' + 'lights_embrace', 'master_of_shadows', 'murderous_intent', 'refractive_shell', 'secure_in_the_light', 'shadowbind', 'shocklight', - 'torment_of_the_weak', + 'torment_the_weak', ) } diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index bdeef4a..cc12e21 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -90,6 +90,90 @@ 'proc_rate': 1.0, 'trigger': 'all_attacks' }, + #netherlight crucible + 'chaotic_darkness': { + 'stat': 'spell_damage', + 'dmg_school': 'shadow', + 'value': 180000, #avg of range 60000 to 5*60000 + 'proc_name': 'Chaotic Darkness', + 'duration': 0, + 'type': 'rppm', + 'source': 'crucible', + 'proc_rate': 2, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'dark_sorrows': { + 'stat': 'spell_damage', + 'dmg_school': 'shadow', + 'aoe': True, + 'value': 186350, + 'proc_name': 'Dark Sorrows', + 'duration': 0, + 'type': 'rppm', + 'source': 'crucible', + 'icd': 8, + 'proc_rate': 1, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'infusion_of_light': { + 'stat': 'spell_damage', + 'dmg_school': 'holy', + 'value': 101000, + 'proc_name': 'Infusion of Light', + 'duration': 0, + 'type': 'rppm', + 'source': 'crucible', + 'icd': 1, + 'proc_rate': 4, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'secure_in_the_light': { + 'stat': 'spell_damage', + 'dmg_school': 'holy', + 'value': 135000, + 'proc_name': 'Secure in the Light', + 'duration': 0, + 'type': 'rppm', + 'source': 'crucible', + 'icd': 1, + 'proc_rate': 3, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'shadowbind': { + 'stat': 'spell_damage', + 'dmg_school': 'shadow', + 'value': 200000, + 'proc_name': 'Shadowbind', + 'duration': 0, + 'type': 'rppm', + 'source': 'crucible', + 'proc_rate': 2, + 'haste_scales': True, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'torment_the_weak': { + 'stat': 'spell_dot', + 'dmg_school': 'shadow', + 'dot_ticks': 5, + 'can_crit': True, + 'value': 16000, + 'duration': 15, + 'max_stacks': 3, + 'proc_name': 'Torment the Weak', + 'type': 'rppm', + 'proc_rate': 4, + 'source': 'crucible', + 'trigger': 'all_attacks' + }, #gear procs 'fury_of_xuen': { 'stat':'physical_damage', diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 18bdb08..8b7ceff 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -54,7 +54,7 @@ def __setattr__(self, name, value): if name == 'level' and value is not None: self._set_constants_for_level() - def get_character_base_stats(self, race, buffs=None): + def get_character_base_stats(self, race, traits=None, buffs=None): base_stats = { 'str': self.str + race.racial_str, 'int': self.int + race.racial_int, @@ -70,6 +70,13 @@ def get_character_base_stats(self, race, buffs=None): for bonus in buff_bonuses: base_stats[bonus] += buff_bonuses[bonus] + #netherlight crucible t2 + if traits: + if traits.light_speed: + base_stats['haste'] += 500 * traits.light_speed + if traits.master_of_shadows: + base_stats['mastery'] += 500 * traits.master_of_shadows + # Other bonuses if self.gear_buffs.rogue_orderhall_6pc: base_stats['agi'] += 500 @@ -90,8 +97,8 @@ def get_character_stat_multipliers(self, race): } return stat_multipliers - def get_character_stats(self, race, buffs=None): - base = self.get_character_base_stats(race, buffs) + def get_character_stats(self, race, traits=None, buffs=None): + base = self.get_character_base_stats(race, traits, buffs) mult = self.get_character_stat_multipliers(race) stats = { } for stat in base: From aa544cef8da8886230a273a4ca286142c885839e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Thu, 5 Oct 2017 21:11:37 +0200 Subject: [PATCH 255/265] Change default potion --- shadowcraft/calcs/rogue/Aldriana/settings_data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index 382a205..6a5bee8 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -307,7 +307,7 @@ def process_overrides(defaults_dict, params_dict, spec): 'label': 'Pre-pot', 'description': '', 'type': 'dropdown', - 'default': 'old_war_pot', + 'default': 'prolonged_power_pot', 'options': { 'old_war_pot': 'Potion of the Old War', 'prolonged_power_pot': 'Potion of Prolonged Power', @@ -319,7 +319,7 @@ def process_overrides(defaults_dict, params_dict, spec): 'label': 'Combat Potion', 'description': '', 'type': 'dropdown', - 'default': 'old_war_prepot', + 'default': 'prolonged_power_prepot', 'options': { 'old_war_prepot': 'Potion of the Old War', 'prolonged_power_prepot': 'Potion of Prolonged Power', From b524677e7853afeeed227d221ac9cf758b679f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 7 Oct 2017 13:50:28 +0200 Subject: [PATCH 256/265] [Procs] Add Void Stalker's Contract --- shadowcraft/objects/proc_data.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index cc12e21..5daa5f7 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -845,6 +845,22 @@ 'trigger': 'all_attacks' }, + 'void_stalkers_contract': { #Use: Call upon two Void Stalkers to strike your target from two directions inflicting up to (209877 * 2) Physical damage to all enemies in their paths. (1 Min, 30 Sec Cooldown) + 'stat':'physical_damage', + 'value': 0, #rpp-scaled + 'aoe': True, + 'duration': 0, + 'proc_name': 'Void Slash', + 'scaling': 84.93605, + 'item_level': 845, + 'type': 'icd', + 'source': 'trinket', + 'icd': 90, + 'proc_rate': 1, + 'can_crit': True, + 'trigger': 'all_attacks' + }, + 'windscar_whetstone': { #Use: A Slicing Maelstrom surrounds you, inflicting X Physical damage to nearby enemies over 6 sec. (2 Min Cooldown) 'stat':'physical_damage', 'value': 0, #rpp-scaled From a6c4b28a6cfc1f37f91325c67927fefdd1ee4449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 7 Oct 2017 15:55:36 +0200 Subject: [PATCH 257/265] [Settings] Set default EP stat to Agility --- shadowcraft/calcs/rogue/Aldriana/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 36a5b1a..77069d7 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -7,7 +7,7 @@ class Settings(object): def __init__(self, cycle, **kwargs): self.cycle = cycle - self.default_ep_stat = kwargs.get('default_ep_stat', 'ap') + self.default_ep_stat = kwargs.get('default_ep_stat', 'agi') self.feint_interval = int(kwargs.get('feint_interval', 0)) #Get defaults from settings_data From 5ac96e3cae34bcec87220dbfbdf6e92b8e79a037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sat, 7 Oct 2017 19:03:00 +0200 Subject: [PATCH 258/265] [Subtlety] Fix for Master of Subtlety --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index c31ab3e..0e21dd7 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2452,10 +2452,9 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Full additive assumption for now if self.talents.master_of_subtlety: - stealth_time = 8. * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] + self.mos_time = 9 * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] if self.talents.subterfuge: - stealth_time = 10. * attacks_per_second['shadow_dance'] + 8 * attacks_per_second['vanish'] - self.mos_time = stealth_time / self.settings.duration + self.mos_time += 1 * attacks_per_second['shadow_dance'] + 3 * attacks_per_second['vanish'] for ability in list(attacks_per_second.keys()): if not attacks_per_second[ability]: @@ -2481,7 +2480,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): for cp in range(7): attacks_per_second[ability][cp] *= 1.06 else: - attacks_per_second[ability] *=1.06 + attacks_per_second[ability] *= 1.06 #for a in attacks_per_second: # if isinstance(attacks_per_second[a], list): From 91b76e9dd5587bae780f783275f6664cef8aec73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Tue, 10 Oct 2017 14:43:16 +0200 Subject: [PATCH 259/265] [Subtlety] Some modifier improvements --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 30 +++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 0e21dd7..80a1970 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -1989,10 +1989,13 @@ def subtlety_dps_breakdown(self): #talent specific modifiers if self.talents.nightstalker: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_ssk', None, ['shadowstrike'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_full', None, ['shadowstrike', 'shadow_nova'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_shuriken_storm', None, ['shuriken_storm'])) - #self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_nightblade', None, ['nightblade_ticks'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_evis', None, ['eviscerate'])) + # The following creates a blacklist so only AA abilities and procs are affected + other_whitelist = ['shadow_blades', 'soul_rip'] + self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker_other', None, + [item for item in self.subtlety_damage_sources if item not in other_whitelist], blacklist=True, all_damage=True)) if self.talents.master_of_subtlety: self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_ssk', None, ['shadowstrike'])) @@ -2018,7 +2021,7 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('shadow_fangs', 1.04, [], blacklist=True, dmg_schools=['physical', 'shadow'])) if self.traits.finality: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('finality', None, ['nightblade_ticks', 'eviscerate'])) + self.damage_modifiers.register_modifier(modifiers.DamageModifier('finality', None, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike'])) if self.traits.legionblade: self.damage_modifiers.register_modifier(modifiers.DamageModifier('legionblade', @@ -2084,15 +2087,15 @@ def subtlety_dps_breakdown(self): #nightstalker if self.talents.nightstalker: ns_full_multiplier = 0.12 - self.damage_modifiers.update_modifier_value('nightstalker_ssk', 1 + ns_full_multiplier) + self.damage_modifiers.update_modifier_value('nightstalker_full', 1 + ns_full_multiplier) self.damage_modifiers.update_modifier_value('nightstalker_shuriken_storm', 1 + (0.12 * self.stealth_shuriken_uptime)) - #Re-add? self.damage_modifiers.update_modifier_value('nightstalker_nightblade', 1 + (0.12 * self.dance_nb_uptime)) self.damage_modifiers.update_modifier_value('nightstalker_evis', 1 + (0.12 * self.stealth_evis_uptime)) + self.damage_modifiers.update_modifier_value('nightstalker_other', 1 + (0.12 * self.stealthed_uptime)) #master of subtlety if self.talents.master_of_subtlety: mos_full_multiplier = 1.1 - mos_uptime_multipler = 1. + (0.1 * self.mos_time) + mos_uptime_multipler = 1. + (0.1 * self.mos_uptime) self.damage_modifiers.update_modifier_value('mos_ssk', mos_full_multiplier) self.damage_modifiers.update_modifier_value('mos_shuriken_storm', 1 + (0.1 * self.stealth_shuriken_uptime)) self.damage_modifiers.update_modifier_value('mos_evis', 1 + (0.1 * self.stealth_evis_uptime)) @@ -2121,8 +2124,11 @@ def subtlety_dps_breakdown(self): dfa_mod *= 1.3 if self.talents.nightstalker: dfa_mod *= 1.12 - elif self.talents.master_of_subtlety: - dfa_mod *= mos_uptime_multipler + if self.talents.master_of_subtlety: + dfa_mod *= 1.1 + else: + if self.talents.master_of_subtlety: + dfa_mod *= mos_uptime_multipler self.damage_modifiers.update_modifier_value('dfa_mods', dfa_mod) if self.traits.finality: @@ -2450,11 +2456,13 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): attacks_per_second['shuriken_storm'] = attacks_per_second['shuriken_storm'] + attacks_per_second['shuriken_storm-no-dance'] del attacks_per_second['shuriken_storm-no-dance'] + self.stealthed_uptime = 4 * attacks_per_second['shadow_dance'] + if self.talents.subterfuge: + self.stealthed_uptime += 1 * attacks_per_second['shadow_dance'] + 3 * attacks_per_second['vanish'] + #Full additive assumption for now if self.talents.master_of_subtlety: - self.mos_time = 9 * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] - if self.talents.subterfuge: - self.mos_time += 1 * attacks_per_second['shadow_dance'] + 3 * attacks_per_second['vanish'] + self.mos_uptime = self.stealthed_uptime + 5 * attacks_per_second['shadow_dance'] + 5 * attacks_per_second['vanish'] for ability in list(attacks_per_second.keys()): if not attacks_per_second[ability]: From c9053d797e23fe93b18dab01977dacd929001091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 11 Oct 2017 11:40:24 +0200 Subject: [PATCH 260/265] [Talents] It's called Stratagem not Strategem --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 28 ++++++++++---------- shadowcraft/calcs/rogue/__init__.py | 2 +- shadowcraft/objects/talents_data.py | 6 ++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 80a1970..7f82592 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -869,8 +869,8 @@ def assassination_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('nightstalker', None, ['rupture_ticks'])) if self.talents.subterfuge: self.damage_modifiers.register_modifier(modifiers.DamageModifier('subterfuge_garrote', None, ['garrote_ticks'])) - if self.talents.deeper_strategem: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) + if self.talents.deeper_stratagem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_stratagem', 1.05, ['rupture_ticks', 'envenom', 'death_from_above_pulse', 'death_from_above_strike'])) #trait specific modifiers if self.traits.kingsbane: @@ -1073,7 +1073,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): else: raise InputNotModeledException(_('Finisher thresholds less than 4 unimplemented')) max_cps = 5 - if self.talents.deeper_strategem: + if self.talents.deeper_stratagem: max_cps = 6 builders_per_finisher = 0.0 avg_finisher_size = 0.0 @@ -1146,7 +1146,7 @@ def assassination_attack_counts(self, current_stats, crit_rates=None): #compute cooldowned talents: if self.talents.marked_for_death: mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') - mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + mfd_cps = (5. + self.talents.deeper_stratagem) * (mfd_base_count + self.settings.marked_for_death_resets) cp_budget += mfd_cps if self.stats.gear_buffs.the_dreadlords_deceit: @@ -1427,8 +1427,8 @@ def outlaw_dps_breakdown(self): 'pistol_shot', 'run_through', 'saber_slash'])) # Talent specific modifiers - if self.talents.deeper_strategem: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['between_the_eyes', 'run_through', 'death_from_above_pulse', 'death_from_above_strike'])) + if self.talents.deeper_stratagem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_stratagem', 1.05, ['between_the_eyes', 'run_through', 'death_from_above_pulse', 'death_from_above_strike'])) # Trait specific modifiers if self.traits.cursed_steel: @@ -1720,11 +1720,11 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll gcd_size -= .2 max_cps = 5 - if self.talents.deeper_strategem: + if self.talents.deeper_stratagem: max_cps += 1 #fetch minicycle value - minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_strategem), bool(self.talents.quick_draw), + minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_stratagem), bool(self.talents.quick_draw), bool(self.talents.swordmaster), broadsides, jolly) ss_count, ps_count, finisher_list = self.minicycle_table[minicycle_key] @@ -1793,7 +1793,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll #consider MfD if self.talents.marked_for_death: mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') - mfd_cps = (5. + self.talents.deeper_strategem) * (mfd_base_count + self.settings.marked_for_death_resets) + mfd_cps = (5. + self.talents.deeper_stratagem) * (mfd_base_count + self.settings.marked_for_death_resets) bonus_cps += mfd_cps #consider Curse of the Dreadblades @@ -1869,7 +1869,7 @@ def outlaw_attack_counts_mincycle(self, current_stats, snd=False, ar=False, joll def outlaw_attack_counts_reroll(self, current_stats, ar=False, jolly=False, melee=False, buried=False, broadsides=False, alacrity_stacks=0): #fetch minicycle value - minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_strategem), bool(self.talents.quick_draw), + minicycle_key = (self.settings.finisher_threshold, bool(self.talents.deeper_stratagem), bool(self.talents.quick_draw), bool(self.talents.swordmaster), broadsides, jolly) ss_count, ps_count, finisher_list = self.minicycle_table[minicycle_key] reroll_energy_cost = (ss_count * self.saber_slash_energy_cost) + self.roll_the_bones_cost @@ -1955,7 +1955,7 @@ def subtlety_dps_breakdown(self): self.cp_builder = 'gloomblade' self.max_spend_cps = 5 - if self.talents.deeper_strategem: + if self.talents.deeper_stratagem: self.max_spend_cps += 1 self.max_store_cps = self.max_spend_cps if self.talents.anticipation: @@ -2003,8 +2003,8 @@ def subtlety_dps_breakdown(self): self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_evis', None, ['eviscerate'])) self.damage_modifiers.register_modifier(modifiers.DamageModifier('mos_other', None, ['shadowstrike', 'eviscerate', 'shuriken_storm', 'death_from_above_strike'], blacklist=True, all_damage=True)) - if self.talents.deeper_strategem: - self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_strategem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) + if self.talents.deeper_stratagem: + self.damage_modifiers.register_modifier(modifiers.DamageModifier('deeper_stratagem', 1.05, ['nightblade_ticks', 'eviscerate', 'death_from_above_strike', 'death_from_above_pulse'])) if self.talents.dark_shadow: self.damage_modifiers.register_modifier(modifiers.DamageModifier('dark_shadow_ssk', None, ['shadowstrike'])) @@ -2201,7 +2201,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): self.cp_budget = 0 if self.talents.marked_for_death: mfd_base_count = 1 + self.settings.duration / self.get_spell_cd('marked_for_death') - mfd_cps = (6 if self.talents.deeper_strategem else 5) * (mfd_base_count + self.settings.marked_for_death_resets) + mfd_cps = (6 if self.talents.deeper_stratagem else 5) * (mfd_base_count + self.settings.marked_for_death_resets) self.cp_budget += mfd_cps #Very VERY simple implementation for The First of the Dead legendary (this should be handled better) diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index 27d89a0..f47cb35 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -233,7 +233,7 @@ def get_ability_dps(self, ap, ability, attacks_per_second, crit_rate, modifier, def get_damage_breakdown(self, current_stats, attacks_per_second, crit_rates, damage_procs, additional_info): average_ap = current_stats['ap'] + current_stats['agi'] * self.stat_multipliers['ap'] - max_cps = 5 + int(self.talents.deeper_strategem) + max_cps = 5 + int(self.talents.deeper_stratagem) self.setup_unique_procs(current_stats, average_ap) diff --git a/shadowcraft/objects/talents_data.py b/shadowcraft/objects/talents_data.py index 2d1f3d3..372cf7a 100644 --- a/shadowcraft/objects/talents_data.py +++ b/shadowcraft/objects/talents_data.py @@ -73,7 +73,7 @@ ('rogue', 'assassination'): ( ('master_poisoner', 'elaborate_planning', 'hemorrhage'), ('nightstalker', 'subterfuge', 'shadow_focus'), - ('deeper_strategem', 'anticipation', 'vigor'), + ('deeper_stratagem', 'anticipation', 'vigor'), ('leeching_poison', 'elusiveness', 'cheat_death'), ('thuggee', 'prey_on_the_weak', 'internal_bleeding'), ('toxic_blade', 'alacrity', 'exsanguinate'), @@ -82,7 +82,7 @@ ('rogue', 'outlaw'): ( ('ghostly_strike', 'swordmaster', 'quick_draw'), ('grappling_hook', 'acrobatic_strikes', 'hit_and_run'), - ('deeper_strategem', 'anticipation', 'vigor'), + ('deeper_stratagem', 'anticipation', 'vigor'), ('iron_stomach', 'elusiveness', 'cheat_death'), ('parley', 'prey_on_the_weak', 'dirty_tricks'), ('cannonball_barrage', 'alacrity', 'killing_spree'), @@ -91,7 +91,7 @@ ('rogue', 'subtlety'): ( ('master_of_subtlety', 'weaponmaster', 'gloomblade'), ('nightstalker', 'subterfuge', 'shadow_focus'), - ('deeper_strategem', 'anticipation', 'vigor'), + ('deeper_stratagem', 'anticipation', 'vigor'), ('soothing_darkness', 'elusiveness', 'cheat_death'), ('strike_from_the_shadows', 'prey_on_the_weak', 'tangled_shadow'), ('dark_shadow', 'alacrity', 'enveloping_shadows'), From 2b462d3626faaf1f521d481d1955514c989b1c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 11 Oct 2017 12:39:40 +0200 Subject: [PATCH 261/265] [Subtlety] Improvements for shadow dance --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 57 ++++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 7f82592..9a43728 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -2271,11 +2271,16 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): if self.traits.shadows_whisper: self.energy_budget += 8 * shadow_techniques_procs + # Init stealth evis counter + stealth_evis_per_vanish = 0 + stealth_evis_per_dance = 0 + #vanish handling vanish_count = self.settings.duration / self.get_spell_cd('vanish') #Treat subterfuge as a mini-dance if self.talents.subterfuge or self.talents.nightstalker: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher='eviscerate', vanish=True) + stealth_evis_per_vanish += sum(attack_counts['eviscerate']) else: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None, vanish=True) self.energy_budget += vanish_count * net_energy @@ -2286,6 +2291,7 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): #Generate one final dance templates if self.settings.cycle.dance_finishers_allowed: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher='eviscerate') + stealth_evis_per_dance += sum(attack_counts['eviscerate']) else: net_energy, net_cps, spent_cps, attack_counts = self.get_dance_resources(finisher=None) @@ -2470,11 +2476,11 @@ def subtlety_attack_counts(self, current_stats, crit_rates=None): elif isinstance(attacks_per_second[ability], list) and not any(attacks_per_second[ability]): del attacks_per_second[ability] - #determine how many evis used during dance + #determine how many evis used during stealth if self.settings.cycle.dance_finishers_allowed: - stealth_evis = attacks_per_second['shadow_dance'] + stealth_evis = stealth_evis_per_dance * attacks_per_second['shadow_dance'] if self.talents.subterfuge: - stealth_evis += attacks_per_second['vanish'] + stealth_evis += stealth_evis_per_vanish * attacks_per_second['vanish'] else: stealth_evis = 0 self.stealth_evis_uptime = stealth_evis / sum(attacks_per_second['eviscerate']) @@ -2523,31 +2529,36 @@ def get_dance_resources(self, finisher=None, vanish=False): max_dance_energy = dance_gcds * self.energy_regen + self.max_energy - if finisher: - net_energy += self.relentless_strikes_energy_return_per_cp * self.settings.finisher_threshold - self.get_spell_cost(finisher) - dance_gcds -= 1 - net_cps -= self.settings.finisher_threshold - attack_counts[finisher] = [0, 0, 0, 0, 0, 0, 0] - attack_counts[finisher][self.settings.finisher_threshold] += 1 - spent_cps += self.settings.finisher_threshold - #fill remaining gcds with shadowstrikes - cp_builder = self.dance_cp_builder - cp_builder_cost = float(self.get_spell_cost(cp_builder, cost_mod=cost_mod)) - builder_count = min(dance_gcds, (net_energy + max_dance_energy) / cp_builder_cost) if vanish: - attack_counts[cp_builder] = builder_count attack_counts['vanish'] = 1 else: - attack_counts[cp_builder] = builder_count attack_counts['shadow_dance'] = 1 - net_energy -= attack_counts[cp_builder] * cp_builder_cost - if cp_builder == 'shadowstrike': - net_cps += attack_counts['shadowstrike'] * (2 + self.shadow_blades_uptime) - if self.stats.gear_buffs.rogue_t19_4pc: - net_cps += attack_counts['shadowstrike'] * 0.3 - elif cp_builder == 'shuriken_storm': - net_cps += min(1 + self.settings.num_boss_adds + self.shadow_blades_uptime, self.max_store_cps) + cp_builder_cost = self.get_spell_cost(self.dance_cp_builder, cost_mod=cost_mod) + attack_counts[self.dance_cp_builder] = 0 + if finisher: + finisher_cost = self.get_spell_cost(finisher, cost_mod=cost_mod) + attack_counts[finisher] = [0, 0, 0, 0, 0, 0, 0] + + while dance_gcds > 0: + remaining_energy = (net_energy + max_dance_energy) + if finisher and net_cps >= self.settings.finisher_threshold and remaining_energy >= finisher_cost: + use_cps = min(int(net_cps), self.max_spend_cps) + net_energy += self.relentless_strikes_energy_return_per_cp * use_cps - finisher_cost + attack_counts[finisher][use_cps] += 1 + spent_cps += use_cps + net_cps -= use_cps + elif remaining_energy >= cp_builder_cost: + attack_counts[self.dance_cp_builder] += 1 + net_energy -= cp_builder_cost + if self.dance_cp_builder == 'shadowstrike': + net_cps += 2 + self.shadow_blades_uptime + if self.stats.gear_buffs.rogue_t19_4pc: + net_cps += 0.3 + elif self.dance_cp_builder == 'shuriken_storm': + net_cps += min(1 + self.settings.num_boss_adds + self.shadow_blades_uptime, self.max_store_cps) + net_cps = min(net_cps, self.max_store_cps) + dance_gcds -= 1 return net_energy, net_cps, spent_cps, attack_counts From 0c0945117ef5490690aaf21b6b6e2fe9d502922e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Wed, 11 Oct 2017 18:25:31 +0200 Subject: [PATCH 262/265] Fix for Python 2 compat --- shadowcraft/objects/stats.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 8b7ceff..4f0d5d1 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -65,13 +65,13 @@ def get_character_base_stats(self, race, traits=None, buffs=None): 'mastery': self.mastery, 'versatility': self.versatility, } - if buffs: + if buffs is not None: buff_bonuses = buffs.get_stat_bonuses(race.epicurean) for bonus in buff_bonuses: base_stats[bonus] += buff_bonuses[bonus] #netherlight crucible t2 - if traits: + if traits is not None: if traits.light_speed: base_stats['haste'] += 500 * traits.light_speed if traits.master_of_shadows: From e3c7041ba8ffe9c422a4bd8382bf7224a2987324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 15 Oct 2017 00:24:23 +0200 Subject: [PATCH 263/265] Implement Insignia of the Grand Army legendary --- shadowcraft/calcs/rogue/Aldriana/__init__.py | 13 +++++++------ shadowcraft/objects/stats.py | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 9a43728..364ea5e 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -282,30 +282,31 @@ def set_constants(self): self.stats.procs.concordance_of_the_legionfall.value['crit'] = 1500 * self.traits.shocklight #netherlight crucible t2 procs + insigniaMod = 1.5 if self.stats.gear_buffs.insignia_of_the_grand_army else 1 self.stats.procs.del_proc('chaotic_darkness') if self.traits.chaotic_darkness: self.stats.procs.set_proc('chaotic_darkness') - self.stats.procs.chaotic_darkness.value *= self.traits.chaotic_darkness + self.stats.procs.chaotic_darkness.value *= self.traits.chaotic_darkness * insigniaMod self.stats.procs.del_proc('dark_sorrows') if self.traits.dark_sorrows: self.stats.procs.set_proc('dark_sorrows') - self.stats.procs.dark_sorrows.value *= self.traits.dark_sorrows + self.stats.procs.dark_sorrows.value *= self.traits.dark_sorrows * insigniaMod self.stats.procs.del_proc('infusion_of_light') if self.traits.infusion_of_light: self.stats.procs.set_proc('infusion_of_light') - self.stats.procs.infusion_of_light.value *= self.traits.infusion_of_light + self.stats.procs.infusion_of_light.value *= self.traits.infusion_of_light * insigniaMod self.stats.procs.del_proc('secure_in_the_light') if self.traits.secure_in_the_light: self.stats.procs.set_proc('secure_in_the_light') - self.stats.procs.secure_in_the_light.value *= self.traits.secure_in_the_light + self.stats.procs.secure_in_the_light.value *= self.traits.secure_in_the_light * insigniaMod self.stats.procs.del_proc('shadowbind') if self.traits.shadowbind: self.stats.procs.set_proc('shadowbind') - self.stats.procs.shadowbind.value *= self.traits.shadowbind + self.stats.procs.shadowbind.value *= self.traits.shadowbind * insigniaMod self.stats.procs.del_proc('torment_the_weak') if self.traits.torment_the_weak: self.stats.procs.set_proc('torment_the_weak') - self.stats.procs.torment_the_weak.value *= self.traits.torment_the_weak + self.stats.procs.torment_the_weak.value *= self.traits.torment_the_weak * insigniaMod #hit chances self.dw_mh_hit_chance = self.dual_wield_mh_hit_chance() diff --git a/shadowcraft/objects/stats.py b/shadowcraft/objects/stats.py index 4f0d5d1..53c17b0 100755 --- a/shadowcraft/objects/stats.py +++ b/shadowcraft/objects/stats.py @@ -72,10 +72,11 @@ def get_character_base_stats(self, race, traits=None, buffs=None): #netherlight crucible t2 if traits is not None: + insigniaMod = 1.5 if self.gear_buffs.insignia_of_the_grand_army else 1 if traits.light_speed: - base_stats['haste'] += 500 * traits.light_speed + base_stats['haste'] += 500 * traits.light_speed * insigniaMod if traits.master_of_shadows: - base_stats['mastery'] += 500 * traits.master_of_shadows + base_stats['mastery'] += 500 * traits.master_of_shadows * insigniaMod # Other bonuses if self.gear_buffs.rogue_orderhall_6pc: @@ -238,6 +239,7 @@ class GearBuffs(object): 'the_first_of_the_dead', #For 2 sec after activating Symbols of Death, Shadowstrike generates 3 additional combo points and Backstab generates 4 additional combo points. 'the_curse_of_restlessness', #NYI 'soul_of_the_shadowblade', #Gain the Vigor talent. + 'insignia_of_the_grand_army', #Increase the effects of Light and Shadow powers granted by the Netherlight Crucible by 50%. #Other 'jeweled_signet_of_melandrus', #Increases your autoattack damage by 10%. 'gnawed_thumb_ring', #Use: Have a nibble, increasing your healing and magic damage done by 5% for 12 sec. (3 Min Cooldown) From 7205235cb0330fd05d378cd2a479d0acefe787c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Sun, 15 Oct 2017 00:24:59 +0200 Subject: [PATCH 264/265] Add EditorConfig --- .editorconfig | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ed4527a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true From e6362d2b72fc22dc6813e55b23a384146d536688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Mei=C3=9Fner?= Date: Fri, 24 Nov 2017 17:32:52 +0100 Subject: [PATCH 265/265] Add Pantheon trinkets --- shadowcraft/calcs/__init__.py | 31 +++++++++++ shadowcraft/calcs/rogue/Aldriana/__init__.py | 9 ++++ shadowcraft/calcs/rogue/Aldriana/settings.py | 1 + .../calcs/rogue/Aldriana/settings_data.py | 7 +++ shadowcraft/calcs/rogue/__init__.py | 11 ++++ shadowcraft/objects/proc_data.py | 53 +++++++++++++++++++ 6 files changed, 112 insertions(+) diff --git a/shadowcraft/calcs/__init__.py b/shadowcraft/calcs/__init__.py index b5061ca..c078ea2 100755 --- a/shadowcraft/calcs/__init__.py +++ b/shadowcraft/calcs/__init__.py @@ -66,6 +66,8 @@ def __init__(self, stats, talents, traits, buffs, race, spec, settings=None, lev self.recalculate_hit_constants() self.base_block_chance = .03 + .015 * self.level_difference + self.pantheon_empowerment_uptime = self.get_pantheon_empowerment_uptime(self.settings.pantheon_trinket_users) + def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == 'level': @@ -697,3 +699,32 @@ def target_armor(self, armor=None): if armor is None: armor = self.target_base_armor return armor #* self.buffs.armor_reduction_multiplier() + + # Antorus 7.3 Raid Pantheon Trinket Empowerment, precomputed uptimes (thanks to seriallos from raidbots) + # Updated 2017/11/24 + def get_pantheon_empowerment_uptime(self, wearers): + uptimes = { + 4: 0.023, + 5: 0.061, + 6: 0.103, + 7: 0.141, + 8: 0.176, + 9: 0.205, + 10: 0.228, + 11: 0.248, + 12: 0.264, + 13: 0.276, + 14: 0.287, + 15: 0.295, + 16: 0.302, + 17: 0.306, + 18: 0.311, + 19: 0.313, + 20: 0.316 + } + if wearers in uptimes: + return uptimes[wearers] + elif wearers > 20: + return 0.316 + else: + return 0 diff --git a/shadowcraft/calcs/rogue/Aldriana/__init__.py b/shadowcraft/calcs/rogue/Aldriana/__init__.py index 364ea5e..a21368f 100644 --- a/shadowcraft/calcs/rogue/Aldriana/__init__.py +++ b/shadowcraft/calcs/rogue/Aldriana/__init__.py @@ -281,6 +281,15 @@ def set_constants(self): if self.traits.shocklight: self.stats.procs.concordance_of_the_legionfall.value['crit'] = 1500 * self.traits.shocklight + # Pantheon Empowerment proc setup + self.stats.procs.del_proc('amanthuls_vision_empowered') + if self.stats.procs.amanthuls_vision and self.settings.pantheon_trinket_users >= 4: + self.stats.procs.set_proc('amanthuls_vision_empowered') + self.stats.procs.amanthuls_vision_empowered.duration = self.pantheon_empowerment_uptime * self.stats.procs.amanthuls_vision_empowered.icd + self.stats.procs.del_proc('golganneths_vitality_empowered') + if self.stats.procs.golganneths_vitality and self.settings.pantheon_trinket_users >= 4: + self.stats.procs.set_proc('golganneths_vitality_empowered') + #netherlight crucible t2 procs insigniaMod = 1.5 if self.stats.gear_buffs.insignia_of_the_grand_army else 1 self.stats.procs.del_proc('chaotic_darkness') diff --git a/shadowcraft/calcs/rogue/Aldriana/settings.py b/shadowcraft/calcs/rogue/Aldriana/settings.py index 77069d7..f9eea80 100755 --- a/shadowcraft/calcs/rogue/Aldriana/settings.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings.py @@ -23,6 +23,7 @@ def __init__(self, cycle, **kwargs): self.adv_params = self.interpret_adv_params(kwargs.get('adv_params', defaults['adv_params'])) self.marked_for_death_resets = int(kwargs.get('marked_for_death_resets', defaults['marked_for_death_resets'])) self.finisher_threshold = int(kwargs.get('finisher_threshold', defaults['finisher_threshold'])) + self.pantheon_trinket_users = int(kwargs.get('pantheon_trinket_users', defaults['pantheon_trinket_users'])) def interpret_adv_params(self, s=""): data = {} diff --git a/shadowcraft/calcs/rogue/Aldriana/settings_data.py b/shadowcraft/calcs/rogue/Aldriana/settings_data.py index 6a5bee8..8913ba9 100644 --- a/shadowcraft/calcs/rogue/Aldriana/settings_data.py +++ b/shadowcraft/calcs/rogue/Aldriana/settings_data.py @@ -416,6 +416,13 @@ def process_overrides(defaults_dict, params_dict, spec): 'description': '', 'type': 'text', 'default': '0', + }, + { + 'name': 'pantheon_trinket_users', + 'label': 'Number of players with Pantheon trinkets', + 'description': '', + 'type': 'text', + 'default': '0', } ] }, diff --git a/shadowcraft/calcs/rogue/__init__.py b/shadowcraft/calcs/rogue/__init__.py index f47cb35..6c5a373 100755 --- a/shadowcraft/calcs/rogue/__init__.py +++ b/shadowcraft/calcs/rogue/__init__.py @@ -662,3 +662,14 @@ def add_special_procs_damage(self, current_stats, attacks_per_second, crit_rates if specter_of_betrayal: num_torrents = (1 + 2 * (self.settings.duration / specter_of_betrayal.icd)) / self.settings.duration damage_breakdown[specter_of_betrayal.proc_name] = self.get_proc_damage_contribution(specter_of_betrayal, num_torrents, current_stats, ap, modifier_dict) + + # Golganneth's Thunderous Wrath, use pantheon empowerment uptime value + golganneths_vitality_empowered = self.stats.procs.golganneths_vitality_empowered + if golganneths_vitality_empowered: + autoattacks_per_second = attacks_per_second['mh_autoattacks'] * self.dual_wield_mh_hit_chance() + autoattacks_per_second += attacks_per_second['oh_autoattacks'] * self.dual_wield_oh_hit_chance() + if 'shadow_blades' in attacks_per_second: + autoattacks_per_second += attacks_per_second['shadow_blades'] * 2 #both hands + dmg_per_aa = self.stats.mh.speed * self.get_proc_damage_contribution(golganneths_vitality_empowered, 1, current_stats, ap, modifier_dict) + proc_damage = dmg_per_aa * autoattacks_per_second * (1 + self.settings.num_boss_adds) * self.pantheon_empowerment_uptime + damage_breakdown[golganneths_vitality_empowered.proc_name] = proc_damage diff --git a/shadowcraft/objects/proc_data.py b/shadowcraft/objects/proc_data.py index 5daa5f7..732ec8b 100755 --- a/shadowcraft/objects/proc_data.py +++ b/shadowcraft/objects/proc_data.py @@ -877,6 +877,59 @@ 'trigger': 'all_attacks' }, + ### Antorus Procs ### + 'amanthuls_vision': { #PROXY PROC, only used to set empowerment + 'stat': 'special_model', + 'value': 0, + 'duration': 0, + 'proc_name': 'Aman\'thul Proxy' + }, + 'amanthuls_vision_empowered': { #When empowered by the Pantheon, your primary stat is increased by X for 15 sec. + 'stat':'stats', + 'value': {'agi': 0}, #rpp-scaled + 'duration': 15, #Ignored, use precomputed uptime values, set in set_constants + 'proc_name': 'Aman\'thul\'s Grandeur', + 'scaling': 0.639601, + 'item_level': 1000, + 'source': 'trinket', + 'type': 'icd', + 'icd': 100, #modeled as icd, duration will be set to uptime % and icd 100 + 'proc_rate': 1, #Ignore RPPM + 'trigger': 'all_attacks' + }, + + 'golganneths_vitality': { #Your damaging abilities have a chance to create a Ravaging Storm at your target's location, inflicting Nature damage split among all enemies within 6 yds over 6 sec. + 'stat':'spell_damage', + 'dmg_school': 'nature', + 'value': 0, #rpp-scaled + 'duration': 6, + 'proc_name': 'Ravaging Storm', + 'scaling': 14.58708 * 6, # 6 hits + 'item_level': 940, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 1.8, + 'can_crit': True, + 'haste_scales': True, + 'trigger': 'all_attacks' + }, + 'golganneths_vitality_empowered': { #When empowered by the Pantheon, your autoattacks cause an explosion of lightning dealing [(Mainhand weapon base speed) * X] Nature damage to all enemies within 8 yds of the target. Lasts 15 sec. + 'stat':'special_model', + 'dmg_school': 'nature', + 'aoe': True, + 'value': 0, #rpp-scaled + 'duration': 15, #Ignored, use precomputed uptime values + 'proc_name': 'Golganneth\'s Thunderous Wrath', + 'scaling': 7.98956, + 'item_level': 940, + 'type': 'rppm', + 'source': 'trinket', + 'proc_rate': 0, #Ignore RPPM, special pantheon formula in add_special_procs_damage + 'can_crit': True, + 'haste_scales': True, + 'trigger': 'all_attacks' + }, + #Other Legion procs 'jacins_ruse_2pc': { #Equip: Your spells and attacks have a chance to increase your Mastery by 3000 for 15 sec. 'stat':'stats',