Position class rework.

Adapted from: L2jUnity free files.
This commit is contained in:
MobiusDev 2018-02-16 16:38:58 +00:00
parent dcd5d50110
commit 2392b60087
34 changed files with 549 additions and 799 deletions

View File

@ -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))

View File

@ -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
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%.

View File

@ -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:<br>
* <ul>
* <li>heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))</li>
* <li>side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)</li>
* <li>front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)</li>
* <li>back: otherwise.</li>
* </ul>
* @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;
}
}
}

View File

@ -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))

View File

@ -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:
{

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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))

View File

@ -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
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%.

View File

@ -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:<br>
* <ul>
* <li>heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))</li>
* <li>side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)</li>
* <li>front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)</li>
* <li>back: otherwise.</li>
* </ul>
* @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;
}
}
}

View File

@ -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))

View File

@ -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:
{

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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))

View File

@ -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
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%.

View File

@ -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:<br>
* <ul>
* <li>heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))</li>
* <li>side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)</li>
* <li>front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)</li>
* <li>back: otherwise.</li>
* </ul>
* @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;
}
}
}

View File

@ -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))

View File

@ -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:
{

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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))

View File

@ -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
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%.

View File

@ -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:<br>
* <ul>
* <li>heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))</li>
* <li>side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)</li>
* <li>front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)</li>
* <li>back: otherwise.</li>
* </ul>
* @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;
}
}
}

View File

@ -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))

View File

@ -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:
{

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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
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%.

View File

@ -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:<br>
* <ul>
* <li>heading: (unsigned short) abs(heading - (unsigned short)(int)floor(atan2(toY - fromY, toX - fromX) * 65535.0 / 6.283185307179586))</li>
* <li>side: if (heading >= 0x2000 && heading <= 0x6000 || (unsigned int)(heading - 0xA000) <= 0x4000)</li>
* <li>front: else if ((unsigned int)(heading - 0x2000) <= 0xC000)</li>
* <li>back: otherwise.</li>
* </ul>
* @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;
}
}
}

View File

@ -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))

View File

@ -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:
{

View File

@ -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));
}
}

View File

@ -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;
}