Jump to content

Kinetic/Dark Bulwark Calculations


dipstik

Recommended Posts

I am writing this to disclose and produce discussion about the methodology of determining the absorb contribution due to kinetic/dark bulwark. I had originally used a simulation that used a random number generator against the probability of shielding an attack to fill a table of states that weighted each stack by its uptime. Keyboardninja had found some errors in the simulation, which inspired a new methodology.

 

When I fist encountered this calculation, I thought using a binomial probability would be the most appropriate way to finding the absorb contribution. Unfortunately, if you have X swings in 20 seconds, you have 2^x number of ways to populate the "truth table," each of which needed to be weighted for its uptime and the probability of that set of X states.

 

To give you an example of what I'm talking about: If we have a swing timer of 1 hit every second, then we have 20 states. There are 20 different ways to get one success. If the first trial is a success, then we have an effective absorb contribution of 1*(0-20)/20=1%. If the last hit was shielded, the contribution would be 1*(19-20)/20=0.05%. If there are a total of 2 successes in the 20 hits, there are 20 choose 2 ways (20 choose 2 = 20!/(2!*(20-2)!) for that to happen, each with their own specific absorb weights, depending on when the shielded hits occurred. needless to say i did want to compute 2^20 different states.

 

Some background on binomial probability is due. If you roll a Y sided die, there is a p=1/Y chance (p for probability) of rolling a 1, lets call this a success. The probability of getting i successes from n trials is given by:

[n!/(i!*(n-i)!]*p^i*(1-p)^(n-i).

[n!/(i!*(n-i)!] tells you how many ways there are to roll a 1 i times out of n (this is just n choose i). the p^i part tells you the chance of getting i rolls, while the (1-p)^(n-i) part tells you the probability of not rolling 1 n-i times. For our purposes, p will be the chance of being able to shield a hit, n will be 20 seconds divided by the swing timer, and we will be finding this probability for every possible number of successes (i).

 

After some back and forth, Keyboardninja came up with a way of reducing the computational load required by separating the problem into each swing, with an effective absorb contribution for each swing, by taking all possible states after each trial/hit, using the binomial approach I wanted to start with. Each effective absorb contribution for each swing is then weighted by the amount of time for each swing.

 

Specifically, for each swing we take the probability of having i successes and multiply by the number of stacks associated with that number of successful shield events. We define the number of swings in 20 seconds by floor(20/T), where T is the period in seconds between swings. What we ended up with is:

 

E(n,p):=piecewise(n<=8, Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i=1..n), Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i=1..8)+Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*8,i=9..n)):

 

The function is piecewise because more than 8 successful shields will still only contribute 8 stacks of bulwark. We are summing from 1 success to n successes due to the fact that you cannot have more successes than you have had swings. So we have the binomial probability (n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i) multiplied by the number of stacks for the number of successes in question for that component of the summation, i for n<=8 and 8 for 9 to floor(20/T). We then have to sum each of these terms in order to find the total contribution over the entire 20 seconds, like so:

 

A(T,p):=Summation((E(n,p))/(floor(20/(T))),n=1..floor(20/(T))):

 

so each component contributes equally, since the expected value for each swing has an effective uptime of 1/floor(20/T).

 

As a sanity check, I also made a function that only allowed 15 or less successes to contribute, but have found no cases where this is reflected by the absorb amount.

E1(n,p):=piecewise(n<=8, Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i=1..n), n>8, Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i=1..8)+Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*8,i=9..n),n>15, Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i=1..8)+Summation((n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*8,i=9..15)):

This function gives the same values as E(n,p) listed above, which does not stop the sum at 15. part of this is because the probability of getting more than 15 successes when you have 18 to 25 trials is very small, and those very small weights were not evident in the absorb (no difference up to 4 decimal places for the percent).

 

The probability p will depend on the damage weights (kw is fraction damage melee/ranged kinetic/energy, kf is fraction damage melee/ranged kinetic/energy, since we cannot shield against internal/elemental attacks we do not include this, but we have to make sure we only look at the swing period, T, of kinetic/energy attacks), defense chance (d), shield chance (s), resist chance ®, as well as the fraction of melee/ranged attacks that are at 90% accuracy versus 100% accuracy (fb for fraction bland and fn for fraction named). Putting all these things together we get:

 

p(d,s,r,kw,kf,fb,fn):=1/(kw+kf)*(kf*s*(1-r)+kw*s*(fb*(1-(d+0.1))+fn*(1-d))):

 

For each swing timer i can get an equation for effective absorb contribution in terms of p. For example, for a swing timer of T=1 i get

 

A(1,p)=(705432/5)*p^10-529074*p^11+1220940*p^12-1918620*p^13+(21/2)*p+(10744272/5)*p^14-(8729721/5)*p^15+1027026*p^16-(855855/2)*p^17-(102102/5)*p^19+(7956/5)*p^20+120120*p^18-(88179/5)*p^9

 

For a swing timer of 1.5 seconds i get a much more elegant function:

A(1.5,p)=224*p^10-252*p^11+(1680/13)*p^12-(330/13)*p^13+7*p-77*p^9

 

Once we have a swing timer agreed upon, i can put it into my optimization spreadsheet to find the best values of defense, shield and absorb. For the time being however, I took it upon myself to find a regression in terms of p and T, up to 3 orders in p. Using Minitab with data for p=0 to 1 in 0.05 increments and T=0.5,1 and 1.5 I found:

 

A(T,p) = 2.03129 + 22.4408 p - 4.24114 T - 20.377 p*p - 4.96008 p*T + 1.865 T*T + 2.08018 p*p*p + 10.2966 p*p*T - 2.62597 p*T*T

 

Summary of Model

S = 0.166931 R-Sq = 99.50% R-Sq(adj) = 99.43%

PRESS = 2.39703 R-Sq(pred) = 99.20%

 

This is what i currently have in my spreadsheet, until we choose a swing timer, but this is a great equation with a pretty good correlation coefficient (R-squared value).

 

The swing timers are not in yet, but here is an example using a budget of 2721, a swing timer of 1/0.966=1.035 (swing timer for dread council hm). Some discussion went on between Keyboardninja and Thok-Zeus about the 50% 100% accuracy melee/ranged attack and 50% 90% accuracy assumptions i have been making since i first started reporting optimization numbers. It turns out that much more of the damage is 90% accuracy than 100%. With that in mind, I will begin to use the assumption that fraction bland damage = 75% (fb, or fraction melee/ranged damage at 90% accuracy), and faction named, fn, equal to 25%. I am assuming df/dp average damage weights.

p(.2812, .5849, 0.2e-1, .7327, .2274, .75, .25)= 0.4231. that is a probability of shielding a kinetic/energy hit

A(1.035, .4231) = 4.089. That is an absorb contribution of 4.089%

 

In the spreadsheet i leave p as a variable, so it changes with steps of the optimization. I ended up with:

Defense 528

Shield 980

Absorb 1213

with 4.189% contribution from bulwark (the difference is due to using the regression instead of an equation).

Edited by dipstik
Link to comment
Share on other sites

As an addendum to this, for anyone doing math checking (please do!), here is a screenshot of a more readable rendering of these formulae: http://i.imgur.com/g3aN894.png

 

Another addendum is that dipstik's example stats for a budget of 2721 at the tail end of the post do not consider the effects of the Fortunate Redoubt relic. When those effects are taken into account, optimal defense falls to about 340, while shield and absorb rise commensurately. Just to give you an idea of what the new stat distributions will look like, here is a shadow graph for the average of df/dp (defense is purple, absorb is gold, shield is green): http://i.imgur.com/W8ZM4RX.png

Edited by KeyboardNinja
Link to comment
Share on other sites

If you're still seeking an absorb estimation from a non-simulation, I rewrote my MATLAB parser. I'm like 90% sure that both of you have MATLAB, but if you want me to process a combat log but don't have access to a MATLAB computer, get the file to me somehow.

 

Example of how to run for a combatlog.txt file in the same folder as the .m file:

EDU>> cd C:\Users\John\Desktop\Storage\SWTOR\matlab
EDU>> filelist = dir(pwd)

filelist = 

12x1 struct array with fields:
   name
   date
   bytes
   isdir
   datenum

EDU>> filelist(3).name

ans =

combat_2013-11-21_21_54_00_574819.txt

EDU>> tic; [dict, final_abs] = parser(filelist(3).name); toc; % run with this line

 

I define {shielded attacks} as anything that has the phrases 'ApplyEffect Damage' AND '-shield' in the line, and define {unmitigated attacks} as anything that has phrase ''ApplyEffect Damage' AND (no '-' character in the line); that removes events like dodges and misses. I then define ith effective absorb as mean(shielded attack i)/mean(unmitigated attack i).

 

Then I can naively take the mean of all absorbs by taking the average of i attack types

EDU>> final_abs(isnan(final_abs)) = [];
EDU>> size(final_abs)

ans =

    1    83

EDU>> mean(final_abs)

ans =

   0.4980

And find that 5 months ago, I was getting ~0.4980-0.405 (from character sheet) = 0.093 absorb bonus over time. Note that this bonus includes click relic effects, Reflexive Shield, and absorb adrenal.

 

I think given enough samples, it must do a decent job of marginalizing out your effects that change DTPS like Reflexive Shield (Smoke Grenade is already not counted because it's not a part of the shielded or unmitigated sets), absorb adrenal, click relics, and damage reduction debuffs on the boss. It's both convenient and sensible to leave these things unregressed in the calculation, since it gives me a value that already incorporates all those effects.

 

Edit: this part was written really badly. What I mean is that if I want to estimate DTPS, I could either regress out all effects like Reflexive Shield, absorb adrenal, and click relics, to find my premitigation DTPS. Then I could calculate new DTPS for different stat values. But since I'm likely going to have similar Reflexive Shield/adrenal/relic usage in the future, I'd have no way of incorporating those effects into my new projected DTPS. If I leave those effects unregressed, I don't need to account for them when projecting new DTPS.

 

In your case since it's going to **** things up since you guys do relic calculations, 5% damage reduction, and adrenals separately.

 

Instead of taking a naive average you could edit the code to give you a mean of absorbs weighted by samples. That would approximate your absorb bonus over time easily enough. You could also rewrite a parser to instead track just absorb bonuses over time.

 

Full MATLAB code:

 

 

function [attack_dict, final_abs] = parser(infile)

 

filetext = strread(fileread(infile), '%s', 'delimiter', sprintf('\n'));

attack_dict = containers.Map;

 

outcome = {};

final_abs = [];

for i = 1:size(filetext, 1)

untrimmed = filetext{i}; trimmed = untrimmed;

 

% delete all {\d*} content from lines which we're working with

for j = regexp(filetext{i}, '\{\d*\}')

trimmed = strrep(trimmed, strtok(untrimmed(j:end)), '');

end

 

% skip all non-damage effects

if isempty(strfind(trimmed, 'ApplyEffect Damage')), continue; end

 

% split string into 5 components: time, origin, target, nomination, values

r = strread(trimmed, '%s', 'delimiter', sprintf('['));

r = r(~cellfun('isempty',r));

 

for k = 2:3

r{k} = r{k}((r{k} >= 65 & r{k} <= 90) | (97 <= r{k} & r{k} <= 122));

% change all '[]' origins like howling sandstorm to '[unknown]'

if strcmp(r{k}, '] '), r{k} = '[unknown]'; end

% excise all characters not between 'A' and 'Z' OR 'a' and 'z'

end

 

if size® < 5

%fprintf('Ejected line %d. Unable to parse.\n', i);

%r

continue;

end

 

key = strcat([r{2},'->',r{3},':',r{4}]);

value = r{5};

 

if ~attack_dict.isKey(key), attack_dict(key) = {}; end

valuelist = attack_dict(key);

valuelist{end+1} = value;

attack_dict(key) = valuelist;

% written like this because im bad

 

outcome{end+1} = trimmed;

 

end % end iteration through filetext

 

keylist = attack_dict.keys();

for i = 1:size(keylist, 2)

key = keylist(i); key = key{1};

values = attack_dict(key);

values = strrep(values, 'ApplyEffect Damage (', '');

fprintf('Attack %s, %d samples\n', key, size(values, 2));

 

unmitigated = {}; othermitigated = {}; shielded = {};

for j = 1:size(values,2)

if strfind(values{j}, '-shield')

shielded{end+1} = values{j};

elseif strfind(values{j}, '-')

othermitigated{end+1} = values{j};

else

unmitigated{end+1} = values{j};

end

%fprintf('\t%s\n', values{j});

end

 

outcomes = {shielded; othermitigated; unmitigated};

outcome_labels = {'Shielded'; 'Other mitigated'; 'Unmitigated'};

%shieldvals = {}; othervals = {}; unmitigatedvals = {};

%vals = {shieldvals; othervals; unmitigatedvals};

 

shieldvals = []; unmitigatedvals = [];

for j = 1:size(outcomes, 1)

%fprintf('\t%s:\n', outcome_labels{j});

toprint = outcomes{j};

if j == 1

toprint = strrep(toprint, '-shield ', '');

end

 

for k = 1:size(toprint,2)

%fprintf('\t\t%s\n', toprint{k});

scanned_string = sscanf(toprint{k}, '%d %s <%d>');

if j == 1

shieldvals(end+1) = scanned_string(1);

elseif j == 3

unmitigatedvals(end+1) = scanned_string(1);

end

end % end iteration through specific values of one attack key

end % end iteration over potential outcomes for attack keys

eabs = mean(shieldvals)/mean(unmitigatedvals);

fprintf('\tEffective absorb: %0.2f\n', eabs);

 

final_abs(end+1) = eabs;

end % end iteration through keys

 

end % end function parser

 

 

(quote my post to get a copy of the code that has better indentation)

 

Sample output (after cutting out the sections which contain attacks that originate from myself; since no boss can absorb, those were just a ton of NaN values. Also hit the SWTOR forum character limit of 50,000 characters)

 

 

Attack ->Metallic:Howling Sandstorm, 25 samples

Effective absorb: NaN

Attack BlueTeamPackHunter->Metallic:Bleeding (Physical), 15 samples

Effective absorb: NaN

Attack BlueTeamPackHunter->Metallic:Charged Weapon, 3 samples

Effective absorb: 0.59

Attack BlueTeamPackHunter->Metallic:Gut, 3 samples

Effective absorb: NaN

Attack BlueTeamPackHunter->Metallic:Melee Attack, 11 samples

Effective absorb: NaN

Attack Bodyguard->Metallic:Flame Sweep, 2 samples

Effective absorb: NaN

Attack Bodyguard->Metallic:Lunge, 21 samples

Effective absorb: 0.54

Attack Bodyguard->Metallic:Melee Attack, 24 samples

Effective absorb: 0.44

Attack Bodyguard->Metallic:Rocket Punch, 2 samples

Effective absorb: NaN

Attack Bodyguard->Metallic:Vicious Slash, 3 samples

Effective absorb: 0.58

Attack Bodyguard->Metallic:Wrist Laser Burst, 12 samples

Effective absorb: 0.45

Attack CaptainHoric->Metallic:Ranged Attack, 6 samples

Effective absorb: NaN

Attack CaptainHoric->Metallic:Spray and Pray, 8 samples

Effective absorb: NaN

Attack CartelCombatEngineer->Metallic:Ranged Attack, 6 samples

Effective absorb: 0.47

Attack CartelLieutenant->Metallic:Axe Toss, 9 samples

Effective absorb: 0.61

Attack CartelLieutenant->Metallic:Melee Attack, 3 samples

Effective absorb: 0.38

Attack CrazedMercenary->Metallic:Melee Attack, 28 samples

Effective absorb: 0.30

Attack DashRoode->Metallic:Groundshatter, 5 samples

Effective absorb: 0.64

Attack DashRoode->Metallic:Gutwrenching Kick, 8 samples

Effective absorb: 0.33

Attack DashRoode->Metallic:Melee Attack, 222 samples

Effective absorb: 0.43

Attack DreadGuard->Metallic:Melee Attack, 83 samples

Effective absorb: 0.50

Attack DreadLarva->Metallic:Burrow, 2 samples

Effective absorb: NaN

Attack DreadLarva->Metallic:Swat, 20 samples

Effective absorb: 0.49

Attack DreadMasterBestia->Metallic:Assault, 28 samples

Effective absorb: 0.45

Attack DreadMasterBestia->Metallic:Combusting Seed, 1 samples

Effective absorb: NaN

Attack DreadMasterBestia->Metallic:Dread Charge, 2 samples

Effective absorb: NaN

Attack DreadMasterBestia->Metallic:Dread Pool, 3 samples

Effective absorb: NaN

Attack DreadMasterBestia->Metallic:Dread Scream, 15 samples

Effective absorb: 0.35

Attack DreadMasterBestia->Metallic:Dread Strike, 18 samples

Effective absorb: 0.45

Attack DreadMasterBestia->Metallic:Swelling Despair, 6 samples

Effective absorb: 0.52

Attack DreadMasterStyrak->Metallic:Force Charge, 4 samples

Effective absorb: NaN

Attack DreadMasterStyrak->Metallic:Mass Force Storm, 8 samples

Effective absorb: 0.51

Attack DreadMasterStyrak->Metallic:Melee Attack, 45 samples

Effective absorb: 0.51

Attack DreadMasterStyrak->Metallic:Power of the Master, 17 samples

Effective absorb: NaN

Attack DreadMasterStyrak->Metallic:Saber Throw, 8 samples

Effective absorb: 0.36

Attack DreadMasterStyrak->Metallic:Shocked, 3 samples

Effective absorb: 0.57

Attack DreadMasterStyrak->Metallic:Thundering Blast, 3 samples

Effective absorb: 0.30

Attack DreadMonster->Metallic:Pulverize, 31 samples

Effective absorb: 0.45

Attack DreadMonster->Metallic:Squash, 54 samples

Effective absorb: 0.53

Attack DreadMonster->Metallic:Swipe, 129 samples

Effective absorb: 0.45

Attack DreadTentacle->Metallic:Whip, 11 samples

Effective absorb: 0.45

Attack DustclawAlpha->Metallic:Deepening Wounds, 127 samples

Effective absorb: NaN

Attack DustclawAlpha->Metallic:Melee Attack, 110 samples

Effective absorb: 0.47

Attack DustclawPackling->Metallic:Melee Attack, 69 samples

Effective absorb: 0.53

Attack DustclawRavager->Metallic:Ravaging Frenzy, 95 samples

Effective absorb: 0.44

Attack ElaraDorne->Metallic:Nightmare, 1 samples

Effective absorb: NaN

Attack ElaraDorne->Metallic:Ranged Attack, 11 samples

Effective absorb: 0.37

Attack FrenziedMercenary->Metallic:Melee Attack, 14 samples

Effective absorb: 0.46

Attack HardenedMedtechDroid->Metallic:Ranged Attack, 1 samples

Effective absorb: NaN

Attack IAArtilleryDroid->Metallic:Ion Field, 14 samples

Effective absorb: NaN

Attack IAArtilleryDroid->Metallic:Ranged Attack, 168 samples

Effective absorb: 0.51

Attack InsaneMercenary->Metallic:Fuel Tank Detonation, 1 samples

Effective absorb: NaN

Attack InsaneMercenary->Metallic:Full Burn, 25 samples

Effective absorb: NaN

Attack InsaneMercenary->Metallic:Melee Attack, 12 samples

Effective absorb: 0.70

Attack KellDragon->Metallic:Head Swipe, 8 samples

Effective absorb: 0.32

Attack KellDragon->Metallic:Leap Slam, 5 samples

Effective absorb: 0.56

Attack KellDragon->Metallic:Melee Attack, 24 samples

Effective absorb: 0.44

Attack KellDragon->Metallic:Overcharge, 2 samples

Effective absorb: NaN

Attack KellDragon->Metallic:Shredding Claws, 42 samples

Effective absorb: 0.64

Attack KellDragon->Metallic:Spines, 99 samples

Effective absorb: 0.50

Attack KellDragon->Metallic:Vomit Pool, 10 samples

Effective absorb: NaN

Attack MAFrontlineDroid->Metallic:Retractable Blade, 6 samples

Effective absorb: 0.39

Attack MaddenedMercenary->Metallic:Melee Attack, 46 samples

Effective absorb: 0.53

Attack MercenaryCarver->Metallic:Bleeding (Physical), 8 samples

Effective absorb: NaN

Attack MercenaryCarver->Metallic:Charged Weapon, 18 samples

Effective absorb: 0.46

Attack MercenaryCarver->Metallic:Gut, 17 samples

Effective absorb: 0.74

Attack MercenaryCarver->Metallic:Melee Attack, 56 samples

Effective absorb: 0.38

Attack MercenaryCarver->Metallic:Shockwave Strike, 2 samples

Effective absorb: NaN

Attack MercenaryHeavyGunner->Metallic:Charged Rounds, 57 samples

Effective absorb: 0.55

Attack MercenaryHeavyGunner->Metallic:Full Auto, 26 samples

Effective absorb: 0.65

Attack MercenaryHeavyGunner->Metallic:Grav Round, 20 samples

Effective absorb: 0.73

Attack MercenaryHeavyGunner->Metallic:Ranged Attack, 4 samples

Effective absorb: 0.17

Attack MercenarySniper->Metallic:Aimed Shot, 3 samples

Effective absorb: NaN

Attack MercenarySniper->Metallic:Ranged Attack, 98 samples

Effective absorb: 0.48

Attack OloktheShadow->Metallic:Lacerate, 56 samples

Effective absorb: 0.47

Attack OloktheShadow->Metallic:Missile Blast, 16 samples

Effective absorb: 0.58

Attack OloktheShadow->Metallic:Ranged Attack, 2 samples

Effective absorb: NaN

Attack OloktheShadow->Metallic:Snipe, 3 samples

Effective absorb: 0.45

Attack OperationsChief->Metallic:Explosive Probe (Tech), 1 samples

Effective absorb: NaN

Attack OperationsChief->Metallic:Headshot, 1 samples

Effective absorb: NaN

Attack OperationsChief->Metallic:Rifle Shot, 40 samples

Effective absorb: 0.47

Attack OperationsChief->Metallic:Terminate, 14 samples

Effective absorb: 0.62

Attack PBAssaultDroid->Metallic:Ranged Attack, 99 samples

Effective absorb: 0.44

Attack PXReconDroid->Metallic:Targeted Laser, 15 samples

Effective absorb: 0.44

Attack PirateStimfiend->Metallic:Nylite Toxin (Physical), 5 samples

Effective absorb: NaN

Attack RailTurret->Metallic:Rail Shot, 23 samples

Effective absorb: 0.72

Attack ScavengingWompRat->Metallic:Gnawing Bite, 4 samples

Effective absorb: 0.33

Attack ScavengingWompRat->Metallic:Maul, 3 samples

Effective absorb: 0.35

Attack ScavengingWompRat->Metallic:Melee Attack, 1 samples

Effective absorb: NaN

Attack ShadyCustomer->Metallic:Ranged Attack, 18 samples

Effective absorb: 0.38

Attack ShadyCustomer->Metallic:Scattergun Ambush, 3 samples

Effective absorb: NaN

Attack ShadyCustomer->Metallic:Scattergun Blast, 23 samples

Effective absorb: 0.61

Attack Thrasher->Metallic:Melee Attack, 93 samples

Effective absorb: 0.50

Attack Thrasher->Metallic:Roar, 20 samples

Effective absorb: NaN

Attack Thrasher->Metallic:Stomp, 20 samples

Effective absorb: 0.51

Attack Thrasher->Metallic:Swipe, 12 samples

Effective absorb: 0.52

Attack Titan->Metallic:Explosive Charge, 9 samples

Effective absorb: 0.56

Attack Titan->Metallic:Flame Burst, 2 samples

Effective absorb: NaN

Attack Titan->Metallic:Huge Grenade, 1 samples

Effective absorb: NaN

Attack Titan->Metallic:Kick, 2 samples

Effective absorb: NaN

Attack Titan->Metallic:Lots of Missiles, 43 samples

Effective absorb: 0.53

Attack Titan->Metallic:Missile Burst, 10 samples

Effective absorb: 0.47

Attack Titan->Metallic:Ranged Attack, 114 samples

Effective absorb: 0.49

Attack TitanProbe->Metallic:Targeted Laser, 142 samples

Effective absorb: 0.47

Attack Tuchuk->Metallic:Brutal Blow, 45 samples

Effective absorb: 0.49

Attack Tuchuk->Metallic:Frenzied Swings, 865 samples

Effective absorb: 0.43

Attack Tuchuk->Metallic:Leap, 11 samples

Effective absorb: 0.58

Attack UnderworldArmsTrader->Metallic:Final Offer, 1 samples

Effective absorb: NaN

Attack UnderworldArmsTrader->Metallic:Free Samples, 30 samples

Effective absorb: 0.54

Attack UnderworldArmsTrader->Metallic:Limited Time Offer, 3 samples

Effective absorb: 0.57

Attack UnderworldArmsTrader->Metallic:Ranged Attack, 78 samples

Effective absorb: 0.57

Attack UnderworldArmsTrader->Metallic:Return Policy, 20 samples

Effective absorb: 0.51

Attack UnderworldArmsTrader->Metallic:Stockstrike Knockback, 10 samples

Effective absorb: 0.63

Attack VilusGarr->Metallic:Explosive Surge, 14 samples

Effective absorb: NaN

Attack VilusGarr->Metallic:Pistol Shot, 12 samples

Effective absorb: 0.58

Attack VoraciousXuvva->Metallic:Melee Attack, 22 samples

Effective absorb: 0.63

Attack WealthyBuyer->Metallic:Flurry of Bolts, 20 samples

Effective absorb: 0.57

Attack WealthyBuyer->Metallic:Ranged Attack, 3 samples

Effective absorb: 0.38

Elapsed time is 8.510743 seconds.

 

 

 

Edit: parsed a recent Shadow's log for some DF/DP content. His torparse is here: http://www.torparse.com/a/598192

I found that he had 0.51 effective absorb, and his character sheet absorb is 47.54% / 1126 Absorb rating. That gives 0.51-0.4754 = 0.0346 effective absorb bonus by weight. Note, again, that's not absorb averaged over time; it's slightly different from that computation. It's the average over all attacks of {shielded damage} / {unmitigated damage} when both shielded damage and unmitigated damage occurred.

Edited by MGNMTTRN
Link to comment
Share on other sites

I discovered a bug. abs% should be 1 - mean(shield dam)/mean(raw dam), not shield dam/ raw dam. I am going to fix the bug and extend the script to calculate premitigation DTPS. Maybe. This post should be updated in 2 hours.

 

Edit:

 

Made that change to absorb calculation. I now get a mean effective abs of 0.5020 for my Vanguard tank, or 0.5020 - 0.405 = 0.0970 absorb bonus from Energy Blast and Power Screen. For that Shadow log I linked, I get a mean effective abs of 0.49 and a bonus from Kinetic Bulwark of 0.49 - 0.4754 = 0.0146 from that Shadow log that I posted.

 

Not sure why that bulwark value is so low. If premitigation_damage*(1-abs) = shield_damage and mean(premitigation_damage) and mean(shield_damage) are known, then

premitigation_damage*(1-abs) = shield_damage

1-abs = shield_damage/premitigation_damage

abs-1 = -shield_damage/premitigation_damage

abs = 1 - shield_damage/premitigation_damage

 

Edit #4 or so: manually confirmed for the attack Sporeling->Alara:Rake that the values picked up by the parser were the values being reported by torparse. Also manually confirmed for several attacks that mean(unmitigated)*(1-effective absorb reduction) = ~mean(shielded). So the values I'm reporting seem to be accurate and unbuggy now.

 

Also now producing output of this style:

Attack PalaceInterrogator->Alara:Shocked had 3 total samples: 1 unmitigated, 1 shielded, 1 othermitigated.
Effective shield chance: 0.33
Effective othermit chance: 0.33
Effective absorb reduction: 0.49
Average damage per hit: 601.67
Effective postarmor mitigation coefficient: 0.50
Attack PalaceWatchman->Alara:Double Strike had 20 total samples: 11 unmitigated, 7 shielded, 2 othermitigated.
Effective shield chance: 0.35
Effective othermit chance: 0.10
Effective absorb reduction: 0.53
Average damage per hit: 3070.40
Effective postarmor mitigation coefficient: 0.72

 

Cut and cleaned some of the code

 

 

function [attack_dict, final_abs] = parser(infile)

 

filetext = strread(fileread(infile), '%s', 'delimiter', sprintf('\n'));

attack_dict = containers.Map;

 

outcome = {};

final_abs = [];

for i = 1:size(filetext, 1)

untrimmed = filetext{i}; trimmed = untrimmed;

 

% delete all {\d*} content from lines which we're working with

for j = regexp(filetext{i}, '\{\d*\}')

trimmed = strrep(trimmed, strtok(untrimmed(j:end)), '');

end

 

% skip all non-damage effects

if isempty(strfind(trimmed, 'ApplyEffect Damage')), continue; end

 

% split string into 5 components: time, origin, target, nomination, values

r = strread(trimmed, '%s', 'delimiter', sprintf('['));

r = r(~cellfun('isempty',r));

 

for k = 2:3

r{k} = r{k}((r{k} >= 65 & r{k} <= 90) | (97 <= r{k} & r{k} <= 122));

% change all '[]' origins like howling sandstorm to '[unknown]'

if strcmp(r{k}, '] '), r{k} = '[unknown]'; end

% excise all characters not between 'A' and 'Z' OR 'a' and 'z'

end

 

if size® < 5

%fprintf('Ejected line %d. Unable to parse.\n', i);

%r

continue;

end

 

key = strcat([r{2},'->',r{3},':',r{4}]);

value = r{5};

 

if ~attack_dict.isKey(key), attack_dict(key) = {}; end

valuelist = attack_dict(key);

valuelist{end+1} = value;

attack_dict(key) = valuelist;

% written like this because im bad

 

outcome{end+1} = trimmed;

 

end % end iteration through filetext

 

keylist = attack_dict.keys();

for i = 1:size(keylist, 2)

key = keylist(i); key = key{1};

values = attack_dict(key);

values = strrep(values, 'ApplyEffect Damage (', '');

%fprintf('Attack %s, %d samples\n', key, size(values, 2));

 

unmitigated = []; othermitigated = []; shielded = [];

for j = 1:size(values,2)

as_value = sscanf(strrep(values{j}, '-shield ', ''), '%d %s <%d>');

dam = as_value(1); threat = as_value(end);

textlabel = char(as_value(2:end-1))'; if isempty(textlabel), textlabel = 'unknown'; end

 

if strfind(values{j}, '-shield')

shielded(end+1) = dam;

elseif strfind(values{j}, '-')

othermitigated(end+1) = dam;

else

unmitigated(end+1) = dam;

end

%fprintf('\t%s: parsed as damage %d\n', values{j}, dam);

end

 

samplecount = numel([shielded, othermitigated, unmitigated]);

totaldamage = sum([shielded, unmitigated, othermitigated]);

fprintf('Attack %s had %d total samples: %d unmitigated, %d shielded, %d othermitigated.\n', ...

key, samplecount, numel(unmitigated), numel(shielded), numel(othermitigated));

fprintf('\tEffective shield chance: %0.2f\n', numel(shielded)/samplecount);

fprintf('\tEffective othermit chance: %0.2f\n', numel(othermitigated)/samplecount);

fprintf('\tEffective absorb reduction: %0.2f\n', 1 - mean(shielded)/mean(unmitigated)); % !!!

fprintf('\tAverage damage per hit: %0.2f\n', totaldamage/samplecount);

final_abs(end+1) = 1 - mean(shielded)/mean(unmitigated);

fprintf('\tEffective postarmor mitigation coefficient: %0.2f\n', totaldamage/(mean(unmitigated)*samplecount));

 

end % end iteration through keys

 

end % end function parser

 

 

 

Can KBN modify his Perl script to compute 1 - mean(shielded)/mean(unmitigated)? It would be good to corroborate.

Edited by MGNMTTRN
Link to comment
Share on other sites

Took me way to long to figure this out, but i think i got an expression that takes the more than 14 success cases into account.

p(d,s,r,kw,kf,fb,fn):=1/(kw+kf)*(kf*s*(1-r)+kw*s*(fb*(1-(d+0.1))+fn*(1-d))):

m(T):=floor(20/(T)):

E(n,p):=Summation(piecewise(i<9,(n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*i,i<15,(n!)/(i!*(n-i)!)*p^(i)*(1-p)^(n-i)*8,15<=i,0),i=1..n):

P(m,p):=piecewise(m>=15,(m!)/(15!*(m-15)!)*p^(15)*(1-p)^(m-15),0):

A(T,p):=piecewise(m(T)<15, 1/(m(T))*Summation(E(n,p),n=1..m(T)),

Summation((Summation(E(n,p),n=1..15+i)* P(15+i,p))/(15+i),i=0..m(T)-15)+((1- Summation(P(n,p),n=15..m(T)))*Summation(E(n,p),n=1 ..m(T)))/(m(T))):

 

P computes the probability of having 15 successes given m trials. we then use this weight with a denominator that will change the weight of the expected value per trial (if we have to refresh after 15 trials, we divide by 15, if after 16 trialswe divide by 16 etc.). for the cases where less than 15 successes occur, we use the compliment of P(cumulitive for 15 to m) with the floor(20/T) weight.

 

using this i generated tables for p=0.35 to 0.65 in 15 steps for T=0.75,1,1.25,1.5 and got this regression:

 

Regression Equation

 

A = 3.94583 + 18.9477 P - 8.19726 T - 45.3335 P*P + 21.4219 P*T + 9.53125

P*P*P + 19.4587 P*P*T - 15.8852 P*T*T + 2.07302 T*T*T

 

Summary of Model

 

S = 0.0278044 R-Sq = 99.90% R-Sq(adj) = 99.89%

PRESS = 0.0659578 R-Sq(pred) = 99.85%

 

This regression is off by 0.07 in some places however.

 

Using a timer of 1.035 seconds I get a r squared value of 1 for the following equation:

A=-9.1337*p^2+15.887*p-0.9991

Edited by dipstik
Link to comment
Share on other sites

  • 2 weeks later...

All right, I spoke to Dipstik about what he might find useful and incorporated most of that into this script. KBN never responded concerning his Perl script so I'm not gonna worry about his workflow. I had considered making a completely new thread to post these scripts, but then I'd be inviting people to post their own combat logs for me to parse and that's more trouble than I want to invite on myself.

 

If KBN doesn't post his own breakdown, when NIM comes I will post 16man NIM DTPS parses built off of this script. 8man NIM DTPS parses may follow within a week or two. PVP parses may follow a while after 2.7.

 

(Quote this post to get .m code with some semblance of tabbing preserved)

 

Example of how to invoke (note you must have MATLAB to run this code. It's ~$100 on Amazon):

EDU>> format bank
EDU>> cd C:\Users\John\Desktop\Storage\SWTOR\matlab
EDU>> filelist = dir(pwd);
EDU>> parser_handler(filelist(4).name, [0.4445 0.19 0.2375], 'Alara')

 

To run the parser_handler.m script, call

parser_handler('Name of file to parse, as string', [kinetic_DR internal_DR procced_defense_chance], 'Player name as string if you wish to ignore player's attacks')

Note that the parser pauses after it reports the results of each combat. Hit the <Enter> key to permit it to progress to parsing the next combat.

 

Code for parser.m was updated a bit, the full code is now:

 

 

function [attack_dict, consolidated, participants] = parser(infile, inds_vec, playername, dodge_threshold)

 

if nargin < 3, playername = ''; end % by default skip no lines no matter who the attack originates from

if nargin < 4, dodge_threshold = 0.15; end % percentage of dodged/RESISTED attacks must exceed this value to be considered melee

 

processed = strread(fileread(infile), '%s', 'delimiter', sprintf('\n'));

filetext = {};

for i = inds_vec(1):inds_vec(2), filetext{end+1} = processed{i}; end % end iteration to populate filetext cell array

% quick fix, //TODO vectorize

 

attack_dict = containers.Map;

consolidated = containers.Map;

 

participants = {};

outcome = {};

for i = 1:size(filetext, 2)

untrimmed = filetext{i}; trimmed = untrimmed;

 

% delete all {\d*} content from lines which we're working with

for j = regexp(filetext{i}, '\{\d*\}')

trimmed = strrep(trimmed, strtok(untrimmed(j:end)), '');

end

 

% skip all non-damage effects

if isempty(strfind(trimmed, 'ApplyEffect Damage')), continue; end

 

% split string into 5 components: time, origin, target, nomination, values

r = strread(trimmed, '%s', 'delimiter', sprintf('['));

r = r(~cellfun('isempty',r));

 

for k = 2:3

% strip nonalphabetic characters out of names

r{k} = r{k}((r{k} >= 65 & r{k} <= 90) | (97 <= r{k} & r{k} <= 122));

 

% change all '[]' origins like howling sandstorm to '[unknown]'

% TODO nonfunctional

if strcmp(r{k}, '] '), r{k} = '[unknown]'; end

% excise all characters not between 'A' and 'Z' OR 'a' and 'z'

end

 

if size® < 5

%fprintf('Ejected line %d. Unable to parse.\n', i);

%r

continue;

end

participants = unique([participants, {r{2}, r{3}}]);

key = strcat([r{2},'->',r{3},':',r{4}]);

value = r{5};

if strcmp(r{2},playername), continue; end% skip content that originates from player, don't need to know outgoing player DPS

 

if ~attack_dict.isKey(key), attack_dict(key) = {}; end

valuelist = attack_dict(key);

valuelist{end+1} = value;

attack_dict(key) = valuelist;

% written like this because im bad

 

outcome{end+1} = trimmed;

 

end % end iteration through filetext

 

keylist = attack_dict.keys();

for i = 1:size(keylist, 2)

key = keylist(i); key = key{1};

values = attack_dict(key);

values = strrep(values, 'ApplyEffect Damage (', '');

 

unmitigated = []; avoided = []; shielded = [];

damtype = {'force', 'internal'}; % default force internal

for j = 1:size(values,2)

% replace instances of '-shield' events, then scan them as 'damage string_label <threat>'

as_value = sscanf(strrep(values{j}, '-shield ', ''), '%d %s <%d>');

dam = as_value(1); threat = as_value(end);

 

% //TODO try scanning directly from values{j} instead of as_value,

 

if ~isempty(strfind(values{j}, 'kinetic')) || ~isempty(strfind(values{j}, 'energy'))

damtype{2} = 'kinetic';

end

 

if strfind(values{j}, '-shield')

shielded(end+1) = dam;

damtype{2} = 'kinetic';

% the presence of one shield event indicates the damage is kinetic... could also parse from log, but a bit more difficult

elseif strfind(values{j}, '-')

avoided(end+1) = dam;

else

unmitigated(end+1) = dam;

end

 

end % end iteration through all instances of attacks of type <key>

if numel(avoided)/numel([shielded, avoided, unmitigated]) > dodge_threshold, damtype{1} = 'melee'; end

% if more than dodge_threshold% of attacks are dodged, presume the attack type is melee

 

samplecount = numel([shielded, avoided, unmitigated]);

totaldamage = sum([shielded, unmitigated, avoided]);

%fprintf('Attack %s had %d total samples: %d unmitigated, %d shielded, %d avoided.\n', ...

%key, samplecount, numel(unmitigated), numel(shielded), numel(avoided));

%fprintf('\tEffective shield chance: %0.2f\n', numel(shielded)/samplecount);

%fprintf('\tEffective othermit chance: %0.2f\n', numel(avoided)/samplecount);

%fprintf('\tEffective absorb reduction: %0.2f\n', 1 - mean(shielded)/mean(unmitigated)); % !!!

%fprintf('\tAverage damage per sample: %0.2f\n', totaldamage/samplecount);

%final_abs(end+1) = 1 - mean(shielded)/mean(unmitigated);

%fprintf('\tEffective postarmor mitigation coefficient: %0.2f\n', totaldamage/(mean(unmitigated)*samplecount));

 

consolidated(key) = [samplecount numel(shielded) mean(shielded) (1-mean(shielded)/mean(unmitigated)) numel(avoided) totaldamage mean(unmitigated)];

consolidated(strcat([key, 'damtype'])) = damtype;

end % end iteration through keys

 

end % end function parser

 

 

 

Code for a parser_handler.m script has been written:

 

 

function [] = parser_handler(infile, mit_vec, playername)

if nargin < 2, mit_vec = [0.4 0.2 0.25]; end

if nargin < 3, playername = ''; end

filetext = strread(fileread(infile), '%s', 'delimiter', sprintf('\n'));

 

start_indicies = find(~cellfun('isempty',regexp(filetext, 'EnterCombat')));

end_indicies=find(~cellfun('isempty',regexp(filetext, 'ExitCombat')));

death_indicies = find(~cellfun('isempty', regexp(filetext,'Event \{\d*\}: Death \{\d*\}')));

revive_indicies = find(~cellfun('isempty', regexp(filetext,'Event \{\d*\}: Revived \{\d*\}')));

combat_indicies = unique([start_indicies; end_indicies; death_indicies; revive_indicies]);

 

% define all combat as being from EnterCombat event to next instance of one of the following: <ExitCombat event, Death event, another Enter Combat, Revive>

% we should expect all EnterCombats to be followed ExitCombats, but... things happen

for i = 1:size(start_indicies)

starti = start_indicies(i);

fprintf('Beginning parsing at %d with event %s\n', starti, filetext{starti});

endi = combat_indicies(find(combat_indicies == starti)+1);

fprintf('Ending parsing at %d with event %s\n',endi, filetext{endi});

inds_vec = [starti endi];

start_time = filetext{starti}; start_time = start_time(2:13);

start_time = mod(datenum(start_time,'HH:MM:SS'),1)*86400;

end_time = filetext{endi}; end_time = end_time(2:13);

end_time = mod(datenum(end_time,'HH:MM:SS'),1)*86400;

 

[attack_dict, consolidated, participants] = parser(infile, inds_vec, playername);

keylist = attack_dict.keys();

 

rk_miss_vec = []; rk_vec = [];

ri_miss_vec = []; ri_vec = [];

fk_vec = [];

fi_vec = [];

mit_c_vec = []; mit_weights = [];

 

fprintf('Attacks: \n');

for j = 1:size(keylist,2)

consvec = consolidated(keylist{j}); % consolidated(key) = [samplecount numel(shielded) mean(shielded) (1-mean(shielded)/mean(unmitigated)) numel(avoided) totaldamage mean(unmitigated)];

damtype = consolidated(strcat([keylist{j}, 'damtype']));

fprintf('\t%s\n', keylist{j});

 

fprintf('\t\t%d samples, %d total damage, %0.2f average damage including avoidance events\n', consvec(1), consvec(6), consvec(6)/consvec(1));

fprintf('\t\t%0.2f raw shield occurrence, %0.2f shield chance as proportion of nondefended attacks, %0.2f effective absorb as estimated by (1-mean(shielded))/mean(unmitigated)\n', consvec(2)/consvec(1), consvec(2)/(consvec(1)-consvec(5)), consvec(4));

base_miss = max(0, consvec(5)/consvec(1) - mit_vec(3));

fprintf('\t\t%0.2f total avoid chance, estimated predefense miss chance %0.2f = count(avoided) / count(attacks) - (given defense chance)\n', consvec(5)/consvec(1), base_miss);

premit = consvec(1)*consvec(7);

if strcmp(damtype{2},'kinetic')

prearmor = premit/(1-mit_vec(1)); % prearmir damage = premitigation damage / (1 - kinetic damage reduction)

else

prearmor = premit/(1-mit_vec(2)); % prearmor damage = premitigation damage / (1 - internal damage reduction)

end

 

fprintf('\t\tDamage type %s %s, premitigation %0.2f = count(attacks)*mean(unmitigated damage), estimated total prearmor damage: %0.2f = (premitigation damage) / (1 - type damage reduction)\n', damtype{1}, damtype{2}, premit, prearmor);

 

if ~isnan(prearmor)

if strcmp(damtype{1}, 'melee') && strcmp(damtype{2}, 'kinetic')

% dealing with melee kinetic damage

rk_miss_vec(end+1) = base_miss;

rk_vec(end+1) = prearmor;

elseif strcmp(damtype{1}, 'melee') && strcmp(damtype{2}, 'internal')

% else we must be dealing with melee internal damage

% if this value is ever nonzero that would be cause for

% serious alarm

ri_miss_vec(end+1) = base_miss;

ri_vec(end+1) = prearmor;

elseif strcmp(damtype{1}, 'force') && strcmp(damtype{2}, 'kinetic')

fk_vec(end+1) = prearmor;

else

% catch-all... probably force internal damage

fi_vec(end+1) = prearmor;

end

fprintf('\t\tMitigation coefficient %0.2f, estimated by (observed total damage)/(prearmor estimated damage)\n',consvec(6)/prearmor);

mit_c_vec(end+1) = consvec(6)/prearmor; mit_weights(end+1) = prearmor;

end % end section where nonNaN values are written to DTPS vectors for printout later

 

end % end iteration through attacks of that fight

 

fprintf('Participants: \n');

for j = 1:size(participants, 2), fprintf('\t%s\n', participants{j}); end

 

duration = end_time - start_time;

pmdtps = [];

fprintf('Total duration %0.0f seconds\n', duration);

fprintf('Damage profile:\n');

for j = 1:size(rk_vec,2)

fprintf('\t%0.2f total damage/%0.2f PMDTPS of type {ranged kinetic} at %0.2f base miss chance\n', rk_vec(j), rk_vec(j)/duration, rk_miss_vec(j));

pmdtps(end+1) = rk_vec(j)/duration;

end

mean_mk_dodge_rate = sum(rk_miss_vec.*rk_vec)/sum(rk_vec);

fprintf('\t%0.2f mean dodge rate for melee kinetic damage when weighted by premitigation damage magnitude\n', mean_mk_dodge_rate);

 

for j = 1:size(ri_vec,2)

fprintf('\t%0.2f totdal damage/%0.2f PMDTPS of type {ranged internal} at %0.2f miss chance\n', ri_vec(j), rk_vec(j)/duration, ri_miss_vec(j));

pmdtps(end+1) = ri_vec(j)/duration;

end

mean_mi_dodge_rate = sum(ri_miss_vec.*ri_vec)/sum(ri_vec);

fprintf('\t%0.2f mean dodge rate for melee internal damage when weighted by premitigation damage magnitude\n', mean_mi_dodge_rate);

 

fprintf('\t%0.2f total damage/%0.2f PMDTPS of type {force kinetic}\n', sum(fk_vec), sum(fk_vec)/duration);

pmdtps(end+1) = sum(fk_vec)/duration;

fprintf('\t%0.2f total damage/%0.2f PMDTPS of type {force internal}\n', sum(fi_vec), sum(fi_vec)/duration);

pmdtps(end+1) = sum(fi_vec)/duration;

fprintf('\t%0.2f mean mitigation coefficient when weighted by damage weights, counting base miss chance as mitigation\n', sum(mit_c_vec.*mit_weights)/sum(mit_weights));

pmdtps(isnan(pmdtps)) = []; pmdtps = sum(pmdtps);

fprintf('\t%0.2f total PMDTPS\n', pmdtps);

 

%mit_c_vec(end+1) = consvec(6)/prearmor; mit_weights(end+1) = prearmor;

 

fprintf('Finished displaying data for fight #%d\n', i);

fprintf('\n');

 

pause;

end % end iteration through combat start event indicies

 

end % end function parser_handler

 

 

 

And it now gives output of this style:

 

Beginning parsing at 1327 with event [19:47:09.797] [@Alar'a] [@Alar'a] [] [Event {836045448945472}: EnterCombat {836045448945489}] ()
Ending parsing at 2497 with event [19:50:06.015] [@Alar'a] [@Alar'a] [] [Event {836045448945472}: ExitCombat {836045448945490}] ()
Attacks: 
NefraWhoBarstheWay->Alara:Twin Attack
	141 samples, 444496 total damage, 3152.45 average damage including avoidance events
	0.32 raw shield occurrence, 0.54 shield chance as proportion of nondefended attacks, 0.54 effective absorb as estimated by (1-mean(shielded))/mean(unmitigated)
	0.41 total avoid chance, estimated predefense miss chance 0.17 = count(avoided) / count(attacks) - (given defense chance)
	Damage type melee kinetic, premitigation 1064809.74 = count(attacks)*mean(unmitigated damage), estimated total prearmor damage: 1916849.21 = (premitigation damage) / (1 - type damage reduction)
	Mitigation coefficient 0.23, estimated by (observed total damage)/(prearmor estimated damage)
NefraWhoBarstheWay->Alara:Voice of the Masters (Any)
	13 samples, 23228 total damage, 1786.77 average damage including avoidance events
	0.00 raw shield occurrence, 0.00 shield chance as proportion of nondefended attacks, NaN effective absorb as estimated by (1-mean(shielded))/mean(unmitigated)
	0.08 total avoid chance, estimated predefense miss chance 0.00 = count(avoided) / count(attacks) - (given defense chance)
	Damage type force internal, premitigation 25163.67 = count(attacks)*mean(unmitigated damage), estimated total prearmor damage: 31066.26 = (premitigation damage) / (1 - type damage reduction)
	Mitigation coefficient 0.75, estimated by (observed total damage)/(prearmor estimated damage)
Participants: 
Alara
NefraWhoBarstheWay
Total duration 177 seconds
Damage profile:
1916849.21 total damage/10829.66 PMDTPS of type {ranged kinetic} at 0.17 base miss chance
0.17 mean dodge rate for melee kinetic damage when weighted by premitigation damage magnitude
NaN mean dodge rate for melee internal damage when weighted by premitigation damage magnitude
0.00 total damage/0.00 PMDTPS of type {force kinetic}
31066.26 total damage/175.52 PMDTPS of type {force internal}
0.24 mean mitigation coefficient when weighted by damage weights, counting base miss chance as mitigation
11005.17 total PMDTPS
Finished displaying data for fight #5

 

Breakdowns:

  • sometimes NaN values originate when very few samples of an attack are available and they were all shielded, e.g. 1 hit from Strike in the log and it was shielded. I tried to prevent NaN values from spreading downstream and it seems okay, but if you see 1 attack with a NaN value that's probably what's happening
  • reporting ranged internal damage seemed silly but I threw it in just to be consistent
  • doesn't handle player cooldowns or debuffs and never will. Actual DTPS values and mean mitigation coefficient values will almost always be higher than reported, for this reason.
  • As reported in issue 1, the parser is agnostic toward player's absorb%. This causes trouble when all instances of an attack are shielded; since abs ~ (1-mean(shielded))/mean(unmitigated) some values will appear as NaN if there are no unmitigated values to pull. I could have extended the script to take player abs but frankly it's not common enough to be a serious issue
  • melee attacks must have a raw dodge/miss rate exceeding a threshold, default 0.15. This means 1) if you have n melee attack whose dodge rate is less than 0.15, it'll be misclassified as Force 2) if you have a force attack which you consistently resist, it'll be misclassified as melee. I'm not interested in parsing through all your dodges/misses/parries/resists/deflects and determining what is really going on there
  • does a poor job of understanding things like in combat stealth. Combat is defined as the first instance of 'EnterCombat' event to any of the following events: <'ExitCombat', 'EnterCombat', 'Revive', 'Death'>
  • doesn't support innate boss fight recognition
  • doesn't yet support multilog parsing. The easiest way to get that set up would be to write another script that defines EnterCombat events as being the first instance in time that an EnterCombat event is encountered, EndCombat as the last instance of its occurrence, and then concatenates together the logs over time

Edited by MGNMTTRN
Link to comment
Share on other sites

this tool looks amazing! I'm sure once i start playing with it ill have some questions. I would think even one instance of the parse sating resist would be able to ensure the attack was force, likewise with other types of attacks. i think you can onlyparry a melee attack, deflect a ranged attack and so on. but im not sure.
Link to comment
Share on other sites

this tool looks amazing! I'm sure once i start playing with it ill have some questions. I would think even one instance of the parse sating resist would be able to ensure the attack was force, likewise with other types of attacks. i think you can onlyparry a melee attack, deflect a ranged attack and so on. but im not sure.

 

I'm fairly certain this is not, in fact, the case.

 

Or at least, not consistently.

 

For instance, from Hateful Entity last night:

01:19:20.143 Srs'bsns parries Hateful Entity's Shock, causing 1 threat.

 

Shock is very definitely a ranged attack, but is always parried in the log, rather than deflected.

 

I'll see about looking through logs from other fights when I get a chance to see if there's any kind of consistency.

Link to comment
Share on other sites

well, we dont really need to differentiate between melee and ranged do we? if we can separate melee/ranged versus force/tech, that would suffice. if we see a resist, its force/tech (but that is very rare). if we see a parry/deflect, then it is melee/ranged. if we see internal/elemental, then we know its force/tech. it might be impossible to figure out 90 versus 100% accuracy, even we have 10000 hits to get statistics from.

 

i have no idea what a miss comes from

Link to comment
Share on other sites

well, we dont really need to differentiate between melee and ranged do we? if we can separate melee/ranged versus force/tech, that would suffice. if we see a resist, its force/tech (but that is very rare). if we see a parry/deflect, then it is melee/ranged. if we see internal/elemental, then we know its force/tech. it might be impossible to figure out 90 versus 100% accuracy, even we have 10000 hits to get statistics from.

 

i have no idea what a miss comes from

 

We don't, really, no. Just making sure that we're not engineering any incorrect assumptions in to what looks to be an *awesome* tool.

 

I suspect you're right about 90 vs 100% accuracy. I don't think it's safe to assume that bosses behave like players - basic attacks at 90% accuracy and named attacks at 100% accuracy. Even were this to be categorically true, a parsing tool would still need to account for the wildly varying names of individual enemies' basic attacks (and deal with the fact that some are ambiguous, like the Hateful Entity Shock example I provided, which just makes everything even more unpleasant).

Link to comment
Share on other sites

I can confirm that "miss" is exactly the same as "parry", "deflect", "dodge" and so on. The difference entirely comes down to whether it is ranged or melee, what sort of weapon was used (not just melee vs ranged, but actually what weapon type), what weapon was the defender wielding, and whether or not the game felt like counting the accuracy side of the defense equation rather than the defense side. It's all very weird. And it gets weirder now that the game is giving us weapon attacks coming from weaponless sources (for example: Tyrans's "Shock" is a weapon attack that comes from his hand).

 

Any time I do log parsing, I basically lump "parry", "deflect", "dodge", "miss" and such all into the same bucket: defense of a weapon attack. "Resist" is the only special one, since that is the one and only designator of a force/tech miss. Even experimenting with a really low accuracy attacker, I have never seen the game record a force/tech miss as anything but a "resist".

 

Coming to dipstik's point, I frankly couldn't care less whether or not an attack is melee or ranged. All I care about is whether it is M/R or F/T. It's like not caring whether an attack is internal or elemental (though, hilariously I think that distinction still matters for watchman/annihilation spec). In my experience with attempting to do that sort of inference based on deflect vs resist, the results are actually pretty reliable. I can only think of two instances in the life of the game where that style of inference has come up incorrect: Thrasher's Swipe attack and Corrupter Zero's Sweeping Slash, both of which are force/tech but were initially thought to be melee/range.

 

Answering MGN's question from earlier… It would be fairly easy for me to include the absorb computation you requested in my script. In fact, I'm pretty sure it's already in there, I'm just not using the output. With that said, my script is incredibly crude compared to yours. It has the advantage of not needing the raw log file, and it generates some pretty health graphs and raw mathematica output (which is nice), but ultimately I think your script is in a better place. It should make ratio determination for the upcoming nightmare modes a lot easier.

Link to comment
Share on other sites

×
×
  • Create New...