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

Your login from any MMO-Mechanics forum or site will work here.

Hello There, Guest! Register

Post Reply 
Gunnery/Arsenal DPS, Simulator, A new approach
02-24-2012, 11:41 PM (This post was last modified: 02-27-2012 04:18 PM by bural.)
Post: #11
RE: Gunnery/Arsenal DPS, Simulator, A new approach
Is that the post you meant to refer to Krix?
Reading on in that thread I'd say you're spot on though - or atleast we have the same understanding. Just a note: we need to account for offhand hits and accuracy as well and I have a hunch Torhead is reporting some mainhand abilities as offhand. This is purely a BH complication of course.
Quote:if we want to compare numbers across spreadsheets, we really need a canonical set of stats to start from.
Indeed.
Personally I don't care much if it's an operations entry level set of stats (call it character profile?) or a full set of operations gear. I do however feel it should naturally include Accuracy around the cap level and the access to the 2OP1 and 4OP1 bonuses as both of these may change the way our characters are played optimally.

Additionally, it might be a good idea to agree on set of comparison parameters. As previously mentioned, I feel a damage breakdown and ability usage, Terminal Velocity proc frequency, Barrage effective cooldown along with EP weights and DPS are all relevant in order to understand where discrepancies stem from.

Bural's Arsenal DPS spreadsheet
Find all posts by this user
Quote this message in a reply
02-25-2012, 01:00 AM (This post was last modified: 02-25-2012 01:19 AM by Qed.)
Post: #12
RE: Gunnery/Arsenal DPS, Simulator, A new approach
(02-24-2012 07:17 PM)Aldare Wrote:  Some very interesting numbers indeed. I'm surprised by how bad Surge is, even after the nerf. What is the crit chance you used for the calculations?
The stats for the first few results are: 200 surge, 0 alacrity.

If you stare at the code, the stats I'm using are my own,which had been prioritizing surge over accuracy completely. At equal surge vs accuracy, surge still wins.

Kriz Wrote:How does that post fit into your simulator? I would be happy if someone proved that accuracy works in a different way but for now I am quite confident that is how accuracy works. Providing benefit to only 3 of our single target abilitys in pve.

I had worried about this, but assumed that, like the 8% cap, the community had converged on an accuracy-affects-everything model. I only have one anecdote (read: not data) about this, which is that periodically while leveling I would should something 4-8 levels higher than me and my grav round would show "resist" "resist" "623", "resist", which would seem to point to some kind of accuracy-like-model for tech attacks. I still have normal mode EV up for the week, I'll take off all my accuracy gear and spam grav round on some boss and see if I can replicate this.

bural Wrote:Indeed.
Personally I don't care much if it's an operations entry level set of stats (call it character profile?) or a full set of raid gear. I do however feel it should naturally include Accuracy around the cap level and the access to the 2OP1 and 4OP1 bonuses as both of these may change the way our characters are played optimally.

Additionally, it might be a good idea to agree on set of comparison parameters. As previously mentioned, I feel a damage breakdown and ability usage, Terminal Velocity proc frequency, Barrage effective cooldown along with EP weights and DPS are all relevant in order to understand where discrepancies stem from.

I still just have a lot of things hardcoded on and off: 2piece OP and 4piece OP always on, no proccy trinket, etc. I would agree with all of these points, as they're good crosschecks to make sure that your calculation isn't going off the rails. Having capped accuracy as an assumption would be useful since separates out the questions we can't resolve with these sheets, "How does accuracy work on yellow attacks", from the ones we can, "What is the effective power of various stats".
Find all posts by this user
Quote this message in a reply
02-25-2012, 05:17 AM (This post was last modified: 02-25-2012 09:56 AM by Qed.)
Post: #13
RE: Gunnery/Arsenal DPS, Simulator, A new approach
So, I had some time to put in armor (just a flat reduction stolen from Bural's spreadsheet atm) and run both my and Bural's sheet with the same input stats:

Code:
aim = 1886 * 1.05*consular
cun = 140 * 1.05*consular
tpower = 1217
power = 381
crit = 263
surge = 294
acc = 198
alac = 0
weapondam = 459

From Bural's Sheet (I gave the offhand weapon 0 damage and the appropriate amount of +tech power, I hope this doesn't break anything):

DPS: 1602

Effective Cooldowns:
Code:
High Impact Bolt: 15.18987342
Demolition Round: 15.18987342
Full Auto: 11.34441052
Grav Round: 4.465477805
Rapid Shots: 5.63580539

Effective Power:
Code:
Power: 1.0
Crit: 0.87
(ignoring Hit, per the above discussion)
Surge: 0.39
Alac: 0.25

From Qed's Simulation:

DPS: 1421

Effective Cooldowns:
Code:
High Impact Bolt: 16.2789
Demolition Round: 16.61930
Full Auto: 11.6448316
Grav Round: 4.563688
Rapid Shots: 5.280555

Effective Power:
Code:
Power: 1.0
Crit: 0.88
(ignoring Hit, per the above discussion)
Surge: 0.37
Alac: 0.49

(n.b., I tried to turn off bural's vent heat portion since I don't model it. It seems like you _really_ need to burn way up to almost max heat before using this to get the full benefit).

