Index: java/net/sf/l2j/gameserver/clientpackets/MoveBackwardToLocation.java =================================================================== --- java/net/sf/l2j/gameserver/clientpackets/MoveBackwardToLocation.java (revision 2917) +++ java/net/sf/l2j/gameserver/clientpackets/MoveBackwardToLocation.java (working copy) @@ -108,7 +108,7 @@ { activeChar.sendPacket(new ActionFailed()); } - else if (activeChar.isAttackingNow() && activeChar.getActiveWeaponItem() != null && (activeChar.getActiveWeaponItem().getItemType() == L2WeaponType.BOW)) + else if (activeChar.isAttackingNow() && activeChar.getActiveWeaponItem() != null && ((activeChar.getActiveWeaponItem().getItemType() == L2WeaponType.BOW) || (activeChar.getActiveWeaponItem().getItemType() == L2WeaponType.CROSSBOW))) { activeChar.sendPacket(new ActionFailed()); } Index: java/net/sf/l2j/gameserver/network/SystemMessageId.java =================================================================== --- java/net/sf/l2j/gameserver/network/SystemMessageId.java (revision 2920) +++ java/net/sf/l2j/gameserver/network/SystemMessageId.java (working copy) @@ -785,10 +785,22 @@ /** * ID: 112
- * Message: You may no longer adjust items in the trade because the trade has been confirmed. + * Message: You have run out of arrows. */ NOT_ENOUGH_ARROWS(112), + /** + * ID: 2224
+ * Message: Crossbow is preparing to fire. + */ + CROSSBOW_PREPARING_TO_FIRE(2224), + + /** + * ID: 2226
+ * Message: Not enough bolts. + */ + NOT_ENOUGH_BOLTS(2226), + /** * ID: 129
* Message: Your inventory is full. Index: java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java =================================================================== --- java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java (revision 2917) +++ java/net/sf/l2j/gameserver/model/actor/instance/L2PcInstance.java (working copy) @@ -556,6 +556,7 @@ private long _requestExpireTime = 0; private L2Request _request = new L2Request(this); private L2ItemInstance _arrowItem; + private L2ItemInstance _boltItem; // Used for protection after teleport private long _protectEndTime = 0; @@ -5314,6 +5315,36 @@ return _arrowItem != null; } + /** + * Equip arrows needed in left hand and send a Server->Client packet ItemList to the L2PcINstance then return True.

+ */ + @Override + protected boolean checkAndEquipBolts() + { + // Check if nothing is equiped in left hand + if (getInventory().getPaperdollItem(Inventory.PAPERDOLL_LHAND) == null) + { + // Get the L2ItemInstance of the arrows needed for this bow + _boltItem = getInventory().findBoltForCrossBow(getActiveWeaponItem()); + + if (_boltItem != null) + { + // Equip arrows needed in left hand + getInventory().setPaperdollItem(Inventory.PAPERDOLL_LHAND, _boltItem); + + // Send a Server->Client packet ItemList to this L2PcINstance to update left hand equipement + ItemList il = new ItemList(this, false); + sendPacket(il); + } + } + else + { + // Get the L2ItemInstance of arrows equiped in left hand + _boltItem = getInventory().getPaperdollItem(Inventory.PAPERDOLL_LHAND); + } + + return _boltItem != null; + } /** * Disarm the player's weapon and shield.

*/ Index: java/net/sf/l2j/gameserver/model/L2Character.java =================================================================== --- java/net/sf/l2j/gameserver/model/L2Character.java (revision 2917) +++ java/net/sf/l2j/gameserver/model/L2Character.java (working copy) @@ -35,7 +35,6 @@ import net.sf.l2j.Config; import net.sf.l2j.gameserver.GameTimeController; import net.sf.l2j.gameserver.GeoData; -import net.sf.l2j.gameserver.Olympiad; import net.sf.l2j.gameserver.ThreadPoolManager; import net.sf.l2j.gameserver.ai.CtrlEvent; import net.sf.l2j.gameserver.ai.CtrlIntention; @@ -77,15 +76,12 @@ import net.sf.l2j.gameserver.serverpackets.ChangeWaitType; import net.sf.l2j.gameserver.serverpackets.CharInfo; import net.sf.l2j.gameserver.serverpackets.EtcStatusUpdate; -import net.sf.l2j.gameserver.serverpackets.MoveToLocation; -import net.sf.l2j.gameserver.serverpackets.ExOlympiadSpelledInfo; import net.sf.l2j.gameserver.serverpackets.L2GameServerPacket; -import net.sf.l2j.gameserver.serverpackets.AbnormalStatusUpdate; import net.sf.l2j.gameserver.serverpackets.MagicSkillCanceld; import net.sf.l2j.gameserver.serverpackets.MagicSkillLaunched; import net.sf.l2j.gameserver.serverpackets.MagicSkillUse; +import net.sf.l2j.gameserver.serverpackets.MoveToLocation; import net.sf.l2j.gameserver.serverpackets.NpcInfo; -import net.sf.l2j.gameserver.serverpackets.PartySpelled; import net.sf.l2j.gameserver.serverpackets.PetInfo; import net.sf.l2j.gameserver.serverpackets.RelationChanged; import net.sf.l2j.gameserver.serverpackets.Revive; @@ -547,6 +543,7 @@ * Actions :

*
  • Get the active weapon (always equiped in the right hand)


  • *
  • If weapon is a bow, check for arrows, MP and bow re-use delay (if necessary, equip the L2PcInstance with arrows in left hand)
  • + *
  • If weapon is a crossbow, check for bolts and crossbow re-use delay (if necessary, equip the L2PcInstance with bolts in left hand)
  • *
  • If weapon is a bow, consume MP and set the new period of bow non re-use


  • *
  • Get the Attack Speed of the L2Character (delay (in milliseconds) before next attack)
  • *
  • Select the type of attack to start (Simple, Bow, Pole or Dual) and verify if SoulShot are charged then start calculation
  • @@ -686,10 +683,52 @@ return; } } - else if (this instanceof L2NpcInstance) - { - if (_disableBowAttackEndTime > GameTimeController.getGameTicks()) - return; + } + // Check for a crossbow + if ((weaponItem != null && weaponItem.getItemType() == L2WeaponType.CROSSBOW)) + { + //Check for bolts + if (this instanceof L2PcInstance) + { + // Checking if target has moved to peace zone - only for player-crossbow attacks at the moment + // Other melee is checked in movement code and for offensive spells a check is done every time + if (target.isInsidePeaceZone((L2PcInstance)this)) + { + getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE); + sendPacket(new ActionFailed()); + return; + } + + // Verify if the crossbow can be use + if (_disableCrossBowAttackEndTime <= GameTimeController.getGameTicks()) + { + // Set the period of bow non re-use + _disableCrossBowAttackEndTime = 5 * GameTimeController.TICKS_PER_SECOND + GameTimeController.getGameTicks(); + } + else + { + // Cancel the action because the crossbow can't be re-use at this moment + ThreadPoolManager.getInstance().scheduleAi(new NotifyAITask(CtrlEvent.EVT_READY_TO_ACT), 1000); + + sendPacket(new ActionFailed()); + return; + } + + // Equip bolts needed in left hand and send a Server->Client packet ItemList to the L2PcINstance then return True + if (!checkAndEquipBolts()) + { + // Cancel the action because the L2PcInstance have no arrow + getAI().setIntention(CtrlIntention.AI_INTENTION_IDLE); + + sendPacket(new ActionFailed()); + sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_BOLTS)); + return; + } + } + else if (this instanceof L2NpcInstance) + { + if (_disableCrossBowAttackEndTime > GameTimeController.getGameTicks()) + return; } } @@ -743,6 +782,8 @@ hitted = doAttackHitSimple(attack, target, timeToHit); else if (weaponItem.getItemType() == L2WeaponType.BOW) hitted = doAttackHitByBow(attack, target, timeAtk, reuse); + else if (weaponItem.getItemType() == L2WeaponType.CROSSBOW) + hitted = doAttackHitByCrossBow(attack, target, timeAtk, reuse); else if (weaponItem.getItemType() == L2WeaponType.POLE) hitted = doAttackHitByPole(attack, timeToHit); else if (isUsingDualWeapon()) @@ -878,6 +919,78 @@ return !miss1; } + /** + * Launch a CrossBow attack.

    + * + * Actions :

    + *
  • Calculate if hit is missed or not
  • + *
  • Consumme bolts
  • + *
  • If hit isn't missed, calculate if shield defense is efficient
  • + *
  • If hit isn't missed, calculate if hit is critical
  • + *
  • If hit isn't missed, calculate physical damages
  • + *
  • If the L2Character is a L2PcInstance, Send a Server->Client packet SetupGauge
  • + *
  • Create a new hit task with Medium priority
  • + *
  • Calculate and set the disable delay of the crossbow in function of the Attack Speed
  • + *
  • Add this hit to the Server-Client packet Attack


  • + * + * @param attack Server->Client packet Attack in which the hit will be added + * @param target The L2Character targeted + * @param sAtk The Attack Speed of the attacker + * + * @return True if the hit isn't missed + * + */ + private boolean doAttackHitByCrossBow(Attack attack, L2Character target, int sAtk, int reuse) + { + int damage1 = 0; + boolean shld1 = false; + boolean crit1 = false; + + // Calculate if hit is missed or not + boolean miss1 = Formulas.getInstance().calcHitMiss(this, target); + + // Consumme arrows + reduceBoltCount(); + + _move = null; + + // Check if hit isn't missed + if (!miss1) + { + // Calculate if shield defense is efficient + shld1 = Formulas.getInstance().calcShldUse(this, target); + + // Calculate if hit is critical + crit1 = Formulas.getInstance().calcCrit(getStat().getCriticalHit(target, null)); + + // Calculate physical damages + damage1 = (int)Formulas.getInstance().calcPhysDam(this, target, null, shld1, crit1, false, attack.soulshot); + } + + // Check if the L2Character is a L2PcInstance + if (this instanceof L2PcInstance) + { + // Send a system message + sendPacket(new SystemMessage(SystemMessageId.CROSSBOW_PREPARING_TO_FIRE)); + + // Send a Server->Client packet SetupGauge + SetupGauge sg = new SetupGauge(SetupGauge.RED, sAtk+reuse); + sendPacket(sg); + } + + // Create a new hit task with Medium priority + ThreadPoolManager.getInstance().scheduleAi(new HitTask(target, damage1, crit1, miss1, attack.soulshot, shld1), sAtk); + + // Calculate and set the disable delay of the bow in function of the Attack Speed + _disableCrossBowAttackEndTime = (sAtk+reuse)/GameTimeController.MILLIS_IN_TICK + GameTimeController.getGameTicks(); + + // Add this hit to the Server-Client packet Attack + attack.addHit(target, damage1, miss1, crit1, shld1); + + // Return true if hit isn't missed + return !miss1; + } + /** * Launch a Dual attack.

    * @@ -3022,6 +3135,7 @@ private int _attackEndTime; private int _attacking; private int _disableBowAttackEndTime; + private int _disableCrossBowAttackEndTime; /** Table of calculators containing all standard NPC calculator (ex : ACCURACY_COMBAT, EVASION_RATE */ @@ -4481,6 +4595,18 @@ return true; } + /** + * Retun True if bolts are available.

    + * + * Overriden in :

    + *
  • L2PcInstance


  • + * + */ + protected boolean checkAndEquipBolts() + { + return true; + } + /** * Add Exp and Sp to the L2Character.

    * @@ -4808,6 +4934,11 @@ // default is to do nothin } + protected void reduceBoltCount() + { + // default is to do nothin + } + /** * Manage Forced attack (shift + select target).

    * Index: java/net/sf/l2j/gameserver/model/Inventory.java =================================================================== --- java/net/sf/l2j/gameserver/model/Inventory.java (revision 2917) +++ java/net/sf/l2j/gameserver/model/Inventory.java (working copy) @@ -169,6 +169,33 @@ } } + final class CrossBowListener implements PaperdollListener + { + public void notifyUnequiped(int slot, L2ItemInstance item) + { + if (slot != PAPERDOLL_LRHAND) + return; + if (Config.ASSERT) assert null == getPaperdollItem(PAPERDOLL_LRHAND); + if (item.getItemType() == L2WeaponType.CROSSBOW) + { + L2ItemInstance bolts = getPaperdollItem(PAPERDOLL_LHAND); + if (bolts != null) + setPaperdollItem(PAPERDOLL_LHAND, null); + } + } + public void notifyEquiped(int slot, L2ItemInstance item) + { + if (slot != PAPERDOLL_LRHAND) + return; + if (Config.ASSERT) assert item == getPaperdollItem(PAPERDOLL_LRHAND); + if (item.getItemType() == L2WeaponType.CROSSBOW) + { + L2ItemInstance bolts = findBoltForCrossBow(item.getItem()); + if (bolts != null) + setPaperdollItem(PAPERDOLL_LHAND, bolts); + } + } + } final class BowListener implements PaperdollListener { public void notifyUnequiped(int slot, L2ItemInstance item) @@ -501,6 +528,7 @@ _paperdoll = new L2ItemInstance[25]; _paperdollListeners = new FastList(); addPaperdollListener(new ArmorSetListener()); + addPaperdollListener(new CrossBowListener()); addPaperdollListener(new BowListener()); addPaperdollListener(new ItemPassiveSkillsListener()); addPaperdollListener(new StatsListener()); @@ -1190,6 +1218,29 @@ return getItemByItemId(arrowsId); } + /** + * Return the L2ItemInstance of the bolts needed for this crossbow.

    + * @param crossbow : L2Item designating the crossbow + * @return L2ItemInstance pointing out bolts for crossbow + */ + public L2ItemInstance findBoltForCrossBow(L2Item crossbow) + { + int boltsId = 0; + + switch (crossbow.getCrystalType()) + { + default: + case L2Item.CRYSTAL_NONE: boltsId = 9632; break; // Wooden Bolt + case L2Item.CRYSTAL_D: boltsId = 9633; break; // Bone Bolt + case L2Item.CRYSTAL_C: boltsId = 9634; break; // Steel Bolt + case L2Item.CRYSTAL_B: boltsId = 9635; break; // Silver Bolt + case L2Item.CRYSTAL_A: boltsId = 9636; break; // Mithril Bolt + case L2Item.CRYSTAL_S: boltsId = 9637; break; // Shining Bolt + } + + // Get the L2ItemInstance corresponding to the item identifier and return it + return getItemByItemId(boltsId); + } /** * Get back items in inventory from database */ Index: java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminEnchant.java =================================================================== --- java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminEnchant.java (revision 2917) +++ java/net/sf/l2j/gameserver/handler/admincommandhandlers/AdminEnchant.java (working copy) @@ -157,7 +157,7 @@ itemInstance = parmorInstance; } else { - // for bows and double handed weapons + // for bows/crossbows and double handed weapons parmorInstance = player.getInventory().getPaperdollItem(Inventory.PAPERDOLL_LRHAND); if (parmorInstance != null && parmorInstance.getLocationSlot() == Inventory.PAPERDOLL_LRHAND) itemInstance = parmorInstance;