MMO News and theorycrafting for advanced MMO gamers. News and articles that relate to your gameplay. World of Warcraft, SWTOR, Guild Wars 2, Rift, TERA, Eve Online, Star Wars the Old Republic, Diablo3, The Secret World and all Western AAA MMOs

Hello There, Guest! Register

 Combat, Watchman Simulators
04-07-2012, 01:06 PM (This post was last modified: 07-06-2012 08:14 AM by Qed.)
Post: #1
 Qed Member Posts: 86 Joined: Jan 2012 Reputation: 0
Combat, Watchman Simulators
Inspired by the tremendous amount of work done by others on these forums, I built my trooper DPS simulator (here) into a DPS simulator for Combat and Watchman specs. I post them here for anyone whose interested, and to catch the mistakes I'm sure to have made. Both of these scripts are written in python and should be poked and prodded until they give you the numbers you want.

It is fairly straightforward to calculate stat weights out of this. A script to do this automatically is included below as well.

Now, blocks of code:

Combat:

Code:
import math
import random

#buffs
smuggler = 1
knight = 1
consular = 1

import sys

#base stats
#str = 1505
#will = 140
#fpower = 1213
#power = 345
#crit = 192
#surge = 294
#acc = 147
#alac = 0
#weapondam = 450

str = (1912 )* (1 +.05*consular)
will = 140 * (1 +.05*consular)
fpower = 1261
power = (993 + 113)
crit = 164
surge = 342
acc = 228
alac = 1
mh_dam = 405
oh_dam = 405

str    = int(sys.argv[1]) + str
will   = int(sys.argv[2]) + will
fpower = int(sys.argv[3]) + fpower
power  = int(sys.argv[4]) + power
crit   = int(sys.argv[5]) + crit
surge  = int(sys.argv[6]) + surge
acc    = int(sys.argv[7]) + acc
alac   = int(sys.argv[8]) + alac
mh_dam = int(sys.argv[9]) + mh_dam
oh_dam = int(sys.argv[10])+ oh_dam

#skills

#static bonuses
bonus_acc = 0.07
bonus_crit = 0.01 +0.05*smuggler
bonus_alac = 0.00
bonus_surge = 0.01

def diminishing_returns(x,max,lam):
return max*(1-(1-(0.01/max))**(x/(50*lam)))

#derived stats
#armor_dr = 0.8727
#armor_dr = 0.69
armor_dr = 0.65
mh_hit = min(.82+bonus_acc+diminishing_returns(acc,0.3,0.55) ,1)
oh_hit = min(.49+bonus_acc+diminishing_returns(acc,0.3,0.55) ,1)
mcrit = 0.05+bonus_crit+diminishing_returns(crit,0.3,0.45)+diminishing_returns(str,0.3,2​.5)
critvalue = 0.50+bonus_surge+diminishing_returns(surge,0.3,0.11)
haste = bonus_alac+diminishing_returns(alac,0.3,0.55)
bonusdmg = (str*0.2+power*0.23)*(1+0.05*knight)
bonusfdmg = (str*0.2+will*0.2+power*0.23+fpower*0.23)*(1+0.05*knight)

t = 0
focus = 0
tot_damage = 0
remove_armor = 0
centering = 0
zen = 0
ataru_cd = 0
ataru_oa_buff = 0
ataru_trance = 0
br_ataru_buff = 0
zealous_strike_cd = 0
precision_slash_cd = 0
master_strike_cd = 0
force_stasis_cd = 0
dispatch_cd = 0
valorous_call_cd = 0
proc_trinket_cd = 0

n_as = 0
n_br = 0
n_s = 0
n_zs = 0
n_ps = 0
n_ms = 0
n_bs = 0
n_fs = 0
n_z = 0
n_d = 0
n_vc = 0
n_pt = 0

d_as = 0
d_br = 0
d_s = 0
d_zs = 0
d_ps = 0
d_ms = 0
d_bs = 0
d_fs = 0
d_z = 0
d_d = 0
d_vc = 0

def try_proc_trinket(delay):
global tot_damage
global proc_trinket_cd
global armor_dr
global n_pt
return
if (proc_trinket_cd - delay) > 0: return
thisdmg = 0
mhdmg = (246)
#mhdmg = (0)
thisdmg = thisdmg + mhdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
tot_damage = tot_damage + thisdmg
proc_trinket_cd = 4.5+delay
n_pt = n_pt + 1
#print "Proccy Trinket: ", thisdmg ," , "

def ataru_strike(isfree, delay):
global focus
global tot_damage
global remove_armor
global ataru_cd
global ataru_oa_buff
global ataru_trance
global n_as , d_as
thisdmg = 0
bdmg = (1610*0.038+bonusdmg*0.38+mh_dam*0.385)*1.3
if random.uniform(0,1) < (mh_hit+0.1):
if isfree < 1: ataru_cd = 1.5+delay
#ataru_cd = 1.5+delay
if ataru_trance < 0: ataru_trance = 6.0
if (random.uniform < 0.3): ataru_oa_buff = 1
thisdmg = bdmg
if random.uniform(0,1) < (mcrit):
thisdmg =  bdmg*(1+critvalue+0.3)
if remove_armor < 0: thisdmg = thisdmg*armor_dr
tot_damage = tot_damage + thisdmg
n_as = n_as + 1
d_as = d_as + thisdmg
#print "Ataru Proc: ", thisdmg

def try_ataru(isfree = False, delay = 0):
global br_ataru_buff
global ataru_cd
if (ataru_cd <= delay or isfree):
if (random.uniform(0,1) < (0.2+0.3*br_ataru_buff) or isfree):
ataru_strike(isfree, delay)

global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global br_ataru_buff
global ataru_oa_buff
global n_br , d_br
thisdmg = 0
nhits = 0
mhdmg = (1610*0.134+bonusdmg*1.34+mh_dam*0.89)
ohdmg = (oh_dam*0.89*0.66)
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
br_ataru_buff = 6.0
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue+0.3)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
br_ataru_buff = 6.0
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue+0.3)
thisdmg = thisdmg+ohdmg
if ataru_oa_buff == 1:
thisdmg = thisdmg*1.1
ataru_oa_buff = 0
if remove_armor < 0: thisdmg = thisdmg*armor_dr
try_ataru(True)
#try_ataru(False)
if zen > 0:
focus = focus - 1
passtime = 1
zen = zen - 1
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1/nhits+1*n/nhits
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)

else:
focus = focus - 2
passtime = 1.5
centering = centering + 4
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
br_ataru_buff = 6
tot_damage = tot_damage + thisdmg
n_br = n_br + 1
d_br = d_br + thisdmg
#print "Blade Rush: ", thisdmg ," , " , passtime , focus

def zealous_strike():
global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global zealous_strike_cd
global n_zs , d_zs
thisdmg = 0
nhits = 0
mhdmg = (1610*0.1+bonusdmg*1+mh_dam*0.33)
ohdmg = (oh_dam*0.67*0.66)
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
#try_ataru(False)
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
focus = focus + 6
zealous_strike_cd = 11.5
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_zs = n_zs + 1
d_zs = d_zs + thisdmg
#print "Zealous Strike: ", thisdmg ," , " , passtime , focus

def strike():
global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global n_s , d_s
thisdmg = 0
nhits = 0
mhdmg = (1610*0.0+bonusdmg*1+mh_dam*1.01)
ohdmg = (oh_dam*0.99*0.66)
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
#try_ataru(False)
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
focus = focus + 2
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_s = n_s + 1
d_s = d_s + thisdmg
#print "Strike: ", thisdmg ," , " , passtime , focus

def precision_slash():
global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global ataru_oa_buff
global precision_slash_cd
global n_ps , d_ps
thisdmg = 0
nhits = 0
mhdmg = (1610*0.137+bonusdmg*1.37+mh_dam*0.91)*(.53/.91)
#ohdmg = (oh_dam*0.91*0.66)
ohdmg = (oh_dam*0.57*0.66)
remove_armor = 4.5
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
if ataru_oa_buff == 1:
thisdmg = thisdmg*1.1
ataru_oa_buff = 0
#try_ataru(False)
for n in xrange(nhits):
swing_delay = 0
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
focus = focus - 3
precision_slash_cd = 15
if zen < 1: centering = centering + 4
passtime = 0
tot_damage = tot_damage + thisdmg
n_ps = n_ps + 1
d_ps = d_ps + thisdmg
#print "Precision Slash: ", thisdmg ," , " , passtime , focus

def master_strike():
global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global master_strike_cd
global n_ms , d_ms
thisdmg = 0
nhits = 0
mhdmg = (1610*0.417+bonusdmg*4.17+mh_dam*2.775)*1.08
oh1dmg = (oh_dam*0.925*0.66)*1.08
oh2dmg = (oh_dam*1.85)*1.08
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
oh1dmg =  oh1dmg*(1+critvalue)
thisdmg = thisdmg+oh1dmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
oh2dmg =  oh2dmg*(1+critvalue)
thisdmg = thisdmg+oh2dmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
thisdmg = thisdmg*1.15
#try_ataru(False)
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*3/nhits+3*n/nhits
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
master_strike_cd = 27
passtime = 3
tot_damage = tot_damage + thisdmg
n_ms = n_ms + 1
d_ms = d_ms + thisdmg
#print "Master Strike: ", thisdmg ," , " , passtime , focus

global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global ataru_oa_buff
global n_bs , d_bs
thisdmg = 0
bonuscrit = 0
if ataru_trance > 0: bonuscrit = 1
mhdmg = (1610*0.187+bonusfdmg*1.87)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.06+1*bonuscrit):
mhdmg =  mhdmg*(1+critvalue+0.3)
thisdmg = thisdmg+mhdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
if ataru_oa_buff == 1:
thisdmg = thisdmg*1.1
ataru_oa_buff = 0
focus = focus - 2
if zen < 1: centering = centering + 4
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_bs = n_bs + 1
d_bs = d_bs + thisdmg
#print "Blade Storm: ", thisdmg ," , " , passtime , focus

def force_stasis():
global focus
global tot_damage
global passtime
global remove_armor
global force_stasis_cd
global n_fs , d_fs
thisdmg = 0
mhdmg = (1610*0.272+bonusfdmg*2.72)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.06):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
focus = focus +3
force_stasis_cd = 50
passtime = 3*(1-haste)
tot_damage = tot_damage + thisdmg
n_fs = n_fs + 1
d_fs = d_fs + thisdmg
#print "Force Stasis: ", thisdmg ," , " , passtime , focus

def dispatch():
global focus
global tot_damage
global passtime
global remove_armor
global centering
global zen
global ataru_oa_buff
global dispatch_cd
global n_d , d_d
thisdmg = 0
mhdmg = (1610*0.285+bonusdmg*2.95+mh_dam*1.9)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if remove_armor < 0: thisdmg = thisdmg*armor_dr
if ataru_oa_buff == 1:
thisdmg = thisdmg*1.1
ataru_oa_buff = 0
focus = focus - 1
if zen < 1: centering = centering + 4
swing_delay = random.uniform(0,1)*1.5
try_ataru(False, swing_delay)
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
dispatch_cd = 6
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_d = n_d + 1
d_d = d_d + thisdmg
#print "Dispatch: ", thisdmg ," , " , passtime , focus

def zazen():
global passtime
global centering
global zen
global n_z
centering = 0
zen = 6
passtime = 0
n_z = n_z + 1
#print "Invoking Zen: ", thisdmg ," , " , passtime , focus

def valorous_call():
global passtime
global centering
global valorous_call_cd
global n_vc
centering = 30
valorous_call_cd = 150
passtime = 0
n_vc = n_vc + 1
#print "Valorous Call " , thisdmg , " , " , passtime , focus

for iteration in xrange(9999):
fight_length = 300
iteration_t = 0

focus = 0
remove_armor = 0
centering = 0
zen = 0
ataru_cd = 0
ataru_oa_buff = 0
ataru_trance = 0
br_ataru_buff = 0
zealous_strike_cd = 0
precision_slash_cd = 0
master_strike_cd = 0
force_stasis_cd = 0
dispatch_cd = 0
valorous_call_cd = 0
proc_trinket_cd = 0

choose_ability = 0

