L2Character attack rework.

Adapted from: L2jUnity free files.
This commit is contained in:
MobiusDev
2017-12-06 18:29:40 +00:00
parent 52e54b75ce
commit 86b76fd312
189 changed files with 3462 additions and 3804 deletions

View File

@@ -78,7 +78,7 @@ public class DoppelgangerAI extends L2CharacterAI
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()

View File

@@ -218,7 +218,7 @@ public class FriendlyNpcAI extends L2AttackableAI
return;
}
_actor.doAttack(originalAttackTarget);
_actor.doAutoAttack(originalAttackTarget);
}
@Override

View File

@@ -994,7 +994,7 @@ public class L2AttackableAI extends L2CharacterAI implements Runnable
}
// Attacks target
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
private boolean checkSkillTarget(Skill skill, L2Object target)

View File

@@ -223,7 +223,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
protected void thinkForceAttack()
@@ -264,7 +264,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
return;
}
_actor.doAttack(getForcedTarget());
_actor.doAutoAttack(getForcedTarget());
}
@Override
@@ -364,7 +364,7 @@ public final class L2ControllableMobAI extends L2AttackableAI
}
}
_actor.doAttack(target);
_actor.doAutoAttack(target);
}
}

View File

@@ -262,7 +262,7 @@ public class L2PlayerAI extends L2PlayableAI
return;
}
_actor.doAttack((L2Character) target);
_actor.doAutoAttack((L2Character) target);
}
private void thinkCast()

View File

@@ -107,7 +107,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
return;
}
clientStopMoving(null);
_actor.doAttack(attackTarget);
_actor.doAutoAttack(attackTarget);
}
private void thinkCast()
@@ -276,7 +276,7 @@ public class L2SummonAI extends L2PlayableAI implements Runnable
final L2Summon summon = getActor();
if ((summon.getOwner() != null) && (summon.getOwner() != attacker) && !summon.isMoving() && summon.canAttack(attacker, false) && summon.getOwner().isInsideRadius(_actor, 2 * AVOID_RADIUS, true, false))
{
summon.doAttack(attacker);
summon.doAutoAttack(attacker);
}
}

View File