So, things that are similar: Our weights for everything except alacrity seem to agree remarkably well. I should recheck my hammer shot calculations to be sure that I haven't done anything silly. My overall damage is significantly lower than bural's, in part because a simulation will model some rotation inefficiencies that you don't see in a spreadsheet, (eg: Having Curtain of Fire proc, but not having enough ammo to use Full Auto immediately).

I do note that I seem to use less ammo-consuming abilities overall compared to bural.... weird. We are both above 0.6 ammo/sec, which means that it is probably from different modeling of reserve cell, cell recharger, and the cell charger talent. I'll see if I can turn both of them off...

Not quite, setting the TV HVS and VH Vent Heat columns to 0, I still end up using a total of .67 ammo per minute.

I'll recopy my current iteration of the code back to the first post if people would like to poke.
Find all posts by this user
Quote this message in a reply
03-01-2012, 06:46 AM
Post: #14
RE: Gunnery/Arsenal DPS, Simulator, A new approach
The fact that you saw "resist" as opposed to deflect/shield should actually mean that accuracy doesn't matter for those attacks. "Resist" is what causes Force/Tech attacks to miss, and it would presumably be increased for mobs above your level just like defense is. But accuracy only works against an enemy's defense, not resist, and applies to "ranged" and "melee" attacks, not Force or Tech (this is why sorc's for example don't have accuracy on their gear since all their attacks are force).

I believe all Ranged/melee attacks are normally white whereas force/tech are yellow, but I'm not 100% sure. If you want to check, you can just get a friendly juggernaut or operative, they both have skills that increase their defense chance by alot (50% and 100%) for a limited time. If you attack them while their buff is up and your attack is deflected, then accuracy should help that attack. If not, then it shouldn't matter.

Dorfl/Coriolis of No One Cares, Shadow Hand
Find all posts by this user
Quote this message in a reply
03-01-2012, 07:07 AM (This post was last modified: 03-01-2012 07:10 AM by Qed.)
Post: #15
RE: Gunnery/Arsenal DPS, Simulator, A new approach
The mouse over in the 'tech' tab of the character sheet for accuracy:

"Chance your technological attacks will affect the target. Accuracy over 100% reduces the targets resistance"

On the other hand, having spent a week at 107.5% special accuracy, I have seen a handful of white misses (deflects, dodges, etc) but no yellow misses (resists, etc), in accordance with Kriz's postulate that boss mobs have an 8% defence but 0% resistance. I'll copy over my updated code reflecting this to the front page presently.
Find all posts by this user
Quote this message in a reply
03-01-2012, 01:53 PM (This post was last modified: 03-01-2012 01:57 PM by Qed.)
Post: #16
RE: Gunnery/Arsenal DPS, Simulator, A new approach
In case people haven't been staring at the code as I have stealth updated it (I'm assuming almost nobody has), I've moved things around so that it's very easy to test rotations using this simulator. For example:

Code:
if ammo > 9.2 and fa_cd <= 0: full_auto()
        elif hi_cd <= 0 and ammo < 11: high_impact()
        elif cof_cd > 0:
            if ammo > 11:
                if dr_cd <= 0: demo_round()
                else: grav_round()
            else:
                if ammo > 9.2 and dr_cd <= 0: demo_round()
                else: hammer_shot()
        else:
            if ammo > 9.2:
                if dr_cd <= 0: demo_round()
                else: grav_round()
            else: hammer_shot()

Which is a priority rotation that will try to avoid using grav round when grav round can't proc curtain of fire. The full rotation of the original post now includes logic to burn ammo down when cell recharger is off of cooldown so that you can use it to full effect without hitting the ammo cap. The script's logic does not complain if you use an ability before it has cooled down (though that wouldn't be hard to plug in), or you try to use two abilities during the same decision loop, but it does complain if you cap on ammo, or run out of ammo completely.
Find all posts by this user
Quote this message in a reply
03-01-2012, 08:18 PM
Post: #17
RE: Gunnery/Arsenal DPS, Simulator, A new approach
(03-01-2012 01:53 PM)Qed Wrote:  Which is a priority rotation that will try to avoid using grav round when grav round can't proc curtain of fire.

Nice in theory, but in practical dps environments a nightmare to watch that closely and pay attention to whats going on in the game.
Find all posts by this user
Quote this message in a reply
03-03-2012, 03:59 AM (This post was last modified: 03-03-2012 04:09 AM by Qed.)
Post: #18
RE: Gunnery/Arsenal DPS, Simulator, A new approach
(03-01-2012 08:18 PM)Allah Wrote:  Nice in theory, but in practical dps environments a nightmare to watch that closely and pay attention to whats going on in the game.

This is probably true. To be honest, it is actually a small difference from a simple priority rotation. I do try to avoid grav round when it can't proc curtain of fire, but what this tends to mean avoiding grav round for 1 attack after a full auto. (Assuming you have one attack inbetween CoF proccing and actually using full auto because of the ability queue, then ~3 seconds while full auto is firing, and finally one attack afterwards that shouldn't be grav round).
Find all posts by this user
Quote this message in a reply
04-08-2012, 05:13 AM (This post was last modified: 04-09-2012 02:12 AM by Fungus.)
Post: #19
RE: Gunnery/Arsenal DPS, Simulator, A new approach
Hi everyone, I got inspired by Qed's simulator to register here and also to build my own simulator for the Commando Assault Specialist Spec based on his.

First of all big thanks to Qed for his work! As usually happens in programming, in trying to adapt his code to Assault Specialist, not much of his original code remained Smile