while iteration_t < fight_length:
thisdmg = 0
passtime = 0

######SIMPLE PRIORITY######
#if centering >= 30: zazen()
#elif zealous_strike_cd <= 0: zealous_strike()
#elif br_ataru_buff <= 0 and focus >= 3: blade_rush()
#elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
#elif ataru_trance > 0 and focus >= 2 and blade_storm_cd <= 0: blade_storm()
#elif master_strike_cd <= 0: master_strike()
#elif force_stasis_cd <= 0: force_stasis()
#else: strike()
###END SIMPLE PRIORITY####

######ZEN BURN############
#if zealous_strike_cd <= 0: zealous_strike()
#elif centering >= 30:
#    if br_ataru_buff <= 0 and focus >= 3: blade_rush()
#    elif focus < 8: strike()
#    else: zazen()
#elif zen > 0:
#    if br_ataru_buff <= 0 and focus >= 3: blade_rush()
#    elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
#    elif focus >= 3: blade_rush()
#    else: strike()
#else:
#    if br_ataru_buff <= 0 and focus >= 3: blade_rush()
#    elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
#    elif ataru_trance > 0 and focus >= 2 and blade_storm_cd <= 0: blade_storm()
#    elif master_strike_cd <= 0: master_strike()
#    elif force_stasis_cd <= 0: force_stasis()
#    elif focus >= 3: blade_rush()
#    else: strike()
###END ZEN BURN###########

###PRECISE BURN########
#if centering >= 30: zazen()
#elif zealous_strike_cd <= 0: zealous_strike()
#elif br_ataru_buff <= 0 and focus >= 3: blade_rush()
#elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
#elif precision_slash_cd <= 3 and focus <= 5: strike()
#elif ataru_trance > 0 and focus >= 2 and blade_storm_cd <= 0: blade_storm()
#elif master_strike_cd <= 0: master_strike()
#elif force_stasis_cd <= 0: force_stasis()
#else: strike()
##END PRECISE BURN#####

######PRECISE ZEN BURN############
if zealous_strike_cd <= 0: zealous_strike()
elif centering <= 5 and valorous_call_cd <= 0: valorous_call()
elif centering >= 30: zazen()
elif zen > 0:
if br_ataru_buff <= 0 and focus >= 3: blade_rush()
elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
elif iteration_t > fight_length*0.70 and focus >= 2 and dispatch_cd <=0: dispatch()
else: strike()
else:
if br_ataru_buff <= 0 and focus >= 3: blade_rush()
elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
elif iteration_t > fight_length*0.70 and focus >= 2 and dispatch_cd <=0: dispatch()
elif ataru_trance > 0 and focus >= 2 and blade_storm_cd <= 0: blade_storm()
elif master_strike_cd <= 0: master_strike()
elif force_stasis_cd <= 0: force_stasis()
else: strike()
###END PRECISE ZEN BURN###########

#########ATARU TEST####################
#if choose_ability == 0: strike()
#choose_ability = 1 - choose_ability
####END ATARU TEST#####################

t = t+passtime
iteration_t = iteration_t + passtime
zealous_strike_cd = zealous_strike_cd - passtime
precision_slash_cd = precision_slash_cd - passtime
master_strike_cd = master_strike_cd - passtime
force_stasis_cd = force_stasis_cd - passtime
remove_armor = remove_armor - passtime
ataru_cd = ataru_cd - passtime
dispatch_cd = dispatch_cd - passtime
valorous_call_cd = valorous_call_cd - passtime
if ataru_trance > 0 and (ataru_trance - passtime) <= 0:
focue = focus +1
ataru_trance = ataru_trance - passtime
br_ataru_buff = br_ataru_buff - passtime
proc_trinket_cd = proc_trinket_cd - passtime
tot_damage = tot_damage + thisdmg

print "Total Time: ", t , " seconds"
print "Total Damage: " , tot_damage
print "DPS: " , tot_damage/t
print ""
if n_as > 0: print "Ataru ECD: " , t/n_as , " : " , 100*d_as/tot_damage
if n_br > 0: print "Blade Rush ECD: " , t/n_br , " : " , 100*d_br/tot_damage
if n_zs > 0: print "Zealous Strike ECD: " , t/n_zs , " : " , 100*d_zs/tot_damage
if n_s  > 0: print "Strike ECD: ", t/n_s , " : " , 100*d_s/tot_damage
if n_ps > 0: print "Precision Stike ECD: " , t/n_ps , " : " , 100*d_ps/tot_damage
if n_z  > 0: print "Zen ECD: " , t/n_z
if n_d  > 0: print "Dispatch ECD: " , t/n_d , " : " , 100*d_d/tot_damage
if n_bs > 0: print "Blade Storm ECD: ", t/n_bs , " : " , 100*d_bs/tot_damage
if n_ms > 0: print "Master Strike ECD: ", t/n_ms , " : " , 100*d_ms/tot_damage
if n_fs > 0: print "Force Stasis ECD: ", t/n_fs , " : " , 100*d_fs/tot_damage
if n_vc > 0: print "Valorous Call ECD: ", t/n_vc
if n_pt > 0: print "Proccy Trinket ECD: ", t/n_pt

#import os
#os.environ['PYTHONINSPECT'] = '1'

Watchman

Code:
import math
import random
import array
from array import array

#buffs
smuggler = 1
knight = 1
consular = 0

import sys

#base stats
#str = 1505
#will = 140
#fpower = 1213
#power = 345
#crit = 192
#surge = 294
#acc = 147
#alac = 0
#weapondam = 450

str = 1922 * (1 +.05*consular)
will = 140 * (1 +.05*consular)
fpower = 1261
power = 944
crit = 119
surge = 342
acc = 228
alac = 1
mh_dam = 405
oh_dam = 405

str    = int(sys.argv[1]) + str
will   = int(sys.argv[2]) + will
fpower = int(sys.argv[3]) + fpower
power  = int(sys.argv[4]) + power
crit   = int(sys.argv[5]) + crit
surge  = int(sys.argv[6]) + surge
acc    = int(sys.argv[7]) + acc
alac   = int(sys.argv[8]) + alac
mh_dam = int(sys.argv[9]) + mh_dam
oh_dam = int(sys.argv[10])+ oh_dam

#skills

#static bonuses
bonus_acc = 0.01
bonus_crit = 0.01 +0.05*smuggler
bonus_alac = 0.00
bonus_surge = 0.01

def diminishing_returns(x,max,lam):
return max*(1-(1-(0.01/max))**(x/(50*lam)))

#derived stats
#armor_dr = 0.8727
#armor_dr = 0.69
armor_dr = 0.65
mh_hit = min(.82+bonus_acc+diminishing_returns(acc,0.3,0.55) ,1)
oh_hit = min(.49+bonus_acc+diminishing_returns(acc,0.3,0.55) ,1)
mcrit = 0.05+bonus_crit+diminishing_returns(crit,0.3,0.45)+diminishing_returns(str,0.3,2​.5)
critvalue = 0.50+bonus_surge+diminishing_returns(surge,0.3,0.11)
haste = bonus_alac+diminishing_returns(alac,0.3,0.55)
bonusdmg = (str*0.2+power*0.23)*(1+0.05*knight)
bonusfdmg = (str*0.2+will*0.2+power*0.23+fpower*0.23)*(1+0.05*knight)

t = 0

cauterize_tick_cd_array = array('f',[-1,-1,-1,-1,-1,-1])

focus = 0
juyo_stacks = 0
juyo_cd = 0
tot_damage = 0
remove_armor = 0
centering = 0
zen = 0
zealous_strike_cd = 0
master_strike_cd = 0
force_stasis_cd = 0
force_leap_cd = 0
dispatch_cd = 0
valorous_call_cd = 0
merciless_slash_cd = 0
merciless_buff = 0
merciless_buff_cd = 0
burning_focus_cd = 0
mind_sear_cd = 0
cauterize_cd = 0
proc_trinket_cd = 0

n_st = 0
n_zs = 0
n_ms = 0
n_me = 0
n_fs = 0
n_fl = 0
n_z = 0
n_d = 0
n_vc = 0
n_ca = 0
n_sl = 0
n_os = 0
n_pt = 0

def try_proc_trinket(delay):
global tot_damage
global juyo_stacks
global proc_trinket_cd
global n_pt
if (proc_trinket_cd - delay) > 0: return
thisdmg = 0
mhdmg = (246)
#mhdmg = (0)
thisdmg = thisdmg + mhdmg
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
tot_damage = tot_damage + thisdmg
proc_trinket_cd = 4.5+delay
n_pt = n_pt + 1
#print "Proccy Trinket: ", thisdmg ," , "

global focus
global passtime
global n_os
passtime = 0
focus = focus - 3
n_os = n_os + 1

global focus
global tot_damage
global juyo_stacks
global centering
global zen
global burning_focus_cd
thisdmg = 0
bonuscrit = 0
if zen > 0: bonuscrit = 1
mhdmg = (1610*0.02+bonusfdmg*0.2)*1.15
if random.uniform(0,1) < (mcrit + 0.06 + 0.01*juyo_stacks + bonuscrit):
mhdmg =  mhdmg*(1+critvalue+0.3)
thisdmg = thisdmg*(1+0.02*juyo_stacks)
if burning_focus_cd <= 0 and random.uniform(0,1) < 0.3: focus = focus + 1
if zen > 0: zen = zen - 1
tot_damage = tot_damage + thisdmg
#print "Overload Saber Tick: ", thisdmg

def merciless_slash():
global focus
global tot_damage
global passtime
global juyo_cd
global juyo_stacks
global centering
global zen
global merciless_slash_cd
global merciless_buff
global merciless_buff_cd
global mind_sear_cd
global cauterize_cd
global n_me
thisdmg = 0
nhits = 0
mhdmg = (1610*0.285+bonusdmg*2.85+mh_dam*1.9)
ohdmg = (oh_dam*1.9*0.66)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
nhits = nhits + 1
if random.uniform(0,1) < (oh_hit+0.1):
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
nhits = nhits + 1
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
focus = focus - 4
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
if zen < 1: centering = centering + 4
merciless_slash_cd = 12-1.5*merciless_buff
if merciless_buff < 3: merciless_buff = merciless_buff+1
merciless_buff_cd = 15
if mind_sear_cd <= 0 and random.uniform(0,1) < 0.66: cauterize_cd = 0
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_me = n_me + 1
#print "Merciless Slash: ", thisdmg ," , " , passtime , focus

def slash():
global focus
global tot_damage
global passtime
global juyo_cd
global juyo_stacks
global centering
global zen
global mind_sear_cd
global cauterize_cd
global n_sl
thisdmg = 0
nhits = 0
mhdmg = (1610*0.153+bonusdmg*1.53+mh_dam*1.02)
ohdmg = (oh_dam*1.02*0.66)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.15):
mhdmg =  mhdmg*(1+critvalue)
nhits = nhits + 1
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.15):
ohdmg =  ohdmg*(1+critvalue)
nhits = nhits + 1
thisdmg = thisdmg+ohdmg
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
focus = focus - 2
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
if zen < 1: centering = centering + 4
if mind_sear_cd <= 0 and random.uniform(0,1) < 0.33: cauterize_cd = 0
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_sl = n_sl + 1
#print "Slash: ", thisdmg ," , " , passtime , focus

def cauterize():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global centering
global zen
global cauterize_tick_cd_array
global cauterize_cd
global n_ca
thisdmg = 0
nhits = 0
mhdmg = (1610*0.06+bonusdmg*0.6+mh_dam*0.4)*1.3
ohdmg = (oh_dam*0.6*0.66)*1.3
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
nhits = nhits + 1
if random.uniform(0,1) < (oh_hit+0.1):
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
nhits = nhits + 1
cauterize_tick_cd_array = array('f',[1,2,3,4,5,6])
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
focus = focus - 2
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
if zen < 1: centering = centering + 4
cauterize_cd = 15
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_ca = n_ca + 1
#print "Cauterize Applied: ", thisdmg ," , " , passtime , focus

