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:
+ * + * @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:
+ * + * @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:
+ * + * @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:
+ * + * @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:
+ * + * @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; }