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/L2Character.java =================================================================== --- java/net/sf/l2j/gameserver/model/L2Character.java (revision 2917) +++ java/net/sf/l2j/gameserver/model/L2Character.java (working copy) @@ -686,6 +686,47 @@ 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 (_disableBowAttackEndTime > GameTimeController.getGameTicks()) @@ -743,6 +784,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 +921,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 +3137,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 +4597,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 +4936,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 */