def cauterize_tick():
global focus
global tot_damage
global juyo_stacks
global centering
global zen
global burning_focus_cd
thisdmg = 0
bonuscrit = 0
if zen > 0: bonuscrit = 1
mhdmg = (1610*0.02+bonusfdmg*0.2)*1.15
if random.uniform(0,1) < (mcrit + 0.06 + 0.01*juyo_stacks + bonuscrit):
mhdmg =  mhdmg*(1+critvalue+0.3)
thisdmg = thisdmg+mhdmg
thisdmg = thisdmg*(1+0.02*juyo_stacks)
if burning_focus_cd <= 0 and random.uniform(0,1) < 0.3: focus = focus + 1
if zen > 0: zen = zen - 1
tot_damage = tot_damage + thisdmg
#print "Cauterize Tick: ", thisdmg

def zealous_strike():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global centering
global zen
global zealous_strike_cd
global n_zs
thisdmg = 0
nhits = 0
mhdmg = (1610*0.1+bonusdmg*1+mh_dam*0.33)
ohdmg = (oh_dam*0.67*0.66)
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
focus = focus + 6
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
zealous_strike_cd = 15
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_zs = n_zs + 1
#print "Zealous Strike: ", thisdmg ," , " , passtime , focus

def strike():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global centering
global zen
global n_st
thisdmg = 0
nhits = 0
mhdmg = (1610*0.0+bonusdmg*1+mh_dam*1.01)
ohdmg = (oh_dam*0.99*0.66)
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
ohdmg =  ohdmg*(1+critvalue)
thisdmg = thisdmg+ohdmg
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
focus = focus + 2
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_st = n_st + 1
#print "Strike: ", thisdmg ," , " , passtime , focus

def master_strike():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global centering
global zen
global master_strike_cd
global n_ms
thisdmg = 0
nhits = 0
mhdmg = (1610*0.417+bonusdmg*4.17+mh_dam*2.775)*1.08
oh1dmg = (oh_dam*0.925*0.66)*1.08
oh2dmg = (oh_dam*1.85)*1.08
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mh_hit+0.1):
nhits = nhits+1
if random.uniform(0,1) < (mcrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
oh1dmg =  oh1dmg*(1+critvalue)
thisdmg = thisdmg+oh1dmg
if random.uniform(0,1) < (oh_hit+0.1):
nhits = nhits + 1
if random.uniform(0,1) < (mcrit):
oh2dmg =  oh2dmg*(1+critvalue)
thisdmg = thisdmg+oh2dmg
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*3/nhits+3*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
master_strike_cd = 27
passtime = 3
tot_damage = tot_damage + thisdmg
n_ms = n_ms + 1
#print "Master Strike: ", thisdmg ," , " , passtime , focus

def force_stasis():
global focus
global tot_damage
global passtime
global force_stasis_cd
global n_fs
thisdmg = 0
mhdmg = (1610*0.272+bonusfdmg*2.72)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.06):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
if random.uniform (0,1) < 0.3: try_proc_trinket(0)
focus = focus +3
force_stasis_cd = 50
passtime = 3*(1-haste)
tot_damage = tot_damage + thisdmg
n_fs = n_fs + 1
#print "Force Stasis: ", thisdmg ," , " , passtime , focus

def force_leap():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global force_leap_cd
global n_fl
thisdmg = 0
nhits = 0
mhdmg = (1610*0.091+bonusdmg*.91+mh_dam*0.61)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.06):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
nhits = nhits + 1
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
thisdmg = thisdmg*armor_dr*(1+0.02*juyo_stacks)
focus = focus + 4
force_leap_cd = 12
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_fl = n_fl + 1
#print "Force Leap: ", thisdmg ," , " , passtime , focus

def dispatch():
global focus
global tot_damage
global passtime
global juyo_stacks
global juyo_cd
global centering
global zen
global dispatch_cd
global n_d
nhits = 0
thisdmg = 0
bonuscrit = 0
mhdmg = (1610*0.285+bonusdmg*2.95+mh_dam*1.9)
if random.uniform(0,1) < (mh_hit+0.1):
if random.uniform(0,1) < (mcrit+0.06+1*bonuscrit):
mhdmg =  mhdmg*(1+critvalue)
thisdmg = thisdmg+mhdmg
nhits = nhits + 1
for n in xrange(nhits):
swing_delay = random.uniform(0,1)*1.5/nhits+1.5*n/nhits
if random.uniform (0,1) < 0.3: try_proc_trinket(swing_delay)
focus = focus - 1
if juyo_stacks < 5 and juyo_cd <= 0: juyo_stacks = juyo_stacks+1
if zen < 1: centering = centering + 4
dispatch_cd = 6
passtime = 1.5
tot_damage = tot_damage + thisdmg
n_d = n_d + 1
#print "Dispatch: ", thisdmg ," , " , passtime , focus

def zazen():
global passtime
global centering
global zen
global n_z
centering = 0
zen = 6
passtime = 0
n_z = n_z + 1
#print "Invoking Zen: ", thisdmg ," , " , passtime , focus

def valorous_call():
global passtime
global centering
global valorous_call_cd
global n_vc
centering = 30
valorous_call_cd = 150
passtime = 0
n_vc = n_vc + 1
#print "Valorous Call " , thisdmg , " , " , passtime , focus

for iteration in xrange(30000):
fight_length = 300
iteration_t = 0

focus = 0
remove_armor = 0
centering = 0
zen = 0
zealous_strike_cd = 0
precision_slash_cd = 0
master_strike_cd = 0
force_stasis_cd = 0
dispatch_cd = 0
valorous_call_cd = 0
merciless_slash_cd = 0
merciless_buff = 0
burning_focus_cd = 0
mind_sear_cd = 0
cauterize_cd = 0

cauterize_tick_cd_array = array('f',[-1,-1,-1,-1,-1,-1])

while iteration_t < fight_length:
thisdmg = 0
passtime = 0

######MASH BUTTANS######
#if cauterize_cd <= 0 and focus >= 2: cauterize()
#elif merciless_slash_cd <=0 and focus >= 5: merciless_slash()
#else: strike()
###END MASH BUTTANS#####