@@ -16,6 +16,8 @@
*/
package com.l2jmobius.gameserver.model;
import java.lang.ref.WeakReference;
import com.l2jmobius.gameserver.enums.AttackType;
import com.l2jmobius.gameserver.model.actor.L2Character;
@@ -24,6 +26,7 @@ import com.l2jmobius.gameserver.model.actor.L2Character;
*/
public class Hit
{
private final WeakReference<L2Object> _target;
private final int _targetId;
private final int _damage;
private final int _ssGrade;
@@ -31,6 +34,7 @@ public class Hit
public Hit(L2Object target, int damage, boolean miss, boolean crit, byte shld, boolean soulshot, int ssGrade)
{
_target = new WeakReference<>(target);
_targetId = target.getObjectId();
_damage = damage;
_ssGrade = ssGrade;
@@ -62,6 +66,11 @@ public class Hit
_flags |= type.getMask();
}
public L2Object getTarget()
{
return _target.get();
}
public int getTargetId()
{
return _targetId;
@@ -81,4 +90,24 @@ public class Hit
{
return _ssGrade;
}
public boolean isMiss()
{
return (AttackType.MISSED.getMask() & _flags) != 0;
}
public boolean isCritical()
{
return (AttackType.CRITICAL.getMask() & _flags) != 0;
}
public boolean isShotUsed()
{
return (AttackType.SHOT_USED.getMask() & _flags) != 0;
}
public boolean isBlocked()
{
return (AttackType.BLOCKED.getMask() & _flags) != 0;
}
}

View File

@@ -91,7 +91,7 @@ public final class L2DoorInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@@ -4642,9 +4642,9 @@ public final class L2PcInstance extends L2Playable
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
super.doAttack(target);
super.doAutoAttack(target);
setRecentFakeDeath(false);
if (target.isFakePlayer())
{

View File

@@ -195,7 +195,7 @@ public final class L2StaticObjectInstance extends L2Character
}
@Override
public void doAttack(L2Character target)
public void doAutoAttack(L2Character target)
{
}

View File

@@ -1,61 +0,0 @@
/*
* This file is part of the L2J Mobius project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.l2jmobius.gameserver.model.actor.tasks.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
/**
* Task launching the function onHitTimer().<br>
* <B><U>Actions</U>:</B>
* <ul>
* <li>If the attacker/target is dead or use fake death, notify the AI with EVT_CANCEL and send a Server->Client packet ActionFailed (if attacker is a L2PcInstance)</li>
* <li>If attack isn't aborted, send a message system (critical hit, missed...) to attacker/target if they are L2PcInstance</li>
* <li>If attack isn't aborted and hit isn't missed, reduce HP of the target and calculate reflection damage to reduce HP of attacker if necessary</li>
* <li>if attack isn't aborted and hit isn't missed, manage attack or cast break of the target (calculating rate, sending message...)</li>
* </ul>
* @author xban1x
*/
public final class HitTask implements Runnable
{
private final L2Character _character;
private final L2Character _hitTarget;
private final int _damage;
private final boolean _crit;
private final boolean _miss;
private final byte _shld;
private final boolean _soulshot;
public HitTask(L2Character character, L2Character target, int damage, boolean crit, boolean miss, boolean soulshot, byte shld)
{
_character = character;
_hitTarget = target;
_damage = damage;
_crit = crit;
_shld = shld;
_miss = miss;
_soulshot = soulshot;
}
@Override
public void run()
{
if (_character != null)
{
_character.onHitTimer(_hitTarget, _damage, _crit, _miss, _soulshot, _shld);
}
}
}

View File

@@ -19,6 +19,7 @@ package com.l2jmobius.gameserver.model.events.impl.character;
import com.l2jmobius.gameserver.model.actor.L2Character;
import com.l2jmobius.gameserver.model.events.EventType;
import com.l2jmobius.gameserver.model.events.impl.IBaseEvent;
import com.l2jmobius.gameserver.model.skills.Skill;
/**
* An instantly executed event when L2Character is attacked by L2Character.
@@ -28,11 +29,13 @@ public class OnCreatureAttack implements IBaseEvent
{
private final L2Character _attacker;
private final L2Character _target;
private final Skill _skill;
public OnCreatureAttack(L2Character attacker, L2Character target)
public OnCreatureAttack(L2Character attacker, L2Character target, Skill skill)
{
_attacker = attacker;
_target = target;
_skill = skill;
}
public final L2Character getAttacker()
@@ -45,6 +48,11 @@ public class OnCreatureAttack implements IBaseEvent
return _target;
}
public final Skill getSkill()
{
return _skill;
}
@Override
public EventType getType()
{

View File

@@ -37,6 +37,7 @@ import com.l2jmobius.gameserver.model.actor.instance.L2StaticObjectInstance;
import com.l2jmobius.gameserver.model.cubic.CubicInstance;
import com.l2jmobius.gameserver.model.effects.EffectFlag;
import com.l2jmobius.gameserver.model.effects.L2EffectType;
import com.l2jmobius.gameserver.model.interfaces.ILocational;
import com.l2jmobius.gameserver.model.items.L2Armor;
import com.l2jmobius.gameserver.model.items.L2Item;
import com.l2jmobius.gameserver.model.items.L2Weapon;
@@ -271,8 +272,12 @@ public final class Formulas
}
// 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.
@@ -286,6 +291,37 @@ public final class Formulas
return finalRate > Rnd.get(1000);
}
/**
* Gets the default (10% for side, 30% for back) positional critical rate bonus and multiplies it by any buffs that give positional critical rate bonus.
* @param activeChar the attacker.
* @param target the target.
* @return a multiplier representing the positional critical rate bonus. Autoattacks for example get this bonus on top of the already capped critical rate of 500.
*/
public static double calcCriticalPositionBonus(L2Character activeChar, L2Character target)
{
final Position position = target.isAffected(EffectFlag.ATTACK_BEHIND) ? Position.BACK : Position.getPosition(activeChar, target);
switch (position)
{
case SIDE: // 10% Critical Chance bonus when attacking from side.
{
return 1.1 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.SIDE);
}
case BACK: // 30% Critical Chance bonus when attacking from back.
{
return 1.3 * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.BACK);
}
default: // No Critical Chance bonus when attacking from front.
{
return activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, Position.FRONT);
}
}
}
public static double calcCriticalHeightBonus(ILocational from, ILocational target)
{
return ((((CommonUtil.constrain(from.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
}
/**
* @param attacker
* @param target
@@ -403,16 +439,6 @@ public final class Formulas
return Rnd.get(100) < rate;
}
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
}
/**
* Calculate delay (in milliseconds) for skills cast
* @param attacker
@@ -1023,7 +1049,7 @@ public final class Formulas
return 1;
}
public static void calcDamageReflected(L2Character attacker, L2Character target, Skill skill, boolean crit)
public static void calcCounterAttack(L2Character attacker, L2Character target, Skill skill, boolean crit)
{
// Only melee skills can be reflected
if (skill.isMagic() || (skill.getCastRange() > MELEE_ATTACK_RANGE))
@@ -1049,7 +1075,7 @@ public final class Formulas
double counterdmg = ((target.getPAtk() * 873) / attacker.getPDef()); // Old: (((target.getPAtk(attacker) * 10.0) * 70.0) / attacker.getPDef(target));
counterdmg *= calcWeaponTraitBonus(attacker, target);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false);
counterdmg *= calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true);
counterdmg *= calcAttributeBonus(attacker, target, skill);
attacker.reduceCurrentHp(counterdmg, target, skill);
@@ -1086,35 +1112,37 @@ public final class Formulas
return cha.getStat().getValue(Stats.FALL, (fallHeight * cha.getMaxHp()) / 1000.0);
}
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double blowChance)
/**
* Basic chance formula:<br>
* <ul>
* <li>chance = weapon_critical * dex_bonus * crit_height_bonus * crit_pos_bonus * effect_bonus * fatal_blow_rate</li>
* <li>weapon_critical = (12 for daggers)</li>
* <li>dex_bonus = dex modifier bonus for current dex (Seems unused in GOD, so its not used in formula).</li>
* <li>crit_height_bonus = (z_diff * 4 / 5 + 10) / 100 + 1 or alternatively (z_diff * 0.008) + 1.1. Be aware of z_diff constraint of -25 to 25.</li>
* <li>crit_pos_bonus = crit_pos(front = 1, side = 1.1, back = 1.3) * p_critical_rate_position_bonus</li>
* <li>effect_bonus = (p2 + 100) / 100, p2 - 2nd param of effect. Blow chance of effect.</li>
* </ul>
* Chance cannot be higher than 80%.
* @param activeChar
* @param target
* @param skill
* @param chanceBoost
* @return
*/
public static boolean calcBlowSuccess(L2Character activeChar, L2Character target, Skill skill, double chanceBoost)
{
final double weaponCritical = 12; // Dagger weapon critical mod is 12... TODO: Make it work for other weapons.
final L2Weapon weapon = activeChar.getActiveWeaponItem();
final double weaponCritical = weapon != null ? weapon.getStats(Stats.CRITICAL_RATE, activeChar.getTemplate().getBaseCritRate()) : activeChar.getTemplate().getBaseCritRate();
// double dexBonus = BaseStats.DEX.calcBonus(activeChar); Not used in GOD
final double critHeightBonus = ((((CommonUtil.constrain(activeChar.getZ() - target.getZ(), -25, 25) * 4) / 5) + 10) / 100) + 1;
final Position position = Position.getPosition(activeChar, target);
final double criticalPosition = position == Position.BACK ? 1.3 : position == Position.SIDE ? 1.1 : 1; // 30% chance from back, 10% chance from side.
final double criticalPositionMod = criticalPosition * activeChar.getStat().getPositionTypeValue(Stats.CRITICAL_RATE, position);
final double critHeightBonus = calcCriticalHeightBonus(activeChar, target);
final double criticalPosition = calcCriticalPositionBonus(activeChar, target); // 30% chance from back, 10% chance from side. Include buffs that give positional crit rate.
final double chanceBoostMod = (100 + chanceBoost) / 100;
final double blowRateMod = activeChar.getStat().getValue(Stats.BLOW_RATE, 1);
blowChance = (weaponCritical + blowChance) * 10;
final double rate = blowChance * critHeightBonus * criticalPositionMod * blowRateMod;
// Debug
if (activeChar.isDebug())
{
final StatsSet set = new StatsSet();
set.set("weaponCritical", weaponCritical);
set.set("critHeightBonus", critHeightBonus);
set.set("criticalPosition", criticalPosition);
set.set("criticalPositionMod", criticalPositionMod);
set.set("blowRate", blowRateMod);
set.set("blowChance", blowChance);
set.set("rate(max 800 of 1000)", rate);
Debug.sendSkillDebug(activeChar, target, skill, set);
}
final double rate = criticalPosition * critHeightBonus * weaponCritical * chanceBoostMod * blowRateMod;
// Blow rate is capped at 80%
return Rnd.get(1000) < Math.min(rate, 800);
return Rnd.get(100) < Math.min(rate, 80);
}
public static List<BuffInfo> calcCancelStealEffects(L2Character activeChar, L2Character target, Skill skill, DispelSlotType slot, int rate, int max)
@@ -1224,6 +1252,12 @@ public final class Formulas
*/
public static boolean calcProbability(double baseChance, L2Character attacker, L2Character target, Skill skill)
{
// Skills without set probability should only test against trait invulnerability.
if (Double.isNaN(baseChance))
{
return calcGeneralTraitBonus(attacker, target, skill.getTraitType(), true) > 0;
}
// Outdated formula: return Rnd.get(100) < ((((((skill.getMagicLevel() + baseChance) - target.getLevel()) + 30) - target.getINT()) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
// TODO: Find more retail-like formula
return Rnd.get(100) < (((((skill.getMagicLevel() + baseChance) - target.getLevel()) - getAbnormalResist(skill.getBasicProperty(), target)) * calcAttributeBonus(attacker, target, skill)) * calcGeneralTraitBonus(attacker, target, skill.getTraitType(), false));
@@ -1486,7 +1520,7 @@ public final class Formulas
*/
public static boolean calcStunBreak(L2Character activeChar)
{
// Check if target is stunned and 10% chance.
// Check if target is stunned and 10% chance (retail is 14% and 35% on crit?)
if (activeChar.hasBlockActions() && (Rnd.get(10) == 0))
{
// Any stun that has double duration due to skill mastery, doesn't get removed until its time reaches the usual abnormal time.
@@ -1497,8 +1531,18 @@ public final class Formulas
public static boolean calcRealTargetBreak()
{
// Real Target breaks at 5% probability.
return Rnd.get(20) == 0;
// Real Target breaks at 3% (Rnd > 3.0 doesn't break) probability.
return Rnd.get(100) <= 3;
}
/**
* @param attackSpeed the attack speed of the Creature.
* @return {@code 500000 / attackSpeed}.
*/
public static int calculateTimeBetweenAttacks(int attackSpeed)
{
// Measured Nov 2015 by Nik. Formula: atk.spd/500 = hits per second.
return Math.max(50, (500000 / attackSpeed));
}
/**

View File

@@ -143,6 +143,7 @@ public enum Stats
PHYSICAL_ATTACK_RANGE("pAtkRange", new PRangeFinalizer()),
MAGIC_ATTACK_RANGE("mAtkRange"),
ATTACK_COUNT_MAX("atkCountMax"),
PHYSICAL_POLEARM_TARGET_SINGLE("polearmSingleTarget"),
// Run speed, walk & escape speed are calculated proportionally, magic speed is a buff
MOVE_SPEED("moveSpeed"),
RUN_SPEED("runSpd", new SpeedFinalizer()),
@@ -266,7 +267,8 @@ public enum Stats
// Which base stat ordinal should alter skill critical formula.
STAT_BONUS_SKILL_CRITICAL("statSkillCritical"),
STAT_BONUS_SPEED("statSpeed"),
SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer());
SHOTS_BONUS("shotBonus", new ShotsBonusFinalizer()),
ATTACK_DAMAGE("attackDamage");
static final Logger LOGGER = Logger.getLogger(Stats.class.getName());
public static final int NUM_STATS = values().length;

View File

@@ -29,8 +29,6 @@ import com.l2jmobius.gameserver.network.OutgoingPackets;
public class Attack implements IClientOutgoingPacket
{
private final int _attackerObjId;
private final boolean _soulshot;
private final int _ssGrade;
private final Location _attackerLoc;
private final Location _targetLoc;
private final List<Hit> _hits = new ArrayList<>();
@@ -38,29 +36,26 @@ public class Attack implements IClientOutgoingPacket
/**
* @param attacker
* @param target
* @param useShots
* @param ssGrade
*/
public Attack(L2Character attacker, L2Character target, boolean useShots, int ssGrade)
public Attack(L2Character attacker, L2Character target)
{
_attackerObjId = attacker.getObjectId();
_soulshot = useShots;
_ssGrade = Math.min(ssGrade, 6);
_attackerLoc = new Location(attacker);
_targetLoc = new Location(target);
}
/**
* Adds hit to the attack (Attacks such as dual dagger/sword/fist has two hits)
* @param target
* @param damage
* @param miss
* @param crit
* @param shld
* @param hit
*/
public void addHit(L2Character target, int damage, boolean miss, boolean crit, byte shld)
public void addHit(Hit hit)
{
_hits.add(new Hit(target, damage, miss, crit, shld, _soulshot, _ssGrade));
_hits.add(hit);
}
public List<Hit> getHits()
{
return _hits;
}
/**
@@ -71,14 +66,6 @@ public class Attack implements IClientOutgoingPacket
return !_hits.isEmpty();
}
/**
* @return {@code true} if attack has soul shot charged.
*/
public boolean hasSoulshot()
{
return _soulshot;
}
/**
* Writes current hit
* @param packet

View File

@@ -43,7 +43,7 @@ public class ExAutoSoulShot implements IClientOutgoingPacket
OutgoingPackets.EX_AUTO_SOUL_SHOT.writeId(packet);
packet.writeD(_itemId);
packet.writeD(_enable ? 0x01 : 0x00);
packet.writeD(_enable ? 0x01 : 0x00); // Underground
packet.writeD(_type);
return true;
}