From c22fd24b18a448d535429427b65047a5b3437bc7 Mon Sep 17 00:00:00 2001 From: MobiusDev <8391001+MobiusDevelopment@users.noreply.github.com> Date: Sat, 23 Sep 2017 15:25:27 +0000 Subject: [PATCH] Proper attack hit delay. Contributed by Liamxroy. --- .../AdminFightCalculator.java | 8 +-- .../gameserver/model/actor/L2Character.java | 17 +++-- .../gameserver/model/stats/Formulas.java | 70 +++++++++++++------ .../AdminFightCalculator.java | 8 +-- .../gameserver/model/actor/L2Character.java | 17 +++-- .../gameserver/model/stats/Formulas.java | 70 +++++++++++++------ .../AdminFightCalculator.java | 8 +-- .../gameserver/model/actor/L2Character.java | 17 +++-- .../gameserver/model/stats/Formulas.java | 70 +++++++++++++------ .../AdminFightCalculator.java | 8 +-- .../gameserver/model/actor/L2Character.java | 17 +++-- .../gameserver/model/stats/Formulas.java | 70 +++++++++++++------ 12 files changed, 240 insertions(+), 140 deletions(-) diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java index c71e654d78..7aeed1c607 100644 --- a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java +++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java @@ -204,8 +204,8 @@ public class AdminFightCalculator implements IAdminCommandHandler double dmg2 = 0; // ATTACK speed in milliseconds - int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1, null); - int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2, null); + int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1.getPAtkSpd()); + int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2.getPAtkSpd()); // number of ATTACK per 100 seconds sAtk1 = 100000 / sAtk1; sAtk2 = 100000 / sAtk2; @@ -237,7 +237,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss1) { - final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, 0, _shld1, _crit1, false); + final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, _shld1, _crit1, false); dmg1 += _dmg1; npc1.abortAttack(); } @@ -270,7 +270,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss2) { - final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, 0, _shld2, _crit2, false); + final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, _shld2, _crit2, false); dmg2 += _dmg2; npc2.abortAttack(); } diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9e48674d11..7ba91ec156 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -981,7 +981,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); // Check if attacker's weapon can attack if (weaponItem != null) @@ -1121,7 +1120,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe } final boolean wasSSCharged = isChargedShot(ShotType.SOULSHOTS) || isChargedShot(ShotType.BLESSED_SOULSHOTS); // Verify if soulshots are charged. - final int timeAtk = Formulas.calculateTimeBetweenAttacks(this, weaponType); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) + final int timeAtk = Formulas.calculateTimeBetweenAttacks(getPAtkSpd()); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) final int timeToHit = timeAtk / 2; // the hit is calculated to happen halfway to the animation - might need further tuning e.g. in bow case final int ssGrade = (weaponItem != null) ? weaponItem.getItemGrade().ordinal() : 0; final Attack attack = new Attack(this, target, wasSSCharged, ssGrade); @@ -1264,7 +1263,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); // Bows Ranged Damage Formula (Damage gradually decreases when 60% or lower than full hit range, and increases when 60% or higher). // full hit range is 500 which is the base bow range, and the 60% of this is 800. @@ -1337,7 +1336,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 1 - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); damage1 /= 2; } @@ -1351,7 +1350,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit2 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 2 - damage2 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld2, crit2, attack.hasSoulshot()); + damage2 = (int) Formulas.calcAutoAttackDamage(this, target, shld2, crit2, attack.hasSoulshot()); damage2 /= 2; } @@ -1382,7 +1381,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1402,7 +1401,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1452,7 +1451,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); } // Create a new hit task with Medium priority @@ -1477,7 +1476,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); } ThreadPoolManager.schedule(new HitTask(this, surroundTarget, damage, crit, miss, attack.hasSoulshot(), shld), sAtk); diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java index 6af3a0518a..a63f684e46 100644 --- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -404,14 +404,13 @@ public final class Formulas } /** - * Calculate how many milliseconds takes to make an attack. - * @param rate - * @return the delay between each attack with a minimum of 50 milliseconds (max. 10000 attack speed) + * @param attackSpeed the attack speed of the Creature. + * @return {@code 500000 / attackSpeed}. */ - public static int calcPAtkSpd(double rate) + public static int calculateTimeBetweenAttacks(int attackSpeed) { // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second. - return (int) Math.max(50, (500000 / rate)); + return Math.max(50, (500000 / attackSpeed)); } /** @@ -1383,13 +1382,12 @@ public final class Formulas * Calculated damage caused by ATTACK of attacker on target. * @param attacker player or NPC that makes ATTACK * @param target player or NPC, target of ATTACK - * @param power * @param shld * @param crit if the ATTACK have critical success * @param ss if weapon item was charged by soulshot * @return */ - public static double calcAutoAttackDamage(L2Character attacker, L2Character target, double power, byte shld, boolean crit, boolean ss) + public static double calcAutoAttackDamage(L2Character attacker, L2Character target, byte shld, boolean crit, boolean ss) { final double distance = attacker.calculateDistance(target, true, false); @@ -1504,33 +1502,61 @@ public final class Formulas } /** - * @param activeChar - * @param attackType the type of attack. Different attack types have different time between attacks. - * @return the Attack Speed of the L2Character (delay (in milliseconds) before next attack). + * @param totalAttackTime the time needed to make a full attack. + * @param attackType the weapon type used for attack. + * @param twoHanded if the weapon is two handed. + * @param secondHit calculates the second hit for dual attacks. + * @return the time required from the start of the attack until you hit the target. */ - public static int calculateTimeBetweenAttacks(L2Character activeChar, WeaponType attackType) + public static int calculateTimeToHit(int totalAttackTime, WeaponType attackType, boolean twoHanded, boolean secondHit) { + // Gracia Final Retail confirmed: + // Time to damage (1 hand, 1 hit): TotalBasicAttackTime * 0.644 + // Time to damage (2 hand, 1 hit): TotalBasicAttackTime * 0.735 + // Time to damage (2 hand, 2 hit): TotalBasicAttackTime * 0.2726 and TotalBasicAttackTime * 0.6 + // Time to damage (bow/xbow): TotalBasicAttackTime * 0.978 + + // Measured July 2016 by Nik. + // Due to retail packet delay, we are unable to gather too accurate results. Therefore the below formulas are based on original Gracia Final values. + // Any original values that appear higher than tested have been replaced with the tested values, because even with packet delay its obvious they are wrong. + // All other original values are compared with the test results and differences are considered to be too insignificant and mostly caused due to packet delay. switch (attackType) { case BOW: - { - return (1500 * 345) / activeChar.getPAtkSpd(); - } case CROSSBOW: case TWOHANDCROSSBOW: { - return (1200 * 345) / activeChar.getPAtkSpd(); + return (int) (totalAttackTime * 0.95); } - case DAGGER: + case DUALBLUNT: + case DUALDAGGER: + case DUAL: + case DUALFIST: { - // atkSpd /= 1.15; - break; + if (secondHit) + { + return (int) (totalAttackTime * 0.6); + } + + return (int) (totalAttackTime * 0.2726); + } + default: + { + if (twoHanded) + { + return (int) (totalAttackTime * 0.735); + } + + return (int) (totalAttackTime * 0.644); } } - - return calcPAtkSpd(activeChar.getPAtkSpd()); } + /** + * @param activeChar + * @param weapon + * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed} + */ public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon) { if (weapon == null) @@ -1549,9 +1575,9 @@ public final class Formulas } reuse *= activeChar.getStat().getWeaponReuseModifier(); - final double atkSpd = activeChar.getStat().getPAtkSpd(); + double atkSpd = activeChar.getStat().getPAtkSpd(); - return (int) ((reuse * 345) / atkSpd); // ((reuse * 312) / atkSpd)) for non ranged? + return (int) ((500000 + (333 * reuse)) / atkSpd); } public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit) diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java index c71e654d78..7aeed1c607 100644 --- a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java +++ b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java @@ -204,8 +204,8 @@ public class AdminFightCalculator implements IAdminCommandHandler double dmg2 = 0; // ATTACK speed in milliseconds - int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1, null); - int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2, null); + int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1.getPAtkSpd()); + int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2.getPAtkSpd()); // number of ATTACK per 100 seconds sAtk1 = 100000 / sAtk1; sAtk2 = 100000 / sAtk2; @@ -237,7 +237,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss1) { - final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, 0, _shld1, _crit1, false); + final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, _shld1, _crit1, false); dmg1 += _dmg1; npc1.abortAttack(); } @@ -270,7 +270,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss2) { - final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, 0, _shld2, _crit2, false); + final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, _shld2, _crit2, false); dmg2 += _dmg2; npc2.abortAttack(); } diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9a8e050673..bf268507a8 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -985,7 +985,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); // Check if attacker's weapon can attack if (weaponItem != null) @@ -1125,7 +1124,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe } final boolean wasSSCharged = isChargedShot(ShotType.SOULSHOTS) || isChargedShot(ShotType.BLESSED_SOULSHOTS); // Verify if soulshots are charged. - final int timeAtk = Formulas.calculateTimeBetweenAttacks(this, weaponType); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) + final int timeAtk = Formulas.calculateTimeBetweenAttacks(getPAtkSpd()); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) final int timeToHit = timeAtk / 2; // the hit is calculated to happen halfway to the animation - might need further tuning e.g. in bow case final int ssGrade = (weaponItem != null) ? weaponItem.getItemGrade().ordinal() : 0; final Attack attack = new Attack(this, target, wasSSCharged, ssGrade); @@ -1268,7 +1267,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); // Bows Ranged Damage Formula (Damage gradually decreases when 60% or lower than full hit range, and increases when 60% or higher). // full hit range is 500 which is the base bow range, and the 60% of this is 800. @@ -1341,7 +1340,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 1 - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); damage1 /= 2; } @@ -1355,7 +1354,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit2 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 2 - damage2 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld2, crit2, attack.hasSoulshot()); + damage2 = (int) Formulas.calcAutoAttackDamage(this, target, shld2, crit2, attack.hasSoulshot()); damage2 /= 2; } @@ -1386,7 +1385,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1406,7 +1405,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1456,7 +1455,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); } // Create a new hit task with Medium priority @@ -1481,7 +1480,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); } ThreadPoolManager.schedule(new HitTask(this, surroundTarget, damage, crit, miss, attack.hasSoulshot(), shld), sAtk); diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java index 6af3a0518a..a63f684e46 100644 --- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -404,14 +404,13 @@ public final class Formulas } /** - * Calculate how many milliseconds takes to make an attack. - * @param rate - * @return the delay between each attack with a minimum of 50 milliseconds (max. 10000 attack speed) + * @param attackSpeed the attack speed of the Creature. + * @return {@code 500000 / attackSpeed}. */ - public static int calcPAtkSpd(double rate) + public static int calculateTimeBetweenAttacks(int attackSpeed) { // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second. - return (int) Math.max(50, (500000 / rate)); + return Math.max(50, (500000 / attackSpeed)); } /** @@ -1383,13 +1382,12 @@ public final class Formulas * Calculated damage caused by ATTACK of attacker on target. * @param attacker player or NPC that makes ATTACK * @param target player or NPC, target of ATTACK - * @param power * @param shld * @param crit if the ATTACK have critical success * @param ss if weapon item was charged by soulshot * @return */ - public static double calcAutoAttackDamage(L2Character attacker, L2Character target, double power, byte shld, boolean crit, boolean ss) + public static double calcAutoAttackDamage(L2Character attacker, L2Character target, byte shld, boolean crit, boolean ss) { final double distance = attacker.calculateDistance(target, true, false); @@ -1504,33 +1502,61 @@ public final class Formulas } /** - * @param activeChar - * @param attackType the type of attack. Different attack types have different time between attacks. - * @return the Attack Speed of the L2Character (delay (in milliseconds) before next attack). + * @param totalAttackTime the time needed to make a full attack. + * @param attackType the weapon type used for attack. + * @param twoHanded if the weapon is two handed. + * @param secondHit calculates the second hit for dual attacks. + * @return the time required from the start of the attack until you hit the target. */ - public static int calculateTimeBetweenAttacks(L2Character activeChar, WeaponType attackType) + public static int calculateTimeToHit(int totalAttackTime, WeaponType attackType, boolean twoHanded, boolean secondHit) { + // Gracia Final Retail confirmed: + // Time to damage (1 hand, 1 hit): TotalBasicAttackTime * 0.644 + // Time to damage (2 hand, 1 hit): TotalBasicAttackTime * 0.735 + // Time to damage (2 hand, 2 hit): TotalBasicAttackTime * 0.2726 and TotalBasicAttackTime * 0.6 + // Time to damage (bow/xbow): TotalBasicAttackTime * 0.978 + + // Measured July 2016 by Nik. + // Due to retail packet delay, we are unable to gather too accurate results. Therefore the below formulas are based on original Gracia Final values. + // Any original values that appear higher than tested have been replaced with the tested values, because even with packet delay its obvious they are wrong. + // All other original values are compared with the test results and differences are considered to be too insignificant and mostly caused due to packet delay. switch (attackType) { case BOW: - { - return (1500 * 345) / activeChar.getPAtkSpd(); - } case CROSSBOW: case TWOHANDCROSSBOW: { - return (1200 * 345) / activeChar.getPAtkSpd(); + return (int) (totalAttackTime * 0.95); } - case DAGGER: + case DUALBLUNT: + case DUALDAGGER: + case DUAL: + case DUALFIST: { - // atkSpd /= 1.15; - break; + if (secondHit) + { + return (int) (totalAttackTime * 0.6); + } + + return (int) (totalAttackTime * 0.2726); + } + default: + { + if (twoHanded) + { + return (int) (totalAttackTime * 0.735); + } + + return (int) (totalAttackTime * 0.644); } } - - return calcPAtkSpd(activeChar.getPAtkSpd()); } + /** + * @param activeChar + * @param weapon + * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed} + */ public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon) { if (weapon == null) @@ -1549,9 +1575,9 @@ public final class Formulas } reuse *= activeChar.getStat().getWeaponReuseModifier(); - final double atkSpd = activeChar.getStat().getPAtkSpd(); + double atkSpd = activeChar.getStat().getPAtkSpd(); - return (int) ((reuse * 345) / atkSpd); // ((reuse * 312) / atkSpd)) for non ranged? + return (int) ((500000 + (333 * reuse)) / atkSpd); } public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit) diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java index c71e654d78..7aeed1c607 100644 --- a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java +++ b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java @@ -204,8 +204,8 @@ public class AdminFightCalculator implements IAdminCommandHandler double dmg2 = 0; // ATTACK speed in milliseconds - int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1, null); - int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2, null); + int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1.getPAtkSpd()); + int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2.getPAtkSpd()); // number of ATTACK per 100 seconds sAtk1 = 100000 / sAtk1; sAtk2 = 100000 / sAtk2; @@ -237,7 +237,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss1) { - final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, 0, _shld1, _crit1, false); + final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, _shld1, _crit1, false); dmg1 += _dmg1; npc1.abortAttack(); } @@ -270,7 +270,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss2) { - final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, 0, _shld2, _crit2, false); + final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, _shld2, _crit2, false); dmg2 += _dmg2; npc2.abortAttack(); } diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9a8e050673..bf268507a8 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -985,7 +985,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); // Check if attacker's weapon can attack if (weaponItem != null) @@ -1125,7 +1124,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe } final boolean wasSSCharged = isChargedShot(ShotType.SOULSHOTS) || isChargedShot(ShotType.BLESSED_SOULSHOTS); // Verify if soulshots are charged. - final int timeAtk = Formulas.calculateTimeBetweenAttacks(this, weaponType); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) + final int timeAtk = Formulas.calculateTimeBetweenAttacks(getPAtkSpd()); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) final int timeToHit = timeAtk / 2; // the hit is calculated to happen halfway to the animation - might need further tuning e.g. in bow case final int ssGrade = (weaponItem != null) ? weaponItem.getItemGrade().ordinal() : 0; final Attack attack = new Attack(this, target, wasSSCharged, ssGrade); @@ -1268,7 +1267,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); // Bows Ranged Damage Formula (Damage gradually decreases when 60% or lower than full hit range, and increases when 60% or higher). // full hit range is 500 which is the base bow range, and the 60% of this is 800. @@ -1341,7 +1340,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 1 - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); damage1 /= 2; } @@ -1355,7 +1354,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit2 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 2 - damage2 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld2, crit2, attack.hasSoulshot()); + damage2 = (int) Formulas.calcAutoAttackDamage(this, target, shld2, crit2, attack.hasSoulshot()); damage2 /= 2; } @@ -1386,7 +1385,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1406,7 +1405,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1456,7 +1455,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); } // Create a new hit task with Medium priority @@ -1481,7 +1480,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); } ThreadPoolManager.schedule(new HitTask(this, surroundTarget, damage, crit, miss, attack.hasSoulshot(), shld), sAtk); diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java index 6af3a0518a..a63f684e46 100644 --- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -404,14 +404,13 @@ public final class Formulas } /** - * Calculate how many milliseconds takes to make an attack. - * @param rate - * @return the delay between each attack with a minimum of 50 milliseconds (max. 10000 attack speed) + * @param attackSpeed the attack speed of the Creature. + * @return {@code 500000 / attackSpeed}. */ - public static int calcPAtkSpd(double rate) + public static int calculateTimeBetweenAttacks(int attackSpeed) { // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second. - return (int) Math.max(50, (500000 / rate)); + return Math.max(50, (500000 / attackSpeed)); } /** @@ -1383,13 +1382,12 @@ public final class Formulas * Calculated damage caused by ATTACK of attacker on target. * @param attacker player or NPC that makes ATTACK * @param target player or NPC, target of ATTACK - * @param power * @param shld * @param crit if the ATTACK have critical success * @param ss if weapon item was charged by soulshot * @return */ - public static double calcAutoAttackDamage(L2Character attacker, L2Character target, double power, byte shld, boolean crit, boolean ss) + public static double calcAutoAttackDamage(L2Character attacker, L2Character target, byte shld, boolean crit, boolean ss) { final double distance = attacker.calculateDistance(target, true, false); @@ -1504,33 +1502,61 @@ public final class Formulas } /** - * @param activeChar - * @param attackType the type of attack. Different attack types have different time between attacks. - * @return the Attack Speed of the L2Character (delay (in milliseconds) before next attack). + * @param totalAttackTime the time needed to make a full attack. + * @param attackType the weapon type used for attack. + * @param twoHanded if the weapon is two handed. + * @param secondHit calculates the second hit for dual attacks. + * @return the time required from the start of the attack until you hit the target. */ - public static int calculateTimeBetweenAttacks(L2Character activeChar, WeaponType attackType) + public static int calculateTimeToHit(int totalAttackTime, WeaponType attackType, boolean twoHanded, boolean secondHit) { + // Gracia Final Retail confirmed: + // Time to damage (1 hand, 1 hit): TotalBasicAttackTime * 0.644 + // Time to damage (2 hand, 1 hit): TotalBasicAttackTime * 0.735 + // Time to damage (2 hand, 2 hit): TotalBasicAttackTime * 0.2726 and TotalBasicAttackTime * 0.6 + // Time to damage (bow/xbow): TotalBasicAttackTime * 0.978 + + // Measured July 2016 by Nik. + // Due to retail packet delay, we are unable to gather too accurate results. Therefore the below formulas are based on original Gracia Final values. + // Any original values that appear higher than tested have been replaced with the tested values, because even with packet delay its obvious they are wrong. + // All other original values are compared with the test results and differences are considered to be too insignificant and mostly caused due to packet delay. switch (attackType) { case BOW: - { - return (1500 * 345) / activeChar.getPAtkSpd(); - } case CROSSBOW: case TWOHANDCROSSBOW: { - return (1200 * 345) / activeChar.getPAtkSpd(); + return (int) (totalAttackTime * 0.95); } - case DAGGER: + case DUALBLUNT: + case DUALDAGGER: + case DUAL: + case DUALFIST: { - // atkSpd /= 1.15; - break; + if (secondHit) + { + return (int) (totalAttackTime * 0.6); + } + + return (int) (totalAttackTime * 0.2726); + } + default: + { + if (twoHanded) + { + return (int) (totalAttackTime * 0.735); + } + + return (int) (totalAttackTime * 0.644); } } - - return calcPAtkSpd(activeChar.getPAtkSpd()); } + /** + * @param activeChar + * @param weapon + * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed} + */ public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon) { if (weapon == null) @@ -1549,9 +1575,9 @@ public final class Formulas } reuse *= activeChar.getStat().getWeaponReuseModifier(); - final double atkSpd = activeChar.getStat().getPAtkSpd(); + double atkSpd = activeChar.getStat().getPAtkSpd(); - return (int) ((reuse * 345) / atkSpd); // ((reuse * 312) / atkSpd)) for non ranged? + return (int) ((500000 + (333 * reuse)) / atkSpd); } public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit) diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java index c71e654d78..7aeed1c607 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/data/scripts/handlers/admincommandhandlers/AdminFightCalculator.java @@ -204,8 +204,8 @@ public class AdminFightCalculator implements IAdminCommandHandler double dmg2 = 0; // ATTACK speed in milliseconds - int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1, null); - int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2, null); + int sAtk1 = Formulas.calculateTimeBetweenAttacks(npc1.getPAtkSpd()); + int sAtk2 = Formulas.calculateTimeBetweenAttacks(npc2.getPAtkSpd()); // number of ATTACK per 100 seconds sAtk1 = 100000 / sAtk1; sAtk2 = 100000 / sAtk2; @@ -237,7 +237,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss1) { - final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, 0, _shld1, _crit1, false); + final double _dmg1 = Formulas.calcAutoAttackDamage(npc1, npc2, _shld1, _crit1, false); dmg1 += _dmg1; npc1.abortAttack(); } @@ -270,7 +270,7 @@ public class AdminFightCalculator implements IAdminCommandHandler if (!_miss2) { - final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, 0, _shld2, _crit2, false); + final double _dmg2 = Formulas.calcAutoAttackDamage(npc2, npc1, _shld2, _crit2, false); dmg2 += _dmg2; npc2.abortAttack(); } diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 9a8e050673..bf268507a8 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -985,7 +985,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); // Check if attacker's weapon can attack if (weaponItem != null) @@ -1125,7 +1124,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe } final boolean wasSSCharged = isChargedShot(ShotType.SOULSHOTS) || isChargedShot(ShotType.BLESSED_SOULSHOTS); // Verify if soulshots are charged. - final int timeAtk = Formulas.calculateTimeBetweenAttacks(this, weaponType); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) + final int timeAtk = Formulas.calculateTimeBetweenAttacks(getPAtkSpd()); // Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack) final int timeToHit = timeAtk / 2; // the hit is calculated to happen halfway to the animation - might need further tuning e.g. in bow case final int ssGrade = (weaponItem != null) ? weaponItem.getItemGrade().ordinal() : 0; final Attack attack = new Attack(this, target, wasSSCharged, ssGrade); @@ -1268,7 +1267,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); // Bows Ranged Damage Formula (Damage gradually decreases when 60% or lower than full hit range, and increases when 60% or higher). // full hit range is 500 which is the base bow range, and the 60% of this is 800. @@ -1341,7 +1340,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 1 - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); damage1 /= 2; } @@ -1355,7 +1354,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit2 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages of hit 2 - damage2 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld2, crit2, attack.hasSoulshot()); + damage2 = (int) Formulas.calcAutoAttackDamage(this, target, shld2, crit2, attack.hasSoulshot()); damage2 /= 2; } @@ -1386,7 +1385,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1406,7 +1405,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); damage /= 2; } @@ -1456,7 +1455,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe crit1 = Formulas.calcCrit(getStat().getCriticalHit(), this, target, null); // Calculate physical damages - damage1 = (int) Formulas.calcAutoAttackDamage(this, target, 0, shld1, crit1, attack.hasSoulshot()); + damage1 = (int) Formulas.calcAutoAttackDamage(this, target, shld1, crit1, attack.hasSoulshot()); } // Create a new hit task with Medium priority @@ -1481,7 +1480,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe { shld = Formulas.calcShldUse(this, surroundTarget); crit = Formulas.calcCrit(getStat().getCriticalHit(), this, surroundTarget, null); - damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, 0, shld, crit, attack.hasSoulshot()); + damage = (int) Formulas.calcAutoAttackDamage(this, surroundTarget, shld, crit, attack.hasSoulshot()); } ThreadPoolManager.schedule(new HitTask(this, surroundTarget, damage, crit, miss, attack.hasSoulshot(), shld), sAtk); diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java index 6af3a0518a..a63f684e46 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/stats/Formulas.java @@ -404,14 +404,13 @@ public final class Formulas } /** - * Calculate how many milliseconds takes to make an attack. - * @param rate - * @return the delay between each attack with a minimum of 50 milliseconds (max. 10000 attack speed) + * @param attackSpeed the attack speed of the Creature. + * @return {@code 500000 / attackSpeed}. */ - public static int calcPAtkSpd(double rate) + public static int calculateTimeBetweenAttacks(int attackSpeed) { // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second. - return (int) Math.max(50, (500000 / rate)); + return Math.max(50, (500000 / attackSpeed)); } /** @@ -1383,13 +1382,12 @@ public final class Formulas * Calculated damage caused by ATTACK of attacker on target. * @param attacker player or NPC that makes ATTACK * @param target player or NPC, target of ATTACK - * @param power * @param shld * @param crit if the ATTACK have critical success * @param ss if weapon item was charged by soulshot * @return */ - public static double calcAutoAttackDamage(L2Character attacker, L2Character target, double power, byte shld, boolean crit, boolean ss) + public static double calcAutoAttackDamage(L2Character attacker, L2Character target, byte shld, boolean crit, boolean ss) { final double distance = attacker.calculateDistance(target, true, false); @@ -1504,33 +1502,61 @@ public final class Formulas } /** - * @param activeChar - * @param attackType the type of attack. Different attack types have different time between attacks. - * @return the Attack Speed of the L2Character (delay (in milliseconds) before next attack). + * @param totalAttackTime the time needed to make a full attack. + * @param attackType the weapon type used for attack. + * @param twoHanded if the weapon is two handed. + * @param secondHit calculates the second hit for dual attacks. + * @return the time required from the start of the attack until you hit the target. */ - public static int calculateTimeBetweenAttacks(L2Character activeChar, WeaponType attackType) + public static int calculateTimeToHit(int totalAttackTime, WeaponType attackType, boolean twoHanded, boolean secondHit) { + // Gracia Final Retail confirmed: + // Time to damage (1 hand, 1 hit): TotalBasicAttackTime * 0.644 + // Time to damage (2 hand, 1 hit): TotalBasicAttackTime * 0.735 + // Time to damage (2 hand, 2 hit): TotalBasicAttackTime * 0.2726 and TotalBasicAttackTime * 0.6 + // Time to damage (bow/xbow): TotalBasicAttackTime * 0.978 + + // Measured July 2016 by Nik. + // Due to retail packet delay, we are unable to gather too accurate results. Therefore the below formulas are based on original Gracia Final values. + // Any original values that appear higher than tested have been replaced with the tested values, because even with packet delay its obvious they are wrong. + // All other original values are compared with the test results and differences are considered to be too insignificant and mostly caused due to packet delay. switch (attackType) { case BOW: - { - return (1500 * 345) / activeChar.getPAtkSpd(); - } case CROSSBOW: case TWOHANDCROSSBOW: { - return (1200 * 345) / activeChar.getPAtkSpd(); + return (int) (totalAttackTime * 0.95); } - case DAGGER: + case DUALBLUNT: + case DUALDAGGER: + case DUAL: + case DUALFIST: { - // atkSpd /= 1.15; - break; + if (secondHit) + { + return (int) (totalAttackTime * 0.6); + } + + return (int) (totalAttackTime * 0.2726); + } + default: + { + if (twoHanded) + { + return (int) (totalAttackTime * 0.735); + } + + return (int) (totalAttackTime * 0.644); } } - - return calcPAtkSpd(activeChar.getPAtkSpd()); } + /** + * @param activeChar + * @param weapon + * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed} + */ public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon) { if (weapon == null) @@ -1549,9 +1575,9 @@ public final class Formulas } reuse *= activeChar.getStat().getWeaponReuseModifier(); - final double atkSpd = activeChar.getStat().getPAtkSpd(); + double atkSpd = activeChar.getStat().getPAtkSpd(); - return (int) ((reuse * 345) / atkSpd); // ((reuse * 312) / atkSpd)) for non ranged? + return (int) ((500000 + (333 * reuse)) / atkSpd); } public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit)