Seeing that there is at least one Matlab user in there. I coded a function that proceeds through a GCD. It is not perfect (the numbers in the data are wrong for example and the function has not been fully tested) but here it is for discussion.
Main script where you put your rotation or priorities
% Enter Stats Into Script %
WD = 47;
Main = 505; % Strength for us Monks
Det = 324;
Crit = 484;
Speed = 352;
Weapon_Delay = 2.72; % Enter 0 for Casters. Don't care about casters!
% Calculate DPS Statistics %
Potency = ( WD*0.2714745 + Main*0.1006032 + (Det-202)*0.0241327 + WD*Main*0.0036167 + WD*(Det-202)*0.0010800 - 1 ) / 100;
Autoattack = ( WD*0.2714745 + Main*0.1006032 + (Det-202)*0.0241327 + WD*Main*0.0036167 + WD*(Det-202)*0.0022597 - 1 ) / 3;
Crit_Rate = Crit*0.0697 - 18.437;
Speed_Mod = 2610/(2951-Speed);
% Initialize the thing
res.dd = 0;
res.tp = 0;
res.dur = 0;
res.stance.val = 1;
res.stance.dur = 10;
res.gl.val = 3;
res.gl.dur = 12;
res.dk = 15;
res.twin = 15;
res.demo.dur = 0;
res.demo.tickDur = 0;
res.demo.tickDam = 0;
res.tod.dur = 0;
res.tod.tickDur = 0;
res.tod.tickDam = 0;
res.fracture.dur = 0;
res.fracture.tickDur = 0;
res.fracture.tickDam = 0;
res.pb.dur = 0;
res.pb.cd = 0;
res.bfb.dur = 0;
res.bfb.cd = 0;
res.ir.dur = 0;
res.ir.cd = 0;
res.howl.cd = 0;
res.steel.cd = 0;
res.aa.tickDur = Weapon_Delay;
options.gcd = 2.5 / Speed_Mod;
options.oGcdPriority = [];
options.critRate = Crit_Rate;
options.attPot = Potency;
options.aa.delay = Weapon_Delay;
options.aa.pot = Autoattack;
% Rotation
% rotation = {'dk', 'twin', 'demo', 'boot', 'true', 'snap', 'dk', 'twin', 'snap', 'boot', 'true', 'demo', 'dk', 'twin', 'snap', 'boot', 'true', 'snap'};
rotation = {'dk', 'twin', 'demo', 'boot', 'true', 'snap'};
counter = 0;
% return
while res.dur <= 10*60
counter = counter + 1;
counter = counter - floor(counter/length(rotation))*length(rotation);
if counter == 0, counter = length(rotation); end
res = proceedGcd(rotation{counter}, 'ideal', 'none', res, options);
end
figure, plot(res.dur, res.dd), grid on
The function that proceeds through a GCD
function res = proceedGcd(skillUsed, position, oGcdUsed, oldRes, options);
%
% function res = proceedGcd(skillUsed, position, oGcdUsed, oldRes, options);
%
% Proceeds through one GCD while using skillUsed ability.
% Assumptions:
% - no TP constraints (could be coded if I know exactly how tp regen works)
% - always use one and only one skill
% - one oGCD at a time max
% - always AA whenever possible (not sure how it could be coded)
%
% res and oldRes have the same structure.
%
% Inputs:
% - skillUsed: the skill used during the current GCD
% 'boot', 'true', 'demo', 'dk', 'twin', 'snap', 'tod' or 'fracture'
% - position: 'flank', 'rear', 'ideal' or 'nonIdeal'
% - oGcdUsed: 'auto', 'BfB' (1), 'IR' (2), 'Howl' (3), 'Steel' (4) or 'none'
% If 'auto' is used,
% if options.oGcdPriority is empty, it will used BfB > IR > Howl > Steel in that order of priority
% if options.oGcdPriority is a vector, it will use the priority set
% - oldRes.
% - dd: damage done over the whole fight;
% - tp: tp used over the whole fight;
% - dur: total duration [s];
% - ddOne: damage done during the GCD;
% - tpOne: tp used during the GCD;
% - durOne: duration of the GCD (maybe more than than the GCD actually if the animation delays are more than the GCD);
% - stance.val: current stance (0: no stance; 1: opo-opo, 2: raptor; 3 coeurl)
% - stance.dur: remaining duration of the stance [s];
% - gl.val: number of GL stacks;
% - gl.dur: remaining duration of GL stacks [s];
% - dk: duration left to dk debuff (0: not active) [s];
% - twin: duration left to twin buff (0: not active) [s];
% - demo.dur: remaining duration of Demo [s];
% - demo.tickDur: remaining duration until next tick [s];
% - demo.tickDam: current tick potency;
% - tod.dur: remaining duration of ToD [s];
% - tod.tickDur: remaining duration until next tick [s];
% - tod.tickDam: current tick potency;
% - fracture.dur: remaining duration of Fracture [s];
% - fracture.tickDur: remaining duration until next tick [s];
% - fracture.tickDam: current tick potency;
% - pb.dur: duration left on PB (0: not active) [s];
% - pb.cd: duration left on PB CD [s];
% - bfb.dur: duration left to BfB buff (0: not active) [s];
% - bfb.cd: duration left on BfB CD [s];
% - ir.dur: duration left to IR buff (0: not active) [s];
% - ir.cd: duration left on IR CD [s];
% - steel.cd: duration left on Steel CD [s];
% - howl.cd: duration left on Howl CD [s];
% - aa.tickDur: duration left on AA [s];
% - options.
% - gcd: gcd duration without GL but counting for SkS [s];
% - oGcdPriority: examples
% [1 2 3 4] will use as priority BfB > IR > Howl > Steel
% [1 4 3] will use as priority BfB > Steel > Howl and will not use IR
% - critRate: critical rate (without IR) [%] that is between 0 and 100;
% - attPot: attack Potency;
% (WD*0.2714745 + Main*0.1006032 + (Det-202)*0.0241327 + WD*Main*0.0036167 + WD*(Det-202)*0.0010800 - 1) / 100;
% - aa.delay: duration between two auto attacks [s];
% - aa.pot: potency of AA;
% (WD*0.2714745 + Main*0.1006032 + (Det-202)*0.0241327 + WD*Main*0.0036167 + WD*(Det-202)*0.0022597 - 1) / 3;
%
% Lewena Yaeger, Monk on Moogle, 07 May 2014
% Developped under Matalb 2011b
%
%% Verifying inputs
switch skillUsed
case {'boot', 'true', 'demo', 'dk', 'twin', 'snap', 'tod', 'fracture'}
% Do nothing it is OK.
otherwise
error('Not a possible skill.');
end
switch position
case {'flank', 'rear', 'ideal', 'nonIdeal'}
% Do nothing it is OK.
otherwise
error('Not a position.');
end
switch oGcdUsed
case {'auto', 'BfB', 'IR', 'Howl', 'Steel', 'none'}
% Do nothing it is OK.
otherwise
error('Not an oGCD skill.');
end
if oldRes.pb.dur <= 0
switch skillUsed
case {'true', 'twin'}
if oldRes.stance.val ~= 2
error('Not in the right stance.');
end
case {'demo', 'snap'}
if oldRes.stance.val ~= 3
error('Not in the right stance.');
end
end
end
if isempty(options.oGcdPriority)
% That's good
else
if any(options.oGcdPriority ~= round(options.oGcdPriority))
error('I need an integer vector.');
end
if min(options.oGcdPriority) < 1 || max(options.oGcdPriority) > 4
error('What the fuck?');
end
end
%% Data
tickDur = 3; % Duration between two dot ticks [s]
animDelay = 1; % Animation delay [s]
data.boot.rear.pot = 150;
data.boot.flank.pot = 100;
data.boot.tp = 60;
data.boot.animDelay = animDelay;
data.true.rear.pot = 190;
data.true.flank.pot = 190;
data.true.tp = 50;
data.true.animDelay = animDelay;
data.dk.flank.pot = 150;
data.dk.rear.pot = 150;
data.dk.debuff = 0.10;
data.dk.dur = 15;
data.dk.tp = 60;
data.dk.animDelay = animDelay;
data.twin.flank.pot = 140;
data.twin.rear.pot = 140;
data.twin.buff = 0.15;
data.twin.dur = 15;
data.twin.tp = 60;
data.twin.animDelay = animDelay;
data.snap.flank.pot = 180;
data.snap.rear.pot = 180;
data.snap.tp = 50;
data.snap.animDelay = animDelay;
data.demo.rear.pot = 60;
data.demo.rear.tickPot = 30;
data.demo.flank.pot = 60;
data.demo.flank.tickPot = 30;
data.demo.dur = 18;
data.demo.tickDur = tickDur;
data.demo.tp = 130;
data.demo.animDelay = animDelay;
data.tod.pot = 20;
data.tod.tickPot = 25;
data.tod.dur = 30;
data.tod.tickDur = tickDur;
data.tod.tp = 80;
data.tod.animDelay = animDelay;
data.fract.pot = 40;
data.fract.tickPot = 30;
data.fract.dur = 18;
data.fract.tickDur = tickDur;
data.fract.tp = 80;
data.fract.animDelay = animDelay;
data.bfb.val = 0.1;
data.bfb.dur = 20;
data.bfb.cd = 80;
data.bfb.tp = 0;
data.bfb.animDelay = animDelay;
data.ir.val = 0.3;
data.ir.dur = 15;
data.ir.cd = 60;
data.ir.tp = 0;
data.ir.animDelay = animDelay;
data.howl.pot = 170;
data.howl.cd = 60;
data.howl.tp = 0;
data.howl.animDelay = animDelay;
data.steel.pot = 150;
data.steel.cd = 40;
data.steel.tp = 0;
data.steel.animDelay = animDelay;
data.gl.dam = 0.09;
data.gl.sks = 0.05;
data.gl.dur = 12;
data.crit.val = 0.5;
%% Proceed throught the GCD
res = oldRes; % Initalization
if isempty(options.oGcdPriority) % Setting the oGcd skills priority if empty
options.oGcdPriority = [1 2 3 4];
end
% Compute factor from buff, debuff and crit rate
current.buff = 1 + (oldRes.gl.dur > 0) * oldRes.gl.val * data.gl.dam + (oldRes.twin > 0) * data.twin.buff + (oldRes.bfb.dur > 0) * data.bfb.val;
current.debuff = 1 + (oldRes.dk > 0) * data.dk.debuff;
current.crit = 1 + data.crit.val * min(1, options.critRate/100 + data.ir.val*(oldRes.ir.dur > 0));
% Skill used
switch skillUsed
case 'boot'
switch position
case {'rear', 'ideal'}
res.ddOne = data.boot.rear.pot * options.attPot * current.buff * (1 + data.crit.val) * current.debuff;
otherwise
res.ddOne = data.boot.flank.pot * options.attPot * current.buff * current.crit * current.debuff;
end
res.tpOne = data.boot.tp;
res.durOne = data.boot.animDelay;
res.stance.val = 2;
res.stance.dur = 10;
case 'true'
switch position
case {'rear', 'ideal'}
res.ddOne = data.true.rear.pot * options.attPot * current.buff * current.crit * current.debuff;
otherwise
res.ddOne = data.true.flank.pot * options.attPot * current.buff * current.crit * current.debuff;
end
res.tpOne = data.true.tp;
res.durOne = data.true.animDelay;
res.stance.val = 3;
res.stance.dur = 10;
case 'demo'
switch position
case {'rear', 'ideal'}
res.ddOne = data.demo.rear.pot * options.attPot * current.buff * current.crit;
res.demo.tickDam = data.demo.rear.tickPot * options.attPot * current.buff * current.crit;
otherwise
res.ddOne = data.demo.flank.pot * options.attPot * current.buff * current.crit;
res.demo.tickDam = data.demo.flank.tickPot * options.attPot * current.buff * current.crit;
end
res.gl.val = min(res.gl.val+1, 3);
res.gl.dur = data.gl.dur;
res.demo.dur = data.demo.dur;
res.demo.tickDur = data.demo.tickDur;
res.tpOne = data.demo.tp;
res.durOne = data.demo.animDelay;
res.stance.val = 1;
res.stance.dur = 10;
case 'dk'
switch position
case {'flank', 'ideal'}
res.ddOne = data.dk.flank.pot * options.attPot * current.buff * current.crit * current.debuff;
if oldRes.stance.val == 1
res.dk = data.dk.dur;
end
otherwise
res.ddOne = data.dk.rear.pot * options.attPot * current.buff * current.crit * current.debuff;
end
res.tpOne = data.dk.tp;
res.durOne = data.dk.animDelay;
res.stance.val = 2;
res.stance.dur = 10;
case 'twin'
switch position
case {'flank', 'ideal'}
res.ddOne = data.twin.flank.pot * options.attPot * current.buff * current.crit * current.debuff;
otherwise
res.ddOne = data.twin.rear.pot * options.attPot * current.buff * current.crit * current.debuff;
end
res.tpOne = data.twin.tp;
res.durOne = data.twin.animDelay;
res.twin = data.twin.dur;
res.stance.val = 3;
res.stance.dur = 10;
case 'snap'
switch position
case {'flank', 'ideal'}
res.ddOne = data.snap.flank.pot * options.attPot * current.buff * current.crit * current.debuff;
otherwise
res.ddOne = data.snap.rear.pot * options.attPot * current.buff * current.crit * current.debuff;
end
res.gl.val = min(res.gl.val+1, 3);
res.gl.dur = data.gl.dur;
res.tpOne = data.snap.tp;
res.durOne = data.snap.animDelay;
res.stance.val = 1;
res.stance.dur = 10;
case 'tod'
res.ddOne = data.tod.pot * options.attPot * current.buff * current.crit;
res.tpOne = data.tod.tp;
res.durOne = data.tod.animDelay;
res.tod.dur = data.tod.dur;
res.tod.tickDur = data.tod.tickDur;
res.tod.tickDam = data.tod.tickPot * options.attPot * current.buff * current.crit;
case 'fracture'
res.ddOne = data.fract.pot * options.attPot * current.buff * current.crit;
res.tpOne = data.fract.tp;
res.durOne = data.fract.animDelay;
res.fracture.dur = data.fract.dur;
res.fracture.tickDur = data.fract.tickDur;
res.fracture.tickDam = data.fract.tickPot * options.attPot * current.buff * current.crit;
end
skill = res; % Save for use during AA. Corresponds to the beginning of the skill use
% Re-compute factor from buff, debuff and crit rate for oGCD
current.buff = 1 + (res.gl.dur > res.durOne) * res.gl.val * data.gl.dam + (res.twin > res.durOne) * data.twin.buff + (res.bfb.dur > res.durOne) * data.bfb.val;
current.debuff = 1 + (res.dk > res.durOne) * data.dk.debuff;
current.crit = 1 + data.crit.val * min(1, options.critRate/100 + data.ir.val*(res.ir.dur > res.durOne));
% oGCD used
switch oGcdUsed
case 'Bfb'
if (res.bfb.cd < res.durOne)
res.bfb.dur = data.bfb.dur;
res.bfb.cd = data.bfb.cd;
res.tpOne = res.tpOne + data.bfb.tp;
res.durOne = res.durOne + data.bfb.animDelay;
else
error('BfB not up.');
end
case 'IR'
if (res.ir.cd < res.durOne)
res.ir.dur = data.ir.dur;
res.ir.cd = data.ir.cd;
res.tpOne = res.tpOne + data.ir.tp;
res.durOne = res.durOne + data.ir.animDelay;
else
error('IR not up.');
end
case 'Howl'
if (res.howl.cd < res.durOne)
res.howl.cd = data.howl.cd;
res.ddOne = res.ddOne + data.howl.pot * options.attPot * current.buff * current.crit * current.debuff;
res.tpOne = res.tpOne + data.howl.tp;
res.durOne = res.durOne + data.howl.animDelay;
else
error('Howl not up.');
end
case 'Steel'
if (res.steel.cd < res.durOne)
res.steel.cd = data.steel.cd;
res.ddOne = res.ddOne + data.steel.pot * options.attPot * current.buff * current.crit * current.debuff;
res.tpOne = res.tpOne + data.steel.tp;
res.durOne = res.durOne + data.steel.animDelay;
else
error('Steel not up.');
end
case 'none'
% Do nothing
end
% Compute the whole duration
current.gcd = options.gcd * (1-oldRes.gl.val*data.gl.sks);
res.durOne = max(current.gcd, res.durOne);
% Add dot ticks
if res.demo.dur > 0 % Dot is active
if res.demo.tickDur <= res.durOne % Dot ticks at least one time
tickNb = 1 + floor((res.demo.dur-res.demo.tickDur)/data.demo.tickDur); % Number of ticks during the current GCD
res.ddOne = res.ddOne + res.demo.tickDam * tickNb;
res.demo.tickDur = data.demo.tickDur - (res.durOne - (res.demo.tickDur + (tickNb-1)*data.demo.tickDur));
else
res.demo.tickDur = res.demo.tickDur - res.durOne;
end
res.demo.dur = res.demo.dur - res.durOne;
if res.demo.tickDur <= 0
res.demo.tickDur = data.demo.tickDur;
end
end
if res.demo.dur <= 0
res.demo.dur = 0;
res.demo.tickDur = 0;
res.demo.tickDam = 0;
end
if res.tod.dur > 0 % Dot is active
if res.tod.tickDur < res.durOne % Dot ticks at least one time
tickNb = 1 + floor((res.tod.dur-res.tod.tickDur)/data.tod.tickDur); % Number of ticks during the current GCD
res.ddOne = res.ddOne + res.tod.tickDam * tickNb;
res.tod.tickDur = data.tod.tickDur - (res.durOne - (res.tod.tickDur + (tickNb-1)*data.tod.tickDur));
else
res.tod.tickDur = res.tod.tickDur - res.durOne;
end
res.tod.dur = res.tod.dur - res.durOne;
if res.tod.tickDur <= 0
res.tod.tickDur = data.tod.tickDur;
end
end
if res.tod.dur <= 0
res.tod.dur = 0;
res.tod.tickDur = 0;
res.tod.tickDam = 0;
end
if res.fracture.dur > 0 % Dot is active
if res.fracture.tickDur < res.durOne % Dot ticks at least one time
tickNb = 1 + floor((res.fracture.dur-res.fracture.tickDur)/data.fract.tickDur); % Number of ticks during the current GCD
res.ddOne = res.ddOne + res.fracture.tickDam * tickNb;
res.fracture.tickDur = data.fract.tickDur - (res.durOne - (res.fracture.tickDur + (tickNb-1)*data.fract.tickDur));
else
res.fracture.tickDur = res.fracture.tickDur - res.durOne;
end
res.fracture.dur = res.fracture.dur - res.durOne;
if res.fracture.tickDur <= 0
res.fracture.tickDur = data.fract.tickDur;
end
end
if res.fracture.dur <= 0
res.fracture.dur = 0;
res.fracture.tickDur = 0;
res.fracture.tickDam = 0;
end
% Add AA (much more complex than dots)
if res.aa.tickDur < res.durOne
tickNb = 1 + floor((res.durOne-res.aa.tickDur)/options.aa.delay);
for idx = 1:tickNb
time = res.aa.tickDur+(idx-1)*options.aa.delay; % Time of the tick in the GCD frame
if time >= skill.durOne % Tick is after oGCD use
current.buff = 1 + (res.gl.dur > time) * res.gl.val * data.gl.dam + (res.twin > time) * data.twin.buff + (res.bfb.dur > time-skill.durOne) * data.bfb.val;
current.debuff = 1 + (res.dk > time) * data.dk.debuff;
current.crit = 1 + data.crit.val * min(1, options.critRate/100 + data.ir.val*(res.ir.dur > time-skill.durOne));
else % Tick is between skill and oGCD
current.buff = 1 + (res.gl.dur > res.aa.tickDur) * res.gl.val * data.gl.dam + (res.twin > res.aa.tickDur) * data.twin.buff + (res.bfb.dur > time) * data.bfb.val;
current.debuff = 1 + (res.dk > res.aa.tickDur) * data.dk.debuff;
current.crit = 1 + data.crit.val * min(1, options.critRate/100 + data.ir.val*(res.ir.dur > time));
end
res.ddOne = res.ddOne + options.aa.pot * current.buff * current.crit * current.debuff;
end
res.aa.tickDur = options.aa.delay - (res.durOne - (res.aa.tickDur+tickNb*options.aa.delay));
else
res.aa.tickDur = res.aa.tickDur - res.durOne;
end
% Update results (especially durations)
res.dd = [res.dd res.dd(end)+res.ddOne];
res.tp = [res.tp res.tp(end)+res.tpOne];
res.dur = [res.dur res.dur(end)+res.durOne];
res.gl.dur = max(0, res.gl.dur - res.durOne);
res.gl.val = (res.gl.dur > 0) * res.gl.val;
res.dk = max(0, res.dk - res.durOne);
res.twin = max(0, res.twin - res.durOne);
res.stance.dur = max(res.stance.dur - res.durOne);
res.stance.val = (res.stance.dur > 0) * res.stance.val;
res.pb.dur = max(0, res.pb.dur - res.durOne);
res.pb.cd = max(0, res.pb.cd - res.durOne);
res.bfb.dur = max(0, res.bfb.dur - res.durOne);
res.bfb.cd = max(0, res.bfb.cd - res.durOne);
res.ir.dur = max(0, res.ir.dur - res.durOne);
res.ir.cd = max(0, res.ir.cd - res.durOne);
res.howl.cd = max(0, res.howl.cd - res.durOne);
res.steel.cd = max(0, res.steel.cd - res.durOne);