######SIMPLE PRIORITY######
#if centering >= 30: zazen()
#elif centering <= 5 and valorous_call_cd <= 0: valorous_call()
#elif cauterize_cd <= 0 and focus >= 2: cauterize()
#elif zealous_strike_cd <= 0: zealous_strike()
#elif iteration_t > fight_length*0.7 and focus >= 7 and dispatch_cd <=0: dispatch()
#elif merciless_slash_cd <=0 and focus >= 5: merciless_slash()
#elif master_strike_cd <= 0: master_strike()
#elif force_leap_cd <= 0: force_leap()
#elif force_stasis_cd <= 0: force_stasis()
#elif focus > 5: slash()
#else: strike()
###END SIMPLE PRIORITY####

######HAND-OPTIMIZE######
if centering >= 30: zazen()
elif centering <= 5 and valorous_call_cd <= 0: valorous_call()
elif merciless_slash_cd <=0 and focus >= 5 and merciless_buff_cd <= 2: merciless_slash()
elif cauterize_cd <= 0 and focus >= 2: cauterize()
elif zealous_strike_cd <= 0 and focus <= 6: zealous_strike()
elif force_leap_cd <= 0 and focus <= 8: force_leap()
elif iteration_t > fight_length*0.7 and focus >= 2 and dispatch_cd <=0: dispatch()
elif merciless_slash_cd <=0 and focus >= 5: merciless_slash()
elif master_strike_cd <= 0 and merciless_buff_cd >= 4: master_strike()
#elif force_stasis_cd <= 0 and merciless_buff_cd >= 4: force_stasis()
elif focus > 8: slash()
else: strike()
###END HAND-OPTIMIZE####

t = t+passtime
iteration_t = iteration_t + passtime
zealous_strike_cd = zealous_strike_cd - passtime
precision_slash_cd = precision_slash_cd - passtime
master_strike_cd = master_strike_cd - passtime
force_stasis_cd = force_stasis_cd - passtime
force_leap_cd = force_leap_cd - passtime
remove_armor = remove_armor - passtime
dispatch_cd = dispatch_cd - passtime
valorous_call_cd = valorous_call_cd - passtime
tot_damage = tot_damage + thisdmg
juyo_cd = juyo_cd - passtime
merciless_slash_cd = merciless_slash_cd - passtime
burning_focus_cd = burning_focus_cd - passtime
mind_sear_cd = mind_sear_cd - passtime
cauterize_cd = cauterize_cd - passtime
proc_trinket_cd = proc_trinket_cd - passtime
merciless_buff_cd = merciless_buff_cd - passtime
if merciless_buff_cd <= 0:
merciless_buff = 0
#print "Merciless Fell Off"
if focus > 12: focus = 12

#print focus, centering , zen

for n in xrange(len(cauterize_tick_cd_array)):
if cauterize_tick_cd_array[n] > 0 and cauterize_tick_cd_array[n] - passtime <= 0: cauterize_tick()
cauterize_tick_cd_array[n] = cauterize_tick_cd_array[n] - passtime

print "Total Time: ", t , " seconds"
print "Total Damage: " , tot_damage
print "DPS: " , tot_damage/t
print ""
if n_st > 0: print "Strike ECD: " , t/n_st
if n_zs > 0: print "Zealous Strike ECD:" , t/n_zs
if n_ms > 0: print "Master Strike ECD:" , t/n_ms
if n_me > 0: print "Merciless Slash ECD", t/n_me
if n_fs > 0: print "Force Stasis ECD:" , t/n_fs
if n_fl > 0: print "Force Leap ECD:" , t/n_fl
if n_z  > 0: print "Zen ECD:" , t/n_z
if n_d  > 0: print "Dispatch ECD:" , t/n_d
if n_vc > 0: print "Valorous Call ECD" , t/n_vc
if n_ca > 0: print "Cauterize ECD:" , t/n_ca
if n_sl > 0: print "Slash ECD:" , t/n_sl
if n_os > 0: print "Overload Saber ECD" , t/n_os
if n_vc > 0: print "Valorous Call ECD: ", t/n_vc
if n_pt > 0: print "Proccy Trinket ECD: ", t/n_pt
print ""
print ""

#import os
#os.environ['PYTHONINSPECT'] = '1'

One of the nicer things you can do with these macros is trivially implement any rotation you like, eg:

Code:
######MASH BUTTANS######
if cauterize_cd <= 0 and focus >= 2: cauterize()
elif merciless_slash_cd <=0 and focus >= 5: merciless_slash()
else: strike()
###END MASH BUTTANS#####

or

Code:
######PRECISE ZEN BURN############
if zealous_strike_cd <= 0: zealous_strike()
elif centering <= 5 and valorous_call_cd <= 0: valorous_call()
elif centering >= 30: zazen()
elif zen > 0:
if br_ataru_buff <= 0 and focus >= 3: blade_rush()
elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
elif iteration_t > fight_length*0.8 and focus >= 2 and dispatch_cd <=0: dispatch()
else: strike()
elif centering >= 24:
if br_ataru_buff <= 0 and focus >= 3: blade_rush()
elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
elif precision_slash_cd <= 3 and focus <= 5: strike()
elif iteration_t > fight_length*0.8 and focus >= 7 and dispatch_cd <=0: dispatch()
elif ataru_trance > 0 and focus >= 7 and blade_storm_cd <= 0: blade_storm()
elif master_strike_cd <= 0: master_strike()
elif force_stasis_cd <= 0: force_stasis()
else: strike()
else:
if br_ataru_buff <= 0 and focus >= 3: blade_rush()
elif precision_slash_cd <= 0 and focus >= 3: precision_slash()
elif precision_slash_cd <= 3 and focus <= 5: strike()
elif iteration_t > fight_length*0.8 and focus >= 2 and dispatch_cd <=0: dispatch()
elif ataru_trance > 0 and focus >= 2 and blade_storm_cd <= 0: blade_storm()
elif master_strike_cd <= 0: master_strike()
elif force_stasis_cd <= 0: force_stasis()
else: strike()
###END PRECISE ZEN BURN###########

The rotations will let you break the games rules (not enough focus, off of cooldown), but should still track everything properly even if you do. The big exception to this is that you should only use one ability per iteration of the loop. You can get away with doing more than this, but you will very clearly break the results.

Anyway, enjoy!

For those that don't want to run this themselves, the DPS of the two specs using a simple rotation for each and the same gear is:

Combat:
Code:
python combat_cl.py 0 0 0 0 0 0 0 0 0 0
Total Time:  300978.985374  seconds
Total Damage:  450784232.403
DPS:  1497.72659989

Ataru ECD:  1.35264766854
Zealous Strike ECD:  12.039159415
Strike ECD:  9.27716257356
Precision Stike ECD:  16.4974230089
Zen ECD:  60.1957970748
Master Strike ECD:  28.3061210735
Force Stasis ECD:  50.163164229

python weights.py
acc :  0.288960221211 1502.22246814
alac :  0.0710837357245 1498.34566969
baseline :  0.0 1497.08083693
power :  1.0 1514.87439754
surge :  0.420568725621 1504.56425204
str :  0.980003381122 1514.51858649
error :  0.00570366000512 1497.18232535
crit :  0.75024980006 1510.43045222

Watchman:
Code:
python watchman_cl.py 0 0 0 0 0 0 0 0 0 0
Total Time:  301125.066877  seconds
Total Damage:  461133823.103
DPS:  1531.36976568

Strike ECD:  7.44898124617
Zealous Strike ECD: 12.1534111021
Merciless Slash ECD: 7.3411118476
Force Stasis ECD: 50.1958771256
Zen ECD: 25.1755761957
Dispatch ECD: 64.2331627296
Valorous Call ECD 150.562533438
Cauterize ECD: 8.63342030668
Slash ECD: 9.27994905472

python weights.py
acc :  0.746977573632 1545.57999733
alac :  0.00377735501129 1533.52811283
baseline :  0.0 1533.46685848
power :  1.0 1549.68305998
surge :  0.459749576373 1540.92225025
str :  0.956113551623 1548.97138849
error :  -0.137802682089 1531.23222242
crit :  0.773390920186 1546.00832148

(n.b. The listed Watchman spec has a significantly higher return on accuracy because it isn't yet at the acc cap, while the combat buffs push it over that cap)

Clearly, one can be smarter about these rotations, eg, by conserving focus as Precision Strike is nearing its cooldown.
04-19-2012, 04:03 AM (This post was last modified: 04-19-2012 04:06 AM by Qed.)
Post: #2
 Qed Member Posts: 86 Joined: Jan 2012 Reputation: 0
RE: Combat, Watchman Simulators
I've updated the OP to 1.2.

Combat dps can vary by 100 or more depending on how we model Ataru strike. At the moment, I am assuming that each ability (not each hit) gets one chance to proc Ataru if at least one swing from that ability hits, and the Ataru attack is attempted at a random time during that ability's GCD. This brings my Ataru ECD closest to what I have observed in combat logs, but I would be very happy to play around with this model given more evidence. Much better to be bad at my class and able to improve my Ataru rate then to just be capped out.

(Allowing the Ataru strike to proc at the same moment in every ability lets it proc more often, because you always get an attempt immediately after the cooldown.

Also, for those that would like to calculate stat weights using this simulator, you can use this

weights.py
Code:
import os
os.system("python combat_cl.py 0 0 0 0 0 0 0 0 0 0 > baseline.txt")
os.system("python combat_cl.py 0 0 0 0 0 0 0 0 0 0 > error.txt")
os.system("python combat_cl.py 48 0 0 0 0 0 0 0 0 0 > str.txt")
os.system("python combat_cl.py 0 0 0 48 0 0 0 0 0 0 > power.txt")
os.system("python combat_cl.py 0 0 0 0 48 0 0 0 0 0 > crit.txt")
os.system("python combat_cl.py 0 0 0 0 0 48 0 0 0 0 > surge.txt")
os.system("python combat_cl.py 0 0 0 0 0 0 48 0 0 0 > acc.txt")
os.system("python combat_cl.py 0 0 0 0 0 0 0 48 0 0 > alac.txt")

dps = {}

for stat in ["baseline","error","str","power","crit","surge","acc","alac"]:
file = open(stat+".txt")
for l in file:
if l.find("DPS:") > -1:
words = l.split()
dps[stat] = float(words[1])
file.close()

weight = {}

for stat in dps.keys():
weight[stat] = (dps[stat]-dps["baseline"])/(dps["power"]-dps["baseline"])

for stat in weight.keys():
print stat , ": " , weight[stat] , dps[stat]
04-20-2012, 03:22 AM (This post was last modified: 04-20-2012 04:08 AM by Qed.)
Post: #3
 Qed Member Posts: 86 Joined: Jan 2012 Reputation: 0
RE: Combat, Watchman Simulators
Tested the one-ataru-per-ability theory, and it's untrue. Master strike can, and often does, proc twice if the blade rush buff is up and it's used in isolation. The one-ataru-per-swing model comes very close to properly describing the ataru proc rate in a simple rotation of strike>blade rush>strike>blade rush>...

I get the feeling that I'm not modeling opportune attack properly though. Logs show surprisingly few opportune attack procs, even when there are many ataru procs. I'll put a counter it in the simulator and compare procrates with the simple rotation when I get more time.

Edit: Sigh, hoodwinked by the description of opportune attack. Pretty sure the correct number is 30% from Marauders.
04-28-2012, 03:33 PM
Post: #4
 Ryfe Junior Member Posts: 7 Joined: Apr 2012 Reputation: 0
RE: Combat, Watchman Simulators
If only I knew how to use python.
04-28-2012, 04:52 PM
Post: #5
 Qed Member Posts: 86 Joined: Jan 2012 Reputation: 0
RE: Combat, Watchman Simulators
(04-28-2012 03:33 PM)Ryfe Wrote:  If only I knew how to use python.

Well, if you want a good excuse to learn...

For me, it's much easier to pick apart code than a spreadsheet. Things have helpful names like cauterize_cd, instead of just cell numbers. Plus, it makes it really easy to test things like 'how does the game model ataru procs'. I can just run the same simple rotation ingame and out of game and tweak the sim parameters until things agree.

You should be able to get the above up and running as follows:

Make a new (text) file called, watchman.py, copy the watchman code block into it and save it. (Make sure windows doesn't give it a hidden .txt in the filename)

run python.exe, some text and a prompt should come up

type "import watchman" (without the quotes)

And, done.

Now that it runs, go and poke at all the pieces of it. The easiest thing to change is probably your stats. One of the cooler things to try is to move change the logic behind the rotation. Having an easily changeable rotation was really helpful for me in seeing what things really took priority for my attacks.

I hope that most of what I've written hear is easily understood, and that people feel free to change my code around to answer their own questions about spec and rotation and gear and such.

Granted, that all of this appeals much more to a certain kind of ocd personality, but for those people, It can be really fun to have an easily tweaked simulator on hand.
05-08-2012, 07:24 PM (This post was last modified: 05-08-2012 07:26 PM by Ryfe.)
Post: #6
 Ryfe Junior Member Posts: 7 Joined: Apr 2012 Reputation: 0
RE: Combat, Watchman Simulators
Thanks for the help! I managed to get it all figured out (the basics of loading it anyway). There seems to be a syntax error on line 64:

"mcrit = 0.05+bonus_crit+diminishing_returns(crit,0.3,0.45)+diminishing_returns(str,0.3,2​?.5)"

I just removed the question mark and everything seems to run just fine; however, I am not sure if "2.5" is the intended value.

I'm going to mess around with it a little more and see whats what. I just wanted to give you a heads up.
Edit: I think the syntax problem is due to moving the Unicoded text into an ANSI editor... I checked the original text and everything matches up once that pesky question mark is gone.

Cheers!
05-08-2012, 08:40 PM (This post was last modified: 05-08-2012 08:43 PM by Ryfe.)
Post: #7
 Ryfe Junior Member Posts: 7 Joined: Apr 2012 Reputation: 0
RE: Combat, Watchman Simulators
Alright, so after a couple of hours of trial and error, I think I have the hang of it. I've been working on a few different rotations/priority list, and have a few questions.

1: In the Combat code, how exactly is Zen calculated? Does the simulator take into account both charges and duration?
2: In the Combat code, is there anyway to account for Precision Slash's debuff similar to "ataru_trance"? {eg. elif master_strike_cd <= 0 and precision_slash_debuff > 0: master_strike()}
3: Do these simulators take skill trees into account, namely the 10% dmg to spenders following ataru procs and the focus generation from burns (Burning Focus in Watchman), Combat Trance (in Combat), and the focus return on Blade Rush, Merciless Slash, Slash, and Dispatch from Focused Slash in the watchman tree, and if so, is it possible to "respec" without having to manually change spell values?

Awesome work by the way. I'm sort of addicted to this thing now.
05-08-2012, 11:36 PM (This post was last modified: 05-08-2012 11:38 PM by Ryfe.)
Post: #8
 Ryfe Junior Member Posts: 7 Joined: Apr 2012 Reputation: 0
RE: Combat, Watchman Simulators
I've been using "elif precision_slash_cd >= 9: whatever" to simulate Precision Slash's debuff... Pretty sure that should work. I also wanted to add Opportune Attack to the "is this working properly?" list. Combat seems much harder to simulate due to all the random variables, but I appreciate the feed back.
05-09-2012, 12:22 AM (This post was last modified: 05-09-2012 12:23 AM by Asashdor.)
Post: #9
 Asashdor Junior Member Posts: 3 Joined: Dec 2011 Reputation: 0
RE: Combat, Watchman Simulators
After spending almost 2 weeks messing around with the Watchman/Annihilation part I think i'm able to answer most of your questions - although I'm not able to guarantee my answers on the Combat/Carnage part will be 100% correct.

1: There's an if-clause within the function for Blade Rush (lines 173 to 184) which checks if at least 1 Zen charge is available and then sets the focus cost and global cooldown of the ability and - if necessary - removes one charge of Zen. The maximum number of Zen charges is set in the function "zazen" in lines 433 to 442. The remaining time of Zen (for both Watchman and Combat) is not taken into account but if you're constantly using abilites it shouldn't be possible to run out before using up all charges.

2: Yes, this should be possible by checking if "remove_armor" is greater than 0. It's time is set to 6 seconds in line 279 and there is an if-clause in every ability function checking if it's active (e.g. line 290 for Precision Slash itself).

3: As far as I see it talents are completely hardcoded. If you want to change specific talents you have to change the fomulas or functions - there is no easy way of doing this.
• Insight should be used in the Watchman module and to remove it you have to change the damage formula of both DoT's (Overload Saber and Cauterize) and other affected abilites. The relevant part for calculating Overload Saber ticks is in line 148 ("if random.uniform(0,1) < (mcrit + 0.06 + 0.01*juyo_stacks + bonuscrit):"), where the red 0.06 should account for 3 points in Insight.
• Focus Generation from Burning Focus is in line 152 for Overload Saber ticks and in line 290 for Cauterize.
• Focus return from Focused Slash is also in (assuming fully talented, so those abilities effectively cost 1 Focus less) while checking for the original costs within the rotation itself (e.g if focus >= 5 use Merciless Slash in line 567 but Merciless Slash only costs 4 Focus in line 188). If you want to simulate one or two points in Focused Slash you need to add an additional random function for those costs.
• Combat trance is set in line 123 (its variable is called "ataru_trance" and the focus generation is in lines 567 and 568.
• And also Opportune Attack is in, called "ataru_oa_buff" and checked within every affected ability (e.g. lines 167 to 169 for Blade Rush).
• The only talent i'm not sure about is Steadfast. As far as I can tell it is currently not used but I might be wrong there - Accuracy values are consistent with my ingame (Watchman) values without talenting into it.

I hope that answers some of your questions (or even more than you asked) and I also hope I did not mess something up there.
05-09-2012, 01:14 AM
Post: #10
 Qed Member Posts: 86 Joined: Jan 2012 Reputation: 0
RE: Combat, Watchman Simulators
Ack! Sorry for the mashed keyboard and random question mark.

I'm really glad some other people have enjoyed playing around with these.

Yes, the talents are all completely hardcoded. It's a little obnoxious, but I haven't seen any really wild talent choices from people, so hopefully most people can just pick up and use what's there.

Steadfast should just show up as value in bonus_hit.

You can condition on combat trance via the ataru_trance, and opportune attack via the ataru_oa_buff variables (as mentioned above).