From ca236013fd026e409fac1503238262f8da3302c9 Mon Sep 17 00:00:00 2001 From: MobiusDev <8391001+MobiusDevelopment@users.noreply.github.com> Date: Mon, 26 Nov 2018 00:03:16 +0000 Subject: [PATCH] Used StampedLock to synchronize auto attack method. --- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 397 ++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- .../gameserver/model/actor/L2Character.java | 403 +++++++++--------- 11 files changed, 2296 insertions(+), 2131 deletions(-) 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 b4ff2822ba..3de5019cad 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 @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } 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 b4ff2822ba..3de5019cad 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 @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } 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 b4ff2822ba..3de5019cad 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 @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } 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 b4ff2822ba..3de5019cad 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 @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_5.0_Salvation/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_5.0_Salvation/java/com/l2jmobius/gameserver/model/actor/L2Character.java index b4ff2822ba..3de5019cad 100644 --- a/L2J_Mobius_5.0_Salvation/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_5.0_Salvation/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_5.5_EtinasFate/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_5.5_EtinasFate/java/com/l2jmobius/gameserver/model/actor/L2Character.java index b4ff2822ba..3de5019cad 100644 --- a/L2J_Mobius_5.5_EtinasFate/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_5.5_EtinasFate/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_CT_2.6_HighFive/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_CT_2.6_HighFive/java/com/l2jmobius/gameserver/model/actor/L2Character.java index 25c7634dae..2d6dd0fd92 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -30,6 +30,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.StampedLock; import java.util.logging.Level; import java.util.logging.Logger; @@ -222,6 +223,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -840,252 +843,264 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAttack(L2Character target) + public void doAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - // Notify to scripts - final TerminateReturn attackReturn = EventDispatcher.getInstance().notifyEvent(new OnCreatureAttack(this, target), this, TerminateReturn.class); - if ((attackReturn != null) && attackReturn.terminate()) + try { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Notify to scripts - final TerminateReturn attackedReturn = EventDispatcher.getInstance().notifyEvent(new OnCreatureAttacked(this, target), target, TerminateReturn.class); - if ((attackedReturn != null) && attackedReturn.terminate()) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - if (!isAlikeDead()) - { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + { + return; + } + + // Notify to scripts + final TerminateReturn attackReturn = EventDispatcher.getInstance().notifyEvent(new OnCreatureAttack(this, target), this, TerminateReturn.class); + if ((attackReturn != null) && attackReturn.terminate()) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + // Notify to scripts + final TerminateReturn attackedReturn = EventDispatcher.getInstance().notifyEvent(new OnCreatureAttacked(this, target), target, TerminateReturn.class); + if ((attackedReturn != null) && attackedReturn.terminate()) { - if (target.isDead()) + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + if (!isAlikeDead()) + { + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - - final L2PcInstance actor = getActingPlayer(); - if (actor.isTransformed() && !actor.getTransformation().canAttack()) + else if (isPlayer()) { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + final L2PcInstance actor = getActingPlayer(); + if (actor.isTransformed() && !actor.getTransformation().canAttack()) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + } + + // Check if attacker's weapon can attack + if (getActiveWeaponItem() != null) + { + final L2Weapon wpn = getActiveWeaponItem(); + if (!wpn.isAttackWeapon() && !isGM()) + { + if (wpn.getItemType() == WeaponType.FISHINGROD) + { + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); + } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } sendPacket(ActionFailed.STATIC_PACKET); return; } } - } - - // Check if attacker's weapon can attack - if (getActiveWeaponItem() != null) - { - final L2Weapon wpn = getActiveWeaponItem(); - if (!wpn.isAttackWeapon() && !isGM()) + + if (getActingPlayer() != null) { - if (wpn.getItemType() == WeaponType.FISHINGROD) + if (getActingPlayer().inObserverMode()) { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; } - else + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + if (TerritoryWarManager.getInstance().isTWInProgress()) + { + sendPacket(SystemMessageId.YOU_CANNOT_FORCE_ATTACK_A_MEMBER_OF_THE_SAME_TERRITORY); + } + else + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + } + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; } - sendPacket(ActionFailed.STATIC_PACKET); - return; } - } - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - if (TerritoryWarManager.getInstance().isTWInProgress()) - { - sendPacket(SystemMessageId.YOU_CANNOT_FORCE_ATTACK_A_MEMBER_OF_THE_SAME_TERRITORY); - } - else - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - } - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final L2Weapon weaponItem = getActiveWeaponItem(); - final int timeAtk = calculateTimeBetweenAttacks(); - final int timeToHit = timeAtk / 2; - - final Attack attack = new Attack(this, target, isChargedShot(ShotType.SOULSHOTS), (weaponItem != null) ? weaponItem.getCrystalTypePlus().getLevel() : 0); - setHeading(Util.calculateHeadingFrom(this, target)); - final int reuse = calculateReuseTime(weaponItem); - - boolean hitted = false; - switch (getAttackType()) - { - case BOW: + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (!canUseRangeWeapon()) + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Mobius: Do not move when attack is launched. + if (isMoving()) + { + stopMove(getLocation()); + } + + final L2Weapon weaponItem = getActiveWeaponItem(); + final int timeAtk = calculateTimeBetweenAttacks(); + final int timeToHit = timeAtk / 2; + + final Attack attack = new Attack(this, target, isChargedShot(ShotType.SOULSHOTS), (weaponItem != null) ? weaponItem.getCrystalTypePlus().getLevel() : 0); + setHeading(Util.calculateHeadingFrom(this, target)); + final int reuse = calculateReuseTime(weaponItem); + + boolean hitted = false; + switch (getAttackType()) + { + case BOW: { - return; + if (!canUseRangeWeapon()) + { + return; + } + _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeToHit + (reuse / 2), TimeUnit.MILLISECONDS); + hitted = doAttackHitByBow(attack, target, timeAtk, reuse); + break; } - _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeToHit + (reuse / 2), TimeUnit.MILLISECONDS); - hitted = doAttackHitByBow(attack, target, timeAtk, reuse); - break; - } - case CROSSBOW: - { - if (!canUseRangeWeapon()) + case CROSSBOW: { - return; + if (!canUseRangeWeapon()) + { + return; + } + _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeToHit + (reuse / 2), TimeUnit.MILLISECONDS); + hitted = doAttackHitByCrossBow(attack, target, timeAtk, reuse); + break; } - _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeToHit + (reuse / 2), TimeUnit.MILLISECONDS); - hitted = doAttackHitByCrossBow(attack, target, timeAtk, reuse); - break; - } - case POLE: - { - _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); - hitted = doAttackHitByPole(attack, target, timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case POLE: { + _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); + hitted = doAttackHitByPole(attack, target, timeToHit); + break; + } + case FIST: + { + if (!isPlayer()) + { + hitted = doAttackHitSimple(attack, target, timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALDAGGER: + { + _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); + hitted = doAttackHitByDual(attack, target, timeToHit); + break; + } + default: + { + _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); hitted = doAttackHitSimple(attack, target, timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALDAGGER: - { - _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); - hitted = doAttackHitByDual(attack, target, timeToHit); - break; - } - default: - { - _attackEndTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeAtk, TimeUnit.MILLISECONDS); - hitted = doAttackHitSimple(attack, target, timeToHit); - break; - } - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - if (player.getSummon() != target) - { - player.updatePvPStatus(target); - } - } - - // Check if hit isn't missed - if (!hitted) - { - abortAttack(); // Abort the attack of the L2Character and send Server->Client ActionFailed packet - } - else - { - // If we didn't miss the hit, discharge the shoulshots, if any - setChargedShot(ShotType.SOULSHOTS, false); + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } + } + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); if (player != null) { - if (player.isCursedWeaponEquipped()) + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + if (player.getSummon() != target) { - // If hit by a cursed weapon, CP is reduced to 0 - if (!target.isInvul()) - { - target.setCurrentCp(0); - } + player.updatePvPStatus(target); } - else if (player.isHero()) + } + + // Check if hit isn't missed + if (!hitted) + { + abortAttack(); // Abort the attack of the L2Character and send Server->Client ActionFailed packet + } + else + { + // If we didn't miss the hit, discharge the shoulshots, if any + setChargedShot(ShotType.SOULSHOTS, false); + + if (player != null) { - // If a cursed weapon is hit by a Hero, CP is reduced to 0 - if (target.isPlayer() && target.getActingPlayer().isCursedWeaponEquipped()) + if (player.isCursedWeaponEquipped()) { - target.setCurrentCp(0); + // If hit by a cursed weapon, CP is reduced to 0 + if (!target.isInvul()) + { + target.setCurrentCp(0); + } + } + else if (player.isHero()) + { + // If a cursed weapon is hit by a Hero, CP is reduced to 0 + if (target.isPlayer() && target.getActingPlayer().isCursedWeaponEquipped()) + { + target.setCurrentCp(0); + } } } } + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) + { + broadcastPacket(attack); + } + + // Notify AI with EVT_READY_TO_ACT + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), timeAtk + reuse); } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); + _attackLock.unlockWrite(stamp); } - - // Notify AI with EVT_READY_TO_ACT - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), timeAtk + reuse); } /** 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 d044454b43..4e5e6f6ccc 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 @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_Classic_2.1_Zaken/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.1_Zaken/java/com/l2jmobius/gameserver/model/actor/L2Character.java index d044454b43..4e5e6f6ccc 100644 --- a/L2J_Mobius_Classic_2.1_Zaken/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_Classic_2.1_Zaken/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_Classic_2.2_Antharas/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.2_Antharas/java/com/l2jmobius/gameserver/model/actor/L2Character.java index d044454b43..4e5e6f6ccc 100644 --- a/L2J_Mobius_Classic_2.2_Antharas/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_Classic_2.2_Antharas/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } } diff --git a/L2J_Mobius_Classic_2.3_SevenSigns/java/com/l2jmobius/gameserver/model/actor/L2Character.java b/L2J_Mobius_Classic_2.3_SevenSigns/java/com/l2jmobius/gameserver/model/actor/L2Character.java index d044454b43..4e5e6f6ccc 100644 --- a/L2J_Mobius_Classic_2.3_SevenSigns/java/com/l2jmobius/gameserver/model/actor/L2Character.java +++ b/L2J_Mobius_Classic_2.3_SevenSigns/java/com/l2jmobius/gameserver/model/actor/L2Character.java @@ -34,6 +34,7 @@ import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.StampedLock; import java.util.function.Predicate; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -219,6 +220,8 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe private final byte[] _zones = new byte[ZoneId.getZoneCount()]; protected byte _zoneValidateCounter = 4; + private final StampedLock _attackLock = new StampedLock(); + private Team _team = Team.NONE; protected long _exceptions = 0; @@ -891,262 +894,274 @@ public abstract class L2Character extends L2Object implements ISkillsHolder, IDe * * @param target The L2Character targeted */ - public synchronized void doAutoAttack(L2Character target) + public void doAutoAttack(L2Character target) { - if ((target == null) || isAttackingDisabled() || !target.isTargetable()) + final long stamp = _attackLock.tryWriteLock(); + if (stamp == 0) { return; } - - if (!isAlikeDead()) + try { - if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + if ((target == null) || isAttackingDisabled() || !target.isTargetable()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); return; } - else if (isPlayer()) + + if (!isAlikeDead()) { - if (target.isDead()) + if ((isNpc() && target.isAlikeDead()) || !isInSurroundingRegion(target)) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if (isPlayer()) + { + if (target.isDead()) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + if (checkTransformed(transform -> !transform.canAttack())) + { + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + } + + // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) + final L2Weapon weaponItem = getActiveWeaponItem(); + final WeaponType weaponType = getAttackType(); + + if (getActingPlayer() != null) + { + if (getActingPlayer().inObserverMode()) + { + sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) + { + sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + // Checking if target has moved to peace zone + else if (target.isInsidePeaceZone(getActingPlayer())) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } } - - if (checkTransformed(transform -> !transform.canAttack())) - { - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - } - - // Get the active weapon item corresponding to the active weapon instance (always equipped in the right hand) - final L2Weapon weaponItem = getActiveWeaponItem(); - final WeaponType weaponType = getAttackType(); - - if (getActingPlayer() != null) - { - if (getActingPlayer().inObserverMode()) - { - sendPacket(SystemMessageId.OBSERVERS_CANNOT_PARTICIPATE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - else if ((target.getActingPlayer() != null) && (getActingPlayer().getSiegeState() > 0) && isInsideZone(ZoneId.SIEGE) && (target.getActingPlayer().getSiegeState() == getActingPlayer().getSiegeState()) && (target.getActingPlayer() != this) && (target.getActingPlayer().getSiegeSide() == getActingPlayer().getSiegeSide())) - { - sendPacket(SystemMessageId.FORCE_ATTACK_IS_IMPOSSIBLE_AGAINST_A_TEMPORARY_ALLIED_MEMBER_DURING_A_SIEGE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - // Checking if target has moved to peace zone - else if (target.isInsidePeaceZone(getActingPlayer())) + else if (isInsidePeaceZone(this, target)) { getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - } - else if (isInsidePeaceZone(this, target)) - { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - stopEffectsOnAction(); - - // GeoData Los Check here (or dz > 1000) - if (!GeoEngine.getInstance().canSeeTarget(this, target)) - { - sendPacket(SystemMessageId.CANNOT_SEE_TARGET); - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // BOW and CROSSBOW checks - if (weaponItem != null) - { - if (!weaponItem.isAttackWeapon() && !isGM()) + + stopEffectsOnAction(); + + // GeoData Los Check here (or dz > 1000) + if (!GeoEngine.getInstance().canSeeTarget(this, target)) { - if (weaponItem.getItemType() == WeaponType.FISHINGROD) - { - sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); - } - else - { - sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); - } + sendPacket(SystemMessageId.CANNOT_SEE_TARGET); + getAI().setIntention(AI_INTENTION_ACTIVE); sendPacket(ActionFailed.STATIC_PACKET); return; } - // Ranged weapon checks. - if (weaponItem.getItemType().isRanged()) + // BOW and CROSSBOW checks + if (weaponItem != null) { - // Check if bow delay is still active. - if (_disableRangedAttackEndTime > System.nanoTime()) + if (!weaponItem.isAttackWeapon() && !isGM()) { - if (isPlayer()) + if (weaponItem.getItemType() == WeaponType.FISHINGROD) { - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_LOOK_ODDLY_AT_THE_FISHING_POLE_IN_DISBELIEF_AND_REALIZE_THAT_YOU_CAN_T_ATTACK_ANYTHING_WITH_THIS); } + else + { + sendPacket(SystemMessageId.THAT_WEAPON_CANNOT_PERFORM_ANY_ATTACKS); + } + sendPacket(ActionFailed.STATIC_PACKET); return; } - // Check for arrows and MP - if (isPlayer()) + // Ranged weapon checks. + if (weaponItem.getItemType().isRanged()) { - // Check if there are arrows to use or else cancel the attack. - if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + // Check if bow delay is still active. + if (_disableRangedAttackEndTime > System.nanoTime()) { - // Cancel the action because the L2PcInstance have no arrow - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(ActionFailed.STATIC_PACKET); - sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + if (isPlayer()) + { + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(ActionFailed.STATIC_PACKET); + } return; } - // Checking if target has moved to peace zone - only for player-bow attacks at the moment - // Other melee is checked in movement code and for offensive spells a check is done every time - if (target.isInsidePeaceZone(getActingPlayer())) + // Check for arrows and MP + if (isPlayer()) { - getAI().setIntention(AI_INTENTION_ACTIVE); - sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // Check if player has enough MP to shoot. - int mpConsume = weaponItem.getMpConsume(); - if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) - { - mpConsume = weaponItem.getReducedMpConsume(); - } - mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; - if (_status.getCurrentMp() < mpConsume) - { - // If L2PcInstance doesn't have enough MP, stop the attack - ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); - sendPacket(SystemMessageId.NOT_ENOUGH_MP); - sendPacket(ActionFailed.STATIC_PACKET); - return; - } - - // If L2PcInstance have enough MP, the bow consumes it - if (mpConsume > 0) - { - _status.reduceMp(mpConsume); + // Check if there are arrows to use or else cancel the attack. + if (!checkAndEquipAmmunition(weaponItem.getItemType().isCrossbow() ? EtcItemType.BOLT : EtcItemType.ARROW)) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(ActionFailed.STATIC_PACKET); + sendPacket(SystemMessageId.YOU_HAVE_RUN_OUT_OF_ARROWS); + return; + } + + // Checking if target has moved to peace zone - only for player-bow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone(getActingPlayer())) + { + getAI().setIntention(AI_INTENTION_ACTIVE); + sendPacket(SystemMessageId.YOU_MAY_NOT_ATTACK_IN_A_PEACEFUL_ZONE); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // Check if player has enough MP to shoot. + int mpConsume = weaponItem.getMpConsume(); + if ((weaponItem.getReducedMpConsume() > 0) && (Rnd.get(100) < weaponItem.getReducedMpConsumeChance())) + { + mpConsume = weaponItem.getReducedMpConsume(); + } + mpConsume = isAffected(EffectFlag.CHEAPSHOT) ? 0 : mpConsume; + if (_status.getCurrentMp() < mpConsume) + { + // If L2PcInstance doesn't have enough MP, stop the attack + ThreadPool.schedule(new NotifyAITask(this, CtrlEvent.EVT_READY_TO_ACT), 1000); + sendPacket(SystemMessageId.NOT_ENOUGH_MP); + sendPacket(ActionFailed.STATIC_PACKET); + return; + } + + // If L2PcInstance have enough MP, the bow consumes it + if (mpConsume > 0) + { + _status.reduceMp(mpConsume); + } } } } - } - - // Mobius: Do not move when attack is launched. - if (isMoving()) - { - stopMove(getLocation()); - } - - final WeaponType attackType = getAttackType(); - final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); - final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); - final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); - _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); - - // Make sure that char is facing selected target - // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); - setHeading(Util.calculateHeadingFrom(this, target)); - - // Get the Attack Reuse Delay of the L2Weapon - final Attack attack = generateAttackTargetData(target, weaponItem, attackType); - boolean crossbow = false; - switch (attackType) - { - case CROSSBOW: - case TWOHANDCROSSBOW: + + // Mobius: Do not move when attack is launched. + if (isMoving()) { - crossbow = true; + stopMove(getLocation()); } - case BOW: + + final WeaponType attackType = getAttackType(); + final boolean isTwoHanded = (weaponItem != null) && (weaponItem.getBodyPart() == L2Item.SLOT_LR_HAND); + final int timeAtk = Formulas.calculateTimeBetweenAttacks(_stat.getPAtkSpd()); + final int timeToHit = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, false); + _attackEndTime = System.nanoTime() + (TimeUnit.MILLISECONDS.toNanos(timeAtk)); + + // Make sure that char is facing selected target + // also works: setHeading(Util.convertDegreeToClientHeading(Util.calculateAngleFrom(this, target))); + setHeading(Util.calculateHeadingFrom(this, target)); + + // Get the Attack Reuse Delay of the L2Weapon + final Attack attack = generateAttackTargetData(target, weaponItem, attackType); + boolean crossbow = false; + switch (attackType) { - final int reuse = Formulas.calculateReuseTime(this, weaponItem); - - // Consume arrows - final Inventory inventory = getInventory(); - if (inventory != null) + case CROSSBOW: + case TWOHANDCROSSBOW: { - inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); + crossbow = true; } - - // Check if the L2Character is a L2PcInstance - if (isPlayer()) + case BOW: { - if (crossbow) + final int reuse = Formulas.calculateReuseTime(this, weaponItem); + + // Consume arrows + final Inventory inventory = getInventory(); + if (inventory != null) { - sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + inventory.reduceArrowCount(crossbow ? EtcItemType.BOLT : EtcItemType.ARROW); } - sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + // Check if the L2Character is a L2PcInstance + if (isPlayer()) + { + if (crossbow) + { + sendPacket(SystemMessageId.YOUR_CROSSBOW_IS_PREPARING_TO_FIRE); + } + + sendPacket(new SetupGauge(getObjectId(), SetupGauge.RED, reuse)); + } + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; } - - // Calculate and set the disable delay of the bow in function of the Attack Speed - _disableRangedAttackEndTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(reuse); - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; - } - case FIST: - { - if (!isPlayer()) + case FIST: + { + if (!isPlayer()) + { + _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); + break; + } + } + case DUAL: + case DUALFIST: + case DUALBLUNT: + case DUALDAGGER: + { + final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; + _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); + break; + } + default: { _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); break; } } - case DUAL: - case DUALFIST: - case DUALBLUNT: - case DUALDAGGER: + + // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack + // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character + if (attack.hasHits()) { - final int timeToHit2 = Formulas.calculateTimeToHit(timeAtk, weaponType, isTwoHanded, true) - timeToHit; - _hitTask = ThreadPool.schedule(() -> onFirstHitTimeForDual(weaponItem, attack, timeToHit, timeAtk, timeToHit2), timeToHit); - break; + broadcastPacket(attack); } - default: + + // Flag the attacker if it's a L2PcInstance outside a PvP area + final L2PcInstance player = getActingPlayer(); + if (player != null) { - _hitTask = ThreadPool.schedule(() -> onHitTimeNotDual(weaponItem, attack, timeToHit, timeAtk), timeToHit); - break; + AttackStanceTaskManager.getInstance().addAttackStanceTask(player); + player.updatePvPStatus(target); + } + + if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) + { + final L2Npc npc = ((L2Npc) this); + if (!npc.isScriptValue(1)) + { + npc.setScriptValue(1); // in combat + broadcastInfo(); // update flag status + QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); + } } } - - // If the Server->Client packet Attack contains at least 1 hit, send the Server->Client packet Attack - // to the L2Character AND to all L2PcInstance in the _KnownPlayers of the L2Character - if (attack.hasHits()) + finally { - broadcastPacket(attack); - } - - // Flag the attacker if it's a L2PcInstance outside a PvP area - final L2PcInstance player = getActingPlayer(); - if (player != null) - { - AttackStanceTaskManager.getInstance().addAttackStanceTask(player); - player.updatePvPStatus(target); - } - - if (isFakePlayer() && (target.isPlayable() || target.isFakePlayer())) - { - final L2Npc npc = ((L2Npc) this); - if (!npc.isScriptValue(1)) - { - npc.setScriptValue(1); // in combat - broadcastInfo(); // update flag status - QuestManager.getInstance().getQuest("PvpFlaggingStopTask").notifyEvent("FLAG_CHECK" + npc.getObjectId(), npc, null); - } + _attackLock.unlockWrite(stamp); } }