Skip to content

Instantly share code, notes, and snippets.

@AndyGrant
Last active January 25, 2023 11:59
Show Gist options
  • Save AndyGrant/9358516c1e246d8b554c4fc99bb122e0 to your computer and use it in GitHub Desktop.
Save AndyGrant/9358516c1e246d8b554c4fc99bb122e0 to your computer and use it in GitHub Desktop.
from random import uniform
from concurrent.futures import ThreadPoolExecutor
ITERATIONS = 1000 # Number of times to run the simulation, to reduce variance
LENGTH = 300 * 1000 # Fight is 300 seconds, or 300,000 milliseconds
AA_SPEED = LENGTH / 154.0 # We just compute your in-practice AA-speed, based on the first sim
BASE_CD = 45 * 1000 # This is the CD of Wake of ashes. We assume at-least 1 cast per 45s
INITIAL_CAST = 5.875 * 1000 # According to your first sim, you do not press Wake until this time
PROC_RATE = 0.042 # This is the probability of getting a Wake proc on an auto attack
WINDFURY_GAIN_THEORY = 0.200 # Windfury provides a 20% increase in auto-attacks
WINDFURY_GAIN_PRACTICE = 0.175 # However, in practice, you are only seeing a 17.5% increase
def simulate_single(has_windfury, use_theory):
total_casts = 1 # We get our first Wake cast here
curr_time = INITIAL_CAST # This does not happen until a few seconds into the fight
most_recent_cast = INITIAL_CAST # We track the last time we used Wake, for the CD timer
most_recent_auto = INITIAL_CAST # Lets just assume you get your first auto here.
# Pick a windfury rate. Either the theoetical gain, or the observed gain.
windfury_rate = WINDFURY_GAIN_THEORY if use_theory else WINDFURY_GAIN_PRACTICE
while (curr_time := curr_time + AA_SPEED) < LENGTH:
# We went the full duration without casting, so cast now
if curr_time - most_recent_cast >= BASE_CD:
total_casts = total_casts + 1
most_recent_cast = curr_time
# Do we need to do an auto-attack (?)
if curr_time - most_recent_auto >= AA_SPEED:
# Roll ahead the auto-attack timer by AA_SPEED milliseconds
most_recent_auto = most_recent_auto + AA_SPEED
# We proc 42 (PROC_RATE) times per 1,000 autos.
if uniform(0, 1) < PROC_RATE:
total_casts = total_casts + 1
most_recent_cast = most_recent_auto
continue
# We again proc 42 (PROC_RATE) times per 1,000 autos, if we triggered a Windfury proc.
got_windfury_proc = has_windfury and uniform(0, 1) < windfury_rate
if got_windfury_proc and uniform(0, 1) < PROC_RATE:
total_casts = total_casts + 1
most_recent_cast = most_recent_auto
return total_casts
def simulate_whole(has_windfury=False, use_theory=False):
# Ignore this fancy stuff. We run the sim ITERATIONS number of times
# We return the fewest casts, most casts, and average casts over all sims
args = ((has_windfury, use_theory) for ii in range(ITERATIONS))
with ThreadPoolExecutor() as executor:
sims = list(executor.map(lambda x: simulate_single(*x), args))
return min(sims), max(sims), sum(sims) / len(sims)
print ('Baseline ', simulate_whole(False, None))
print ('Windfury (17.5%)', simulate_whole(True, False))
print ('Windfury (20.0%)', simulate_whole(True, True))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment