def get_damage(sp, hit, crit, num_mages, rotation, response, sim_size): C = Constant(sim_size=sim_size) arrays = init_arrays(C, num_mages, response) if C._LOG_SIM >= 0: log_message(sp, hit, crit) while advance(arrays, sp, hit, crit, rotation, sim_size): still_going = np.where(arrays['running_time'] < arrays['duration'])[0] arrays['total_damage'][still_going] += arrays['damage'][still_going] if C._LOG_SIM >= 0: print('total damage = {:7.0f}'.format(arrays['total_damage'][ C._LOG_SIM][0])) return (arrays['total_damage']/arrays['duration']).mean()
import os import hashlib import subprocess import sys from mako.template import Template import cv2 from constants import Constant import numbers from subprocess import call constant = Constant() def set_workspace(ws): constant.set_workspace(ws) if not os.path.isdir(ws): os.makedirs(ws) def dir(path): dir_path = constant.get_workspace() + "/" + path file_dir = os.path.dirname(dir_path) if not os.path.isdir(file_dir): os.makedirs(file_dir) return dir_path def file_already_exists(file_path): if os.path.isfile(file_path): checksum_file = file_path + ".checksum" if os.path.isfile(checksum_file):
def advance(arrays, plus_damage, hit_chance, crit_chance, rotation, sim_size): C = Constant(sim_size=sim_size) total_damage = arrays['total_damage'] ignite_count = arrays['ignite_count'] ignite_time = arrays['ignite_time'] ignite_tick = arrays['ignite_tick'] ignite_value = arrays['ignite_value'] scorch_count = arrays['scorch_count'] scorch_time = arrays['scorch_time'] running_time = arrays['running_time'] cast_timer = arrays['cast_timer'] cast_type = arrays['cast_type'] comb_stack = arrays['comb_stack'] comb_left = arrays['comb_left'] spell_timer = arrays['spell_timer'] spell_type = arrays['spell_type'] cast_number = arrays['cast_number'] duration = arrays['duration'] arrays['damage'] = np.zeros((C._SIM_SIZE, 1)) damage = arrays['damage'] epsilon = 0.000001 num_mages = cast_timer.shape[1] extra_scorches = C._EXTRA_SCORCHES[num_mages] still_going = np.where(running_time < duration)[0] if still_going.size == 0: return False cast_time = np.min(cast_timer[still_going], axis=1, keepdims=True) spell_time = np.min(spell_timer[still_going], axis=1, keepdims=True) ignite_copy = np.copy(ignite_time[still_going]) zi_array = np.logical_and(np.logical_and(ignite_copy < cast_time, ignite_count[still_going]), np.logical_or(ignite_copy < scorch_time[still_going], np.logical_not(scorch_count[still_going]))) zi_array = np.logical_and(zi_array, ignite_copy < ignite_tick[still_going]) zi_array = np.logical_and(zi_array, ignite_copy < spell_time) zero_ignite = np.where(zi_array)[0] if zero_ignite.size > 0: running_time[still_going[zero_ignite]] += ignite_time[still_going[zero_ignite]] cast_timer[still_going[zero_ignite], :] -= ignite_time[still_going[zero_ignite]] spell_timer[still_going[zero_ignite], :] -= ignite_time[still_going[zero_ignite]] scorch_time[still_going[zero_ignite]] -= ignite_time[still_going[zero_ignite]] if C._LOG_SIM >= 0: if C._LOG_SIM in still_going[zero_ignite]: sub_index = still_going[zero_ignite].tolist().index(C._LOG_SIM) message = ' {:7.0f} ({:6.2f}): ignite expired' print(message.format(total_damage[C._LOG_SIM][0] + damage[C._LOG_SIM][0], running_time[C._LOG_SIM][0])) ignite_count[still_going[zero_ignite]] = 0 ignite_time[still_going[zero_ignite]] = 0.0 ignite_value[still_going[zero_ignite]] = 0.0 ignite_tick[still_going[zero_ignite]] = 0.0 ti_array = np.logical_and(ignite_tick[still_going] < cast_time, ignite_tick[still_going] < scorch_time[still_going]) ti_array = np.logical_and(ti_array, ignite_count[still_going]) ti_array = np.logical_and(ti_array, ignite_tick[still_going] < spell_time) tick_ignite = np.where(ti_array)[0] if tick_ignite.size > 0: running_time[still_going[tick_ignite]] += ignite_tick[still_going[tick_ignite]] cast_timer[still_going[tick_ignite], :] -= ignite_tick[still_going[tick_ignite]] spell_timer[still_going[tick_ignite], :] -= ignite_tick[still_going[tick_ignite]] scorch_time[still_going[tick_ignite]] -= ignite_tick[still_going[tick_ignite]] ignite_time[still_going[tick_ignite]] -= ignite_tick[still_going[tick_ignite]] ignite_tick[still_going[tick_ignite]] = C._IGNITE_TICK multiplier = 1.0 + 0.03*scorch_count[still_going[tick_ignite]] damage[still_going[tick_ignite]] += ignite_value[still_going[tick_ignite]]*multiplier if C._LOG_SIM >= 0: if C._LOG_SIM in still_going[tick_ignite]: sub_index = still_going[tick_ignite].tolist().index( C._LOG_SIM) message = ' {:7.0f} ({:6.2f}): ignite ticked {:4.0f} damage done' print(message.format(total_damage[ C._LOG_SIM][0] + damage[ C._LOG_SIM][0], running_time[ C._LOG_SIM][0], ignite_value[ C._LOG_SIM][0]*multiplier[sub_index][0])) ig_array = np.logical_or(zi_array, ti_array) se_array = np.logical_and(np.logical_and(np.logical_not(ig_array), scorch_time[still_going] < cast_time), scorch_count[still_going]) se_array = np.logical_and(se_array, ignite_copy < spell_time) scorch_expire = np.where(se_array)[0] if scorch_expire.size > 0: if C._LOG_SIM >= 0: if C._LOG_SIM in still_going[scorch_expire]: message = ' ({:6.2f}): scorch expired {:4.2f}' print(message.format(running_time[ C._LOG_SIM][0], scorch_time[still_going[scorch_expire]][0][0])) running_time[still_going[scorch_expire]] += scorch_time[still_going[scorch_expire]] cast_timer[still_going[scorch_expire], :] -= scorch_time[still_going[scorch_expire]] spell_timer[still_going[scorch_expire], :] -= scorch_time[still_going[scorch_expire]] ignite_time[still_going[scorch_expire]] -= scorch_time[still_going[scorch_expire]] ignite_tick[still_going[scorch_expire]] -= scorch_time[still_going[scorch_expire]] scorch_count[still_going[scorch_expire]] = 0 scorch_time[still_going[scorch_expire]] = 0.0 cast_array = np.logical_not(np.logical_or(ig_array, se_array)) cast_array = np.logical_and(cast_array, cast_time < spell_time) cast_ends = np.where(cast_array)[0] if cast_ends.size > 0: cst = still_going[cast_ends] next_hit = np.argmin(cast_timer[cst, :], axis=1) add_time = np.min(cast_timer[cst, :], axis=1, keepdims=True) running_time[cst] += add_time ignite_time[cst] -= add_time ignite_tick[cst] -= add_time cast_timer[cst] -= add_time spell_timer[cst] -= add_time scorch_time[cst] -= add_time react_time = np.abs(C._CONTINUING_SIGMA*np.random.randn(cst.size)) cast_copy = np.copy(cast_type[cst, next_hit]) if C._LOG_SIM >= 0: if C._LOG_SIM in cst: message = ' ({:6.2f}): player {:d} finished casting {:s}' sub_index = cst.tolist().index( C._LOG_SIM) message = message.format(running_time[ C._LOG_SIM][0], next_hit[sub_index] + 1, C._LOG_SPELL[cast_copy[sub_index]]) print(message) # special spell next special_array = cast_number[cst, next_hit] == extra_scorches special = np.where(special_array)[0] if rotation == C._FIRE_BLAST: cast_timer[cst[special], next_hit[special]] = epsilon + react_time[special] cast_type[cst[special], next_hit[special]] = C._CAST_FIRE_BLAST elif rotation == C._FROSTBOLT: cast_timer[cst[special], next_hit[special]] = C._FIREBALL_CASTTIME + C._FROSTBOLT_CASTTIME + react_time[special] cast_type[cst[special], next_hit[special]] = C._CAST_FIREBALL damage[cst[special]] += hit_chance*(1 + C._FROSTBOLT_CRIT_DAMAGE*(crit_chance - C._FROSTBOLT_CRIT_MOD))*(C._FROSTBOLT_DAMAGE + C._FROSTBOLT_MODIFIER*(plus_damage - C._FROSTBOLT_PLUS))*C._FROSTBOLT_OVERALL else: cast_timer[cst[special], next_hit[special]] = C._PYROBLAST_CASTTIME + react_time[special] cast_type[cst[special], next_hit[special]] = C._CAST_PYROBLAST # scorch next scorcher = np.logical_not(np.logical_or(next_hit, special_array)) # scorch mage only scorch_array = np.logical_and(scorcher, np.logical_or(np.squeeze(scorch_count[cst]) < C._SCORCH_STACK, np.squeeze(scorch_time[cst]) < C._MAX_SCORCH_REMAIN)) scorch_array = np.logical_and(scorch_array, cast_copy != C._CAST_SCORCH) scorch_array = np.logical_and(scorch_array, cast_number[cst, next_hit] > extra_scorches + 1) # now scorch array is just for scorcher scorch_array = np.logical_or(scorch_array, cast_number[cst, next_hit] < extra_scorches) # now everyone scorch = np.where(scorch_array)[0] cast_timer[cst[scorch], next_hit[scorch]] = C._SCORCH_CASTTIME + react_time[scorch] cast_type[cst[scorch], next_hit[scorch]] = C._CAST_SCORCH # fireball next fireball = np.where(np.logical_not(np.logical_or(scorch_array, special_array)))[0] cast_timer[cst[fireball], next_hit[fireball]] = C._FIREBALL_CASTTIME + react_time[fireball] cast_type[cst[fireball], next_hit[fireball]] = C._CAST_FIREBALL spell_type[cst, next_hit] = cast_copy spell_timer[cst, next_hit] = C._SPELL_TIME[cast_copy] if rotation == C._FIRE_BLAST: gcd = np.where(cast_number[cst, next_hit] == extra_scorches + 1)[0] cast_timer[cst[gcd], next_hit[gcd]] += C._GLOBAL_COOLDOWN cast_number[cst, next_hit] += 1 spell_lands = np.where(np.logical_not(np.logical_or(np.logical_or(ig_array, se_array), cast_array)))[0] if spell_lands.size > 0: spl = still_going[spell_lands] next_hit = np.argmin(spell_timer[spl, :], axis=1) add_time = np.min(spell_timer[spl, :], axis=1, keepdims=True) running_time[spl] += add_time ignite_time[spl] -= add_time ignite_tick[spl] -= add_time cast_timer[spl] -= add_time spell_timer[spl] -= add_time scorch_time[spl] -= add_time spell_copy = spell_type[spl, next_hit] if C._LOG_SIM >= 0: if C._LOG_SIM in spl: message = ' ({:6.2f}): player {:d} {:s} landed ' sub_index = spl.tolist().index( C._LOG_SIM) message = message.format(running_time[ C._LOG_SIM][0], next_hit[sub_index] + 1, C._LOG_SPELL[spell_copy[sub_index]]) message2 = 'misses ' for spell in range(C._CASTS): is_spell = np.where(spell_copy == spell)[0] spell_hits = np.where(np.random.rand(is_spell.size) < hit_chance)[0] if spell_hits.size > 0: sph = spl[is_spell][spell_hits] spell_damage = C._SPELL_BASE[spell] + \ C._SPELL_RANGE[spell]*np.random.rand(sph.size, 1) +\ C._MULTIPLIER[spell]*plus_damage spell_damage *= (1.0 + 0.03*scorch_count[sph])*C._DAMAGE_MULTIPLIER # ADD ADDITIONAL OVERALL MULTIPLIERS TO _DAMAGE_MULTIPLIER # handle critical hit/ignite ** READ HERE FOR MOST OF THE IGNITE MECHANICS ** ccrit_chance = crit_chance + C._PER_COMBUSTION*comb_stack[sph, next_hit[is_spell][spell_hits]]*comb_left[sph, next_hit[is_spell][spell_hits]] crit_array = np.random.rand(sph.size) < ccrit_chance lcrits = np.where(crit_array)[0] crits = sph[lcrits] # refresh ignite to full 4 seconds ignite_time[crits] = C._IGNITE_TIME + epsilon # if we dont have a full stack mod_val = np.where(ignite_count[crits] < C._IGNITE_STACK)[0] # add to the ignite tick damage -- 1.5 x 0.2 x spell hit damage ignite_value[crits[mod_val]] += C._CRIT_DAMAGE*C._IGNITE_DAMAGE*spell_damage[lcrits[mod_val]] mod_val2 = np.where(ignite_count[crits] == 0)[0] # set the tick ignite_tick[crits[mod_val2]] = C._IGNITE_TICK # increment to max of five (will do nothing if alreeady at 5) ignite_count[crits] = np.minimum(ignite_count[crits] + 1, C._IGNITE_STACK) damage[crits] += C._CRIT_DAMAGE*spell_damage[lcrits] comb_left[crits, next_hit[is_spell][spell_hits][lcrits]] = np.maximum(comb_left[crits, next_hit[is_spell][spell_hits][lcrits]] - 1, 0) # normal hit nocrits = np.where(np.logical_not(crit_array))[0] damage[sph[nocrits]] += spell_damage[nocrits] if C._LOG_SIM >= 0: if C._LOG_SIM in sph: sub_index = sph.tolist().index(C._LOG_SIM) if C._LOG_SIM in crits: message2 = 'crits for {:4.0f} '.format(C._CRIT_DAMAGE*spell_damage[sub_index][0]) else: message2 = ' hits for {:4.0f} '.format(spell_damage[sub_index][0]) # scorch if C._IS_SCORCH[spell]: scorch_time[sph] = C._SCORCH_TIME scorch_count[sph] = np.minimum(scorch_count[sph] + 1, C._SCORCH_STACK) comb_stack[sph, next_hit[is_spell][spell_hits]] += 1 spell_timer[spl, next_hit] = C._DURATION_AVERAGE # cast combustion before pyroblast (don't apply to scorch) comb_array = cast_number[spl, next_hit] == int(rotation == C._FIRE_BLAST) + extra_scorches + 1 do_comb = np.where(comb_array)[0] if C._LOG_SIM >= 0: cmessage = '' if C._LOG_SIM in spl[do_comb]: sub_index = spl[do_comb].tolist().index(C._LOG_SIM) cmessage = ' (------): combustion cast by player {:d}'.format(next_hit[do_comb[sub_index]] + 1) comb_left[spl[do_comb], next_hit[do_comb]] = C._COMBUSTIONS comb_stack[spl[do_comb], next_hit[do_comb]] = 0 if C._LOG_SIM >= 0: if C._LOG_SIM in spl: if cmessage: print(cmessage) sub_index = spl.tolist().index(C._LOG_SIM) dam_done = ' {:7.0f}'.format(total_damage[ C._LOG_SIM][0] + damage[C._LOG_SIM][0]) message3 = C._LOG_SPELL[cast_type[C._LOG_SIM][next_hit[sub_index]]] message = message + message2 + 'next is ' + message3 status = ' ic {:d} it {:4.2f} in {:4.2f} id {:4.0f} sc {:d} st {:5.2f} cs {:2d} cl {:d}' status = status.format(ignite_count[C._LOG_SIM][0], max([ignite_time[C._LOG_SIM][0], 0.0]), max([ignite_tick[C._LOG_SIM][0], 0.0]), ignite_value[C._LOG_SIM][0], scorch_count[C._LOG_SIM][0], max([scorch_time[C._LOG_SIM][0], 0.0]), comb_stack[C._LOG_SIM][next_hit[sub_index]], comb_left[C._LOG_SIM][next_hit[sub_index]]) print(dam_done + message + status) return True
def main(): C = Constant() t0 = time.time() spell_damage = np.arange(C._SP_START, C._SP_END + C._SP_STEP/2.0, C._SP_STEP) hit_chance = np.arange(C._HIT_START, C._HIT_END + C._HIT_STEP/2.0, C._HIT_STEP) crit_chance = np.arange(C._CRIT_START, C._CRIT_END + C._CRIT_STEP/2.0, C._CRIT_STEP) nmages = np.arange(1, C._MAGES_END + 1, dtype=np.int32) for hit in hit_chance: for spd in spell_damage: # First look through the 3 {scorch -> spell -> fireball} rotations # where spell can be fire blast, frostbolt, or pyroblast. # The best rotation is recorded for each combination of: # * spell power # * hit chance # * crit chance # * number of mages filename = '../savestates/rotation_{:3.0f}_{:2.0f}.dat'.format(spd, 100.0*hit) if _DO_ROTATION_SEARCH: sps = np.array([spd]) hits = np.array([hit]) sim_size = np.array([C._ROTATION_SIMSIZE]).astype(np.int32) big_desc = ['Damage(frostbolt rotation)/Damage(pyroblast rotation)', 'Damage(fire blast rotation)/Damage(pyroblast rotation)'] big_fn_desc = ['frostbolt', 'fire_blast'] big_rotations = [C._FROSTBOLT, C._FIRE_BLAST] if _DO_RESPONSE_SEARCH: responses = np.arange(0.0, 3.05, 0.10) else: responses = np.array([C._INITIAL_SIGMA]) print('Hit = {:2.0f} spd = {:3.0f}'.format(100.0*hit, spd)) rotation_map = -np.ones((len(crit_chance), len(nmages)), dtype=np.int16) damage_map = np.zeros((len(crit_chance), len(nmages))) for rindex, (rotation, desc, fn_desc) in enumerate(zip(big_rotations, big_desc, big_fn_desc)): rotations = np.array([0, rotation], dtype=np.int32) args = itertools.product(sps, hits, crit_chance, nmages, rotations, responses, sim_size) largs = [*args] mean_mages = adjust_sim_size(largs) print(' Starting {:d} sims with {:d} samples each. Estimated run time is {:.2f} minutes.'.format(len(largs), C._ROTATION_SIMSIZE, 8e-5*len(largs)*mean_mages*C._ROTATION_SIMSIZE/60.0)) with Pool() as p: out = np.array(p.starmap(get_damage, largs)).reshape((len(sps), len(hits), len(crit_chance), len(nmages), len(rotations), len(responses), len(sim_size))) if _DO_RESPONSE_SEARCH: resp_index = np.argmin(np.abs(responses - C._INITIAL_SIGMA)) dd = [np.squeeze(out[:, :, :, :, 0, resp_index]), np.squeeze(out[:, :, :, :, 1, resp_index])] else: dd = [np.squeeze(out[:, :, :, :, 0, :]), np.squeeze(out[:, :, :, :, 1, :])] for index, damage in zip([0, rotation], dd): replacer = np.where(damage > damage_map) damage_map[replacer] = damage[replacer] rotation_map[replacer] = index if _DO_RESPONSE_SEARCH: for index, crit in enumerate(crit_chance): damages = np.squeeze(out[:, :, index, :, 1, :])/np.squeeze(out[:, :, index, :, 0, :]) plots.plot_response(responses, damages, spd, hit, crit, fn_desc, desc, nmages, sim_size[0], C._DURATION_AVERAGE) os.makedirs('../savestates/', exist_ok=True) with open(filename, 'wb') as fid: pickle.dump(sim_size, fid) pickle.dump(rotation_map, fid) pickle.dump(damage_map, fid) plots.plot_rotation(crit_chance, nmages, rotation_map, spd, hit, sim_size[0], C._DURATION_AVERAGE, C._INITIAL_SIGMA) if _DO_ROTATION_SEARCH: print('{:2.0f} hit rotation/spread complete after {:.0f} seconds'.format(hit*100.0, time.time() - t0)) # Calculate spell power to crit chance equivalency # Use the previously computed best rotation for each parameter set for num_mages in nmages: filename = '../savestates/crit_equiv_{:2.0f}_{:d}.dat'.format(100.0*hit, num_mages) if _DO_CRIT_SP_EQUIV: hits = np.array([hit]) mages = np.array([num_mages]) sim_size = C._CRIT_SIMSIZE*nmages.astype(np.float32).mean()/num_mages sim_size = np.array([sim_size]).astype(np.int32) rotations = np.array([C._DEFAULT_ROTATION]).astype(np.int16) responses = np.array([C._INITIAL_SIGMA]) args = itertools.product(spell_damage, hits, crit_chance, mages, rotations, responses, sim_size) largs = [*args] if C._ADAPT_ROTATION: adjust_rotation(largs, crit_chance, nmages) print('Hit = {:2.0f} # mages = {:d}'.format(100.0*hit, num_mages)) lsim_size = 0 if os.path.exists(filename): with open(filename, 'rb') as fid: lsim_size = pickle.load(fid)[0] conversions = pickle.load(fid) if lsim_size != sim_size[0]: print(' Starting {:d} sims with {:d} mages and {:d} samples. Estimated run time is {:.2f} minutes.'.format(len(largs), num_mages, sim_size[0], 8e-5*len(largs)*num_mages*sim_size[0]/60.0)) with Pool() as p: out = np.array(p.starmap(get_crit_damage_diff, largs)).reshape((len(spell_damage), len(hits), len(crit_chance), len(mages), len(rotations), len(responses), len(sim_size))) conversions = np.squeeze(out) os.makedirs('../savestates/', exist_ok=True) with open(filename, 'wb') as fid: pickle.dump(sim_size, fid) pickle.dump(conversions, fid) plots.plot_equiv(spell_damage, crit_chance, conversions, hit, num_mages, sim_size[0], C._DURATION_AVERAGE, 'crit') # Calculate spell power to hit chance equivalency # Use the previously computed best rotation for each parameter set for num_mages in nmages: filename = '../savestates/hit_equiv_{:2.0f}_{:d}.dat'.format(100.0*hit, num_mages) if _DO_HIT_SP_EQUIV: hits = np.array([hit]) mages = np.array([num_mages]) sim_size = C._HIT_SIMSIZE*nmages.astype(np.float32).mean()/num_mages sim_size = np.array([sim_size]).astype(np.int32) rotations = np.array([C._DEFAULT_ROTATION]).astype(np.int16) responses = np.array([C._INITIAL_SIGMA]) args = itertools.product(spell_damage, hits, crit_chance, mages, rotations, responses, sim_size) largs = [*args] if C._ADAPT_ROTATION: adjust_rotation(largs, crit_chance, nmages) print('Hit = {:2.0f} # mages = {:d}'.format(100.0*hit, num_mages)) lsim_size = 0 if os.path.exists(filename): with open(filename, 'rb') as fid: lsim_size = pickle.load(fid)[0] conversions = pickle.load(fid) if lsim_size != sim_size[0]: print(' Starting {:d} sims with {:d} mages and {:d} samples each. Estimated run time is {:.2f} minutes.'.format(len(largs), num_mages, sim_size[0], 8e-5*len(largs)*num_mages*sim_size[0]/60.0)) with Pool() as p: out = np.array(p.starmap(get_hit_damage_diff, largs)).reshape((len(spell_damage), len(hits), len(crit_chance), len(mages), len(rotations), len(responses), len(sim_size))) conversions = np.squeeze(out) os.makedirs('../savestates/', exist_ok=True) with open(filename, 'wb') as fid: pickle.dump(sim_size, fid) pickle.dump(conversions, fid) plots.plot_equiv(spell_damage, crit_chance, conversions, hit, num_mages, sim_size[0], C._DURATION_AVERAGE, 'hit') if _DO_HIT_SP_EQUIV or _DO_CRIT_SP_EQUIV: os.makedirs('../data/', exist_ok=True) fid = open('../data/equivalencies.csv', 'wt') fid.write('spell damage,hit chance,crit chance,number of mages,hit_simulations,crit_simulations,hit equivalency,crit equivalency\n') for hit in hit_chance: for num_mages in nmages: have_files = [False, False] filename = '../savestates/hit_equiv_{:2.0f}_{:d}.dat'.format(100.0*hit, num_mages) if os.path.exists(filename): have_files[0] = True with open(filename, 'rb') as fid2: sim_size1 = pickle.load(fid2) hit_conversions = pickle.load(fid2) filename = '../savestates/crit_equiv_{:2.0f}_{:d}.dat'.format(100.0*hit, num_mages) if os.path.exists(filename): have_files[1] = True with open(filename, 'rb') as fid2: sim_size2 = pickle.load(fid2) crit_conversions = pickle.load(fid2) if all(have_files): for sindex, spd in enumerate(spell_damage): for cindex, crit in enumerate(crit_chance): output = '{:3.0f},{:2.0f},{:2.0f},{:d},{:d},{:d},{:6.3f},{:6.3f}\n' output = output.format(spd, 100.0*hit, 100.0*crit, num_mages, sim_size1[0], sim_size2[0], hit_conversions[sindex, cindex], crit_conversions[sindex, cindex]) fid.write(output) fid.close() if _DO_DPS_PER_MAGE: os.makedirs('../data/', exist_ok=True) fid = open('../data/damage_per_mage.csv', 'wt') fid.write('spell damage,hit chance,crit chance,number of mages,simulations,dps per mage\n') for spd in spell_damage: for hit in hit_chance: filename = '../savestates/rotation_{:3.0f}_{:2.0f}.dat'.format(spd, 100.0*hit) if os.path.exists(filename): with open(filename, 'rb') as fid2: sim_size = pickle.load(fid2) rotation_map = pickle.load(fid2) damage_map = pickle.load(fid2) for cindex, crit in enumerate(crit_chance): for nindex, num_mages in enumerate(nmages): damage_map[cindex, nindex] /= num_mages output = '{:3.0f},{:2.0f},{:2.0f},{:d},{:d},{:.2f}\n' output = output.format(spd, 100.0*hit, 100.0*crit, num_mages, sim_size[0], damage_map[cindex, nindex]) fid.write(output) plots.plot_dps(crit_chance, nmages, damage_map, spd, hit, sim_size[0], C._DURATION_AVERAGE, C._INITIAL_SIGMA) fid.close()