First a few general observations I made:
  • Assault Specialist seems quite competitive to Gunnery. ~1332dps vs. ~1408dps with same stats in Qed's simulator
  • DoT-clipping is a huge issue. I estimate a loss of about half of Plasma Cell's theoretical dps
  • How does Hammer Shot work? In Game I seem to get varying amounts of hits per cast, which vary wildly in damage amount. Is this only a limit of the floating combat text? I simulate it with 6 shots spread over the 1.5 seconds
  • Plasma Cell's tooltip is wrong. It basically pretends there are three ticks, when there are only two (at -3 and -6 seconds)
  • I can't figure out a really good rotation... Incendiary Round's high cost often brings me below 8 ammo
  • What can proc Plasma Cell? My testing results on this are: Stockstrike:no, Assault Plastique:no, Pulse Cannon:no, Hail of Bolts:yes, Hammer Shot:yes, Full Auto:yes, Charged Bolts:yes


So now some comments on my code:
  • I use Python 3.2 - I think that "print" is the only thing in my code that could cause trouble when trying to run it with 2.x, apologies in advance if that is not the case Smile
  • I model ammo as exactly a way as possible like our displayed ammo-bar. I have no idea what hidden algorithms Bioware uses for it.
  • I have commented a lot in the code to make it easier to understand and modify Smile
  • no shielded attacks
  • I do not use average damage values, but randomize linearily between min and max
  • not all possible talents are included yet, but you can play around with the spec quite a bit
  • I simulate a fixed combat length many times to get averages.
And here is the code:

Code:
## a note on conventions used:
## upper case letters denote variables that have something to do with the game (like skillpoints or stats)
## variables only in lower case are variables I nedd to make this simulation work (like hit chances or damage modifiers)
## abbreviations are explained in at least one place even if used in multiple variables, so if you dunno what xy stands for you can maybe find a comment at the first use of xy_cd or at n_xy

## a note on averages:
## While using average damage values might reduce variance and increase speed, this would be wrong if there ever were a power over an average value. I don't think there are, but playing it safe as long as I don't have a really firm grasp on swtor-mechanics

## shielding is not implemented yet

## alacrity is assumed to not influence tick-times and durations for dots. obviously if this were different alacrity would be much more powerful for dot classes, but the calculation would be difficult with First Responder or relics that buff alacrity for a short time only
## refreshing a dot does not reset time to tick. no idea how it works for real.

## damage-numbers in game seem to be integers. I couldn't be bothered

import math
import random

######################################################
##user input section:
######################################################
#buffs [0,1]
Smuggler = 1
Knight = 1
Consular = 1

#active cell [0,1]
Plasma_Cell = 1
Combat_Support_Cell = 0 #only static effect of skill Special Munitions implemented
Armor_Piercing_Cell = 0 #only static effect of skill Special Munitions implemented


#AoE [0,1,...]; it is assumed that all AoEs are centered on the primary target !!NYI!!
#add_targets_in_5m = 0       #ADDITIONAL targets in 5m radius (one target is always hit, this is the number of targets beyond 1)
#add_targets_in_8m = 0       #ADDITIONAL targets that are not in the first radius and also not the primary target

#simulation parameters
number_of_simulations = 10000    #10000 seems to be the minimum to get any useful statweights from
duration_of_simulation = 300    #length of the fight
combat_log = 0                  #[0,1]

#base stats [0,1,...]
Aim = 1606
Cun = 128
TPower = 1210
Power = 269
Crit = 419
Surge = 292
Acc = 288
Alac = 0
MH_Dmg_Min = 317
MH_Dmg_Max = 589
Expertise = 94       #nyi since this is just a general multiplier. might still be interesting for statweights for pvp

#skills [skillpoints]
## several assumptions here: my current pvp-build is being used, skills I do not have are not (yet) implemented
## Also only talents that affect damage are used, i.e. no spending gcds for defensive actions or something
## no T2 Combat Medic skills yet
Assault_Plastique = 1       #[0,1]
#Adrenaline_Fueled = 0       #[0..2]
Burnout = 3                 #[0...3] ambiguous wording, but the internet tells me that the 3% crit is always active; modeling of the dot dmg increase is more difficult. my model: increase 30% of dot-dmg by 30%: dmg * ((1-0.3)+0.3*(1+0.1*Burnout)))
Rain_of_Fire = 3            #[0..3]
Assault_Trooper = 2         #[0..2] how does it stack? assumption: additive stacking
#Rapid_Recharge = 0          #[0..2]
Ionic_Accelerator = 3       #[0..3]
High_Friction_Bolts = 2     #[0..2]
Incendiary_Round = 1        #[0,1]
Superheated_Plasma = 3      #[0..3]
Target_Lock = 3             #[0..3]
Weapon_Calibrations = 0     #[0..2]
Havoc_Rounds = 0            #[0..2]
#Ironsights = 3              #[0..3] not implemented, because it's bonus is already reflected in the char-sheet afaik
#Steadied_Aim = 0            #[0..3] pushback nyi, probably never
Muzzle_Fluting = 1          #[0,1] patch will change this!
Special_Munitions = 1       #[0,1] so far only works with Plasma Cell - wording for Plasma Cell and Combat Support Cell is different, is the effect different too?
#Cell_Capacitor = 0          #[0..2]
Field_Training = 3          #[0..3] patch!

