dipstik Posted February 19, 2014 Share Posted February 19, 2014 (edited) 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 February 19, 2014 by dipstik Link to comment Share on other sites More sharing options...
KeyboardNinja Posted February 19, 2014 Share Posted February 19, 2014 (edited) 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 February 19, 2014 by KeyboardNinja Link to comment Share on other sites More sharing options...
MGNMTTRN Posted February 21, 2014 Share Posted February 21, 2014 (edited) 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 February 21, 2014 by MGNMTTRN Link to comment Share on other sites More sharing options...
dipstik Posted February 21, 2014 Author Share Posted February 21, 2014 this codes looks like it could get a pretty good number for accuracy of each attack too. thanks! Link to comment Share on other sites More sharing options...
MGNMTTRN Posted February 21, 2014 Share Posted February 21, 2014 (edited) 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 February 21, 2014 by MGNMTTRN Link to comment Share on other sites More sharing options...
dipstik Posted February 28, 2014 Author Share Posted February 28, 2014 (edited) 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 March 3, 2014 by dipstik Link to comment Share on other sites More sharing options...
MGNMTTRN Posted March 9, 2014 Share Posted March 9, 2014 (edited) 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 March 9, 2014 by MGNMTTRN Link to comment Share on other sites More sharing options...
KareBarey Posted March 9, 2014 Share Posted March 9, 2014 Is there an English translation to this? Link to comment Share on other sites More sharing options...
dipstik Posted March 9, 2014 Author Share Posted March 9, 2014 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 More sharing options...
Omophorus Posted March 9, 2014 Share Posted March 9, 2014 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 More sharing options...
dipstik Posted March 10, 2014 Author Share Posted March 10, 2014 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 More sharing options...
Omophorus Posted March 10, 2014 Share Posted March 10, 2014 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 More sharing options...
KeyboardNinja Posted March 10, 2014 Share Posted March 10, 2014 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 More sharing options...
Recommended Posts