Clarify damage calculation for Blade’s main hand and off hand
Hello!
I have noticed some unclear points regarding the damage calculation for the Blade’s main hand and off hand weapons, and I would like to kindly request some clarification.
From my understanding, the Blade’s auto attack animation cycle should proceed as follows:
-
Main hand damage
-
Main hand + Off hand damage
-
Repeat the cycle...
Could you please confirm if my understanding of the auto attack animation in the game is correct?
If that’s the case, auto-attack-worker.js calculates main and off hand damage alternately, which does not correspond with the in game mechanics.
// ../components/calculations/workers/auto-attack-worker.js
self.onmessage = function (event) {
/* Code omitted for brevity */
let leftHand = false;
/* Code omitted for brevity */
for (let i = 0; i < cycles; i++) {
/* Code omitted for brevity */
// It’ll always calculate the damage for each hand separately.
if (Context.player.job.id === 2246 && Context.player.equipment.offhand != null) {
leftHand = !leftHand;
}
const res = {
damage: getDamage(leftHand),
critical: (Context.attackFlags & Utils.ATTACK_FLAGS.CRITICAL) !== 0,
block: (Context.attackFlags & Utils.ATTACK_FLAGS.BLOCKING) !== 0,
miss: (Context.attackFlags & Utils.ATTACK_FLAGS.MISS) !== 0,
parry: (Context.attackFlags & Utils.ATTACK_FLAGS.PARRY) !== 0,
double: (Context.attackFlags & Utils.ATTACK_FLAGS.DOUBLE) !== 0,
afterDamageProps: Context.afterDamageProps
};
out.push(res);
}
self.postMessage(out);
};
I was thinking it might be helpful to adjust it so that it loops more like the auto attack animation in the game, something like this:
// ../flyff/flyffutils.js
export const HAND_FLAGS = {
RIGHT: 1 << 0,
LEFT: 1 << 1,
DUAL: (1 << 0) | (1 << 1)
}
// ../flyff/flyffdamagecalculator.js
/**
* Get the damage done by a simulated attack in the current context.
* @param isDualWield Whether the attack uses dual wield (attacking with both hands)
* @returns The total damage value
*/
export function getDamage(isDualWield) {
// Check for miss is the first thing
if (Context.settings.missingEnabled && !Context.isSkillAttack() && (Context.attackFlags & Utils.ATTACK_FLAGS.MAGIC) == 0) {
const hitResult = checkHitRate();
if (hitResult != 0) {
Context.attackFlags |= hitResult;
return 0;
}
}
let damage = 0;
let handFlag = Utils.HAND_FLAGS.RIGHT;
if (isDualWield) {
handFlag = Utils.HAND_FLAGS.DUAL;
}
for (let flag = 0x01; flag <= 0x02; ++flag) {
if (handFlag & flag) {
damage += calcDamage(flag == Utils.HAND_FLAGS.LEFT);
}
}
return damage;
}
// Change to calcDamage(leftHand)
function calcDamage(leftHand) {
/* ... */
}
// ../components/calculations/workers/auto-attack-worker.js
let isDualWield = false;
const res = {
damage: getDamage(isDualWield),
critical: (Context.attackFlags & Utils.ATTACK_FLAGS.CRITICAL) !== 0,
block: (Context.attackFlags & Utils.ATTACK_FLAGS.BLOCKING) !== 0,
miss: (Context.attackFlags & Utils.ATTACK_FLAGS.MISS) !== 0,
parry: (Context.attackFlags & Utils.ATTACK_FLAGS.PARRY) !== 0,
double: (Context.attackFlags & Utils.ATTACK_FLAGS.DOUBLE) !== 0,
afterDamageProps: Context.afterDamageProps
};
if (Context.player.job.id === 2246 && Context.player.equipment.offhand != null) {
isDualWield = !isDualWield;
}
I noticed that since getDamage() includes the logic for checking hit rate, it can cause situations where the two hands don't land their hits at the same time.
It might be a good idea to separate the hit rate calculation to more accurately reflect how damage is actually handled in the game.
Another thing I’m not sure about is whether the icon only shows up when both hands crit or block at the same time, it doesn't appear with just one hand.
Maybe other options could work better, looking forward to any feedback.
Thank you!
And the current check for the class id is incorrect, so instead of alternating damage like it’s supposed to, it’s only calculating the main-hand damage right now.
// The jobId property doesn’t exist.
if (Context.player.jobId === 2246 && Context.player.equipment.offhand != null) {
leftHand = !leftHand;
}
it should be:
if (Context.player.job.id === 2246 && Context.player.equipment.offhand != null) {
leftHand = !leftHand;
}
I was thinking that it might make sense to calculate DPS by dividing the total actual damage dealt by the time it actually took to land the hits.
This could give us numbers that better match what really happens in game.
For example:
// ../flyff/flyffutils.js
export function getFormatNumber(num) {
if (num >= 1_000_000) {
return (num / 1_000_000).toFixed(3).replace(/\.?0+$/, '') + 'M';
} else if (num >= 1_000) {
return (num / 1_000).toFixed(3).replace(/\.?0+$/, '') + 'k';
} else {
return num.toString();
}
}
let totalDamage = 0;
for (let i = 0; i < cycles; i++) {
totalDamage += damage;
}
// The number of frames consumed, including delay frames, in one auto attack cycle for each weapon type.
const frames = framesPerAutoAttackCycle[Context.player.mainhand.subcategory];
// The number of attacks performed by the weapon within a single auto attack cycle.
const hits = hitsPerAutoAttackCycle[Context.player.mainhand.subcategory];
// Hits Per Second
let hps = (30 / frames) * hits * Context.player.getAttackSpeed();
// Total attack duration in seconds.
let totalSeconds = cycles / hps;
// Damage Per Second
let dps = Utils.getFormatNumber(totalDamage / totalSeconds);