#set-boni [0,1]
Set_Columi_2 = 1            #+15% crit chance for Charged Bolts and Grav Round
Set_Columi_4 = 1            #-1 ammo cost for High Impact Bolt
Set_Champion_4 = 0          #+15% crit chance for High Impact Bolt

######################################################
##end of user input section:
######################################################

######################################################
##global theorycrafting values section:
######################################################
base_r_acc = 0.9
base_rs_acc = 1
base_t_acc = 1
base_r_crit = 0.05
base_t_crit = 0.05
target_r_defense = 0.05
target_t_defense = 0
base_r_crit_value = 0.5         #using 0.5 instead of the 1.5 multiplier here because the 0.5 gets modified by skills, not the 1.5
base_t_crit_value = 0.5
max_ammo = 12

def diminishing_returns(x,max,lam):     #this function translates "ratings" to "values"
    return max*(1-(1-(0.01/max))**(x/(50*lam)))


#permanently modified stats or chances or boni (through buffs, skills,...)
Aim = Aim * (1 + 0.05*Consular)
Cun = Cun * (1 + 0.05*Consular)
bonus_r_acc = 0.01 * Target_Lock
bonus_t_acc = 0.01 * Target_Lock
bonus_r_crit = 0.05*Smuggler + 0.02*Field_Training
bonus_t_crit = 0.05*Smuggler + 0.02*Field_Training + 0.03*Special_Munitions*Plasma_Cell + 0.03*Special_Munitions*Combat_Support_Cell + 0.01*Burnout
bonus_alac = 0.02*Weapon_Calibrations
plasma_dot_chance = 0.1 + 0.02*Superheated_Plasma
armor_ignore = 0    #nyi
armor_reduction = max(0.8727, 0.25) #one minus the amount target's armor reduces your damage by; no idea how to theorycraft this
internal_reduction = 1              #one minus the amount of resistance versus internal or elemental dmamage the target has; is there an armor piercing for this too?
r_hit_chance = min(base_r_acc + bonus_r_acc + diminishing_returns(Acc,0.3,0.55) - target_r_defense, 1)  #only Hammer Shot uses this?
rs_hit_chance = min(base_rs_acc + bonus_r_acc + diminishing_returns(Acc,0.3,0.55) - target_r_defense, 1) #ranged "specials". not sure what classifies, just using anything but Hammer Shot
t_hit_chance = min(base_t_acc + bonus_t_acc + diminishing_returns(Acc,0.3,0.55) - target_t_defense, 1)  #Tech hit chance (no real defense)
r_crit_chance = base_r_crit + bonus_r_crit + diminishing_returns(Crit,0.3,0.45) + diminishing_returns(Aim,0.3,2.5)
t_crit_chance = base_t_crit + bonus_t_crit + diminishing_returns(Crit,0.3,0.45) + diminishing_returns(Aim,0.3,2.5) + diminishing_returns(Cun,0.3,2.5)
r_crit_value = base_r_crit_value + diminishing_returns(Surge,0.3,0.11)
t_crit_value = base_t_crit_value + diminishing_returns(Surge,0.3,0.11)
haste = bonus_alac + diminishing_returns(Alac,0.3,0.55)
bonus_r_dmg = (Aim*0.2 + Power*0.23) * (1 + 0.05*Knight)
bonus_t_dmg = (Aim*0.2 + Cun*0.2 + Power*0.23 + TPower*0.23) * (1 + 0.05*Knight)

######################################################
##casts:
######################################################
def ammo_regen():   #assumption: ammo regeneration works in ticks, basically the same way our resource bar is displayed (not sure if this is how it is handled internally)
    global ammo     #problem with my model is that the time till the next tick is set at the beginning of the last tick and does not care if ammo went lower (or higher) inbetween ticks
    global regen_cd #the cheap way around this is to modify the current regen_cd if ammo drops below 8 (or 3). (done in the postprocessing loop)
    global ammo_regime
    global problematic_ammo_event_counter
    this_name = "Ammo Regeneration                  "
    if ammo >=12 :
        problematic_ammo_event_counter = problematic_ammo_event_counter + 1
        if combat_log : print(":::","Ammo Regeneration                   - Ammo maxed out at 12!!")
    ammo = min(ammo + 1, 12)    #ammo regenerates by 1 (and can't be more than 12). what happens to the tick time if the bar is full??
    #when will the next regen-tick happen? note regen_cd can still be a negative number after this (meaning the previous action took long enough for more than one tick to happen), leading immediately to a new regen tick
    if ammo < 3:     #0.24 ammo/second
        ammo_regime = 0.24
    elif ammo < 8:     #0.36 ammo/second
        ammo_regime = 0.36
    else:               #0.6 ammo/second
        ammo_regime = 0.6
    if combat_log : print(":::",this_name,"- now at",ammo,"ammo at",time + regen_cd,"seconds. Next tick will use:", ammo_regime,"per second")
    regen_cd = regen_cd + 1/ammo_regime

def dummy_cast_1():
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    this_name = "Dummy Cast 1                       "
    this_ammo = 2               #ammo cost of cast
    this_cast_time = 1.5        #total time spent for this cast, be it global cooldown or casttime
    this_ammo_time = 1.5        #time at which ammo is subtracted. = this_cast_time when ammo is payed at the end
    this_action_time = 0        #time at which damage happens. either =0 or =this_cast_time
    base_dmg = 1
    this_event = 1
    this_dmg = this_event * base_dmg
    dummy_procc_1_event()       # only when attack is a hit though

def assault_plastique(): #tech. kinetic. lvl40? tooltip error!
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global assault_plastique_cd
    this_name = "Assault Plastique                  "
    this_ammo = 2
    this_cast_time = 1.5
    this_ammo_time = 0
    this_action_time = 0    #explosion is instantaneous
    base_dmg = random.uniform( 0.222*1610 , 0.262*1610 ) + 2.42*bonus_t_dmg #lvl50 is assumed here
    if random.uniform(0,1) > t_hit_chance: #miss
        this_event = 0
    elif random.uniform(0,1) < t_crit_chance:  #crit, second roll
        this_event = (1 + t_crit_value + Assault_Trooper*0.15)
    else:
        this_event = 1
    this_dmg = base_dmg * this_event * armor_reduction
    assault_plastique_cd = 15          #cooldown starts at activation, so you use the tooltip value here

def high_impact_bolt(): #ranged. energy. lvl50.
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global high_impact_bolt_cd
    global ionic_accelerator_procc
    global plasma_tl
    global plasma_ttt
    this_name = "High Impact Bolt                   "
    if ionic_accelerator_procc == 1:                #check if ionic accelerator did proc before
        this_ammo = 0
        ionic_accelerator_procc = 0
    elif ionic_accelerator_procc == 0:
        this_ammo = (2 - 1*Set_Columi_4 - Armor_Piercing_Cell*Special_Munitions)
    this_cast_time = 1.5
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = random.uniform( (0.27+1)*MH_Dmg_Min + 0.19*1610 , (0.27+1)*MH_Dmg_Max + 0.19*1610 ) + 1.9*bonus_r_dmg
    if plasma_tl > 0 and random.uniform(0,1) < High_Friction_Bolts*0.5:             #procc of High Friction Bolts?
        plasma_tl = 2   #procc refreshes Plasma Cell dot (and time to tick, which i am not sure about but would be consistent)
        plasma_ttt = 3
        if combat_log : print("    Plasma Cell's DoT refreshed by High Friction Bolts procc at",time+this_action_time,"seconds!")
        this_ammo = this_ammo - 1   #+ 1 ammo
    if random.uniform(0,1) > rs_hit_chance: #miss
        this_event = 0
    elif random.uniform(0,1) < (r_crit_chance + 0.15*Set_Champion_4):  #crit, second roll
        this_event = (1 + r_crit_value + Assault_Trooper*0.15)
        if plasma_tl <= 0 : plasma_cell()   #High Impact Bolt can procc Plasma Cell I think
    else:
        this_event = 1
        if plasma_tl <= 0 : plasma_cell()   #High Impact Bolt can procc Plasma Cell I think
    this_dmg = base_dmg * this_event * (armor_reduction*(1+ 0.15*High_Friction_Bolts)) * (1 + burn_YN*0.03*Rain_of_Fire)                   #probably not the right way to model High Friction Bolt's armor penetration buff
    high_impact_bolt_cd = 15           #cooldown starts at activation, so you use the tooltip value here

def charged_bolts():    #ranged. energy. lvl 50
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global high_impact_bolt_cd
    global ionic_accelerator_procc
    this_name = "Charged Bolts                      "
    this_ammo = 2
    this_cast_time = 1.5 * (1 - haste)
    this_ammo_time = this_cast_time
    this_action_time = this_cast_time
    base_dmg = random.uniform( (0.33+1)*MH_Dmg_Min + 0.199*1610 , (0.33+1)*MH_Dmg_Max + 0.199*1610 ) + 1.99*bonus_r_dmg
    if random.uniform(0,1) > rs_hit_chance: #miss
        this_event = 0
    elif random.uniform(0,1) < (r_crit_chance + 0.15*Set_Columi_2):  #crit, second roll
        this_event = (1 + r_crit_value)
        plasma_cell()
    else:
        this_event = 1
        plasma_cell()
    this_dmg = base_dmg * this_event * armor_reduction * (1 + burn_YN*0.03*Rain_of_Fire) * (1 + 0.03*Havoc_Rounds)
    if random.uniform(0,1) < Ionic_Accelerator*0.1:       #procc of Ionic Accelerator?
        if combat_log : print("    Ionic Accelerator Procc! Chance was:",Ionic_Accelerator * 0.1)
        high_impact_bolt_cd = 0.5  #this is 0.5 instead of 0 because of reaction time and ability queue
        ionic_accelerator_procc = 1

def incendiary_round():     #tech. elemental. lvl20? tooltip error!
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global incendiary_round_dot_tl
    global incendiary_round_dot_ttt
    this_name = "Incendiary Round                   "
    this_ammo = 3
    this_cast_time = 1.5
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = random.uniform( 0.035*1610 , 0.075*1610 ) + 0.55*bonus_t_dmg        #assuming lvl 50 for base damage
    if random.uniform(0,1) > t_hit_chance: #miss
        this_event = 0
    elif random.uniform(0,1) < t_crit_chance:  #crit, second roll
        this_event = (1 + t_crit_value + Assault_Trooper*0.15)
        plasma_cell()   #can it procc plasma cell?
    else:
        this_event = 1
        plasma_cell()   #can it procc plasma cell?
    this_dmg = base_dmg * this_event * internal_reduction
    incendiary_round_dot_tl = 6       #dot has 6 ticks
    incendiary_round_dot_ttt = 3      #assuming here: dot-clipping and no immediate tick

def hammer_shot():  #ranged. energy. lvl50.
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    this_name = "Hammer Shot                        "
    this_ammo = 0
    this_cast_time = 1.5
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = random.uniform(MH_Dmg_Min, MH_Dmg_Max) + bonus_r_dmg     #tooltip damage seems to be a fairly well approximation of it's total damage (but crit seems to be undervalued in my tests). can't find out how often it really "ticks"
    this_event = 0
    hs_ticks = 6 #how many shots are there in one hammer shot? always felt like 3 but after some testing i count up to 6 white numbers (which vary wildly!), often less. this is weird, but i assume for now it ticks 6 times and each tick can crit and procc stuff individually
    for i in range(hs_ticks):
        this_action_time = (i+1)/hs_ticks*this_cast_time     #I assume that there really are timely spaced ticks of Hammer Shot and each can procc Plasma Cell at a different time
        if random.uniform(0,1) > r_hit_chance: #miss
            this_event = this_event + 0
        elif random.uniform(0,1) < r_crit_chance:  #crit, second roll
            this_event = this_event + (1 + r_crit_value)
            plasma_cell()
        else:
            this_event = this_event + 1
            plasma_cell()
    this_dmg = base_dmg * this_event/hs_ticks * armor_reduction * (1 + burn_YN*0.03*Rain_of_Fire)

def full_auto():    #ranged. energy. lvl50.
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global high_impact_bolt_cd
    global ionic_accelerator_procc
    global full_auto_cd
    this_name = "Full Auto                          "
    this_ammo = 2
    this_cast_time = 3 * (1 - haste)
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = random.uniform( (-0.3+1)*MH_Dmg_Min + 0.105*1610 , (-0.3+1)*MH_Dmg_Max + 0.105*1610 ) + 1.05*bonus_r_dmg
    this_event = 0
    for i in range(3):      #number of ticks
        this_action_time = (i+1)/3*this_cast_time
        if random.uniform(0,1) > rs_hit_chance: #miss
            this_event = this_event + 0
        elif random.uniform(0,1) < r_crit_chance:  #crit, second roll
            this_event = this_event + (1 + r_crit_value)
            plasma_cell()
        else:
            this_event = this_event + 1
            plasma_cell()
    if random.uniform(0,1) < Ionic_Accelerator*0.2:       #procc of Ionic Accelerator?
        if combat_log : print("    Ionic Accelerator Procc! Chance was:",Ionic_Accelerator * 0.2)
        high_impact_bolt_cd = 0
        ionic_accelerator_procc = 1
    this_dmg = base_dmg * this_event * armor_reduction * (1 + burn_YN*0.03*Rain_of_Fire)
    full_auto_cd = 15 - this_cast_time      #cooldown starts at end of channeling

def dummy_dot_1_tick():
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global dummy_dot_1_tl
    global dummy_dot_1_ttt
    this_name = "Dummy Dot Tick no. "+str(3-dummy_dot_1_tl)+"               "
    this_ammo = 0
    this_cast_time = 0
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = 1
    this_event = 1
    this_dmg = this_event * base_dmg
    dummy_dot_1_tl = dummy_dot_1_tl - 1
    dummy_dot_1_ttt = dummy_dot_1_ttt + 3

def incendiary_round_dot_tick():     #tech. elemental. lvl20? tooltip error!
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global incendiary_round_dot_tl
    global incendiary_round_dot_ttt
    this_name = "Incendiary Round's DoT, Tick no. "+str(7-incendiary_round_dot_tl)+" "
    this_ammo = 0
    this_cast_time = 0
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = 0.293*bonus_t_dmg + 0.0293*1610      #assuming lvl 50 here
    if random.uniform(0,1) > t_hit_chance: #miss; can dot ticks even miss(be resisted)? or is this like wow where only the initial application can miss?
        this_event = 0
    elif random.uniform(0,1) < t_crit_chance:  #crit, second roll
        this_event = (1 + t_crit_value + Assault_Trooper*0.15)      #here I assume that Assault Trooper also buffs the dot part of Incendiary Round
    else:
        this_event = 1
    this_dmg = base_dmg * this_event * internal_reduction * ((1-0.3)+0.3*(1+0.1*Burnout))
    incendiary_round_dot_tl = incendiary_round_dot_tl - 1
    incendiary_round_dot_ttt = incendiary_round_dot_ttt +3

def plasma_cell_dot_tick():      #tech. elemental. lvl50.
    global this_name
    global this_ammo
    global this_cast_time
    global this_ammo_time
    global this_action_time
    global this_event
    global this_dmg
    global plasma_tl
    global plasma_ttt
    this_name = "Plasma Cell's DoT, Tick no. "+str(3-plasma_tl)+"      "
    this_ammo = 0
    this_cast_time = 0
    this_ammo_time = 0
    this_action_time = 0
    base_dmg = 0.34*bonus_t_dmg + 0.034*1610    #tooltip is wrong. it shows as if there were three ticks, but there really are only two!!
    if random.uniform(0,1) > t_hit_chance:      #miss; can dot ticks even miss(be resisted)? or is this like wow where only the initial application can miss?
        this_event = 0
    elif random.uniform(0,1) < t_crit_chance:   #crit, second roll
        this_event = (1 + t_crit_value + Assault_Trooper*0.15)
    else:
        this_event = 1
    this_dmg = base_dmg * this_event * internal_reduction * (1 + Superheated_Plasma*0.1) * ((1-0.3)+0.3*(1+0.1*Burnout))
    plasma_tl = plasma_tl - 1
    plasma_ttt = plasma_ttt + 3

