diff --git a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
index 51b3c7f53a..845a7cb1b0 100644
--- a/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
+++ b/L2J_Mobius_1.0_Ertheia/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
@@ -324,7 +324,7 @@ public final class BalokWarzone extends AbstractInstance
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon)
{
- if (!npc.isDead() && caster.isBehindTarget())
+ if (!npc.isDead() && caster.isBehind(npc))
{
final BuffInfo info = npc.getEffectList().getBuffInfoBySkillId(INVINCIBILITY_ACTIVATION.getSkillId());
if ((info != null) && (getRandom(100) < 40))
diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
index ee076bff4b..8f4bebba95 100644
--- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
+++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
import com.l2jmobius.commons.util.IGameXmlReader;
import com.l2jmobius.gameserver.GameTimeController;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
@@ -134,17 +135,23 @@ public final class HitConditionBonusData implements IGameXmlReader
}
// Get side bonus
- if (attacker.isBehindTarget(true))
+ switch (Position.getPosition(attacker, target))
{
- mod += backBonus;
- }
- else if (attacker.isInFrontOfTarget())
- {
- mod += frontBonus;
- }
- else
- {
- mod += sideBonus;
+ case SIDE:
+ {
+ mod += sideBonus;
+ break;
+ }
+ case BACK:
+ {
+ mod += backBonus;
+ break;
+ }
+ default:
+ {
+ mod += frontBonus;
+ break;
+ }
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/enums/Position.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/enums/Position.java
index 0e37ff3488..55ea6a6228 100644
--- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/enums/Position.java
+++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/enums/Position.java
@@ -16,7 +16,7 @@
*/
package com.l2jmobius.gameserver.enums;
-import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.interfaces.ILocational;
/**
* @author Sdw
@@ -27,8 +27,32 @@ public enum Position
SIDE,
BACK;
- public static Position getPosition(L2Character effector, L2Character effected)
+ /**
+ * Position calculation based on the retail-like formulas:
+ *
+ * - heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
+ * - side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
+ * - front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
+ * - back: otherwise.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public static Position getPosition(ILocational from, ILocational to)
{
- return effector.isInFrontOf(effected) ? FRONT : (effector.isBehind(effected) ? BACK : SIDE);
+ final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to));
+ if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000))
+ {
+ return SIDE;
+ }
+ else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000)
+ {
+ return FRONT;
+ }
+ else
+ {
+ return BACK;
+ }
}
}
diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java
index 9fe3e3c542..eee62b70f8 100644
--- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java
+++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/actor/L2Character.java
@@ -55,6 +55,7 @@ import com.l2jmobius.gameserver.enums.BasicProperty;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemSkillType;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
@@ -315,11 +316,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
setInstanceType(InstanceType.L2Character);
- initCharStat();
- initCharStatus();
-
// Set its template to the new L2Character
_template = template;
+ initCharStat();
+ initCharStatus();
if (isNpc())
{
@@ -4048,125 +4048,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
- /**
- * @param target
- * @return True if the L2Character is behind the target and can't be seen.
- */
- public boolean isBehind(L2Object target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
-
- if (target == null)
- {
- return false;
- }
-
- if (target.isCharacter())
- {
- final L2Character target1 = (L2Character) target;
- angleChar = Util.calculateAngleFrom(this, target1);
- angleTarget = Util.convertHeadingToDegree(target1.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- if (Math.abs(angleDiff) <= maxAngleDiff)
- {
- return true;
- }
- }
- return false;
- }
-
- public boolean isBehindTarget()
- {
- return isBehind(getTarget());
- }
-
- /**
- * @param isAttacking if its an attack to be check, or the character itself.
- * @return
- */
- public boolean isBehindTarget(boolean isAttacking)
- {
- if (isAttacking && isAffected(EffectFlag.ATTACK_BEHIND))
- {
- return true;
- }
-
- return isBehind(getTarget());
- }
-
- /**
- * @param target
- * @return True if the target is facing the L2Character.
- */
- public boolean isInFrontOf(L2Character target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
- if (target == null)
- {
- return false;
- }
-
- angleTarget = Util.calculateAngleFrom(target, this);
- angleChar = Util.convertHeadingToDegree(target.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- /**
- * @param target
- * @param maxAngle
- * @return true if target is in front of L2Character (shield def etc)
- */
- public boolean isFacing(L2Object target, int maxAngle)
- {
- double angleChar, angleTarget, angleDiff, maxAngleDiff;
- if (target == null)
- {
- return false;
- }
- maxAngleDiff = maxAngle / 2.;
- angleTarget = Util.calculateAngleFrom(this, target);
- angleChar = Util.convertHeadingToDegree(getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- public boolean isInFrontOfTarget()
- {
- final L2Object target = getTarget();
- if (target instanceof L2Character)
- {
- return isInFrontOf((L2Character) target);
- }
- return false;
- }
-
/**
* @return the Level Modifier ((level + 89) / 100).
*/
@@ -4422,11 +4303,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
if (!reflect && !isDOT)
{
- // RearDamage effect bonus.
- if (isBehind(target))
- {
- damage *= getStat().getValue(Stats.REAR_DAMAGE_RATE, 1);
- }
+ damage *= getStat().getPositionTypeValue(Stats.ATTACK_DAMAGE, Position.getPosition(this, target));
// Counterattacks happen before damage received.
if (!target.isDead() && (skill != null))
diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
index 4be5a3fc3f..7d78918857 100644
--- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
+++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
@@ -78,11 +78,11 @@ public class ConditionPlayerState extends Condition
}
case BEHIND:
{
- return (effector.isBehindTarget() == _required);
+ return (effector.isBehind(effected) == _required);
}
case FRONT:
{
- return (effector.isInFrontOfTarget() == _required);
+ return (effector.isInFrontOf(effected) == _required);
}
case CHAOTIC:
{
diff --git a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
index a2bf433488..e8bed0399f 100644
--- a/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
+++ b/L2J_Mobius_1.0_Ertheia/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
@@ -16,6 +16,9 @@
*/
package com.l2jmobius.gameserver.model.interfaces;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.util.Util;
+
/**
* Object world location storage interface.
* @author xban1x
@@ -51,4 +54,40 @@ public interface ILocational
* @return a {@link ILocational} object containing the current position of this object
*/
ILocational getLocation();
+
+ /**
+ * @param to
+ * @return the heading to the target specified
+ */
+ default int calculateHeadingTo(ILocational to)
+ {
+ return Util.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY());
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in front of the target location based on the game's concept of position.
+ */
+ default boolean isInFrontOf(ILocational target)
+ {
+ return Position.FRONT.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position.
+ */
+ default boolean isOnSideOf(ILocational target)
+ {
+ return Position.SIDE.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is behind the target location based on the game's concept of position.
+ */
+ default boolean isBehind(ILocational target)
+ {
+ return Position.BACK.equals(Position.getPosition(this, target));
+ }
}
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 a021d55681..f41aefc16a 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
@@ -49,6 +49,7 @@ import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.SkillCaster;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+import com.l2jmobius.gameserver.util.Util;
/**
* Global calculations.
@@ -227,41 +228,43 @@ public final class Formulas
// 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);
+ byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL);
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
+ statBonus = BaseStats.values()[skillCritRateStat].calcBonus(activeChar);
}
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;
+ final double rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL, 1);
+ 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));
+ // 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())
+ // Autoattack critical depends on level difference at high levels as well.
+ if ((activeChar.getLevel() >= 78) || (target.getLevel() >= 78))
{
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
+ rate = rate + (Math.sqrt(activeChar.getLevel()) * (activeChar.getLevel() - target.getLevel()) * 0.125);
}
- 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);
+ // Autoattack critical rate is limited between 3%-97%.
+ rate = CommonUtil.constrain(rate, 3, 97);
+
+ return rate > Rnd.get(100);
}
/**
@@ -462,7 +465,7 @@ public final class Formulas
double factor = 0.0;
if (skill.getMagicType() == 1)
{
- final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values
+ final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement propper values
factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time)
}
else
@@ -538,7 +541,7 @@ public final class Formulas
}
final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ if ((degreeside < 360) && (Math.abs(target.calculateDirectionTo(attacker) - Util.convertHeadingToDegree(target.getHeading())) > (degreeside / 2)))
{
return 0;
}
diff --git a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
index 51b3c7f53a..845a7cb1b0 100644
--- a/L2J_Mobius_2.5_Underground/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
+++ b/L2J_Mobius_2.5_Underground/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
@@ -324,7 +324,7 @@ public final class BalokWarzone extends AbstractInstance
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon)
{
- if (!npc.isDead() && caster.isBehindTarget())
+ if (!npc.isDead() && caster.isBehind(npc))
{
final BuffInfo info = npc.getEffectList().getBuffInfoBySkillId(INVINCIBILITY_ACTIVATION.getSkillId());
if ((info != null) && (getRandom(100) < 40))
diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
index ee076bff4b..8f4bebba95 100644
--- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
+++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
import com.l2jmobius.commons.util.IGameXmlReader;
import com.l2jmobius.gameserver.GameTimeController;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
@@ -134,17 +135,23 @@ public final class HitConditionBonusData implements IGameXmlReader
}
// Get side bonus
- if (attacker.isBehindTarget(true))
+ switch (Position.getPosition(attacker, target))
{
- mod += backBonus;
- }
- else if (attacker.isInFrontOfTarget())
- {
- mod += frontBonus;
- }
- else
- {
- mod += sideBonus;
+ case SIDE:
+ {
+ mod += sideBonus;
+ break;
+ }
+ case BACK:
+ {
+ mod += backBonus;
+ break;
+ }
+ default:
+ {
+ mod += frontBonus;
+ break;
+ }
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/enums/Position.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/enums/Position.java
index 0e37ff3488..55ea6a6228 100644
--- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/enums/Position.java
+++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/enums/Position.java
@@ -16,7 +16,7 @@
*/
package com.l2jmobius.gameserver.enums;
-import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.interfaces.ILocational;
/**
* @author Sdw
@@ -27,8 +27,32 @@ public enum Position
SIDE,
BACK;
- public static Position getPosition(L2Character effector, L2Character effected)
+ /**
+ * Position calculation based on the retail-like formulas:
+ *
+ * - heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
+ * - side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
+ * - front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
+ * - back: otherwise.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public static Position getPosition(ILocational from, ILocational to)
{
- return effector.isInFrontOf(effected) ? FRONT : (effector.isBehind(effected) ? BACK : SIDE);
+ final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to));
+ if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000))
+ {
+ return SIDE;
+ }
+ else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000)
+ {
+ return FRONT;
+ }
+ else
+ {
+ return BACK;
+ }
}
}
diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java
index 9fe3e3c542..eee62b70f8 100644
--- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java
+++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/actor/L2Character.java
@@ -55,6 +55,7 @@ import com.l2jmobius.gameserver.enums.BasicProperty;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemSkillType;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
@@ -315,11 +316,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
setInstanceType(InstanceType.L2Character);
- initCharStat();
- initCharStatus();
-
// Set its template to the new L2Character
_template = template;
+ initCharStat();
+ initCharStatus();
if (isNpc())
{
@@ -4048,125 +4048,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
- /**
- * @param target
- * @return True if the L2Character is behind the target and can't be seen.
- */
- public boolean isBehind(L2Object target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
-
- if (target == null)
- {
- return false;
- }
-
- if (target.isCharacter())
- {
- final L2Character target1 = (L2Character) target;
- angleChar = Util.calculateAngleFrom(this, target1);
- angleTarget = Util.convertHeadingToDegree(target1.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- if (Math.abs(angleDiff) <= maxAngleDiff)
- {
- return true;
- }
- }
- return false;
- }
-
- public boolean isBehindTarget()
- {
- return isBehind(getTarget());
- }
-
- /**
- * @param isAttacking if its an attack to be check, or the character itself.
- * @return
- */
- public boolean isBehindTarget(boolean isAttacking)
- {
- if (isAttacking && isAffected(EffectFlag.ATTACK_BEHIND))
- {
- return true;
- }
-
- return isBehind(getTarget());
- }
-
- /**
- * @param target
- * @return True if the target is facing the L2Character.
- */
- public boolean isInFrontOf(L2Character target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
- if (target == null)
- {
- return false;
- }
-
- angleTarget = Util.calculateAngleFrom(target, this);
- angleChar = Util.convertHeadingToDegree(target.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- /**
- * @param target
- * @param maxAngle
- * @return true if target is in front of L2Character (shield def etc)
- */
- public boolean isFacing(L2Object target, int maxAngle)
- {
- double angleChar, angleTarget, angleDiff, maxAngleDiff;
- if (target == null)
- {
- return false;
- }
- maxAngleDiff = maxAngle / 2.;
- angleTarget = Util.calculateAngleFrom(this, target);
- angleChar = Util.convertHeadingToDegree(getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- public boolean isInFrontOfTarget()
- {
- final L2Object target = getTarget();
- if (target instanceof L2Character)
- {
- return isInFrontOf((L2Character) target);
- }
- return false;
- }
-
/**
* @return the Level Modifier ((level + 89) / 100).
*/
@@ -4422,11 +4303,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
if (!reflect && !isDOT)
{
- // RearDamage effect bonus.
- if (isBehind(target))
- {
- damage *= getStat().getValue(Stats.REAR_DAMAGE_RATE, 1);
- }
+ damage *= getStat().getPositionTypeValue(Stats.ATTACK_DAMAGE, Position.getPosition(this, target));
// Counterattacks happen before damage received.
if (!target.isDead() && (skill != null))
diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
index 4be5a3fc3f..7d78918857 100644
--- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
+++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
@@ -78,11 +78,11 @@ public class ConditionPlayerState extends Condition
}
case BEHIND:
{
- return (effector.isBehindTarget() == _required);
+ return (effector.isBehind(effected) == _required);
}
case FRONT:
{
- return (effector.isInFrontOfTarget() == _required);
+ return (effector.isInFrontOf(effected) == _required);
}
case CHAOTIC:
{
diff --git a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
index a2bf433488..e8bed0399f 100644
--- a/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
+++ b/L2J_Mobius_2.5_Underground/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
@@ -16,6 +16,9 @@
*/
package com.l2jmobius.gameserver.model.interfaces;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.util.Util;
+
/**
* Object world location storage interface.
* @author xban1x
@@ -51,4 +54,40 @@ public interface ILocational
* @return a {@link ILocational} object containing the current position of this object
*/
ILocational getLocation();
+
+ /**
+ * @param to
+ * @return the heading to the target specified
+ */
+ default int calculateHeadingTo(ILocational to)
+ {
+ return Util.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY());
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in front of the target location based on the game's concept of position.
+ */
+ default boolean isInFrontOf(ILocational target)
+ {
+ return Position.FRONT.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position.
+ */
+ default boolean isOnSideOf(ILocational target)
+ {
+ return Position.SIDE.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is behind the target location based on the game's concept of position.
+ */
+ default boolean isBehind(ILocational target)
+ {
+ return Position.BACK.equals(Position.getPosition(this, target));
+ }
}
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 a021d55681..f41aefc16a 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
@@ -49,6 +49,7 @@ import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.SkillCaster;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+import com.l2jmobius.gameserver.util.Util;
/**
* Global calculations.
@@ -227,41 +228,43 @@ public final class Formulas
// 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);
+ byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL);
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
+ statBonus = BaseStats.values()[skillCritRateStat].calcBonus(activeChar);
}
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;
+ final double rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL, 1);
+ 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));
+ // 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())
+ // Autoattack critical depends on level difference at high levels as well.
+ if ((activeChar.getLevel() >= 78) || (target.getLevel() >= 78))
{
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
+ rate = rate + (Math.sqrt(activeChar.getLevel()) * (activeChar.getLevel() - target.getLevel()) * 0.125);
}
- 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);
+ // Autoattack critical rate is limited between 3%-97%.
+ rate = CommonUtil.constrain(rate, 3, 97);
+
+ return rate > Rnd.get(100);
}
/**
@@ -462,7 +465,7 @@ public final class Formulas
double factor = 0.0;
if (skill.getMagicType() == 1)
{
- final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values
+ final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement propper values
factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time)
}
else
@@ -538,7 +541,7 @@ public final class Formulas
}
final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ if ((degreeside < 360) && (Math.abs(target.calculateDirectionTo(attacker) - Util.convertHeadingToDegree(target.getHeading())) > (degreeside / 2)))
{
return 0;
}
diff --git a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
index 51b3c7f53a..845a7cb1b0 100644
--- a/L2J_Mobius_3.0_Helios/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
+++ b/L2J_Mobius_3.0_Helios/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
@@ -324,7 +324,7 @@ public final class BalokWarzone extends AbstractInstance
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon)
{
- if (!npc.isDead() && caster.isBehindTarget())
+ if (!npc.isDead() && caster.isBehind(npc))
{
final BuffInfo info = npc.getEffectList().getBuffInfoBySkillId(INVINCIBILITY_ACTIVATION.getSkillId());
if ((info != null) && (getRandom(100) < 40))
diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
index ee076bff4b..8f4bebba95 100644
--- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
+++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
import com.l2jmobius.commons.util.IGameXmlReader;
import com.l2jmobius.gameserver.GameTimeController;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
@@ -134,17 +135,23 @@ public final class HitConditionBonusData implements IGameXmlReader
}
// Get side bonus
- if (attacker.isBehindTarget(true))
+ switch (Position.getPosition(attacker, target))
{
- mod += backBonus;
- }
- else if (attacker.isInFrontOfTarget())
- {
- mod += frontBonus;
- }
- else
- {
- mod += sideBonus;
+ case SIDE:
+ {
+ mod += sideBonus;
+ break;
+ }
+ case BACK:
+ {
+ mod += backBonus;
+ break;
+ }
+ default:
+ {
+ mod += frontBonus;
+ break;
+ }
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/enums/Position.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/enums/Position.java
index 0e37ff3488..55ea6a6228 100644
--- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/enums/Position.java
+++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/enums/Position.java
@@ -16,7 +16,7 @@
*/
package com.l2jmobius.gameserver.enums;
-import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.interfaces.ILocational;
/**
* @author Sdw
@@ -27,8 +27,32 @@ public enum Position
SIDE,
BACK;
- public static Position getPosition(L2Character effector, L2Character effected)
+ /**
+ * Position calculation based on the retail-like formulas:
+ *
+ * - heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
+ * - side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
+ * - front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
+ * - back: otherwise.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public static Position getPosition(ILocational from, ILocational to)
{
- return effector.isInFrontOf(effected) ? FRONT : (effector.isBehind(effected) ? BACK : SIDE);
+ final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to));
+ if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000))
+ {
+ return SIDE;
+ }
+ else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000)
+ {
+ return FRONT;
+ }
+ else
+ {
+ return BACK;
+ }
}
}
diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java
index 9fe3e3c542..eee62b70f8 100644
--- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java
+++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/actor/L2Character.java
@@ -55,6 +55,7 @@ import com.l2jmobius.gameserver.enums.BasicProperty;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemSkillType;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
@@ -315,11 +316,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
setInstanceType(InstanceType.L2Character);
- initCharStat();
- initCharStatus();
-
// Set its template to the new L2Character
_template = template;
+ initCharStat();
+ initCharStatus();
if (isNpc())
{
@@ -4048,125 +4048,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
- /**
- * @param target
- * @return True if the L2Character is behind the target and can't be seen.
- */
- public boolean isBehind(L2Object target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
-
- if (target == null)
- {
- return false;
- }
-
- if (target.isCharacter())
- {
- final L2Character target1 = (L2Character) target;
- angleChar = Util.calculateAngleFrom(this, target1);
- angleTarget = Util.convertHeadingToDegree(target1.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- if (Math.abs(angleDiff) <= maxAngleDiff)
- {
- return true;
- }
- }
- return false;
- }
-
- public boolean isBehindTarget()
- {
- return isBehind(getTarget());
- }
-
- /**
- * @param isAttacking if its an attack to be check, or the character itself.
- * @return
- */
- public boolean isBehindTarget(boolean isAttacking)
- {
- if (isAttacking && isAffected(EffectFlag.ATTACK_BEHIND))
- {
- return true;
- }
-
- return isBehind(getTarget());
- }
-
- /**
- * @param target
- * @return True if the target is facing the L2Character.
- */
- public boolean isInFrontOf(L2Character target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
- if (target == null)
- {
- return false;
- }
-
- angleTarget = Util.calculateAngleFrom(target, this);
- angleChar = Util.convertHeadingToDegree(target.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- /**
- * @param target
- * @param maxAngle
- * @return true if target is in front of L2Character (shield def etc)
- */
- public boolean isFacing(L2Object target, int maxAngle)
- {
- double angleChar, angleTarget, angleDiff, maxAngleDiff;
- if (target == null)
- {
- return false;
- }
- maxAngleDiff = maxAngle / 2.;
- angleTarget = Util.calculateAngleFrom(this, target);
- angleChar = Util.convertHeadingToDegree(getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- public boolean isInFrontOfTarget()
- {
- final L2Object target = getTarget();
- if (target instanceof L2Character)
- {
- return isInFrontOf((L2Character) target);
- }
- return false;
- }
-
/**
* @return the Level Modifier ((level + 89) / 100).
*/
@@ -4422,11 +4303,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
if (!reflect && !isDOT)
{
- // RearDamage effect bonus.
- if (isBehind(target))
- {
- damage *= getStat().getValue(Stats.REAR_DAMAGE_RATE, 1);
- }
+ damage *= getStat().getPositionTypeValue(Stats.ATTACK_DAMAGE, Position.getPosition(this, target));
// Counterattacks happen before damage received.
if (!target.isDead() && (skill != null))
diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
index 4be5a3fc3f..7d78918857 100644
--- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
+++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
@@ -78,11 +78,11 @@ public class ConditionPlayerState extends Condition
}
case BEHIND:
{
- return (effector.isBehindTarget() == _required);
+ return (effector.isBehind(effected) == _required);
}
case FRONT:
{
- return (effector.isInFrontOfTarget() == _required);
+ return (effector.isInFrontOf(effected) == _required);
}
case CHAOTIC:
{
diff --git a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
index a2bf433488..e8bed0399f 100644
--- a/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
+++ b/L2J_Mobius_3.0_Helios/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
@@ -16,6 +16,9 @@
*/
package com.l2jmobius.gameserver.model.interfaces;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.util.Util;
+
/**
* Object world location storage interface.
* @author xban1x
@@ -51,4 +54,40 @@ public interface ILocational
* @return a {@link ILocational} object containing the current position of this object
*/
ILocational getLocation();
+
+ /**
+ * @param to
+ * @return the heading to the target specified
+ */
+ default int calculateHeadingTo(ILocational to)
+ {
+ return Util.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY());
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in front of the target location based on the game's concept of position.
+ */
+ default boolean isInFrontOf(ILocational target)
+ {
+ return Position.FRONT.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position.
+ */
+ default boolean isOnSideOf(ILocational target)
+ {
+ return Position.SIDE.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is behind the target location based on the game's concept of position.
+ */
+ default boolean isBehind(ILocational target)
+ {
+ return Position.BACK.equals(Position.getPosition(this, target));
+ }
}
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 a021d55681..f41aefc16a 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
@@ -49,6 +49,7 @@ import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.SkillCaster;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+import com.l2jmobius.gameserver.util.Util;
/**
* Global calculations.
@@ -227,41 +228,43 @@ public final class Formulas
// 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);
+ byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL);
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
+ statBonus = BaseStats.values()[skillCritRateStat].calcBonus(activeChar);
}
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;
+ final double rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL, 1);
+ 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));
+ // 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())
+ // Autoattack critical depends on level difference at high levels as well.
+ if ((activeChar.getLevel() >= 78) || (target.getLevel() >= 78))
{
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
+ rate = rate + (Math.sqrt(activeChar.getLevel()) * (activeChar.getLevel() - target.getLevel()) * 0.125);
}
- 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);
+ // Autoattack critical rate is limited between 3%-97%.
+ rate = CommonUtil.constrain(rate, 3, 97);
+
+ return rate > Rnd.get(100);
}
/**
@@ -462,7 +465,7 @@ public final class Formulas
double factor = 0.0;
if (skill.getMagicType() == 1)
{
- final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values
+ final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement propper values
factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time)
}
else
@@ -538,7 +541,7 @@ public final class Formulas
}
final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ if ((degreeside < 360) && (Math.abs(target.calculateDirectionTo(attacker) - Util.convertHeadingToDegree(target.getHeading())) > (degreeside / 2)))
{
return 0;
}
diff --git a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
index 51b3c7f53a..845a7cb1b0 100644
--- a/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
+++ b/L2J_Mobius_4.0_GrandCrusade/dist/game/data/scripts/ai/bosses/Balok/BalokWarzone.java
@@ -324,7 +324,7 @@ public final class BalokWarzone extends AbstractInstance
@Override
public String onSkillSee(L2Npc npc, L2PcInstance caster, Skill skill, L2Object[] targets, boolean isSummon)
{
- if (!npc.isDead() && caster.isBehindTarget())
+ if (!npc.isDead() && caster.isBehind(npc))
{
final BuffInfo info = npc.getEffectList().getBuffInfoBySkillId(INVINCIBILITY_ACTIVATION.getSkillId());
if ((info != null) && (getRandom(100) < 40))
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
index ee076bff4b..8f4bebba95 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
import com.l2jmobius.commons.util.IGameXmlReader;
import com.l2jmobius.gameserver.GameTimeController;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
@@ -134,17 +135,23 @@ public final class HitConditionBonusData implements IGameXmlReader
}
// Get side bonus
- if (attacker.isBehindTarget(true))
+ switch (Position.getPosition(attacker, target))
{
- mod += backBonus;
- }
- else if (attacker.isInFrontOfTarget())
- {
- mod += frontBonus;
- }
- else
- {
- mod += sideBonus;
+ case SIDE:
+ {
+ mod += sideBonus;
+ break;
+ }
+ case BACK:
+ {
+ mod += backBonus;
+ break;
+ }
+ default:
+ {
+ mod += frontBonus;
+ break;
+ }
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/enums/Position.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/enums/Position.java
index 0e37ff3488..55ea6a6228 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/enums/Position.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/enums/Position.java
@@ -16,7 +16,7 @@
*/
package com.l2jmobius.gameserver.enums;
-import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.interfaces.ILocational;
/**
* @author Sdw
@@ -27,8 +27,32 @@ public enum Position
SIDE,
BACK;
- public static Position getPosition(L2Character effector, L2Character effected)
+ /**
+ * Position calculation based on the retail-like formulas:
+ *
+ * - heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
+ * - side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
+ * - front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
+ * - back: otherwise.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public static Position getPosition(ILocational from, ILocational to)
{
- return effector.isInFrontOf(effected) ? FRONT : (effector.isBehind(effected) ? BACK : SIDE);
+ final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to));
+ if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000))
+ {
+ return SIDE;
+ }
+ else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000)
+ {
+ return FRONT;
+ }
+ else
+ {
+ return BACK;
+ }
}
}
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java
index 9fe3e3c542..eee62b70f8 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/actor/L2Character.java
@@ -55,6 +55,7 @@ import com.l2jmobius.gameserver.enums.BasicProperty;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemSkillType;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
@@ -315,11 +316,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
setInstanceType(InstanceType.L2Character);
- initCharStat();
- initCharStatus();
-
// Set its template to the new L2Character
_template = template;
+ initCharStat();
+ initCharStatus();
if (isNpc())
{
@@ -4048,125 +4048,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
- /**
- * @param target
- * @return True if the L2Character is behind the target and can't be seen.
- */
- public boolean isBehind(L2Object target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
-
- if (target == null)
- {
- return false;
- }
-
- if (target.isCharacter())
- {
- final L2Character target1 = (L2Character) target;
- angleChar = Util.calculateAngleFrom(this, target1);
- angleTarget = Util.convertHeadingToDegree(target1.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- if (Math.abs(angleDiff) <= maxAngleDiff)
- {
- return true;
- }
- }
- return false;
- }
-
- public boolean isBehindTarget()
- {
- return isBehind(getTarget());
- }
-
- /**
- * @param isAttacking if its an attack to be check, or the character itself.
- * @return
- */
- public boolean isBehindTarget(boolean isAttacking)
- {
- if (isAttacking && isAffected(EffectFlag.ATTACK_BEHIND))
- {
- return true;
- }
-
- return isBehind(getTarget());
- }
-
- /**
- * @param target
- * @return True if the target is facing the L2Character.
- */
- public boolean isInFrontOf(L2Character target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
- if (target == null)
- {
- return false;
- }
-
- angleTarget = Util.calculateAngleFrom(target, this);
- angleChar = Util.convertHeadingToDegree(target.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- /**
- * @param target
- * @param maxAngle
- * @return true if target is in front of L2Character (shield def etc)
- */
- public boolean isFacing(L2Object target, int maxAngle)
- {
- double angleChar, angleTarget, angleDiff, maxAngleDiff;
- if (target == null)
- {
- return false;
- }
- maxAngleDiff = maxAngle / 2.;
- angleTarget = Util.calculateAngleFrom(this, target);
- angleChar = Util.convertHeadingToDegree(getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- public boolean isInFrontOfTarget()
- {
- final L2Object target = getTarget();
- if (target instanceof L2Character)
- {
- return isInFrontOf((L2Character) target);
- }
- return false;
- }
-
/**
* @return the Level Modifier ((level + 89) / 100).
*/
@@ -4422,11 +4303,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
if (!reflect && !isDOT)
{
- // RearDamage effect bonus.
- if (isBehind(target))
- {
- damage *= getStat().getValue(Stats.REAR_DAMAGE_RATE, 1);
- }
+ damage *= getStat().getPositionTypeValue(Stats.ATTACK_DAMAGE, Position.getPosition(this, target));
// Counterattacks happen before damage received.
if (!target.isDead() && (skill != null))
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
index 4be5a3fc3f..7d78918857 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
@@ -78,11 +78,11 @@ public class ConditionPlayerState extends Condition
}
case BEHIND:
{
- return (effector.isBehindTarget() == _required);
+ return (effector.isBehind(effected) == _required);
}
case FRONT:
{
- return (effector.isInFrontOfTarget() == _required);
+ return (effector.isInFrontOf(effected) == _required);
}
case CHAOTIC:
{
diff --git a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
index a2bf433488..e8bed0399f 100644
--- a/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
+++ b/L2J_Mobius_4.0_GrandCrusade/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
@@ -16,6 +16,9 @@
*/
package com.l2jmobius.gameserver.model.interfaces;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.util.Util;
+
/**
* Object world location storage interface.
* @author xban1x
@@ -51,4 +54,40 @@ public interface ILocational
* @return a {@link ILocational} object containing the current position of this object
*/
ILocational getLocation();
+
+ /**
+ * @param to
+ * @return the heading to the target specified
+ */
+ default int calculateHeadingTo(ILocational to)
+ {
+ return Util.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY());
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in front of the target location based on the game's concept of position.
+ */
+ default boolean isInFrontOf(ILocational target)
+ {
+ return Position.FRONT.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position.
+ */
+ default boolean isOnSideOf(ILocational target)
+ {
+ return Position.SIDE.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is behind the target location based on the game's concept of position.
+ */
+ default boolean isBehind(ILocational target)
+ {
+ return Position.BACK.equals(Position.getPosition(this, target));
+ }
}
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 412b1ca2c9..52e0e91bc2 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
@@ -49,6 +49,7 @@ import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.SkillCaster;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+import com.l2jmobius.gameserver.util.Util;
/**
* Global calculations.
@@ -227,41 +228,43 @@ public final class Formulas
// 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);
+ byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL);
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
+ statBonus = BaseStats.values()[skillCritRateStat].calcBonus(activeChar);
}
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;
+ final double rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL, 1);
+ 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));
+ // 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())
+ // Autoattack critical depends on level difference at high levels as well.
+ if ((activeChar.getLevel() >= 78) || (target.getLevel() >= 78))
{
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
+ rate = rate + (Math.sqrt(activeChar.getLevel()) * (activeChar.getLevel() - target.getLevel()) * 0.125);
}
- 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);
+ // Autoattack critical rate is limited between 3%-97%.
+ rate = CommonUtil.constrain(rate, 3, 97);
+
+ return rate > Rnd.get(100);
}
/**
@@ -462,7 +465,7 @@ public final class Formulas
double factor = 0.0;
if (skill.getMagicType() == 1)
{
- final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values
+ final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement propper values
factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time)
}
else
@@ -538,7 +541,7 @@ public final class Formulas
}
final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ if ((degreeside < 360) && (Math.abs(target.calculateDirectionTo(attacker) - Util.convertHeadingToDegree(target.getHeading())) > (degreeside / 2)))
{
return 0;
}
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
index ee076bff4b..8f4bebba95 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/data/xml/impl/HitConditionBonusData.java
@@ -25,6 +25,7 @@ import org.w3c.dom.Node;
import com.l2jmobius.commons.util.IGameXmlReader;
import com.l2jmobius.gameserver.GameTimeController;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
@@ -134,17 +135,23 @@ public final class HitConditionBonusData implements IGameXmlReader
}
// Get side bonus
- if (attacker.isBehindTarget(true))
+ switch (Position.getPosition(attacker, target))
{
- mod += backBonus;
- }
- else if (attacker.isInFrontOfTarget())
- {
- mod += frontBonus;
- }
- else
- {
- mod += sideBonus;
+ case SIDE:
+ {
+ mod += sideBonus;
+ break;
+ }
+ case BACK:
+ {
+ mod += backBonus;
+ break;
+ }
+ default:
+ {
+ mod += frontBonus;
+ break;
+ }
}
// If (mod / 100) is less than 0, return 0, because we can't lower more than 100%.
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/enums/Position.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/enums/Position.java
index 0e37ff3488..55ea6a6228 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/enums/Position.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/enums/Position.java
@@ -16,7 +16,7 @@
*/
package com.l2jmobius.gameserver.enums;
-import com.l2jmobius.gameserver.model.actor.L2Character;
+import com.l2jmobius.gameserver.model.interfaces.ILocational;
/**
* @author Sdw
@@ -27,8 +27,32 @@ public enum Position
SIDE,
BACK;
- public static Position getPosition(L2Character effector, L2Character effected)
+ /**
+ * Position calculation based on the retail-like formulas:
+ *
+ * - heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))
+ * - side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)
+ * - front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)
+ * - back: otherwise.
+ *
+ * @param from
+ * @param to
+ * @return
+ */
+ public static Position getPosition(ILocational from, ILocational to)
{
- return effector.isInFrontOf(effected) ? FRONT : (effector.isBehind(effected) ? BACK : SIDE);
+ final int heading = Math.abs(to.getHeading() - from.calculateHeadingTo(to));
+ if (((heading >= 0x2000) && (heading <= 0x6000)) || (Integer.toUnsignedLong(heading - 0xA000) <= 0x4000))
+ {
+ return SIDE;
+ }
+ else if (Integer.toUnsignedLong(heading - 0x2000) <= 0xC000)
+ {
+ return FRONT;
+ }
+ else
+ {
+ return BACK;
+ }
}
}
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java
index 9fe3e3c542..eee62b70f8 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/actor/L2Character.java
@@ -55,6 +55,7 @@ import com.l2jmobius.gameserver.enums.BasicProperty;
import com.l2jmobius.gameserver.enums.CategoryType;
import com.l2jmobius.gameserver.enums.InstanceType;
import com.l2jmobius.gameserver.enums.ItemSkillType;
+import com.l2jmobius.gameserver.enums.Position;
import com.l2jmobius.gameserver.enums.Race;
import com.l2jmobius.gameserver.enums.ShotType;
import com.l2jmobius.gameserver.enums.StatusUpdateType;
@@ -315,11 +316,10 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
setInstanceType(InstanceType.L2Character);
- initCharStat();
- initCharStatus();
-
// Set its template to the new L2Character
_template = template;
+ initCharStat();
+ initCharStatus();
if (isNpc())
{
@@ -4048,125 +4048,6 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
}
- /**
- * @param target
- * @return True if the L2Character is behind the target and can't be seen.
- */
- public boolean isBehind(L2Object target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
-
- if (target == null)
- {
- return false;
- }
-
- if (target.isCharacter())
- {
- final L2Character target1 = (L2Character) target;
- angleChar = Util.calculateAngleFrom(this, target1);
- angleTarget = Util.convertHeadingToDegree(target1.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- if (Math.abs(angleDiff) <= maxAngleDiff)
- {
- return true;
- }
- }
- return false;
- }
-
- public boolean isBehindTarget()
- {
- return isBehind(getTarget());
- }
-
- /**
- * @param isAttacking if its an attack to be check, or the character itself.
- * @return
- */
- public boolean isBehindTarget(boolean isAttacking)
- {
- if (isAttacking && isAffected(EffectFlag.ATTACK_BEHIND))
- {
- return true;
- }
-
- return isBehind(getTarget());
- }
-
- /**
- * @param target
- * @return True if the target is facing the L2Character.
- */
- public boolean isInFrontOf(L2Character target)
- {
- double angleChar, angleTarget, angleDiff;
- final double maxAngleDiff = 60;
- if (target == null)
- {
- return false;
- }
-
- angleTarget = Util.calculateAngleFrom(target, this);
- angleChar = Util.convertHeadingToDegree(target.getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- /**
- * @param target
- * @param maxAngle
- * @return true if target is in front of L2Character (shield def etc)
- */
- public boolean isFacing(L2Object target, int maxAngle)
- {
- double angleChar, angleTarget, angleDiff, maxAngleDiff;
- if (target == null)
- {
- return false;
- }
- maxAngleDiff = maxAngle / 2.;
- angleTarget = Util.calculateAngleFrom(this, target);
- angleChar = Util.convertHeadingToDegree(getHeading());
- angleDiff = angleChar - angleTarget;
- if (angleDiff <= (-360 + maxAngleDiff))
- {
- angleDiff += 360;
- }
- if (angleDiff >= (360 - maxAngleDiff))
- {
- angleDiff -= 360;
- }
- return Math.abs(angleDiff) <= maxAngleDiff;
- }
-
- public boolean isInFrontOfTarget()
- {
- final L2Object target = getTarget();
- if (target instanceof L2Character)
- {
- return isInFrontOf((L2Character) target);
- }
- return false;
- }
-
/**
* @return the Level Modifier ((level + 89) / 100).
*/
@@ -4422,11 +4303,7 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe
if (!reflect && !isDOT)
{
- // RearDamage effect bonus.
- if (isBehind(target))
- {
- damage *= getStat().getValue(Stats.REAR_DAMAGE_RATE, 1);
- }
+ damage *= getStat().getPositionTypeValue(Stats.ATTACK_DAMAGE, Position.getPosition(this, target));
// Counterattacks happen before damage received.
if (!target.isDead() && (skill != null))
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
index 4be5a3fc3f..7d78918857 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/conditions/ConditionPlayerState.java
@@ -78,11 +78,11 @@ public class ConditionPlayerState extends Condition
}
case BEHIND:
{
- return (effector.isBehindTarget() == _required);
+ return (effector.isBehind(effected) == _required);
}
case FRONT:
{
- return (effector.isInFrontOfTarget() == _required);
+ return (effector.isInFrontOf(effected) == _required);
}
case CHAOTIC:
{
diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
index a2bf433488..e8bed0399f 100644
--- a/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
+++ b/L2J_Mobius_Classic_2.0_Saviors/java/com/l2jmobius/gameserver/model/interfaces/ILocational.java
@@ -16,6 +16,9 @@
*/
package com.l2jmobius.gameserver.model.interfaces;
+import com.l2jmobius.gameserver.enums.Position;
+import com.l2jmobius.gameserver.util.Util;
+
/**
* Object world location storage interface.
* @author xban1x
@@ -51,4 +54,40 @@ public interface ILocational
* @return a {@link ILocational} object containing the current position of this object
*/
ILocational getLocation();
+
+ /**
+ * @param to
+ * @return the heading to the target specified
+ */
+ default int calculateHeadingTo(ILocational to)
+ {
+ return Util.calculateHeadingFrom(getX(), getY(), to.getX(), to.getY());
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in front of the target location based on the game's concept of position.
+ */
+ default boolean isInFrontOf(ILocational target)
+ {
+ return Position.FRONT.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is in one of the sides of the target location based on the game's concept of position.
+ */
+ default boolean isOnSideOf(ILocational target)
+ {
+ return Position.SIDE.equals(Position.getPosition(this, target));
+ }
+
+ /**
+ * @param target
+ * @return {@code true} if this location is behind the target location based on the game's concept of position.
+ */
+ default boolean isBehind(ILocational target)
+ {
+ return Position.BACK.equals(Position.getPosition(this, target));
+ }
}
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 a021d55681..f41aefc16a 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
@@ -49,6 +49,7 @@ import com.l2jmobius.gameserver.model.skills.Skill;
import com.l2jmobius.gameserver.model.skills.SkillCaster;
import com.l2jmobius.gameserver.network.SystemMessageId;
import com.l2jmobius.gameserver.network.serverpackets.SystemMessage;
+import com.l2jmobius.gameserver.util.Util;
/**
* Global calculations.
@@ -227,41 +228,43 @@ public final class Formulas
// 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);
+ byte skillCritRateStat = (byte) activeChar.getStat().getValue(Stats.STAT_BONUS_SKILL_CRITICAL);
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
+ statBonus = BaseStats.values()[skillCritRateStat].calcBonus(activeChar);
}
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;
+ final double rateBonus = activeChar.getStat().getValue(Stats.CRITICAL_RATE_SKILL, 1);
+ 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));
+ // 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())
+ // Autoattack critical depends on level difference at high levels as well.
+ if ((activeChar.getLevel() >= 78) || (target.getLevel() >= 78))
{
- final double levelMod = 1 + (activeChar.getLevelMod() - target.getLevelMod());
- rate *= levelMod;
+ rate = rate + (Math.sqrt(activeChar.getLevel()) * (activeChar.getLevel() - target.getLevel()) * 0.125);
}
- 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);
+ // Autoattack critical rate is limited between 3%-97%.
+ rate = CommonUtil.constrain(rate, 3, 97);
+
+ return rate > Rnd.get(100);
}
/**
@@ -462,7 +465,7 @@ public final class Formulas
double factor = 0.0;
if (skill.getMagicType() == 1)
{
- final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement proper values
+ final double spiritshotHitTime = (creature.isChargedShot(ShotType.SPIRITSHOTS) || creature.isChargedShot(ShotType.BLESSED_SPIRITSHOTS)) ? 0.4 : 0; // TODO: Implement propper values
factor = creature.getStat().getMAttackSpeedMultiplier() + (creature.getStat().getMAttackSpeedMultiplier() * spiritshotHitTime); // matkspdmul + (matkspdmul * spiritshot_hit_time)
}
else
@@ -538,7 +541,7 @@ public final class Formulas
}
final int degreeside = target.isAffected(EffectFlag.PHYSICAL_SHIELD_ANGLE_ALL) ? 360 : 120;
- if ((degreeside < 360) && (!target.isFacing(attacker, degreeside)))
+ if ((degreeside < 360) && (Math.abs(target.calculateDirectionTo(attacker) - Util.convertHeadingToDegree(target.getHeading())) > (degreeside / 2)))
{
return 0;
}