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 ee956d47ac..2442dac54d 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
@@ -37,7 +37,6 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
-import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -272,12 +271,8 @@ public final class Formulas
}
// Autoattack critical rate.
- // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
- // TODO: Find retail-like calculation for criticalRateMod.
- final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
- final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
- final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
- rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
+ // It is capped to 500, but unbound by positional critical rate and level diff bonus.
+ rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -291,37 +286,6 @@ public final class Formulas
return finalRate > Rnd.get(1000);
}
- /**
- * Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
- * @param activeChar the attacker.
- * @param target the target.
- * @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
- */
- public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
- {
- final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
- switch (position)
- {
- case SIDE: // 10% Critical Chance bonus when attacking from side.
- {
- return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
- }
- case BACK: // 30% Critical Chance bonus when attacking from back.
- {
- return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
- }
- default: // No Critical Chance bonus when attacking from front.
- {
- return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
- }
- }
- }
-
- public static double calcCriticalHeightBonus(ILocational from, ILocational target)
- {
- return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
- }
-
/**
* @param attacker
* @param target
@@ -1075,7 +1039,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target);
- counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
+ counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1112,37 +1076,21 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
}
- /**
- * Basic chance formula:
- *
- * - chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate
- * - weapon_critical = (12 for daggers)
- * - dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).
- * - crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.
- * - crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus
- * - effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.
- *
- * Chance cannot be higher than 80%.
- * @param activeChar
- * @param target
- * @param skill
- * @param chanceBoost
- * @return
- */
- public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
+ public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
{
- final L2Weapon weapon = activeChar.getActiveWeaponItem();
- final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
+ final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
- final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
- final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
- final double chanceBoostMod = (100 + chanceBoost) / 100;
+ final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
+ final Position position = Position.getPosition(activeChar, target);
+ final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
+ final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
+ blowChance = (weaponCritical + blowChance) * 10;
- final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
+ final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
// Blow rate is capped at 80%
- return Rnd.get(100) < Math.min(rate, 80);
+ return Rnd.get(1000) < Math.min(rate, 800);
}
public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1252,12 +1200,6 @@ public final class Formulas
*/
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{
- // Skills without set probability should only test against trait invulnerability.
- if (Double.isNaN(baseChance))
- {
- return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
- }
-
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
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 ee956d47ac..2442dac54d 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
@@ -37,7 +37,6 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
-import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -272,12 +271,8 @@ public final class Formulas
}
// Autoattack critical rate.
- // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
- // TODO: Find retail-like calculation for criticalRateMod.
- final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
- final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
- final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
- rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
+ // It is capped to 500, but unbound by positional critical rate and level diff bonus.
+ rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -291,37 +286,6 @@ public final class Formulas
return finalRate > Rnd.get(1000);
}
- /**
- * Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
- * @param activeChar the attacker.
- * @param target the target.
- * @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
- */
- public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
- {
- final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
- switch (position)
- {
- case SIDE: // 10% Critical Chance bonus when attacking from side.
- {
- return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
- }
- case BACK: // 30% Critical Chance bonus when attacking from back.
- {
- return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
- }
- default: // No Critical Chance bonus when attacking from front.
- {
- return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
- }
- }
- }
-
- public static double calcCriticalHeightBonus(ILocational from, ILocational target)
- {
- return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
- }
-
/**
* @param attacker
* @param target
@@ -1075,7 +1039,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target);
- counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
+ counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1112,37 +1076,21 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
}
- /**
- * Basic chance formula:
- *
- * - chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate
- * - weapon_critical = (12 for daggers)
- * - dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).
- * - crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.
- * - crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus
- * - effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.
- *
- * Chance cannot be higher than 80%.
- * @param activeChar
- * @param target
- * @param skill
- * @param chanceBoost
- * @return
- */
- public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
+ public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
{
- final L2Weapon weapon = activeChar.getActiveWeaponItem();
- final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
+ final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
- final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
- final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
- final double chanceBoostMod = (100 + chanceBoost) / 100;
+ final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
+ final Position position = Position.getPosition(activeChar, target);
+ final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
+ final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
+ blowChance = (weaponCritical + blowChance) * 10;
- final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
+ final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
// Blow rate is capped at 80%
- return Rnd.get(100) < Math.min(rate, 80);
+ return Rnd.get(1000) < Math.min(rate, 800);
}
public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1252,12 +1200,6 @@ public final class Formulas
*/
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{
- // Skills without set probability should only test against trait invulnerability.
- if (Double.isNaN(baseChance))
- {
- return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
- }
-
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
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 ee956d47ac..2442dac54d 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
@@ -37,7 +37,6 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
-import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -272,12 +271,8 @@ public final class Formulas
}
// Autoattack critical rate.
- // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
- // TODO: Find retail-like calculation for criticalRateMod.
- final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
- final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
- final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
- rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
+ // It is capped to 500, but unbound by positional critical rate and level diff bonus.
+ rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -291,37 +286,6 @@ public final class Formulas
return finalRate > Rnd.get(1000);
}
- /**
- * Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
- * @param activeChar the attacker.
- * @param target the target.
- * @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
- */
- public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
- {
- final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
- switch (position)
- {
- case SIDE: // 10% Critical Chance bonus when attacking from side.
- {
- return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
- }
- case BACK: // 30% Critical Chance bonus when attacking from back.
- {
- return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
- }
- default: // No Critical Chance bonus when attacking from front.
- {
- return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
- }
- }
- }
-
- public static double calcCriticalHeightBonus(ILocational from, ILocational target)
- {
- return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
- }
-
/**
* @param attacker
* @param target
@@ -1075,7 +1039,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target);
- counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
+ counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1112,37 +1076,21 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
}
- /**
- * Basic chance formula:
- *
- * - chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate
- * - weapon_critical = (12 for daggers)
- * - dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).
- * - crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.
- * - crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus
- * - effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.
- *
- * Chance cannot be higher than 80%.
- * @param activeChar
- * @param target
- * @param skill
- * @param chanceBoost
- * @return
- */
- public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
+ public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
{
- final L2Weapon weapon = activeChar.getActiveWeaponItem();
- final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
+ final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
- final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
- final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
- final double chanceBoostMod = (100 + chanceBoost) / 100;
+ final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
+ final Position position = Position.getPosition(activeChar, target);
+ final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
+ final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
+ blowChance = (weaponCritical + blowChance) * 10;
- final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
+ final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
// Blow rate is capped at 80%
- return Rnd.get(100) < Math.min(rate, 80);
+ return Rnd.get(1000) < Math.min(rate, 800);
}
public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1252,12 +1200,6 @@ public final class Formulas
*/
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{
- // Skills without set probability should only test against trait invulnerability.
- if (Double.isNaN(baseChance))
- {
- return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
- }
-
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java
index ee956d47ac..442fbd88de 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/stats/Formulas.java
@@ -1,1697 +1,1639 @@
-/*
- * This file is part of the L2J Mobius project.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package com.l2jmobius.gameserver.model.stats;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import com.l2jmobius.Config;
-import com.l2jmobius.commons.util.CommonUtil;
-import com.l2jmobius.commons.util.Rnd;
-import com.l2jmobius.gameserver.data.xml.impl.HitConditionBonusData;
-import com.l2jmobius.gameserver.data.xml.impl.KarmaData;
-import com.l2jmobius.gameserver.enums.AttributeType;
-import com.l2jmobius.gameserver.enums.BasicProperty;
-import com.l2jmobius.gameserver.enums.DispelSlotType;
-import com.l2jmobius.gameserver.enums.Position;
-import com.l2jmobius.gameserver.enums.ShotType;
-import com.l2jmobius.gameserver.model.StatsSet;
-import com.l2jmobius.gameserver.model.actor.L2Character;
-import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
-import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance;
-import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
-import com.l2jmobius.gameserver.model.cubic.CubicInstance;
-import com.l2jmobius.gameserver.model.effects.EffectFlag;
-import com.l2jmobius.gameserver.model.effects.L2EffectType;
-import com.l2jmobius.gameserver.model.interfaces.ILocational;
-import com.l2jmobius.gameserver.model.items.L2Armor;
-import com.l2jmobius.gameserver.model.items.L2Item;
-import com.l2jmobius.gameserver.model.items.L2Weapon;
-import com.l2jmobius.gameserver.model.items.type.ArmorType;
-import com.l2jmobius.gameserver.model.items.type.WeaponType;
-import com.l2jmobius.gameserver.model.skills.AbnormalType;
-import com.l2jmobius.gameserver.model.skills.BuffInfo;
-import com.l2jmobius.gameserver.model.skills.Skill;
-import com.l2jmobius.gameserver.model.skills.SkillCaster;
-import com.l2jmobius.gameserver.network.Debug;
-import com.l2jmobius.gameserver.network.SystemMessageId;
-import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
-
-/**
- * Global calculations.
- */
-public final class Formulas
-{
- /** Regeneration Task period. */
- private static final int HP_REGENERATE_PERIOD = 3000; // 3 secs
-
- public static final byte SHIELD_DEFENSE_FAILED = 0; // no shield defense
- public static final byte SHIELD_DEFENSE_SUCCEED = 1; // normal shield defense
- public static final byte SHIELD_DEFENSE_PERFECT_BLOCK = 2; // perfect block
-
- public static final int SKILL_LAUNCH_TIME = 500; // The time to pass after the skill launching until the skill to affect targets. In milliseconds
- private static final byte MELEE_ATTACK_RANGE = 40;
-
- /**
- * Return the period between 2 regeneration task (3s for L2Character, 5 min for L2DoorInstance).
- * @param cha
- * @return
- */
- public static int getRegeneratePeriod(L2Character cha)
- {
- return cha.isDoor() ? HP_REGENERATE_PERIOD * 100 : HP_REGENERATE_PERIOD;
- }
-
- public static double calcBlowDamage(L2Character attacker, L2Character target, Skill skill, boolean backstab, double power, byte shld, boolean ss)
- {
- final double distance = attacker.calculateDistance(target, true, false);
- if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
- {
- return 0;
- }
-
- double defence = target.getPDef();
-
- switch (shld)
- {
- case Formulas.SHIELD_DEFENSE_SUCCEED:
- {
- defence += target.getShldDef();
- break;
- }
- case Formulas.SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block
- {
- return 1;
- }
- }
-
- // Critical
- final double criticalMod = (attacker.getStat().getValue(Stats.CRITICAL_DAMAGE, 1));
- final double criticalPositionMod = attacker.getStat().getPositionTypeValue(Stats.CRITICAL_DAMAGE, Position.getPosition(attacker, target));
- final double criticalVulnMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE, 1));
- final double criticalAddMod = (attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_ADD, 0));
- final double criticalAddVuln = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_ADD, 0);
- // Trait, elements
- final double weaponTraitMod = calcWeaponTraitBonus(attacker, target);
- final double generalTraitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
- final double attributeMod = calcAttributeBonus(attacker, target, skill);
- final double randomMod = attacker.getRandomDamageMultiplier();
- final double pvpPveMod = calculatePvpPveBonus(attacker, target, skill, true);
-
- // Initial damage
- final double ssmod = ss ? (2 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : 1; // 2.04 for dual weapon?
- final double cdMult = criticalMod * (((criticalPositionMod - 1) / 2) + 1) * (((criticalVulnMod - 1) / 2) + 1);
- final double cdPatk = criticalAddMod + criticalAddVuln;
- final Position position = Position.getPosition(attacker, target);
- final double isPosition = position == Position.BACK ? 0.2 : position == Position.SIDE ? 0.05 : 0;
-
- // ........................_____________________________Initial Damage____________________________...___________Position Additional Damage___________..._CriticalAdd_
- // ATTACK CALCULATION 77 * [(skillpower+patk) * 0.666 * cdbonus * cdPosBonusHalf * cdVulnHalf * ss + isBack0.2Side0.05 * (skillpower+patk*ss) * random + 6 * cd_patk] / pdef
- // ````````````````````````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^```^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^```^^^^^^^^^^^^
- final double baseMod = ((77 * (((power + attacker.getPAtk()) * 0.666 * ssmod * cdMult) + (isPosition * (power + (attacker.getPAtk() * ssmod)) * randomMod) + (6 * cdPatk))) / defence);
- final double damage = baseMod * weaponTraitMod * generalTraitMod * attributeMod * randomMod * pvpPveMod;
-
- if (attacker.isDebug())
- {
- final StatsSet set = new StatsSet();
- set.set("skillPower", power);
- set.set("ssboost", ssmod);
- set.set("isPosition", isPosition);
- set.set("baseMod", baseMod);
- set.set("criticalMod", criticalMod);
- set.set("criticalVulnMod", criticalVulnMod);
- set.set("criticalAddMod", criticalAddMod);
- set.set("criticalAddVuln", criticalAddVuln);
- set.set("weaponTraitMod", weaponTraitMod);
- set.set("generalTraitMod", generalTraitMod);
- set.set("attributeMod", attributeMod);
- set.set("weaponMod", randomMod);
- set.set("penaltyMod", pvpPveMod);
- set.set("damage", (int) damage);
- Debug.sendSkillDebug(attacker, target, skill, set);
- }
-
- return damage;
- }
-
- public static double calcMagicDam(L2Character attacker, L2Character target, Skill skill, double mAtk, double power, double mDef, boolean sps, boolean bss, boolean mcrit)
- {
- final double distance = attacker.calculateDistance(target, true, false);
- if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
- {
- return 0;
- }
-
- // Bonus Spirit shot
- final double shotsBonus = bss ? (4 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : sps ? (2 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : 1;
- final double critMod = mcrit ? (2 * calcCritDamage(attacker, target, skill)) : 1; // TODO not really a proper way... find how it works then implement. // damage += attacker.getStat().getValue(Stats.MAGIC_CRIT_DMG_ADD, 0);
-
- // Trait, elements
- final double generalTraitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
- final double attributeMod = calcAttributeBonus(attacker, target, skill);
- final double randomMod = attacker.getRandomDamageMultiplier();
- final double pvpPveMod = calculatePvpPveBonus(attacker, target, skill, mcrit);
-
- // MDAM Formula.
- double damage = (91 * power * Math.sqrt(mAtk * shotsBonus)) / mDef;
-
- // Failure calculation
- if (Config.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(attacker, target, skill))
- {
- if (attacker.isPlayer())
- {
- if (calcMagicSuccess(attacker, target, skill) && ((target.getLevel() - attacker.getLevel()) <= 9))
- {
- if (skill.hasEffectType(L2EffectType.HP_DRAIN))
- {
- attacker.sendPacket(SystemMessageId.DRAIN_WAS_ONLY_50_PERCENT_SUCCESSFUL);
- }
- else
- {
- attacker.sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED);
- }
- damage /= 2;
- }
- else
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
- sm.addCharName(target);
- sm.addSkillName(skill);
- attacker.sendPacket(sm);
- damage = 1;
- }
- }
-
- if (target.isPlayer())
- {
- final SystemMessage sm = (skill.hasEffectType(L2EffectType.HP_DRAIN)) ? SystemMessage.getSystemMessage(SystemMessageId.YOU_RESISTED_C1_S_DRAIN) : SystemMessage.getSystemMessage(SystemMessageId.YOU_RESISTED_C1_S_MAGIC);
- sm.addCharName(attacker);
- target.sendPacket(sm);
- }
- }
-
- damage = damage * critMod * generalTraitMod * attributeMod * randomMod * pvpPveMod;
- damage = attacker.getStat().getValue(Stats.MAGICAL_SKILL_POWER, damage);
-
- return damage;
- }
-
- public static double calcMagicDam(CubicInstance attacker, L2Character target, Skill skill, double power, boolean mcrit, byte shld)
- {
- final double mAtk = attacker.getTemplate().getPower();
- return calcMagicDam(attacker.getOwner(), target, skill, mAtk, power, shld, false, false, mcrit);
- }
-
- /**
- * Returns true in case of critical hit
- * @param rate
- * @param skill
- * @param activeChar
- * @param target
- * @return
- */
- public static boolean calcCrit(double rate, L2Character activeChar, L2Character target, Skill skill)
- {
- // Skill critical rate is calculated up to the first decimal, thats why multiply by 10 and compare to 1000.
- if (skill != null)
- {
- // Magic Critical Rate
- if (skill.isMagic())
- {
- rate = activeChar.getStat().getValue(Stats.MAGIC_CRITICAL_RATE);
- if ((target == null) || !skill.isBad())
- {
- return Math.min(rate, 320) > Rnd.get(1000);
- }
-
- double finalRate = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_RATE_ADD, 0);
- if ((activeChar.getLevel() >= 78) && (target.getLevel() >= 78))
- {
- finalRate += Math.sqrt(activeChar.getLevel()) + ((activeChar.getLevel() - target.getLevel()) / 25);
- return Math.min(finalRate, 320) > Rnd.get(1000);
- }
-
- return Math.min(finalRate, 200) > Rnd.get(1000);
- }
-
- // Physical skill critical rate
- final double statBonus;
- final double rateBonus;
-
- // There is a chance that activeChar has altered base stat for skill critical.
- final byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL, -1);
- if ((skillCritRateStat >= 0) && (skillCritRateStat < BaseStats.values().length))
- {
- // Best tested
- statBonus = BaseStats.STR.getValue(activeChar.getDEX()) * 2;
- rateBonus = (activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL) * 2) - 1; // Tests made by retail GMs show that 3x10% increase yields to 16.2 -> 26.1
- }
- else
- {
- // Default base stat used for skill critical formula is STR.
- statBonus = BaseStats.STR.calcBonus(activeChar);
- rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL);
- }
-
- final double finalRate = rate * statBonus * rateBonus * 10;
- return finalRate > Rnd.get(1000);
- }
-
- // Autoattack critical rate.
- // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
- // TODO: Find retail-like calculation for criticalRateMod.
- final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
- final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
- final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
- rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
-
- // In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
- // Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
- if (activeChar.isNpc() || target.isNpc())
- {
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
- }
-
- final double finalRate = target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0);
- return finalRate > Rnd.get(1000);
- }
-
- /**
- * Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
- * @param activeChar the attacker.
- * @param target the target.
- * @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
- */
- public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
- {
- final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
- switch (position)
- {
- case SIDE: // 10% Critical Chance bonus when attacking from side.
- {
- return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
- }
- case BACK: // 30% Critical Chance bonus when attacking from back.
- {
- return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
- }
- default: // No Critical Chance bonus when attacking from front.
- {
- return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
- }
- }
- }
-
- public static double calcCriticalHeightBonus(ILocational from, ILocational target)
- {
- return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
- }
-
- /**
- * @param attacker
- * @param target
- * @param skill {@code skill} to be used in the calculation, else calculation will result for autoattack.
- * @return regular critical damage bonus. Positional bonus is excluded!
- */
- public static double calcCritDamage(L2Character attacker, L2Character target, Skill skill)
- {
- final double criticalDamage;
- final double defenceCriticalDamage;
-
- if (skill != null)
- {
- if (skill.isMagic())
- {
- // Magic critical damage.
- criticalDamage = attacker.getStat().getValue(Stats.MAGIC_CRITICAL_DAMAGE, 1);
- defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_DAMAGE, 1);
- }
- else
- {
- criticalDamage = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_SKILL, 1);
- defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_SKILL, 1);
- }
- }
- else
- {
- // Autoattack critical damage.
- criticalDamage = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE, 1) * attacker.getStat().getPositionTypeValue(Stats.CRITICAL_DAMAGE, Position.getPosition(attacker, target));
- defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE, 1);
- }
-
- return criticalDamage * defenceCriticalDamage;
- }
-
- /**
- * @param attacker
- * @param target
- * @param skill {@code skill} to be used in the calculation, else calculation will result for autoattack.
- * @return critical damage additional bonus, not multiplier!
- */
- public static double calcCritDamageAdd(L2Character attacker, L2Character target, Skill skill)
- {
- final double criticalDamageAdd;
- final double defenceCriticalDamageAdd;
-
- if (skill != null)
- {
- if (skill.isMagic())
- {
- // Magic critical damage.
- criticalDamageAdd = attacker.getStat().getValue(Stats.MAGIC_CRITICAL_DAMAGE_ADD, 0);
- defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_DAMAGE_ADD, 0);
- }
- else
- {
- criticalDamageAdd = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_SKILL_ADD, 0);
- defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_SKILL_ADD, 0);
- }
- }
- else
- {
- // Autoattack critical damage.
- criticalDamageAdd = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_ADD, 0);
- defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_ADD, 0);
- }
-
- return criticalDamageAdd + defenceCriticalDamageAdd;
- }
-
- /**
- * @param target
- * @param dmg
- * @return true in case when ATTACK is canceled due to hit
- */
- public static boolean calcAtkBreak(L2Character target, double dmg)
- {
- if (target.isChanneling())
- {
- return false;
- }
-
- double init = 0;
-
- if (Config.ALT_GAME_CANCEL_CAST && target.isCastingNow(SkillCaster::canAbortCast))
- {
- init = 15;
- }
- if (Config.ALT_GAME_CANCEL_BOW && target.isAttackingNow())
- {
- final L2Weapon wpn = target.getActiveWeaponItem();
- if ((wpn != null) && (wpn.getItemType() == WeaponType.BOW))
- {
- init = 15;
- }
- }
-
- if (target.isRaid() || target.isHpBlocked() || (init <= 0))
- {
- return false; // No attack break
- }
-
- // Chance of break is higher with higher dmg
- init += Math.sqrt(13 * dmg);
-
- // Chance is affected by target MEN
- init -= ((BaseStats.MEN.calcBonus(target) * 100) - 100);
-
- // Calculate all modifiers for ATTACK_CANCEL
- double rate = target.getStat().getValue(Stats.ATTACK_CANCEL, init);
-
- // Adjust the rate to be between 1 and 99
- rate = Math.max(Math.min(rate, 99), 1);
-
- return Rnd.get(100) < rate;
- }
-
- /**
- * Calculate delay (in milliseconds) for skills cast
- * @param attacker
- * @param skill
- * @param skillTime
- * @return
- */
- public static int calcAtkSpd(L2Character attacker, Skill skill, double skillTime)
- {
- if (skill.isMagic())
- {
- return (int) ((skillTime / attacker.getMAtkSpd()) * 333);
- }
- return (int) ((skillTime / attacker.getPAtkSpd()) * 300);
- }
-
- /**
- * TODO: Implement those:
- *
- * - Skill cool time is block player from doing anything (moving, casting, attacking).
- * - Seems hardcoded channeling value is not used for the skill task
- *
- * @param creature
- * @param skill
- * @return the hit time of the skill.
- */
- public static int calcHitTime(L2Character creature, Skill skill)
- {
- int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME;
-
- // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd).
- if (!skill.isStatic())
- {
- skillTime = calcAtkSpd(creature, skill, skillTime);
- }
- // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS)
- if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)))
- {
- skillTime = (int) (0.6 * skillTime);
- }
-
- return Math.max(skillTime, 0);
- }
-
- public static int calcSkillCancelTime(L2Character creature, Skill skill)
- {
- // Fishing skills.
- if ((skill.getId() == 1312) || (skill.getId() == 1314) || (skill.getId() == 1315))
- {
- return 0;
- }
- // return (int) Math.max(skill.getCancelTime() / calcSkillTimeFactor(attacker, skill), 500);
- return (int) Math.max(skill.getHitTime() / calcSkillTimeFactor(creature, skill), SKILL_LAUNCH_TIME);
- }
-
- /**
- * TODO: Implement armor bonus and NPC Divider
- * @param creature
- * @param skill
- * @return
- */
- public static double calcSkillTimeFactor(L2Character creature, Skill skill)
- {
- double factor = 0;
- if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3
- {
- final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me!
- final double dexBonus = BaseStats.DEX.calcBonus(creature);
- final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868
- final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED);
- final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED);
- factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333);
- }
- else if (skill.isMagic()) // is_magic = 1
- {
- final double armorBonus = 1; // TODO: Implement me!
- final double witBonus = BaseStats.WIT.calcBonus(creature);
- final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed
- final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED);
- factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333);
- }
- else if (skill.isStatic()) // is_magic = 2
- {
- factor = 1;
- }
-
- if (skill.isChanneling()) // operate type = 5 or 6 or 7
- {
- factor = 1;
- }
-
- if (creature.isNpc() || creature.isSummon())
- {
- // TODO: Implement me!
- // if (attacker.unk08B0 > 0)
- {
- // factor /= attacker.unk08B0;
- }
- }
-
- return Math.max(factor, 0.01);
- }
-
- /**
- * Formula based on http://l2p.l2wh.com/nonskillattacks.html
- * @param attacker
- * @param target
- * @return {@code true} if hit missed (target evaded), {@code false} otherwise.
- */
- public static boolean calcHitMiss(L2Character attacker, L2Character target)
- {
- int chance = (80 + (2 * (attacker.getAccuracy() - target.getEvasionRate()))) * 10;
-
- // Get additional bonus from the conditions when you are attacking
- chance *= HitConditionBonusData.getInstance().getConditionBonus(attacker, target);
-
- chance = Math.max(chance, 200);
- chance = Math.min(chance, 980);
-
- return chance < Rnd.get(1000);
- }
-
- /**
- * Returns:
- * 0 = shield defense doesn't succeed
- * 1 = shield defense succeed
- * 2 = perfect block
- * @param attacker
- * @param target
- * @param sendSysMsg
- * @return
- */
- public static byte calcShldUse(L2Character attacker, L2Character target, boolean sendSysMsg)
- {
- final L2Item item = target.getSecondaryWeaponItem();
- if ((item == null) || !(item instanceof L2Armor) || (((L2Armor) item).getItemType() == ArmorType.SIGIL))
- {
- return 0;
- }
-
- double shldRate = target.getStat().getValue(Stats.SHIELD_DEFENCE_RATE, 0) * BaseStats.DEX.calcBonus(target);
-
- // if attacker use bow and target wear shield, shield block rate is multiplied by 1.3 (30%)
- if (attacker.getAttackType().isRanged())
- {
- shldRate *= 1.3;
- }
-
- final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
- {
- return 0;
- }
-
- byte shldSuccess = SHIELD_DEFENSE_FAILED;
-
- // Check shield success
- if (shldRate > Rnd.get(100))
- {
- // If shield succeed, check perfect block.
- if (((100 - (2 * BaseStats.DEX.calcBonus(target))) < Rnd.get(100)))
- {
- shldSuccess = SHIELD_DEFENSE_PERFECT_BLOCK;
- }
- else
- {
- shldSuccess = SHIELD_DEFENSE_SUCCEED;
- }
- }
-
- if (sendSysMsg && target.isPlayer())
- {
- final L2PcInstance enemy = target.getActingPlayer();
-
- switch (shldSuccess)
- {
- case SHIELD_DEFENSE_SUCCEED:
- {
- enemy.sendPacket(SystemMessageId.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);
- break;
- }
- case SHIELD_DEFENSE_PERFECT_BLOCK:
- {
- enemy.sendPacket(SystemMessageId.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
- break;
- }
- }
- }
-
- return shldSuccess;
- }
-
- public static byte calcShldUse(L2Character attacker, L2Character target)
- {
- return calcShldUse(attacker, target, true);
- }
-
- public static boolean calcMagicAffected(L2Character actor, L2Character target, Skill skill)
- {
- // TODO: CHECK/FIX THIS FORMULA UP!!
- double defence = 0;
- if (skill.isActive() && skill.isBad())
- {
- defence = target.getMDef();
- }
-
- final double attack = 2 * actor.getMAtk() * calcGeneralTraitBonus(actor, target, skill.getTraitType(), false);
- double d = (attack - defence) / (attack + defence);
-
- if (skill.isDebuff())
- {
- if (target.getAbnormalShieldBlocks() > 0)
- {
- if (target.decrementAbnormalShieldBlocks() == 0)
- {
- target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
- }
- return false;
- }
- }
-
- d += 0.5 * Rnd.nextGaussian();
- return d > 0;
- }
-
- public static double calcLvlBonusMod(L2Character attacker, L2Character target, Skill skill)
- {
- final int attackerLvl = skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel();
- final double skillLvlBonusRateMod = 1 + (skill.getLvlBonusRate() / 100.);
- final double lvlMod = 1 + ((attackerLvl - target.getLevel()) / 100.);
- return skillLvlBonusRateMod * lvlMod;
- }
-
- /**
- * Calculates the effect landing success.
- * @param attacker the attacker
- * @param target the target
- * @param skill the skill
- * @return {@code true} if the effect lands
- */
- public static boolean calcEffectSuccess(L2Character attacker, L2Character target, Skill skill)
- {
- // StaticObjects can not receive continuous effects.
- if (target.isDoor() || (target instanceof L2SiegeFlagInstance) || (target instanceof L2StaticObjectInstance))
- {
- return false;
- }
-
- if (skill.isDebuff())
- {
- boolean resisted = target.isCastingNow(s -> s.getSkill().getAbnormalResists().contains(skill.getAbnormalType()));
- if (!resisted)
- {
- if (target.getAbnormalShieldBlocks() > 0)
- {
- if (target.decrementAbnormalShieldBlocks() == 0)
- {
- target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
- }
- resisted = true;
- }
- }
-
- if (!resisted)
- {
- final double distance = attacker.calculateDistance(target, true, false);
- if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
- {
- resisted = true;
- }
- }
-
- if (resisted)
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
- sm.addCharName(target);
- sm.addSkillName(skill);
- attacker.sendPacket(sm);
- return false;
- }
- }
-
- final int activateRate = skill.getActivateRate();
- if ((activateRate == -1))
- {
- return true;
- }
-
- int magicLevel = skill.getMagicLevel();
- if (magicLevel <= -1)
- {
- magicLevel = target.getLevel() + 3;
- }
-
- final double targetBasicProperty = getAbnormalResist(skill.getBasicProperty(), target);
- final double baseMod = ((((((magicLevel - target.getLevel()) + 3) * skill.getLvlBonusRate()) + activateRate) + 30.0) - targetBasicProperty);
- final double elementMod = calcAttributeBonus(attacker, target, skill);
- final double traitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
- final double basicPropertyResist = getBasicPropertyResistBonus(skill.getBasicProperty(), target);
- final double buffDebuffMod = skill.isDebuff() ? target.getStat().getValue(Stats.RESIST_ABNORMAL_DEBUFF, 1) : 0;
- final double rate = baseMod * elementMod * traitMod * buffDebuffMod;
- final double finalRate = traitMod > 0 ? CommonUtil.constrain(rate, skill.getMinChance(), skill.getMaxChance()) * basicPropertyResist : 0;
-
- if (attacker.isDebug())
- {
- final StatsSet set = new StatsSet();
- set.set("baseMod", baseMod);
- set.set("elementMod", elementMod);
- set.set("traitMod", traitMod);
- set.set("buffDebuffMod", buffDebuffMod);
- set.set("rate", rate);
- set.set("finalRate", finalRate);
- Debug.sendSkillDebug(attacker, target, skill, set);
- }
-
- if ((finalRate <= Rnd.get(100)) && (target != attacker))
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
- sm.addCharName(target);
- sm.addSkillName(skill);
- attacker.sendPacket(sm);
- return false;
- }
- return true;
- }
-
- public static boolean calcCubicSkillSuccess(CubicInstance attacker, L2Character target, Skill skill, byte shld)
- {
- if (skill.isDebuff())
- {
- if (skill.getActivateRate() == -1)
- {
- return true;
- }
-
- if (target.getAbnormalShieldBlocks() > 0)
- {
- if (target.decrementAbnormalShieldBlocks() == 0)
- {
- target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
- }
- return false;
- }
- }
-
- // Perfect Shield Block.
- if (shld == SHIELD_DEFENSE_PERFECT_BLOCK)
- {
- return false;
- }
-
- // if target reflect this skill then the effect will fail
- if (calcBuffDebuffReflection(target, skill))
- {
- return false;
- }
-
- final double targetBasicProperty = getAbnormalResist(skill.getBasicProperty(), target);
-
- // Calculate BaseRate.
- final double baseRate = skill.getActivateRate();
- final double statMod = 1 + (targetBasicProperty / 100);
- double rate = (baseRate / statMod);
-
- // Resist Modifier.
- final double resMod = calcGeneralTraitBonus(attacker.getOwner(), target, skill.getTraitType(), false);
- rate *= resMod;
-
- // Lvl Bonus Modifier.
- final double lvlBonusMod = calcLvlBonusMod(attacker.getOwner(), target, skill);
- rate *= lvlBonusMod;
-
- // Element Modifier.
- final double elementMod = calcAttributeBonus(attacker.getOwner(), target, skill);
- rate *= elementMod;
-
- final double basicPropertyResist = getBasicPropertyResistBonus(skill.getBasicProperty(), target);
-
- // Add Matk/Mdef Bonus (TODO: Pending)
-
- // Check the Rate Limits.
- final double finalRate = CommonUtil.constrain(rate, skill.getMinChance(), skill.getMaxChance()) * basicPropertyResist;
-
- if (attacker.getOwner().isDebug())
- {
- final StatsSet set = new StatsSet();
- set.set("baseMod", baseRate);
- set.set("resMod", resMod);
- set.set("statMod", statMod);
- set.set("elementMod", elementMod);
- set.set("lvlBonusMod", lvlBonusMod);
- set.set("rate", rate);
- set.set("finalRate", finalRate);
- Debug.sendSkillDebug(attacker.getOwner(), target, skill, set);
- }
-
- return Rnd.get(100) < finalRate;
- }
-
- public static boolean calcMagicSuccess(L2Character attacker, L2Character target, Skill skill)
- {
- // FIXME: Fix this LevelMod Formula.
- final int lvlDifference = (target.getLevel() - (skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel()));
- final double lvlModifier = Math.pow(1.3, lvlDifference);
- float targetModifier = 1;
- if (target.isAttackable() && !target.isRaid() && !target.isRaidMinion() && (target.getLevel() >= Config.MIN_NPC_LVL_MAGIC_PENALTY) && (attacker.getActingPlayer() != null) && ((target.getLevel() - attacker.getActingPlayer().getLevel()) >= 3))
- {
- final int lvlDiff = target.getLevel() - attacker.getActingPlayer().getLevel() - 2;
- if (lvlDiff >= Config.NPC_SKILL_CHANCE_PENALTY.size())
- {
- targetModifier = Config.NPC_SKILL_CHANCE_PENALTY.get(Config.NPC_SKILL_CHANCE_PENALTY.size() - 1);
- }
- else
- {
- targetModifier = Config.NPC_SKILL_CHANCE_PENALTY.get(lvlDiff);
- }
- }
- // general magic resist
- final double resModifier = target.getStat().getValue(Stats.MAGIC_SUCCESS_RES, 1);
- final int rate = 100 - Math.round((float) (lvlModifier * targetModifier * resModifier));
-
- if (attacker.isDebug())
- {
- final StatsSet set = new StatsSet();
- set.set("lvlDifference", lvlDifference);
- set.set("lvlModifier", lvlModifier);
- set.set("resModifier", resModifier);
- set.set("targetModifier", targetModifier);
- set.set("rate", rate);
- Debug.sendSkillDebug(attacker, target, skill, set);
- }
-
- return (Rnd.get(100) < rate);
- }
-
- public static double calcManaDam(L2Character attacker, L2Character target, Skill skill, double power, byte shld, boolean sps, boolean bss, boolean mcrit, double critLimit)
- {
- // Formula: (SQR(M.Atk)*Power*(Target Max MP/97))/M.Def
- double mAtk = attacker.getMAtk();
- double mDef = target.getMDef();
- final double mp = target.getMaxMp();
-
- switch (shld)
- {
- case SHIELD_DEFENSE_SUCCEED:
- {
- mDef += target.getShldDef();
- break;
- }
- case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block
- {
- return 1;
- }
- }
-
- // Bonus Spiritshot
- final double shotsBonus = attacker.getStat().getValue(Stats.SHOTS_BONUS);
- double sapphireBonus = 0;
- if (attacker.isPlayer() && (attacker.getActingPlayer().getActiveShappireJewel() != null))
- {
- sapphireBonus = attacker.getActingPlayer().getActiveShappireJewel().getBonus();
- }
- mAtk *= bss ? 4 * (shotsBonus + sapphireBonus) : sps ? 2 * (shotsBonus + sapphireBonus) : 1;
-
- double damage = (Math.sqrt(mAtk) * power * (mp / 97)) / mDef;
- damage *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
- damage *= calculatePvpPveBonus(attacker, target, skill, mcrit);
-
- // Failure calculation
- if (Config.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(attacker, target, skill))
- {
- if (attacker.isPlayer())
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.DAMAGE_IS_DECREASED_BECAUSE_C1_RESISTED_C2_S_MAGIC);
- sm.addCharName(target);
- sm.addCharName(attacker);
- attacker.sendPacket(sm);
- damage /= 2;
- }
-
- if (target.isPlayer())
- {
- final SystemMessage sm2 = SystemMessage.getSystemMessage(SystemMessageId.C1_WEAKLY_RESISTED_C2_S_MAGIC);
- sm2.addCharName(target);
- sm2.addCharName(attacker);
- target.sendPacket(sm2);
- }
- }
-
- if (mcrit)
- {
- damage *= 3;
- damage = Math.min(damage, critLimit);
- attacker.sendPacket(SystemMessageId.M_CRITICAL);
- }
- return damage;
- }
-
- public static double calculateSkillResurrectRestorePercent(double baseRestorePercent, L2Character caster)
- {
- if ((baseRestorePercent == 0) || (baseRestorePercent == 100))
- {
- return baseRestorePercent;
- }
-
- double restorePercent = baseRestorePercent * BaseStats.WIT.calcBonus(caster);
- if ((restorePercent - baseRestorePercent) > 20.0)
- {
- restorePercent += 20.0;
- }
-
- restorePercent = Math.max(restorePercent, baseRestorePercent);
- restorePercent = Math.min(restorePercent, 90.0);
-
- return restorePercent;
- }
-
- public static boolean calcPhysicalSkillEvasion(L2Character activeChar, L2Character target, Skill skill)
- {
- if (Rnd.get(100) < target.getStat().getSkillEvasionTypeValue(skill.getMagicType()))
- {
- if (activeChar.isPlayer())
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_DODGED_THE_ATTACK);
- sm.addString(target.getName());
- activeChar.getActingPlayer().sendPacket(sm);
- }
- if (target.isPlayer())
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_DODGED_C1_S_ATTACK);
- sm.addString(activeChar.getName());
- target.getActingPlayer().sendPacket(sm);
- }
- return true;
- }
- return false;
- }
-
- public static boolean calcSkillMastery(L2Character actor, Skill sk)
- {
- // Static Skills are not affected by Skill Mastery.
- if (sk.isStatic() || !actor.isPlayer())
- {
- return false;
- }
-
- final int val = (int) actor.getStat().getValue(Stats.SKILL_CRITICAL, -1);
-
- if (val == -1)
- {
- return false;
- }
-
- final double chance = BaseStats.values()[val].calcBonus(actor) * actor.getStat().getValue(Stats.SKILL_CRITICAL_PROBABILITY, 1);
-
- return ((Rnd.nextDouble() * 100.) < chance);
- }
-
- /**
- * Calculates the attribute bonus with the following formula:
- * diff > 0, so AttBonus = 1,025 + sqrt[(diff^3) / 2] * 0,0001, cannot be above 1,25!
- * diff < 0, so AttBonus = 0,975 - sqrt[(diff^3) / 2] * 0,0001, cannot be below 0,75!
- * diff == 0, so AttBonus = 1
- * @param attacker
- * @param target
- * @param skill Can be {@code null} if there is no skill used for the attack.
- * @return The attribute bonus
- */
- public static double calcAttributeBonus(L2Character attacker, L2Character target, Skill skill)
- {
- int attack_attribute;
- int defence_attribute;
-
- if (skill != null)
- {
- if ((skill.getAttributeType() == AttributeType.NONE) || (skill.getAttributeType() == AttributeType.NONE_ARMOR))
- {
- attack_attribute = 0;
- defence_attribute = target.getDefenseElementValue(AttributeType.NONE_ARMOR);
- }
- else if (attacker.getAttackElement() == skill.getAttributeType())
- {
- attack_attribute = attacker.getAttackElementValue(attacker.getAttackElement()) + skill.getAttributeValue();
- defence_attribute = target.getDefenseElementValue(attacker.getAttackElement());
- }
- else
- {
- attack_attribute = skill.getAttributeValue();
- defence_attribute = target.getDefenseElementValue(skill.getAttributeType());
- }
- }
- else
- {
- attack_attribute = attacker.getAttackElementValue(attacker.getAttackElement());
- defence_attribute = target.getDefenseElementValue(attacker.getAttackElement());
- }
-
- final int diff = attack_attribute - defence_attribute;
- if (diff > 0)
- {
- return Math.min(1.025 + (Math.sqrt(Math.pow(diff, 3) / 2) * 0.0001), 1.25);
- }
- else if (diff < 0)
- {
- return Math.max(0.975 - (Math.sqrt(Math.pow(-diff, 3) / 2) * 0.0001), 0.75);
- }
-
- return 1;
- }
-
- public static void calcCounterAttack(L2Character attacker, L2Character target, Skill skill, boolean crit)
- {
- // Only melee skills can be reflected
- if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE))
- {
- return;
- }
-
- final double chance = target.getStat().getValue(Stats.VENGEANCE_SKILL_PHYSICAL_DAMAGE, 0);
- if (Rnd.get(100) < chance)
- {
- if (target.isPlayer())
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_COUNTERED_C1_S_ATTACK);
- sm.addCharName(attacker);
- target.sendPacket(sm);
- }
- if (attacker.isPlayer())
- {
- final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_IS_PERFORMING_A_COUNTERATTACK);
- sm.addCharName(target);
- attacker.sendPacket(sm);
- }
-
- double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
- counterdmg *= calcWeaponTraitBonus(attacker, target);
- counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
- counterdmg *= calcAttributeBonus(attacker, target, skill);
-
- attacker.reduceCurrentHp(counterdmg, target, skill);
- }
- }
-
- /**
- * Calculate buff/debuff reflection.
- * @param target
- * @param skill
- * @return {@code true} if reflect, {@code false} otherwise.
- */
- public static boolean calcBuffDebuffReflection(L2Character target, Skill skill)
- {
- if (!skill.isDebuff() || (skill.getActivateRate() == -1))
- {
- return false;
- }
- return target.getStat().getValue(skill.isMagic() ? Stats.REFLECT_SKILL_MAGIC : Stats.REFLECT_SKILL_PHYSIC, 0) > Rnd.get(100);
- }
-
- /**
- * Calculate damage caused by falling
- * @param cha
- * @param fallHeight
- * @return damage
- */
- public static double calcFallDam(L2Character cha, int fallHeight)
- {
- if (!Config.ENABLE_FALLING_DAMAGE || (fallHeight < 0))
- {
- return 0;
- }
- return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
- }
-
- /**
- * Basic chance formula:
- *
- * - chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate
- * - weapon_critical = (12 for daggers)
- * - dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).
- * - crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.
- * - crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus
- * - effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.
- *
- * Chance cannot be higher than 80%.
- * @param activeChar
- * @param target
- * @param skill
- * @param chanceBoost
- * @return
- */
- public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
- {
- final L2Weapon weapon = activeChar.getActiveWeaponItem();
- final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
- // double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
- final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
- final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
- final double chanceBoostMod = (100 + chanceBoost) / 100;
- final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
-
- final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
-
- // Blow rate is capped at 80%
- return Rnd.get(100) < Math.min(rate, 80);
- }
-
- public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
- {
- final List canceled = new ArrayList<>(max);
- switch (slot)
- {
- case BUFF:
- {
- // Resist Modifier.
- final int cancelMagicLvl = skill.getMagicLevel();
- if (activeChar.isDebug())
- {
- final StatsSet set = new StatsSet();
- set.set("baseMod", rate);
- set.set("magicLevel", cancelMagicLvl);
- set.set("resMod", target.getStat().getValue(Stats.RESIST_DISPEL_BUFF, 1));
- set.set("rate", rate);
- Debug.sendSkillDebug(activeChar, target, skill, set);
- }
-
- // Prevent initialization.
- final List buffs = target.getEffectList().getBuffs();
-
- for (int i = buffs.size() - 1; i >= 0; i--) // reverse order
- {
- final BuffInfo info = buffs.get(i);
- if (!info.getSkill().canBeStolen() || ((rate < 100) && !calcCancelSuccess(info, cancelMagicLvl, rate, skill, target)))
- {
- continue;
- }
- canceled.add(info);
- if (canceled.size() >= max)
- {
- break;
- }
- }
- break;
- }
- case DEBUFF:
- {
- final List debuffs = target.getEffectList().getDebuffs();
- for (int i = debuffs.size() - 1; i >= 0; i--)
- {
- final BuffInfo info = debuffs.get(i);
- if (info.getSkill().canBeDispelled() && (Rnd.get(100) <= rate))
- {
- canceled.add(info);
- if (canceled.size() >= max)
- {
- break;
- }
- }
- }
- break;
- }
- }
- return canceled;
- }
-
- public static boolean calcCancelSuccess(BuffInfo info, int cancelMagicLvl, int rate, Skill skill, L2Character target)
- {
- final int chance = (int) (rate + ((cancelMagicLvl - info.getSkill().getMagicLevel()) * 2) + ((info.getAbnormalTime() / 120) * target.getStat().getValue(Stats.RESIST_DISPEL_BUFF, 1)));
- return Rnd.get(100) < CommonUtil.constrain(chance, 25, 75); // TODO: i_dispel_by_slot_probability min = 40, max = 95.
- }
-
- /**
- * Calculates the abnormal time for an effect.
- * The abnormal time is taken from the skill definition, and it's global for all effects present in the skills.
- * @param caster the caster
- * @param target the target
- * @param skill the skill
- * @return the time that the effect will last
- */
- public static int calcEffectAbnormalTime(L2Character caster, L2Character target, Skill skill)
- {
- int time = (skill == null) || skill.isPassive() || skill.isToggle() ? -1 : skill.getAbnormalTime();
-
- // If the skill is a mastery skill, the effect will last twice the default time.
- if ((skill != null) && Formulas.calcSkillMastery(caster, skill))
- {
- time *= 2;
- }
-
- return time;
- }
-
- /**
- * Calculate Probability in following effects:
- * TargetCancel,
- * TargetMeProbability,
- * SkillTurning,
- * Betray,
- * Bluff,
- * DeleteHate,
- * RandomizeHate,
- * DeleteHateOfMe,
- * TransferHate,
- * Confuse
- * Knockback
- * Pull
- * @param baseChance chance from effect parameter
- * @param attacker
- * @param target
- * @param skill
- * @return chance for effect to succeed
- */
- public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
- {
- // Skills without set probability should only test against trait invulnerability.
- if (Double.isNaN(baseChance))
- {
- return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
- }
-
- // Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
- // TODO: Find more retail-like formula
- return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
- }
-
- /**
- * Calculates karma lost upon death.
- * @param player
- * @param finalExp
- * @return the amount of karma player has loosed.
- */
- public static int calculateKarmaLost(L2PcInstance player, double finalExp)
- {
- final double karmaLooseMul = KarmaData.getInstance().getMultiplier(player.getLevel());
- if (finalExp > 0) // Received exp
- {
- finalExp /= Config.RATE_KARMA_LOST;
- }
- return (int) ((Math.abs(finalExp) / karmaLooseMul) / 30);
- }
-
- /**
- * Calculates karma gain upon playable kill.
- * Updated to High Five on 10.09.2014 by Zealar tested in retail.
- * @param pkCount
- * @param isSummon
- * @return karma points that will be added to the player.
- */
- public static int calculateKarmaGain(int pkCount, boolean isSummon)
- {
- int result = 43200;
-
- if (isSummon)
- {
- result = (int) ((((pkCount * 0.375) + 1) * 60) * 4) - 150;
-
- if (result > 10800)
- {
- return 10800;
- }
- }
-
- if (pkCount < 99)
- {
- result = (int) ((((pkCount * 0.5) + 1) * 60) * 12);
- }
- else if (pkCount < 180)
- {
- result = (int) ((((pkCount * 0.125) + 37.75) * 60) * 12);
- }
-
- return result;
- }
-
- public static double calcGeneralTraitBonus(L2Character attacker, L2Character target, TraitType traitType, boolean ignoreResistance)
- {
- if (traitType == TraitType.NONE)
- {
- return 1.0;
- }
-
- if (target.getStat().isTraitInvul(traitType))
- {
- return 0;
- }
-
- switch (traitType.getType())
- {
- case 2:
- {
- if (!attacker.getStat().hasAttackTrait(traitType) || !target.getStat().hasDefenceTrait(traitType))
- {
- return 1.0;
- }
- break;
- }
- case 3:
- {
- if (ignoreResistance)
- {
- return 1.0;
- }
- break;
- }
- default:
- {
- return 1.0;
- }
- }
-
- final double result = (attacker.getStat().getAttackTrait(traitType) - target.getStat().getDefenceTrait(traitType)) + 1.0;
- return CommonUtil.constrain(result, 0.05, 2.0);
- }
-
- public static double calcWeaponTraitBonus(L2Character attacker, L2Character target)
- {
- final TraitType type = attacker.getAttackType().getTraitType();
- final double result = target.getStat().getDefenceTraits()[type.getId()] - 1.0;
- return 1.0 - result;
- }
-
- public static double calcAttackTraitBonus(L2Character attacker, L2Character target)
- {
- final double weaponTraitBonus = calcWeaponTraitBonus(attacker, target);
- if (weaponTraitBonus == 0)
- {
- return 0;
- }
-
- double weaknessBonus = 1.0;
- for (TraitType traitType : TraitType.values())
- {
- if (traitType.getType() == 2)
- {
- weaknessBonus *= calcGeneralTraitBonus(attacker, target, traitType, true);
- if (weaknessBonus == 0)
- {
- return 0;
- }
- }
- }
-
- return CommonUtil.constrain((weaponTraitBonus * weaknessBonus), 0.05, 2.0);
- }
-
- public static double getBasicPropertyResistBonus(BasicProperty basicProperty, L2Character target)
- {
- if ((basicProperty == BasicProperty.NONE) || !target.hasBasicPropertyResist())
- {
- return 1.0;
- }
-
- final BasicPropertyResist resist = target.getBasicPropertyResist(basicProperty);
- switch (resist.getResistLevel())
- {
- case 0:
- {
- return 1.0;
- }
- case 1:
- {
- return 0.6;
- }
- case 2:
- {
- return 0.3;
- }
- default:
- {
- return 0;
- }
- }
- }
-
- /**
- * 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 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, byte shld, boolean crit, boolean ss)
- {
- final double distance = attacker.calculateDistance(target, true, false);
-
- if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
- {
- return 0;
- }
-
- // DEFENCE CALCULATION (pDef + sDef)
- double defence = target.getPDef();
-
- switch (shld)
- {
- case SHIELD_DEFENSE_SUCCEED:
- {
- defence += target.getShldDef();
- break;
- }
- case SHIELD_DEFENSE_PERFECT_BLOCK:
- {
- return 1.;
- }
- }
-
- final L2Weapon weapon = attacker.getActiveWeaponItem();
- final boolean isRanged = (weapon != null) && weapon.getItemType().isRanged();
- final double shotsBonus = attacker.getStat().getValue(Stats.SHOTS_BONUS);
-
- final double cAtk = crit ? (2 * calcCritDamage(attacker, target, null)) : 1;
- final double cAtkAdd = crit ? calcCritDamageAdd(attacker, target, null) : 0;
- final double critMod = crit ? (isRanged ? 0.5 : 1) : 0;
- final double ssBonus = ss ? 2 * shotsBonus : 1;
- final double random_damage = attacker.getRandomDamageMultiplier();
- final double proxBonus = (attacker.isInFrontOf(target) ? 0 : (attacker.isBehind(target) ? 0.2 : 0.05)) * attacker.getPAtk();
- double attack = (attacker.getPAtk() * random_damage) + proxBonus;
-
- // ....................______________Critical Section___________________...._______Non-Critical Section______
- // ATTACK CALCULATION (((pAtk * cAtk * ss + cAtkAdd) * crit) * weaponMod) + (pAtk (1 - crit) * ss * weaponMod)
- // ````````````````````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- attack = ((((attack * cAtk * ssBonus) + cAtkAdd) * critMod) * (isRanged ? 154 : 77)) + (attack * (1 - critMod) * ssBonus * (isRanged ? 154 : 77));
-
- // DAMAGE CALCULATION (ATTACK / DEFENCE) * trait bonus * attr bonus * pvp bonus * pve bonus
- double damage = attack / defence;
- damage *= calcAttackTraitBonus(attacker, target);
- damage *= calcAttributeBonus(attacker, target, null);
- damage *= calculatePvpPveBonus(attacker, target, null, crit);
-
- damage = Math.max(0, damage);
-
- return damage;
- }
-
- public static double getAbnormalResist(BasicProperty basicProperty, L2Character target)
- {
- switch (basicProperty)
- {
- case PHYSICAL:
- {
- return target.getStat().getValue(Stats.ABNORMAL_RESIST_PHYSICAL);
- }
- case MAGIC:
- {
- return target.getStat().getValue(Stats.ABNORMAL_RESIST_MAGICAL);
- }
- default:
- {
- return 0;
- }
- }
- }
-
- public static double calcPveDamagePenalty(L2Character attacker, L2Character target, Skill skill, boolean crit)
- {
- if (target.isAttackable() && (target.getLevel() >= Config.MIN_NPC_LVL_DMG_PENALTY) && (attacker.getActingPlayer() != null) && ((target.getLevel() - attacker.getActingPlayer().getLevel()) > 1))
- {
- final int lvlDiff = target.getLevel() - attacker.getActingPlayer().getLevel() - 1;
- if (skill != null)
- {
- return Config.NPC_SKILL_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_SKILL_DMG_PENALTY.size() - 1));
- }
- else if (crit)
- {
- return Config.NPC_CRIT_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_CRIT_DMG_PENALTY.size() - 1));
- }
-
- return Config.NPC_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_DMG_PENALTY.size() - 1));
- }
-
- return 1.0;
- }
-
- /**
- * Calculates if the specified creature can get its stun effect removed due to damage taken.
- * @param activeChar the character to be checked
- * @return {@code true} if character should get its stun effects removed, {@code false} otherwise.
- */
- public static boolean calcStunBreak(L2Character activeChar)
- {
- // Check if target is stunned and 10% chance (retail is 14% and 35% on crit?)
- if (activeChar.hasBlockActions() && (Rnd.get(10) == 0))
- {
- // Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time.
- return activeChar.getEffectList().hasAbnormalType(AbnormalType.STUN, info -> info.getTime() <= info.getSkill().getAbnormalTime());
- }
- return false;
- }
-
- public static boolean calcRealTargetBreak()
- {
- // Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
- return Rnd.get(100) <= 3;
- }
-
- /**
- * @param attackSpeed the attack speed of the Creature.
- * @return {@code 500000 / attackSpeed}.
- */
- public static int calculateTimeBetweenAttacks(int attackSpeed)
- {
- // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
- return Math.max(50, (500000 / attackSpeed));
- }
-
- /**
- * @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 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:
- case CROSSBOW:
- case TWOHANDCROSSBOW:
- {
- return (int) (totalAttackTime * 0.95);
- }
- case DUALBLUNT:
- case DUALDAGGER:
- case DUAL:
- case DUALFIST:
- {
- 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);
- }
- }
- }
-
- /**
- * @param activeChar
- * @param weapon
- * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed}
- */
- public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon)
- {
- if (weapon == null)
- {
- return 0;
- }
-
- final WeaponType defaultAttackType = weapon.getItemType();
- final WeaponType weaponType = activeChar.getTransformation().map(transform -> transform.getBaseAttackType(activeChar, defaultAttackType)).orElse(defaultAttackType);
- int reuse = weapon.getReuseDelay();
-
- // only bows should continue for now
- if ((reuse == 0) || !weaponType.isRanged())
- {
- return 0;
- }
-
- reuse *= activeChar.getStat().getWeaponReuseModifier();
- double atkSpd = activeChar.getStat().getPAtkSpd();
-
- return (int) ((500000 + (333 * reuse)) / atkSpd);
- }
-
- public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit)
- {
- // PvP bonus
- if (attacker.isPlayable() && target.isPlayable())
- {
- final double pvpAttack;
- final double pvpDefense;
- if (skill != null)
- {
- if (skill.isMagic())
- {
- // Magical Skill PvP
- pvpAttack = attacker.getStat().getValue(Stats.PVP_MAGICAL_SKILL_DAMAGE, 1);
- pvpDefense = target.getStat().getValue(Stats.PVP_MAGICAL_SKILL_DEFENCE, 1);
- }
- else
- {
- // Physical Skill PvP
- pvpAttack = attacker.getStat().getValue(Stats.PVP_PHYSICAL_SKILL_DAMAGE, 1);
- pvpDefense = target.getStat().getValue(Stats.PVP_PHYSICAL_SKILL_DEFENCE, 1);
- }
- }
- else
- {
- // Autoattack PvP
- pvpAttack = attacker.getStat().getValue(Stats.PVP_PHYSICAL_ATTACK_DAMAGE, 1);
- pvpDefense = target.getStat().getValue(Stats.PVP_PHYSICAL_ATTACK_DEFENCE, 1);
- }
-
- return 1 + (pvpAttack - pvpDefense);
- }
-
- // PvE Bonus
- if (target.isAttackable() || attacker.isAttackable())
- {
- final double pveAttack;
- final double pveDefense;
- final double pveRaidDefense;
- final double pvePenalty = calcPveDamagePenalty(attacker, target, skill, crit);
-
- if (skill != null)
- {
- if (skill.isMagic())
- {
- // Magical Skill PvE
- pveAttack = attacker.getStat().getValue(Stats.PVE_MAGICAL_SKILL_DAMAGE, 1);
- pveDefense = target.getStat().getValue(Stats.PVE_MAGICAL_SKILL_DEFENCE, 1);
- pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_MAGICAL_SKILL_DEFENCE, 1) : 1;
- }
- else
- {
- // Physical Skill PvE
- pveAttack = attacker.getStat().getValue(Stats.PVE_PHYSICAL_SKILL_DAMAGE, 1);
- pveDefense = target.getStat().getValue(Stats.PVE_PHYSICAL_SKILL_DEFENCE, 1);
- pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_PHYSICAL_SKILL_DEFENCE, 1) : 1;
- }
- }
- else
- {
- // Autoattack PvE
- pveAttack = attacker.getStat().getValue(Stats.PVE_PHYSICAL_ATTACK_DAMAGE, 1);
- pveDefense = target.getStat().getValue(Stats.PVE_PHYSICAL_ATTACK_DEFENCE, 1);
- pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_PHYSICAL_ATTACK_DEFENCE, 1) : 1;
- }
-
- return (1 + (pveAttack - (pveDefense * pveRaidDefense))) * pvePenalty;
- }
-
- return 1;
- }
-}
+/*
+ * This file is part of the L2J Mobius project.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.l2jmobius.gameserver.model.stats;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.l2jmobius.Config;
+import com.l2jmobius.commons.util.CommonUtil;
+import com.l2jmobius.commons.util.Rnd;
+import com.l2jmobius.gameserver.data.xml.impl.HitConditionBonusData;
+import com.l2jmobius.gameserver.data.xml.impl.KarmaData;
+import com.l2jmobius.gameserver.enums.AttributeType;
+import com.l2jmobius.gameserver.enums.BasicProperty;
+import com.l2jmobius.gameserver.enums.DispelSlotType;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.enums.ShotType;
+import com.l2jmobius.gameserver.model.StatsSet;
+import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.actor.instance.L2PcInstance;
+import com.l2jmobius.gameserver.model.actor.instance.L2SiegeFlagInstance;
+import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
+import com.l2jmobius.gameserver.model.cubic.CubicInstance;
+import com.l2jmobius.gameserver.model.effects.EffectFlag;
+import com.l2jmobius.gameserver.model.effects.L2EffectType;
+import com.l2jmobius.gameserver.model.items.L2Armor;
+import com.l2jmobius.gameserver.model.items.L2Item;
+import com.l2jmobius.gameserver.model.items.L2Weapon;
+import com.l2jmobius.gameserver.model.items.type.ArmorType;
+import com.l2jmobius.gameserver.model.items.type.WeaponType;
+import com.l2jmobius.gameserver.model.skills.AbnormalType;
+import com.l2jmobius.gameserver.model.skills.BuffInfo;
+import com.l2jmobius.gameserver.model.skills.Skill;
+import com.l2jmobius.gameserver.model.skills.SkillCaster;
+import com.l2jmobius.gameserver.network.Debug;
+import com.l2jmobius.gameserver.network.SystemMessageId;
+import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+
+/**
+ * Global calculations.
+ */
+public final class Formulas
+{
+ /** Regeneration Task period. */
+ private static final int HP_REGENERATE_PERIOD = 3000; // 3 secs
+
+ public static final byte SHIELD_DEFENSE_FAILED = 0; // no shield defense
+ public static final byte SHIELD_DEFENSE_SUCCEED = 1; // normal shield defense
+ public static final byte SHIELD_DEFENSE_PERFECT_BLOCK = 2; // perfect block
+
+ public static final int SKILL_LAUNCH_TIME = 500; // The time to pass after the skill launching until the skill to affect targets. In milliseconds
+ private static final byte MELEE_ATTACK_RANGE = 40;
+
+ /**
+ * Return the period between 2 regeneration task (3s for L2Character, 5 min for L2DoorInstance).
+ * @param cha
+ * @return
+ */
+ public static int getRegeneratePeriod(L2Character cha)
+ {
+ return cha.isDoor() ? HP_REGENERATE_PERIOD * 100 : HP_REGENERATE_PERIOD;
+ }
+
+ public static double calcBlowDamage(L2Character attacker, L2Character target, Skill skill, boolean backstab, double power, byte shld, boolean ss)
+ {
+ final double distance = attacker.calculateDistance(target, true, false);
+ if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
+ {
+ return 0;
+ }
+
+ double defence = target.getPDef();
+
+ switch (shld)
+ {
+ case Formulas.SHIELD_DEFENSE_SUCCEED:
+ {
+ defence += target.getShldDef();
+ break;
+ }
+ case Formulas.SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block
+ {
+ return 1;
+ }
+ }
+
+ // Critical
+ final double criticalMod = (attacker.getStat().getValue(Stats.CRITICAL_DAMAGE, 1));
+ final double criticalPositionMod = attacker.getStat().getPositionTypeValue(Stats.CRITICAL_DAMAGE, Position.getPosition(attacker, target));
+ final double criticalVulnMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE, 1));
+ final double criticalAddMod = (attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_ADD, 0));
+ final double criticalAddVuln = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_ADD, 0);
+ // Trait, elements
+ final double weaponTraitMod = calcWeaponTraitBonus(attacker, target);
+ final double generalTraitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
+ final double attributeMod = calcAttributeBonus(attacker, target, skill);
+ final double randomMod = attacker.getRandomDamageMultiplier();
+ final double pvpPveMod = calculatePvpPveBonus(attacker, target, skill, true);
+
+ // Initial damage
+ final double ssmod = ss ? (2 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : 1; // 2.04 for dual weapon?
+ final double cdMult = criticalMod * (((criticalPositionMod - 1) / 2) + 1) * (((criticalVulnMod - 1) / 2) + 1);
+ final double cdPatk = criticalAddMod + criticalAddVuln;
+ final Position position = Position.getPosition(attacker, target);
+ final double isPosition = position == Position.BACK ? 0.2 : position == Position.SIDE ? 0.05 : 0;
+
+ // ........................_____________________________Initial Damage____________________________...___________Position Additional Damage___________..._CriticalAdd_
+ // ATTACK CALCULATION 77 * [(skillpower+patk) * 0.666 * cdbonus * cdPosBonusHalf * cdVulnHalf * ss + isBack0.2Side0.05 * (skillpower+patk*ss) * random + 6 * cd_patk] / pdef
+ // ````````````````````````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^```^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^```^^^^^^^^^^^^
+ final double baseMod = ((77 * (((power + attacker.getPAtk()) * 0.666 * ssmod * cdMult) + (isPosition * (power + (attacker.getPAtk() * ssmod)) * randomMod) + (6 * cdPatk))) / defence);
+ final double damage = baseMod * weaponTraitMod * generalTraitMod * attributeMod * randomMod * pvpPveMod;
+
+ if (attacker.isDebug())
+ {
+ final StatsSet set = new StatsSet();
+ set.set("skillPower", power);
+ set.set("ssboost", ssmod);
+ set.set("isPosition", isPosition);
+ set.set("baseMod", baseMod);
+ set.set("criticalMod", criticalMod);
+ set.set("criticalVulnMod", criticalVulnMod);
+ set.set("criticalAddMod", criticalAddMod);
+ set.set("criticalAddVuln", criticalAddVuln);
+ set.set("weaponTraitMod", weaponTraitMod);
+ set.set("generalTraitMod", generalTraitMod);
+ set.set("attributeMod", attributeMod);
+ set.set("weaponMod", randomMod);
+ set.set("penaltyMod", pvpPveMod);
+ set.set("damage", (int) damage);
+ Debug.sendSkillDebug(attacker, target, skill, set);
+ }
+
+ return damage;
+ }
+
+ public static double calcMagicDam(L2Character attacker, L2Character target, Skill skill, double mAtk, double power, double mDef, boolean sps, boolean bss, boolean mcrit)
+ {
+ final double distance = attacker.calculateDistance(target, true, false);
+ if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
+ {
+ return 0;
+ }
+
+ // Bonus Spirit shot
+ final double shotsBonus = bss ? (4 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : sps ? (2 * attacker.getStat().getValue(Stats.SHOTS_BONUS)) : 1;
+ final double critMod = mcrit ? (2 * calcCritDamage(attacker, target, skill)) : 1; // TODO not really a proper way... find how it works then implement. // damage += attacker.getStat().getValue(Stats.MAGIC_CRIT_DMG_ADD, 0);
+
+ // Trait, elements
+ final double generalTraitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
+ final double attributeMod = calcAttributeBonus(attacker, target, skill);
+ final double randomMod = attacker.getRandomDamageMultiplier();
+ final double pvpPveMod = calculatePvpPveBonus(attacker, target, skill, mcrit);
+
+ // MDAM Formula.
+ double damage = (91 * power * Math.sqrt(mAtk * shotsBonus)) / mDef;
+
+ // Failure calculation
+ if (Config.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(attacker, target, skill))
+ {
+ if (attacker.isPlayer())
+ {
+ if (calcMagicSuccess(attacker, target, skill) && ((target.getLevel() - attacker.getLevel()) <= 9))
+ {
+ if (skill.hasEffectType(L2EffectType.HP_DRAIN))
+ {
+ attacker.sendPacket(SystemMessageId.DRAIN_WAS_ONLY_50_PERCENT_SUCCESSFUL);
+ }
+ else
+ {
+ attacker.sendPacket(SystemMessageId.YOUR_ATTACK_HAS_FAILED);
+ }
+ damage /= 2;
+ }
+ else
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
+ sm.addCharName(target);
+ sm.addSkillName(skill);
+ attacker.sendPacket(sm);
+ damage = 1;
+ }
+ }
+
+ if (target.isPlayer())
+ {
+ final SystemMessage sm = (skill.hasEffectType(L2EffectType.HP_DRAIN)) ? SystemMessage.getSystemMessage(SystemMessageId.YOU_RESISTED_C1_S_DRAIN) : SystemMessage.getSystemMessage(SystemMessageId.YOU_RESISTED_C1_S_MAGIC);
+ sm.addCharName(attacker);
+ target.sendPacket(sm);
+ }
+ }
+
+ damage = damage * critMod * generalTraitMod * attributeMod * randomMod * pvpPveMod;
+ damage = attacker.getStat().getValue(Stats.MAGICAL_SKILL_POWER, damage);
+
+ return damage;
+ }
+
+ public static double calcMagicDam(CubicInstance attacker, L2Character target, Skill skill, double power, boolean mcrit, byte shld)
+ {
+ final double mAtk = attacker.getTemplate().getPower();
+ return calcMagicDam(attacker.getOwner(), target, skill, mAtk, power, shld, false, false, mcrit);
+ }
+
+ /**
+ * Returns true in case of critical hit
+ * @param rate
+ * @param skill
+ * @param activeChar
+ * @param target
+ * @return
+ */
+ public static boolean calcCrit(double rate, L2Character activeChar, L2Character target, Skill skill)
+ {
+ // Skill critical rate is calculated up to the first decimal, thats why multiply by 10 and compare to 1000.
+ if (skill != null)
+ {
+ // Magic Critical Rate
+ if (skill.isMagic())
+ {
+ rate = activeChar.getStat().getValue(Stats.MAGIC_CRITICAL_RATE);
+ if ((target == null) || !skill.isBad())
+ {
+ return Math.min(rate, 320) > Rnd.get(1000);
+ }
+
+ double finalRate = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_RATE_ADD, 0);
+ if ((activeChar.getLevel() >= 78) && (target.getLevel() >= 78))
+ {
+ finalRate += Math.sqrt(activeChar.getLevel()) + ((activeChar.getLevel() - target.getLevel()) / 25);
+ return Math.min(finalRate, 320) > Rnd.get(1000);
+ }
+
+ return Math.min(finalRate, 200) > Rnd.get(1000);
+ }
+
+ // Physical skill critical rate
+ final double statBonus;
+ final double rateBonus;
+
+ // There is a chance that activeChar has altered base stat for skill critical.
+ final byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL, -1);
+ if ((skillCritRateStat >= 0) && (skillCritRateStat < BaseStats.values().length))
+ {
+ // Best tested
+ statBonus = BaseStats.STR.getValue(activeChar.getDEX()) * 2;
+ rateBonus = (activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL) * 2) - 1; // Tests made by retail GMs show that 3x10% increase yields to 16.2 -> 26.1
+ }
+ else
+ {
+ // Default base stat used for skill critical formula is STR.
+ statBonus = BaseStats.STR.calcBonus(activeChar);
+ rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL);
+ }
+
+ final double finalRate = rate * statBonus * rateBonus * 10;
+ return finalRate > Rnd.get(1000);
+ }
+
+ // Autoattack critical rate.
+ // It is capped to 500, but unbound by positional critical rate and level diff bonus.
+ rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
+
+ // In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
+ // Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
+ if (activeChar.isNpc() || target.isNpc())
+ {
+ final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
+ rate *= levelMod;
+ }
+
+ final double finalRate = target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0);
+ return finalRate > Rnd.get(1000);
+ }
+
+ /**
+ * @param attacker
+ * @param target
+ * @param skill {@code skill} to be used in the calculation, else calculation will result for autoattack.
+ * @return regular critical damage bonus. Positional bonus is excluded!
+ */
+ public static double calcCritDamage(L2Character attacker, L2Character target, Skill skill)
+ {
+ final double criticalDamage;
+ final double defenceCriticalDamage;
+
+ if (skill != null)
+ {
+ if (skill.isMagic())
+ {
+ // Magic critical damage.
+ criticalDamage = attacker.getStat().getValue(Stats.MAGIC_CRITICAL_DAMAGE, 1);
+ defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_DAMAGE, 1);
+ }
+ else
+ {
+ criticalDamage = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_SKILL, 1);
+ defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_SKILL, 1);
+ }
+ }
+ else
+ {
+ // Autoattack critical damage.
+ criticalDamage = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE, 1) * attacker.getStat().getPositionTypeValue(Stats.CRITICAL_DAMAGE, Position.getPosition(attacker, target));
+ defenceCriticalDamage = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE, 1);
+ }
+
+ return criticalDamage * defenceCriticalDamage;
+ }
+
+ /**
+ * @param attacker
+ * @param target
+ * @param skill {@code skill} to be used in the calculation, else calculation will result for autoattack.
+ * @return critical damage additional bonus, not multiplier!
+ */
+ public static double calcCritDamageAdd(L2Character attacker, L2Character target, Skill skill)
+ {
+ final double criticalDamageAdd;
+ final double defenceCriticalDamageAdd;
+
+ if (skill != null)
+ {
+ if (skill.isMagic())
+ {
+ // Magic critical damage.
+ criticalDamageAdd = attacker.getStat().getValue(Stats.MAGIC_CRITICAL_DAMAGE_ADD, 0);
+ defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_MAGIC_CRITICAL_DAMAGE_ADD, 0);
+ }
+ else
+ {
+ criticalDamageAdd = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_SKILL_ADD, 0);
+ defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_SKILL_ADD, 0);
+ }
+ }
+ else
+ {
+ // Autoattack critical damage.
+ criticalDamageAdd = attacker.getStat().getValue(Stats.CRITICAL_DAMAGE_ADD, 0);
+ defenceCriticalDamageAdd = target.getStat().getValue(Stats.DEFENCE_CRITICAL_DAMAGE_ADD, 0);
+ }
+
+ return criticalDamageAdd + defenceCriticalDamageAdd;
+ }
+
+ /**
+ * @param target
+ * @param dmg
+ * @return true in case when ATTACK is canceled due to hit
+ */
+ public static boolean calcAtkBreak(L2Character target, double dmg)
+ {
+ if (target.isChanneling())
+ {
+ return false;
+ }
+
+ double init = 0;
+
+ if (Config.ALT_GAME_CANCEL_CAST && target.isCastingNow(SkillCaster::canAbortCast))
+ {
+ init = 15;
+ }
+ if (Config.ALT_GAME_CANCEL_BOW && target.isAttackingNow())
+ {
+ final L2Weapon wpn = target.getActiveWeaponItem();
+ if ((wpn != null) && (wpn.getItemType() == WeaponType.BOW))
+ {
+ init = 15;
+ }
+ }
+
+ if (target.isRaid() || target.isHpBlocked() || (init <= 0))
+ {
+ return false; // No attack break
+ }
+
+ // Chance of break is higher with higher dmg
+ init += Math.sqrt(13 * dmg);
+
+ // Chance is affected by target MEN
+ init -= ((BaseStats.MEN.calcBonus(target) * 100) - 100);
+
+ // Calculate all modifiers for ATTACK_CANCEL
+ double rate = target.getStat().getValue(Stats.ATTACK_CANCEL, init);
+
+ // Adjust the rate to be between 1 and 99
+ rate = Math.max(Math.min(rate, 99), 1);
+
+ return Rnd.get(100) < rate;
+ }
+
+ /**
+ * Calculate delay (in milliseconds) for skills cast
+ * @param attacker
+ * @param skill
+ * @param skillTime
+ * @return
+ */
+ public static int calcAtkSpd(L2Character attacker, Skill skill, double skillTime)
+ {
+ if (skill.isMagic())
+ {
+ return (int) ((skillTime / attacker.getMAtkSpd()) * 333);
+ }
+ return (int) ((skillTime / attacker.getPAtkSpd()) * 300);
+ }
+
+ /**
+ * TODO: Implement those:
+ *
+ * - Skill cool time is block player from doing anything (moving, casting, attacking).
+ * - Seems hardcoded channeling value is not used for the skill task
+ *
+ * @param creature
+ * @param skill
+ * @return the hit time of the skill.
+ */
+ public static int calcHitTime(L2Character creature, Skill skill)
+ {
+ int skillTime = skill.getHitTime() - SKILL_LAUNCH_TIME;
+
+ // Calculate the Casting Time of the "Non-Static" Skills (with caster PAtk/MAtkSpd).
+ if (!skill.isStatic())
+ {
+ skillTime = calcAtkSpd(creature, skill, skillTime);
+ }
+ // Calculate the Casting Time of Magic Skills (reduced in 40% if using SPS/BSPS)
+ if (skill.isMagic() && (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)))
+ {
+ skillTime = (int) (0.6 * skillTime);
+ }
+
+ return Math.max(skillTime, 0);
+ }
+
+ public static int calcSkillCancelTime(L2Character creature, Skill skill)
+ {
+ // Fishing skills.
+ if ((skill.getId() == 1312) || (skill.getId() == 1314) || (skill.getId() == 1315))
+ {
+ return 0;
+ }
+ // return (int) Math.max(skill.getCancelTime() / calcSkillTimeFactor(attacker, skill), 500);
+ return (int) Math.max(skill.getHitTime() / calcSkillTimeFactor(creature, skill), SKILL_LAUNCH_TIME);
+ }
+
+ /**
+ * TODO: Implement armor bonus and NPC Divider
+ * @param creature
+ * @param skill
+ * @return
+ */
+ public static double calcSkillTimeFactor(L2Character creature, Skill skill)
+ {
+ double factor = 0;
+ if (skill.isPhysical() || skill.isDance()) // is_magic = 0 or 3
+ {
+ final double armorBonus = 1; // EquipedArmorSpeedByCrystal TODO: Implement me!
+ final double dexBonus = BaseStats.DEX.calcBonus(creature);
+ final double weaponAttackSpeed = Stats.weaponBaseValue(creature, Stats.PHYSICAL_ATTACK_SPEED) / armorBonus; // unk868
+ final double attackSpeedPerBonus = creature.getStat().getMul(Stats.PHYSICAL_ATTACK_SPEED);
+ final double attackSpeedDiffBonus = creature.getStat().getAdd(Stats.PHYSICAL_ATTACK_SPEED);
+ factor = (dexBonus * (weaponAttackSpeed / 333) * attackSpeedPerBonus) + (attackSpeedDiffBonus / 333);
+ }
+ else if (skill.isMagic()) // is_magic = 1
+ {
+ final double armorBonus = 1; // TODO: Implement me!
+ final double witBonus = BaseStats.WIT.calcBonus(creature);
+ final double castingSpeedPerBonus = creature.getStat().getMul(Stats.MAGIC_ATTACK_SPEED); // m_use_speed
+ final double castingSpeedDiffBonus = creature.getStat().getAdd(Stats.MAGIC_ATTACK_SPEED);
+ factor = ((1 / armorBonus) * witBonus * castingSpeedPerBonus) + (castingSpeedDiffBonus / 333);
+ }
+ else if (skill.isStatic()) // is_magic = 2
+ {
+ factor = 1;
+ }
+
+ if (skill.isChanneling()) // operate type = 5 or 6 or 7
+ {
+ factor = 1;
+ }
+
+ if (creature.isNpc() || creature.isSummon())
+ {
+ // TODO: Implement me!
+ // if (attacker.unk08B0 > 0)
+ {
+ // factor /= attacker.unk08B0;
+ }
+ }
+
+ return Math.max(factor, 0.01);
+ }
+
+ /**
+ * Formula based on http://l2p.l2wh.com/nonskillattacks.html
+ * @param attacker
+ * @param target
+ * @return {@code true} if hit missed (target evaded), {@code false} otherwise.
+ */
+ public static boolean calcHitMiss(L2Character attacker, L2Character target)
+ {
+ int chance = (80 + (2 * (attacker.getAccuracy() - target.getEvasionRate()))) * 10;
+
+ // Get additional bonus from the conditions when you are attacking
+ chance *= HitConditionBonusData.getInstance().getConditionBonus(attacker, target);
+
+ chance = Math.max(chance, 200);
+ chance = Math.min(chance, 980);
+
+ return chance < Rnd.get(1000);
+ }
+
+ /**
+ * Returns:
+ * 0 = shield defense doesn't succeed
+ * 1 = shield defense succeed
+ * 2 = perfect block
+ * @param attacker
+ * @param target
+ * @param sendSysMsg
+ * @return
+ */
+ public static byte calcShldUse(L2Character attacker, L2Character target, boolean sendSysMsg)
+ {
+ final L2Item item = target.getSecondaryWeaponItem();
+ if ((item == null) || !(item instanceof L2Armor) || (((L2Armor) item).getItemType() == ArmorType.SIGIL))
+ {
+ return 0;
+ }
+
+ double shldRate = target.getStat().getValue(Stats.SHIELD_DEFENCE_RATE, 0) * BaseStats.DEX.calcBonus(target);
+
+ // if attacker use bow and target wear shield, shield block rate is multiplied by 1.3 (30%)
+ if (attacker.getAttackType().isRanged())
+ {
+ shldRate *= 1.3;
+ }
+
+ final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
+ if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ {
+ return 0;
+ }
+
+ byte shldSuccess = SHIELD_DEFENSE_FAILED;
+
+ // Check shield success
+ if (shldRate > Rnd.get(100))
+ {
+ // If shield succeed, check perfect block.
+ if (((100 - (2 * BaseStats.DEX.calcBonus(target))) < Rnd.get(100)))
+ {
+ shldSuccess = SHIELD_DEFENSE_PERFECT_BLOCK;
+ }
+ else
+ {
+ shldSuccess = SHIELD_DEFENSE_SUCCEED;
+ }
+ }
+
+ if (sendSysMsg && target.isPlayer())
+ {
+ final L2PcInstance enemy = target.getActingPlayer();
+
+ switch (shldSuccess)
+ {
+ case SHIELD_DEFENSE_SUCCEED:
+ {
+ enemy.sendPacket(SystemMessageId.YOUR_SHIELD_DEFENSE_HAS_SUCCEEDED);
+ break;
+ }
+ case SHIELD_DEFENSE_PERFECT_BLOCK:
+ {
+ enemy.sendPacket(SystemMessageId.YOUR_EXCELLENT_SHIELD_DEFENSE_WAS_A_SUCCESS);
+ break;
+ }
+ }
+ }
+
+ return shldSuccess;
+ }
+
+ public static byte calcShldUse(L2Character attacker, L2Character target)
+ {
+ return calcShldUse(attacker, target, true);
+ }
+
+ public static boolean calcMagicAffected(L2Character actor, L2Character target, Skill skill)
+ {
+ // TODO: CHECK/FIX THIS FORMULA UP!!
+ double defence = 0;
+ if (skill.isActive() && skill.isBad())
+ {
+ defence = target.getMDef();
+ }
+
+ final double attack = 2 * actor.getMAtk() * calcGeneralTraitBonus(actor, target, skill.getTraitType(), false);
+ double d = (attack - defence) / (attack + defence);
+
+ if (skill.isDebuff())
+ {
+ if (target.getAbnormalShieldBlocks() > 0)
+ {
+ if (target.decrementAbnormalShieldBlocks() == 0)
+ {
+ target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
+ }
+ return false;
+ }
+ }
+
+ d += 0.5 * Rnd.nextGaussian();
+ return d > 0;
+ }
+
+ public static double calcLvlBonusMod(L2Character attacker, L2Character target, Skill skill)
+ {
+ final int attackerLvl = skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel();
+ final double skillLvlBonusRateMod = 1 + (skill.getLvlBonusRate() / 100.);
+ final double lvlMod = 1 + ((attackerLvl - target.getLevel()) / 100.);
+ return skillLvlBonusRateMod * lvlMod;
+ }
+
+ /**
+ * Calculates the effect landing success.
+ * @param attacker the attacker
+ * @param target the target
+ * @param skill the skill
+ * @return {@code true} if the effect lands
+ */
+ public static boolean calcEffectSuccess(L2Character attacker, L2Character target, Skill skill)
+ {
+ // StaticObjects can not receive continuous effects.
+ if (target.isDoor() || (target instanceof L2SiegeFlagInstance) || (target instanceof L2StaticObjectInstance))
+ {
+ return false;
+ }
+
+ if (skill.isDebuff())
+ {
+ boolean resisted = target.isCastingNow(s -> s.getSkill().getAbnormalResists().contains(skill.getAbnormalType()));
+ if (!resisted)
+ {
+ if (target.getAbnormalShieldBlocks() > 0)
+ {
+ if (target.decrementAbnormalShieldBlocks() == 0)
+ {
+ target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
+ }
+ resisted = true;
+ }
+ }
+
+ if (!resisted)
+ {
+ final double distance = attacker.calculateDistance(target, true, false);
+ if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
+ {
+ resisted = true;
+ }
+ }
+
+ if (resisted)
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
+ sm.addCharName(target);
+ sm.addSkillName(skill);
+ attacker.sendPacket(sm);
+ return false;
+ }
+ }
+
+ final int activateRate = skill.getActivateRate();
+ if ((activateRate == -1))
+ {
+ return true;
+ }
+
+ int magicLevel = skill.getMagicLevel();
+ if (magicLevel <= -1)
+ {
+ magicLevel = target.getLevel() + 3;
+ }
+
+ final double targetBasicProperty = getAbnormalResist(skill.getBasicProperty(), target);
+ final double baseMod = ((((((magicLevel - target.getLevel()) + 3) * skill.getLvlBonusRate()) + activateRate) + 30.0) - targetBasicProperty);
+ final double elementMod = calcAttributeBonus(attacker, target, skill);
+ final double traitMod = calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
+ final double basicPropertyResist = getBasicPropertyResistBonus(skill.getBasicProperty(), target);
+ final double buffDebuffMod = skill.isDebuff() ? target.getStat().getValue(Stats.RESIST_ABNORMAL_DEBUFF, 1) : 0;
+ final double rate = baseMod * elementMod * traitMod * buffDebuffMod;
+ final double finalRate = traitMod > 0 ? CommonUtil.constrain(rate, skill.getMinChance(), skill.getMaxChance()) * basicPropertyResist : 0;
+
+ if (attacker.isDebug())
+ {
+ final StatsSet set = new StatsSet();
+ set.set("baseMod", baseMod);
+ set.set("elementMod", elementMod);
+ set.set("traitMod", traitMod);
+ set.set("buffDebuffMod", buffDebuffMod);
+ set.set("rate", rate);
+ set.set("finalRate", finalRate);
+ Debug.sendSkillDebug(attacker, target, skill, set);
+ }
+
+ if ((finalRate <= Rnd.get(100)) && (target != attacker))
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_HAS_RESISTED_YOUR_S2);
+ sm.addCharName(target);
+ sm.addSkillName(skill);
+ attacker.sendPacket(sm);
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean calcCubicSkillSuccess(CubicInstance attacker, L2Character target, Skill skill, byte shld)
+ {
+ if (skill.isDebuff())
+ {
+ if (skill.getActivateRate() == -1)
+ {
+ return true;
+ }
+
+ if (target.getAbnormalShieldBlocks() > 0)
+ {
+ if (target.decrementAbnormalShieldBlocks() == 0)
+ {
+ target.stopEffects(EffectFlag.ABNORMAL_SHIELD);
+ }
+ return false;
+ }
+ }
+
+ // Perfect Shield Block.
+ if (shld == SHIELD_DEFENSE_PERFECT_BLOCK)
+ {
+ return false;
+ }
+
+ // if target reflect this skill then the effect will fail
+ if (calcBuffDebuffReflection(target, skill))
+ {
+ return false;
+ }
+
+ final double targetBasicProperty = getAbnormalResist(skill.getBasicProperty(), target);
+
+ // Calculate BaseRate.
+ final double baseRate = skill.getActivateRate();
+ final double statMod = 1 + (targetBasicProperty / 100);
+ double rate = (baseRate / statMod);
+
+ // Resist Modifier.
+ final double resMod = calcGeneralTraitBonus(attacker.getOwner(), target, skill.getTraitType(), false);
+ rate *= resMod;
+
+ // Lvl Bonus Modifier.
+ final double lvlBonusMod = calcLvlBonusMod(attacker.getOwner(), target, skill);
+ rate *= lvlBonusMod;
+
+ // Element Modifier.
+ final double elementMod = calcAttributeBonus(attacker.getOwner(), target, skill);
+ rate *= elementMod;
+
+ final double basicPropertyResist = getBasicPropertyResistBonus(skill.getBasicProperty(), target);
+
+ // Add Matk/Mdef Bonus (TODO: Pending)
+
+ // Check the Rate Limits.
+ final double finalRate = CommonUtil.constrain(rate, skill.getMinChance(), skill.getMaxChance()) * basicPropertyResist;
+
+ if (attacker.getOwner().isDebug())
+ {
+ final StatsSet set = new StatsSet();
+ set.set("baseMod", baseRate);
+ set.set("resMod", resMod);
+ set.set("statMod", statMod);
+ set.set("elementMod", elementMod);
+ set.set("lvlBonusMod", lvlBonusMod);
+ set.set("rate", rate);
+ set.set("finalRate", finalRate);
+ Debug.sendSkillDebug(attacker.getOwner(), target, skill, set);
+ }
+
+ return Rnd.get(100) < finalRate;
+ }
+
+ public static boolean calcMagicSuccess(L2Character attacker, L2Character target, Skill skill)
+ {
+ // FIXME: Fix this LevelMod Formula.
+ final int lvlDifference = (target.getLevel() - (skill.getMagicLevel() > 0 ? skill.getMagicLevel() : attacker.getLevel()));
+ final double lvlModifier = Math.pow(1.3, lvlDifference);
+ float targetModifier = 1;
+ if (target.isAttackable() && !target.isRaid() && !target.isRaidMinion() && (target.getLevel() >= Config.MIN_NPC_LVL_MAGIC_PENALTY) && (attacker.getActingPlayer() != null) && ((target.getLevel() - attacker.getActingPlayer().getLevel()) >= 3))
+ {
+ final int lvlDiff = target.getLevel() - attacker.getActingPlayer().getLevel() - 2;
+ if (lvlDiff >= Config.NPC_SKILL_CHANCE_PENALTY.size())
+ {
+ targetModifier = Config.NPC_SKILL_CHANCE_PENALTY.get(Config.NPC_SKILL_CHANCE_PENALTY.size() - 1);
+ }
+ else
+ {
+ targetModifier = Config.NPC_SKILL_CHANCE_PENALTY.get(lvlDiff);
+ }
+ }
+ // general magic resist
+ final double resModifier = target.getStat().getValue(Stats.MAGIC_SUCCESS_RES, 1);
+ final int rate = 100 - Math.round((float) (lvlModifier * targetModifier * resModifier));
+
+ if (attacker.isDebug())
+ {
+ final StatsSet set = new StatsSet();
+ set.set("lvlDifference", lvlDifference);
+ set.set("lvlModifier", lvlModifier);
+ set.set("resModifier", resModifier);
+ set.set("targetModifier", targetModifier);
+ set.set("rate", rate);
+ Debug.sendSkillDebug(attacker, target, skill, set);
+ }
+
+ return (Rnd.get(100) < rate);
+ }
+
+ public static double calcManaDam(L2Character attacker, L2Character target, Skill skill, double power, byte shld, boolean sps, boolean bss, boolean mcrit, double critLimit)
+ {
+ // Formula: (SQR(M.Atk)*Power*(Target Max MP/97))/M.Def
+ double mAtk = attacker.getMAtk();
+ double mDef = target.getMDef();
+ final double mp = target.getMaxMp();
+
+ switch (shld)
+ {
+ case SHIELD_DEFENSE_SUCCEED:
+ {
+ mDef += target.getShldDef();
+ break;
+ }
+ case SHIELD_DEFENSE_PERFECT_BLOCK: // perfect block
+ {
+ return 1;
+ }
+ }
+
+ // Bonus Spiritshot
+ final double shotsBonus = attacker.getStat().getValue(Stats.SHOTS_BONUS);
+ double sapphireBonus = 0;
+ if (attacker.isPlayer() && (attacker.getActingPlayer().getActiveShappireJewel() != null))
+ {
+ sapphireBonus = attacker.getActingPlayer().getActiveShappireJewel().getBonus();
+ }
+ mAtk *= bss ? 4 * (shotsBonus + sapphireBonus) : sps ? 2 * (shotsBonus + sapphireBonus) : 1;
+
+ double damage = (Math.sqrt(mAtk) * power * (mp / 97)) / mDef;
+ damage *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
+ damage *= calculatePvpPveBonus(attacker, target, skill, mcrit);
+
+ // Failure calculation
+ if (Config.ALT_GAME_MAGICFAILURES && !calcMagicSuccess(attacker, target, skill))
+ {
+ if (attacker.isPlayer())
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.DAMAGE_IS_DECREASED_BECAUSE_C1_RESISTED_C2_S_MAGIC);
+ sm.addCharName(target);
+ sm.addCharName(attacker);
+ attacker.sendPacket(sm);
+ damage /= 2;
+ }
+
+ if (target.isPlayer())
+ {
+ final SystemMessage sm2 = SystemMessage.getSystemMessage(SystemMessageId.C1_WEAKLY_RESISTED_C2_S_MAGIC);
+ sm2.addCharName(target);
+ sm2.addCharName(attacker);
+ target.sendPacket(sm2);
+ }
+ }
+
+ if (mcrit)
+ {
+ damage *= 3;
+ damage = Math.min(damage, critLimit);
+ attacker.sendPacket(SystemMessageId.M_CRITICAL);
+ }
+ return damage;
+ }
+
+ public static double calculateSkillResurrectRestorePercent(double baseRestorePercent, L2Character caster)
+ {
+ if ((baseRestorePercent == 0) || (baseRestorePercent == 100))
+ {
+ return baseRestorePercent;
+ }
+
+ double restorePercent = baseRestorePercent * BaseStats.WIT.calcBonus(caster);
+ if ((restorePercent - baseRestorePercent) > 20.0)
+ {
+ restorePercent += 20.0;
+ }
+
+ restorePercent = Math.max(restorePercent, baseRestorePercent);
+ restorePercent = Math.min(restorePercent, 90.0);
+
+ return restorePercent;
+ }
+
+ public static boolean calcPhysicalSkillEvasion(L2Character activeChar, L2Character target, Skill skill)
+ {
+ if (Rnd.get(100) < target.getStat().getSkillEvasionTypeValue(skill.getMagicType()))
+ {
+ if (activeChar.isPlayer())
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_DODGED_THE_ATTACK);
+ sm.addString(target.getName());
+ activeChar.getActingPlayer().sendPacket(sm);
+ }
+ if (target.isPlayer())
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_DODGED_C1_S_ATTACK);
+ sm.addString(activeChar.getName());
+ target.getActingPlayer().sendPacket(sm);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean calcSkillMastery(L2Character actor, Skill sk)
+ {
+ // Static Skills are not affected by Skill Mastery.
+ if (sk.isStatic() || !actor.isPlayer())
+ {
+ return false;
+ }
+
+ final int val = (int) actor.getStat().getValue(Stats.SKILL_CRITICAL, -1);
+
+ if (val == -1)
+ {
+ return false;
+ }
+
+ final double chance = BaseStats.values()[val].calcBonus(actor) * actor.getStat().getValue(Stats.SKILL_CRITICAL_PROBABILITY, 1);
+
+ return ((Rnd.nextDouble() * 100.) < chance);
+ }
+
+ /**
+ * Calculates the attribute bonus with the following formula:
+ * diff > 0, so AttBonus = 1,025 + sqrt[(diff^3) / 2] * 0,0001, cannot be above 1,25!
+ * diff < 0, so AttBonus = 0,975 - sqrt[(diff^3) / 2] * 0,0001, cannot be below 0,75!
+ * diff == 0, so AttBonus = 1
+ * @param attacker
+ * @param target
+ * @param skill Can be {@code null} if there is no skill used for the attack.
+ * @return The attribute bonus
+ */
+ public static double calcAttributeBonus(L2Character attacker, L2Character target, Skill skill)
+ {
+ int attack_attribute;
+ int defence_attribute;
+
+ if (skill != null)
+ {
+ if ((skill.getAttributeType() == AttributeType.NONE) || (skill.getAttributeType() == AttributeType.NONE_ARMOR))
+ {
+ attack_attribute = 0;
+ defence_attribute = target.getDefenseElementValue(AttributeType.NONE_ARMOR);
+ }
+ else if (attacker.getAttackElement() == skill.getAttributeType())
+ {
+ attack_attribute = attacker.getAttackElementValue(attacker.getAttackElement()) + skill.getAttributeValue();
+ defence_attribute = target.getDefenseElementValue(attacker.getAttackElement());
+ }
+ else
+ {
+ attack_attribute = skill.getAttributeValue();
+ defence_attribute = target.getDefenseElementValue(skill.getAttributeType());
+ }
+ }
+ else
+ {
+ attack_attribute = attacker.getAttackElementValue(attacker.getAttackElement());
+ defence_attribute = target.getDefenseElementValue(attacker.getAttackElement());
+ }
+
+ final int diff = attack_attribute - defence_attribute;
+ if (diff > 0)
+ {
+ return Math.min(1.025 + (Math.sqrt(Math.pow(diff, 3) / 2) * 0.0001), 1.25);
+ }
+ else if (diff < 0)
+ {
+ return Math.max(0.975 - (Math.sqrt(Math.pow(-diff, 3) / 2) * 0.0001), 0.75);
+ }
+
+ return 1;
+ }
+
+ public static void calcCounterAttack(L2Character attacker, L2Character target, Skill skill, boolean crit)
+ {
+ // Only melee skills can be reflected
+ if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE))
+ {
+ return;
+ }
+
+ final double chance = target.getStat().getValue(Stats.VENGEANCE_SKILL_PHYSICAL_DAMAGE, 0);
+ if (Rnd.get(100) < chance)
+ {
+ if (target.isPlayer())
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.YOU_COUNTERED_C1_S_ATTACK);
+ sm.addCharName(attacker);
+ target.sendPacket(sm);
+ }
+ if (attacker.isPlayer())
+ {
+ final SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.C1_IS_PERFORMING_A_COUNTERATTACK);
+ sm.addCharName(target);
+ attacker.sendPacket(sm);
+ }
+
+ double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
+ counterdmg *= calcWeaponTraitBonus(attacker, target);
+ counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
+ counterdmg *= calcAttributeBonus(attacker, target, skill);
+
+ attacker.reduceCurrentHp(counterdmg, target, skill);
+ }
+ }
+
+ /**
+ * Calculate buff/debuff reflection.
+ * @param target
+ * @param skill
+ * @return {@code true} if reflect, {@code false} otherwise.
+ */
+ public static boolean calcBuffDebuffReflection(L2Character target, Skill skill)
+ {
+ if (!skill.isDebuff() || (skill.getActivateRate() == -1))
+ {
+ return false;
+ }
+ return target.getStat().getValue(skill.isMagic() ? Stats.REFLECT_SKILL_MAGIC : Stats.REFLECT_SKILL_PHYSIC, 0) > Rnd.get(100);
+ }
+
+ /**
+ * Calculate damage caused by falling
+ * @param cha
+ * @param fallHeight
+ * @return damage
+ */
+ public static double calcFallDam(L2Character cha, int fallHeight)
+ {
+ if (!Config.ENABLE_FALLING_DAMAGE || (fallHeight < 0))
+ {
+ return 0;
+ }
+ return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
+ }
+
+ public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
+ {
+ final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
+ // double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
+ final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
+ final Position position = Position.getPosition(activeChar, target);
+ final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
+ final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
+ final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
+ blowChance = (weaponCritical + blowChance) * 10;
+
+ final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
+
+ // Blow rate is capped at 80%
+ return Rnd.get(1000) < Math.min(rate, 800);
+ }
+
+ public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
+ {
+ final List canceled = new ArrayList<>(max);
+ switch (slot)
+ {
+ case BUFF:
+ {
+ // Resist Modifier.
+ final int cancelMagicLvl = skill.getMagicLevel();
+ if (activeChar.isDebug())
+ {
+ final StatsSet set = new StatsSet();
+ set.set("baseMod", rate);
+ set.set("magicLevel", cancelMagicLvl);
+ set.set("resMod", target.getStat().getValue(Stats.RESIST_DISPEL_BUFF, 1));
+ set.set("rate", rate);
+ Debug.sendSkillDebug(activeChar, target, skill, set);
+ }
+
+ // Prevent initialization.
+ final List buffs = target.getEffectList().getBuffs();
+
+ for (int i = buffs.size() - 1; i >= 0; i--) // reverse order
+ {
+ final BuffInfo info = buffs.get(i);
+ if (!info.getSkill().canBeStolen() || ((rate < 100) && !calcCancelSuccess(info, cancelMagicLvl, rate, skill, target)))
+ {
+ continue;
+ }
+ canceled.add(info);
+ if (canceled.size() >= max)
+ {
+ break;
+ }
+ }
+ break;
+ }
+ case DEBUFF:
+ {
+ final List debuffs = target.getEffectList().getDebuffs();
+ for (int i = debuffs.size() - 1; i >= 0; i--)
+ {
+ final BuffInfo info = debuffs.get(i);
+ if (info.getSkill().canBeDispelled() && (Rnd.get(100) <= rate))
+ {
+ canceled.add(info);
+ if (canceled.size() >= max)
+ {
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ return canceled;
+ }
+
+ public static boolean calcCancelSuccess(BuffInfo info, int cancelMagicLvl, int rate, Skill skill, L2Character target)
+ {
+ final int chance = (int) (rate + ((cancelMagicLvl - info.getSkill().getMagicLevel()) * 2) + ((info.getAbnormalTime() / 120) * target.getStat().getValue(Stats.RESIST_DISPEL_BUFF, 1)));
+ return Rnd.get(100) < CommonUtil.constrain(chance, 25, 75); // TODO: i_dispel_by_slot_probability min = 40, max = 95.
+ }
+
+ /**
+ * Calculates the abnormal time for an effect.
+ * The abnormal time is taken from the skill definition, and it's global for all effects present in the skills.
+ * @param caster the caster
+ * @param target the target
+ * @param skill the skill
+ * @return the time that the effect will last
+ */
+ public static int calcEffectAbnormalTime(L2Character caster, L2Character target, Skill skill)
+ {
+ int time = (skill == null) || skill.isPassive() || skill.isToggle() ? -1 : skill.getAbnormalTime();
+
+ // If the skill is a mastery skill, the effect will last twice the default time.
+ if ((skill != null) && Formulas.calcSkillMastery(caster, skill))
+ {
+ time *= 2;
+ }
+
+ return time;
+ }
+
+ /**
+ * Calculate Probability in following effects:
+ * TargetCancel,
+ * TargetMeProbability,
+ * SkillTurning,
+ * Betray,
+ * Bluff,
+ * DeleteHate,
+ * RandomizeHate,
+ * DeleteHateOfMe,
+ * TransferHate,
+ * Confuse
+ * Knockback
+ * Pull
+ * @param baseChance chance from effect parameter
+ * @param attacker
+ * @param target
+ * @param skill
+ * @return chance for effect to succeed
+ */
+ public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
+ {
+ // Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
+ // TODO: Find more retail-like formula
+ return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
+ }
+
+ /**
+ * Calculates karma lost upon death.
+ * @param player
+ * @param finalExp
+ * @return the amount of karma player has loosed.
+ */
+ public static int calculateKarmaLost(L2PcInstance player, double finalExp)
+ {
+ final double karmaLooseMul = KarmaData.getInstance().getMultiplier(player.getLevel());
+ if (finalExp > 0) // Received exp
+ {
+ finalExp /= Config.RATE_KARMA_LOST;
+ }
+ return (int) ((Math.abs(finalExp) / karmaLooseMul) / 30);
+ }
+
+ /**
+ * Calculates karma gain upon playable kill.
+ * Updated to High Five on 10.09.2014 by Zealar tested in retail.
+ * @param pkCount
+ * @param isSummon
+ * @return karma points that will be added to the player.
+ */
+ public static int calculateKarmaGain(int pkCount, boolean isSummon)
+ {
+ int result = 43200;
+
+ if (isSummon)
+ {
+ result = (int) ((((pkCount * 0.375) + 1) * 60) * 4) - 150;
+
+ if (result > 10800)
+ {
+ return 10800;
+ }
+ }
+
+ if (pkCount < 99)
+ {
+ result = (int) ((((pkCount * 0.5) + 1) * 60) * 12);
+ }
+ else if (pkCount < 180)
+ {
+ result = (int) ((((pkCount * 0.125) + 37.75) * 60) * 12);
+ }
+
+ return result;
+ }
+
+ public static double calcGeneralTraitBonus(L2Character attacker, L2Character target, TraitType traitType, boolean ignoreResistance)
+ {
+ if (traitType == TraitType.NONE)
+ {
+ return 1.0;
+ }
+
+ if (target.getStat().isTraitInvul(traitType))
+ {
+ return 0;
+ }
+
+ switch (traitType.getType())
+ {
+ case 2:
+ {
+ if (!attacker.getStat().hasAttackTrait(traitType) || !target.getStat().hasDefenceTrait(traitType))
+ {
+ return 1.0;
+ }
+ break;
+ }
+ case 3:
+ {
+ if (ignoreResistance)
+ {
+ return 1.0;
+ }
+ break;
+ }
+ default:
+ {
+ return 1.0;
+ }
+ }
+
+ final double result = (attacker.getStat().getAttackTrait(traitType) - target.getStat().getDefenceTrait(traitType)) + 1.0;
+ return CommonUtil.constrain(result, 0.05, 2.0);
+ }
+
+ public static double calcWeaponTraitBonus(L2Character attacker, L2Character target)
+ {
+ final TraitType type = attacker.getAttackType().getTraitType();
+ final double result = target.getStat().getDefenceTraits()[type.getId()] - 1.0;
+ return 1.0 - result;
+ }
+
+ public static double calcAttackTraitBonus(L2Character attacker, L2Character target)
+ {
+ final double weaponTraitBonus = calcWeaponTraitBonus(attacker, target);
+ if (weaponTraitBonus == 0)
+ {
+ return 0;
+ }
+
+ double weaknessBonus = 1.0;
+ for (TraitType traitType : TraitType.values())
+ {
+ if (traitType.getType() == 2)
+ {
+ weaknessBonus *= calcGeneralTraitBonus(attacker, target, traitType, true);
+ if (weaknessBonus == 0)
+ {
+ return 0;
+ }
+ }
+ }
+
+ return CommonUtil.constrain((weaponTraitBonus * weaknessBonus), 0.05, 2.0);
+ }
+
+ public static double getBasicPropertyResistBonus(BasicProperty basicProperty, L2Character target)
+ {
+ if ((basicProperty == BasicProperty.NONE) || !target.hasBasicPropertyResist())
+ {
+ return 1.0;
+ }
+
+ final BasicPropertyResist resist = target.getBasicPropertyResist(basicProperty);
+ switch (resist.getResistLevel())
+ {
+ case 0:
+ {
+ return 1.0;
+ }
+ case 1:
+ {
+ return 0.6;
+ }
+ case 2:
+ {
+ return 0.3;
+ }
+ default:
+ {
+ return 0;
+ }
+ }
+ }
+
+ /**
+ * 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 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, byte shld, boolean crit, boolean ss)
+ {
+ final double distance = attacker.calculateDistance(target, true, false);
+
+ if (distance > target.getStat().getValue(Stats.SPHERIC_BARRIER_RANGE, Integer.MAX_VALUE))
+ {
+ return 0;
+ }
+
+ // DEFENCE CALCULATION (pDef + sDef)
+ double defence = target.getPDef();
+
+ switch (shld)
+ {
+ case SHIELD_DEFENSE_SUCCEED:
+ {
+ defence += target.getShldDef();
+ break;
+ }
+ case SHIELD_DEFENSE_PERFECT_BLOCK:
+ {
+ return 1.;
+ }
+ }
+
+ final L2Weapon weapon = attacker.getActiveWeaponItem();
+ final boolean isRanged = (weapon != null) && weapon.getItemType().isRanged();
+ final double shotsBonus = attacker.getStat().getValue(Stats.SHOTS_BONUS);
+
+ final double cAtk = crit ? (2 * calcCritDamage(attacker, target, null)) : 1;
+ final double cAtkAdd = crit ? calcCritDamageAdd(attacker, target, null) : 0;
+ final double critMod = crit ? (isRanged ? 0.5 : 1) : 0;
+ final double ssBonus = ss ? 2 * shotsBonus : 1;
+ final double random_damage = attacker.getRandomDamageMultiplier();
+ final double proxBonus = (attacker.isInFrontOf(target) ? 0 : (attacker.isBehind(target) ? 0.2 : 0.05)) * attacker.getPAtk();
+ double attack = (attacker.getPAtk() * random_damage) + proxBonus;
+
+ // ....................______________Critical Section___________________...._______Non-Critical Section______
+ // ATTACK CALCULATION (((pAtk * cAtk * ss + cAtkAdd) * crit) * weaponMod) + (pAtk (1 - crit) * ss * weaponMod)
+ // ````````````````````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^````^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ attack = ((((attack * cAtk * ssBonus) + cAtkAdd) * critMod) * (isRanged ? 154 : 77)) + (attack * (1 - critMod) * ssBonus * (isRanged ? 154 : 77));
+
+ // DAMAGE CALCULATION (ATTACK / DEFENCE) * trait bonus * attr bonus * pvp bonus * pve bonus
+ double damage = attack / defence;
+ damage *= calcAttackTraitBonus(attacker, target);
+ damage *= calcAttributeBonus(attacker, target, null);
+ damage *= calculatePvpPveBonus(attacker, target, null, crit);
+
+ damage = Math.max(0, damage);
+
+ return damage;
+ }
+
+ public static double getAbnormalResist(BasicProperty basicProperty, L2Character target)
+ {
+ switch (basicProperty)
+ {
+ case PHYSICAL:
+ {
+ return target.getStat().getValue(Stats.ABNORMAL_RESIST_PHYSICAL);
+ }
+ case MAGIC:
+ {
+ return target.getStat().getValue(Stats.ABNORMAL_RESIST_MAGICAL);
+ }
+ default:
+ {
+ return 0;
+ }
+ }
+ }
+
+ public static double calcPveDamagePenalty(L2Character attacker, L2Character target, Skill skill, boolean crit)
+ {
+ if (target.isAttackable() && (target.getLevel() >= Config.MIN_NPC_LVL_DMG_PENALTY) && (attacker.getActingPlayer() != null) && ((target.getLevel() - attacker.getActingPlayer().getLevel()) > 1))
+ {
+ final int lvlDiff = target.getLevel() - attacker.getActingPlayer().getLevel() - 1;
+ if (skill != null)
+ {
+ return Config.NPC_SKILL_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_SKILL_DMG_PENALTY.size() - 1));
+ }
+ else if (crit)
+ {
+ return Config.NPC_CRIT_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_CRIT_DMG_PENALTY.size() - 1));
+ }
+
+ return Config.NPC_DMG_PENALTY.get(Math.min(lvlDiff, Config.NPC_DMG_PENALTY.size() - 1));
+ }
+
+ return 1.0;
+ }
+
+ /**
+ * Calculates if the specified creature can get its stun effect removed due to damage taken.
+ * @param activeChar the character to be checked
+ * @return {@code true} if character should get its stun effects removed, {@code false} otherwise.
+ */
+ public static boolean calcStunBreak(L2Character activeChar)
+ {
+ // Check if target is stunned and 10% chance (retail is 14% and 35% on crit?)
+ if (activeChar.hasBlockActions() && (Rnd.get(10) == 0))
+ {
+ // Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time.
+ return activeChar.getEffectList().hasAbnormalType(AbnormalType.STUN, info -> info.getTime() <= info.getSkill().getAbnormalTime());
+ }
+ return false;
+ }
+
+ public static boolean calcRealTargetBreak()
+ {
+ // Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
+ return Rnd.get(100) <= 3;
+ }
+
+ /**
+ * @param attackSpeed the attack speed of the Creature.
+ * @return {@code 500000 / attackSpeed}.
+ */
+ public static int calculateTimeBetweenAttacks(int attackSpeed)
+ {
+ // Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
+ return Math.max(50, (500000 / attackSpeed));
+ }
+
+ /**
+ * @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 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:
+ case CROSSBOW:
+ case TWOHANDCROSSBOW:
+ {
+ return (int) (totalAttackTime * 0.95);
+ }
+ case DUALBLUNT:
+ case DUALDAGGER:
+ case DUAL:
+ case DUALFIST:
+ {
+ 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);
+ }
+ }
+ }
+
+ /**
+ * @param activeChar
+ * @param weapon
+ * @return {@code (500_000 millis + 333 * WeaponItemReuseDelay) / PAttackSpeed}
+ */
+ public static int calculateReuseTime(L2Character activeChar, L2Weapon weapon)
+ {
+ if (weapon == null)
+ {
+ return 0;
+ }
+
+ final WeaponType defaultAttackType = weapon.getItemType();
+ final WeaponType weaponType = activeChar.getTransformation().map(transform -> transform.getBaseAttackType(activeChar, defaultAttackType)).orElse(defaultAttackType);
+ int reuse = weapon.getReuseDelay();
+
+ // only bows should continue for now
+ if ((reuse == 0) || !weaponType.isRanged())
+ {
+ return 0;
+ }
+
+ reuse *= activeChar.getStat().getWeaponReuseModifier();
+ double atkSpd = activeChar.getStat().getPAtkSpd();
+
+ return (int) ((500000 + (333 * reuse)) / atkSpd);
+ }
+
+ public static double calculatePvpPveBonus(L2Character attacker, L2Character target, Skill skill, boolean crit)
+ {
+ // PvP bonus
+ if (attacker.isPlayable() && target.isPlayable())
+ {
+ final double pvpAttack;
+ final double pvpDefense;
+ if (skill != null)
+ {
+ if (skill.isMagic())
+ {
+ // Magical Skill PvP
+ pvpAttack = attacker.getStat().getValue(Stats.PVP_MAGICAL_SKILL_DAMAGE, 1);
+ pvpDefense = target.getStat().getValue(Stats.PVP_MAGICAL_SKILL_DEFENCE, 1);
+ }
+ else
+ {
+ // Physical Skill PvP
+ pvpAttack = attacker.getStat().getValue(Stats.PVP_PHYSICAL_SKILL_DAMAGE, 1);
+ pvpDefense = target.getStat().getValue(Stats.PVP_PHYSICAL_SKILL_DEFENCE, 1);
+ }
+ }
+ else
+ {
+ // Autoattack PvP
+ pvpAttack = attacker.getStat().getValue(Stats.PVP_PHYSICAL_ATTACK_DAMAGE, 1);
+ pvpDefense = target.getStat().getValue(Stats.PVP_PHYSICAL_ATTACK_DEFENCE, 1);
+ }
+
+ return 1 + (pvpAttack - pvpDefense);
+ }
+
+ // PvE Bonus
+ if (target.isAttackable() || attacker.isAttackable())
+ {
+ final double pveAttack;
+ final double pveDefense;
+ final double pveRaidDefense;
+ final double pvePenalty = calcPveDamagePenalty(attacker, target, skill, crit);
+
+ if (skill != null)
+ {
+ if (skill.isMagic())
+ {
+ // Magical Skill PvE
+ pveAttack = attacker.getStat().getValue(Stats.PVE_MAGICAL_SKILL_DAMAGE, 1);
+ pveDefense = target.getStat().getValue(Stats.PVE_MAGICAL_SKILL_DEFENCE, 1);
+ pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_MAGICAL_SKILL_DEFENCE, 1) : 1;
+ }
+ else
+ {
+ // Physical Skill PvE
+ pveAttack = attacker.getStat().getValue(Stats.PVE_PHYSICAL_SKILL_DAMAGE, 1);
+ pveDefense = target.getStat().getValue(Stats.PVE_PHYSICAL_SKILL_DEFENCE, 1);
+ pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_PHYSICAL_SKILL_DEFENCE, 1) : 1;
+ }
+ }
+ else
+ {
+ // Autoattack PvE
+ pveAttack = attacker.getStat().getValue(Stats.PVE_PHYSICAL_ATTACK_DAMAGE, 1);
+ pveDefense = target.getStat().getValue(Stats.PVE_PHYSICAL_ATTACK_DEFENCE, 1);
+ pveRaidDefense = attacker.isRaid() ? attacker.getStat().getValue(Stats.PVE_RAID_PHYSICAL_ATTACK_DEFENCE, 1) : 1;
+ }
+
+ return (1 + (pveAttack - (pveDefense * pveRaidDefense))) * pvePenalty;
+ }
+
+ return 1;
+ }
+}
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 ee956d47ac..2442dac54d 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
@@ -37,7 +37,6 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
-import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -272,12 +271,8 @@ public final class Formulas
}
// Autoattack critical rate.
- // Even though, visible critical rate is capped to 500, you can reach higher than 50% chance with position and level modifiers.
- // TODO: Find retail-like calculation for criticalRateMod.
- final double criticalRateMod = (target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE, rate) + target.getStat().getValue(Stats.DEFENCE_CRITICAL_RATE_ADD, 0)) / 10;
- final double criticalLocBonus = calcCriticalPositionBonus(activeChar, target);
- final double criticalHeightBonus = calcCriticalHeightBonus(activeChar, target);
- rate = criticalLocBonus * criticalRateMod * criticalHeightBonus;
+ // It is capped to 500, but unbound by positional critical rate and level diff bonus.
+ rate *= activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.getPosition(activeChar, target));
// In retail, it appears that when you are higher level attacking lower level mobs, your critical rate is much higher.
// Level 91 attacking level 1 appear that nearly all hits are critical. Unconfirmed for skills and pvp.
@@ -291,37 +286,6 @@ public final class Formulas
return finalRate > Rnd.get(1000);
}
- /**
- * Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
- * @param activeChar the attacker.
- * @param target the target.
- * @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
- */
- public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
- {
- final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
- switch (position)
- {
- case SIDE: // 10% Critical Chance bonus when attacking from side.
- {
- return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
- }
- case BACK: // 30% Critical Chance bonus when attacking from back.
- {
- return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
- }
- default: // No Critical Chance bonus when attacking from front.
- {
- return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
- }
- }
- }
-
- public static double calcCriticalHeightBonus(ILocational from, ILocational target)
- {
- return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
- }
-
/**
* @param attacker
* @param target
@@ -1075,7 +1039,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target);
- counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
+ counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1112,37 +1076,21 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
}
- /**
- * Basic chance formula:
- *
- * - chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate
- * - weapon_critical = (12 for daggers)
- * - dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).
- * - crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.
- * - crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus
- * - effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.
- *
- * Chance cannot be higher than 80%.
- * @param activeChar
- * @param target
- * @param skill
- * @param chanceBoost
- * @return
- */
- public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
+ public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
{
- final L2Weapon weapon = activeChar.getActiveWeaponItem();
- final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
+ final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
- final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
- final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
- final double chanceBoostMod = (100 + chanceBoost) / 100;
+ final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
+ final Position position = Position.getPosition(activeChar, target);
+ final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
+ final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
+ blowChance = (weaponCritical + blowChance) * 10;
- final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
+ final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
// Blow rate is capped at 80%
- return Rnd.get(100) < Math.min(rate, 80);
+ return Rnd.get(1000) < Math.min(rate, 800);
}
public static List calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1252,12 +1200,6 @@ public final class Formulas
*/
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{
- // Skills without set probability should only test against trait invulnerability.
- if (Double.isNaN(baseChance))
- {
- return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
- }
-
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));