def dummy_procc_1_event():      #can simulate proccing buffs or dots; only tested for dots
    global dummy_dot_1_tl
    global dummy_dot_1_ttt
    dummy_procc_1_chance = 0.16
    if random.uniform(0,1) < dummy_procc_1_chance:
        if dummy_dot_1_tl <= 0 :
            if combat_log : print("    Dummy Procc 1 Event applied at",time+this_action_time,"seconds! Chance was:",dummy_procc_1_chance)
        else:
            if combat_log : print("    Dummy Procc 1 Event refreshed at",time+this_action_time,"seconds! Chance was:",dummy_procc_1_chance)
        dummy_dot_1_tl = 2    #ticks left. for buffs this is 1, for dots this is > 1
        dummy_dot_1_ttt = 3 + this_action_time   #time to tick (dots) or time to run (buffs). assuming dot-clipping and no immediate tick.

def plasma_cell():  #plasma cell dot procc check - wording is weird "rifle attacks" (Bounty Hunter: "blaster attacks"), I could not get Stockstrike, Assault Plastique or Pulse Cannon to procc it, but Hail of Bolts did...
                    #so this probably means only anything with a "normal" shooting animation... will have to test Mortar Strike and Incendiary Round someday
    global plasma_tl
    global plasma_ttt
    if random.uniform(0,1) < plasma_dot_chance:
        if plasma_tl <= 0 :
            if combat_log : print("    Plasma Cell's DoT applied at",time+this_action_time,"seconds! Chance was:",plasma_dot_chance)
        else:
            if combat_log : print("    Plasma Cell's DoT refreshed at",time+this_action_time,"seconds! Chance was:",plasma_dot_chance)
        plasma_tl = 2
        plasma_ttt = 3 + this_action_time     #assuming dot-clipping and no immediate tick although that would make so much sense!

## base_dmg = random.uniform( (AmountModifierPercent+1)*MH_Dmg_Min + Coefficient*DamageBonus + StandardHealthPercentMin*StandardHealth , (AmountModifierPercent+1)*MH_Dmg_Max + Coefficient*DamageBonus + StandardHealthPercentMax*StandardHealth )

######################################################
##simulations:
######################################################
#setting the stage for all simulations
tot_dmg = 0
tot_time = 0
problematic_ammo_event_counter = 0  #this tracks times at 12 ammo and at less than 8 ammo

print("------ Starting simulations ------")
for number_of_simulation in range(1, number_of_simulations+1):

    print("---- Starting Simulation",number_of_simulation,"----")
    #setting the stage
    time = 0
    dmg = 0
    ammo = 12
    this_ammo = 0
    this_cast_time = 0
    this_ammo_time = 0                      #time from start of cast to when ammo is payed
    this_action_time = 0                    #time from start of cast when dmg happens
    this_name =""                           #name of the current cast for output purposes
    this_event = 0                          #does the current cast miss, hit or crit? also used for more complex stuff
    this_dmg = 0                            #damage of the currently simulated skill
    ammo_regime = 0.36                      #ammo regeneration per second
    dummy_dot_1_tl = 0                      #time to next tick of dot
    dummy_dot_1_ttt = 0                     #ticks left in dot
    incendiary_round_dot_tl = 0
    incendiary_round_dot_ttt = 0
    plasma_tl = 0
    plasma_ttt = 0
    regen_cd = random.uniform(0, 1/0.6)     #cooldown to the next regeneration tick, randomly determined before first action. how is this behaviour really?
    assault_plastique_cd = 0                #current cooldown of Assault Plastique
    high_impact_bolt_cd = 0
    full_auto_cd = 0
    ionic_accelerator_procc = 0
    

    ######################################################
    ##actual simulation:
    ######################################################
    while time < duration_of_simulation:

        #0.1: check if it is time for an ammo-regen tick, if yes, regenerate and check again
        while regen_cd <=0:
            ammo_regen()
        #0.2: pay ammo costs of casts that pay at the end of their cast-time, ie. after the regeneration during their cast-time
        if this_ammo_time > 0 :
            ammo = min(ammo - this_ammo, 12)
            if combat_log : print("    Paying",this_ammo,"ammo for",this_name,". Now at",ammo,"ammo")
        this_ammo = 0

        #cheap way to model dipping below 8 (or 3) ammo increasing the time till the next regen tick. i hope this is close to reality
        if ammo < 3 and ammo_regime == 0.36:
            regen_cd = regen_cd*0.36/0.24
            ammo_regime = 0.24
            if combat_log : print(":::","Ammo Regeneration                   - Time to next tick increased because dropping below 3 ammo")
        elif ammo < 8 and ammo_regime == 0.6:
            regen_cd = regen_cd*0.6/0.36
            ammo_regime = 0.36
            if combat_log : print(":::","Ammo Regeneration                   - Time to next tick increased because dropping below 8 ammo")
            problematic_ammo_event_counter = problematic_ammo_event_counter + 1    

        #0.3: establish environment stuff at this step, ie. is the target burning
        if plasma_tl > 0 or incendiary_round_dot_tl > 0: burn_YN = 1    #check if a burn effect is present
        else:
            if combat_log : print("no burn effect on target")
            burn_YN = 0
        
        #0.5: check if there are dots to tick
        if incendiary_round_dot_ttt <= 0 and incendiary_round_dot_tl > 0 :
            incendiary_round_dot_tick()
        elif plasma_ttt <= 0 and plasma_tl > 0 :
            plasma_cell_dot_tick()
        else:
            #1.1: Incendiary Round if there is no burn on the target
            if burn_YN == 0 : incendiary_round()
            #1.2: High Impact Bolt
            elif high_impact_bolt_cd <= 0 and burn_YN == 1 : high_impact_bolt()
            #1.3: Assault Plastique
            elif assault_plastique_cd <=0 and Assault_Plastique >0 and ammo >= 10 : assault_plastique()
            #1.4: Full Auto
            elif full_auto_cd <= 0 and ammo >= 10 : full_auto()
            #1.5: Incendiary Round if it's dot ran out, check ammo first
            elif incendiary_round_dot_tl <= 0 and ammo >= 11 : incendiary_round()
            #1.6: Charged Bolts
            elif ammo >= 11 : charged_bolts()
            #1.Last: Happy Shot
            else: hammer_shot()
        
        #2: post-processing of current cast
        #2.1: paying ammo if the spell has to be payed at execution
        if this_ammo_time == 0 :
            ammo = min(ammo - this_ammo,12)
            if combat_log : print("    Paying",this_ammo,"ammo for",this_name,". Now at",ammo,"ammo")
            this_ammo = 0

        #3: common steps
        #3.1: increasing/decreasing times
        time = time + this_cast_time    #simulation time. this is now the time when the next cast can take place
        #3.1.1: dots
        dummy_dot_1_ttt = dummy_dot_1_ttt - this_cast_time
        incendiary_round_dot_ttt = incendiary_round_dot_ttt - this_cast_time
        plasma_ttt = plasma_ttt - this_cast_time
        #3.1.2: cooldowns
        assault_plastique_cd = assault_plastique_cd - this_cast_time
        high_impact_bolt_cd = high_impact_bolt_cd - this_cast_time
        full_auto_cd = full_auto_cd - this_cast_time
        
        #cheap way to model dipping below 8 (or 3) ammo increasing the time till the next regen tick. i hope this is close to reality
        if ammo < 3 and ammo_regime == 0.36:
            regen_cd = regen_cd*0.36/0.24 -this_cast_time
            ammo_regime = 0.24
            if combat_log : print(":::","Ammo Regeneration                   - Time to next tick increased because dropping below 3 ammo")
        elif ammo < 8 and ammo_regime == 0.6:
            regen_cd = regen_cd*0.6/0.36 - this_cast_time
            ammo_regime = 0.36
            if combat_log : print(":::","Ammo Regeneration                   - Time to next tick increased because dropping below 8 ammo")
            problematic_ammo_event_counter = problematic_ammo_event_counter + 1
        else : regen_cd = regen_cd - this_cast_time

        #3.2: counting damage
        dmg = dmg + this_dmg        

        #3.3: "combat log"
        if combat_log : print("--&gt;",this_name,"does:",this_event,"for",this_dmg,"dmg","at",time+this_action_time-this_cast_time,"seconds.")

      
    print("---- End of Simulation",number_of_simulation,"  Damage was",dmg,"in",time,"seconds. ----")
    print()
    #counting
    tot_dmg = tot_dmg + dmg
    tot_time = tot_time + time
    if number_of_simulation == 1 :
        min_dmg = dmg
        max_dmg = dmg
    min_dmg = min(dmg,min_dmg)
    max_dmg = max(dmg,max_dmg)
    



else:
    print("------ Simulations finished! ------")
    print("------ Total damage over all simulations was:",tot_dmg,"dmg")
    print("------ Total time of all simulations was:",tot_time,"seconds")
    print("------ =",tot_dmg/tot_time,"average dps")
    print("------ there were",problematic_ammo_event_counter,"problematic ammo events (>=12 or <8).")
    print("------ Minimum Damage in a simulation was:",min_dmg)
    print("------ Maximum Damage in a simulation was:",max_dmg)
    print()

EDIT: I don't seem to be able to start a new thread, that's why I post this here
EDIT2: Fixed bugs, which brings my dps up quite a bit. Also added the code as an attachment for ease of use.


Attached File(s)
.txt  Assault Specialist 1.1-1.0.py.txt (Size: 29.39 KB / Downloads: 72)
Find all posts by this user
Quote this message in a reply
04-14-2012, 09:12 AM
Post: #20
RE: Gunnery/Arsenal DPS, Simulator, A new approach
That's awesome!

I think a lot of the work on these forms reinforces itself, and it's really cool to see other people working on similar projects.

To have a post that isn't entirely fluff: My theory at the moment is that hammer shots is actually just 1 attack, in spite of the floating combat text. The info for the attack lists a 'flurryblows' as a characteristic, which I think is distinct from attacks that actually have several damage calculations (rapid fire, master strike).

Then again, this could still be somewhat wrong. I swear hammer shots procs my gravity wave device almost every time shooting the first pillar on each Soa transition phase...
Find all posts by this user
Quote this message in a reply
Post Reply 


Forum Jump:


User(s) browsing this thread: 1 Guest(s)