}
}
private void doExchange(L2PcInstance player, MultiSellEntry templateEntry, boolean applyTaxes, boolean maintainEnchantment, int enchantment)
{
PcInventory inv = player.getInventory();
boolean maintainItemFound = false;
// given the template entry and information about maintaining enchantment and applying taxes re-create the instance of
// the entry that will be used for this exchange i.e. change the enchantment level of select ingredient/products and adena amount appropriately.
L2NpcInstance merchant = player.getTarget() instanceof L2NpcInstance ? (L2NpcInstance) player.getTarget() : null;
// if(merchant == null) TODO: Check this
// return;
MultiSellEntry entry = prepareEntry(merchant, templateEntry, applyTaxes, maintainEnchantment, enchantment);
// Generate a list of distinct ingredients and counts in order to check if the correct item-counts
// are possessed by the player
FastList<MultiSellIngredient> _ingredientsList = new FastList<MultiSellIngredient>();
boolean newIng = true;
for (MultiSellIngredient e : entry.getIngredients())
{
newIng = true;
// at this point, the template has already been modified so that enchantments are properly included
// whenever they need to be applied. Uniqueness of items is thus judged by item id AND enchantment level
for (MultiSellIngredient ex : _ingredientsList)
{
// if the item was already added in the list, merely increment the count
// this happens if 1 list entry has the same ingredient twice (example 2 swords = 1 dual)
if (ex.getItemId() == e.getItemId() && ex.getEnchantmentLevel() == e.getEnchantmentLevel())
{
if ((double) ex.getItemCount() + e.getItemCount() > Integer.MAX_VALUE)
{
player.sendPacket(new SystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
_ingredientsList.clear();
_ingredientsList = null;
return;
}
ex.setItemCount(ex.getItemCount() + e.getItemCount());
newIng = false;
}
}
if (newIng)
{
// If there is a maintainIngredient, then we do not need to check the enchantment parameter as the enchant level will be checked elsewhere
if (maintainEnchantment)
{
maintainItemFound = true;
}
// if it's a new ingredient, just store its info directly (item id, count, enchantment)
_ingredientsList.add(new MultiSellIngredient(e));
}
}
// If there is no maintainIngredient, then we must make sure that the enchantment is not kept from the client packet, as it may have been forged
if (!maintainItemFound)
{
for (MultiSellIngredient product : entry.getProducts())
{
product.setEnchantmentLevel(0);
}
}
// now check if the player has sufficient items in the inventory to cover the ingredients' expences
for (MultiSellIngredient e : _ingredientsList)
{
if ((double) e.getItemCount() * _amount > Integer.MAX_VALUE)
{
player.sendPacket(new SystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
_ingredientsList.clear();
_ingredientsList = null;
return;
}
if (e.getItemId() != 65336 && e.getItemId() != 65436)
{
// if this is not a list that maintains enchantment, check the count of all items that have the given id.
// otherwise, check only the count of items with exactly the needed enchantment level
if (inv.getInventoryItemCount(e.getItemId(), maintainEnchantment ? e.getEnchantmentLevel() : -1) < (Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMantainIngredient() ? e.getItemCount() * _amount : e.getItemCount()))
{
player.sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));
_ingredientsList.clear();
_ingredientsList = null;
return;
}
}
else
{
if (e.getItemId() == 65336)
{
if (player.getClan() == null)
{
player.sendPacket(new SystemMessage(SystemMessageId.YOU_ARE_NOT_A_CLAN_MEMBER));
return;
}
if (!player.isClanLeader())
{
player.sendPacket(new SystemMessage(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED));
return;
}
if (player.getClan().getReputationScore() < e.getItemCount() * _amount)
{
player.sendPacket(new SystemMessage(SystemMessageId.THE_CLAN_REPUTATION_SCORE_IS_TOO_LOW));
return;
}
}
if (e.getItemId() == 65436 && e.getItemCount() * _amount > player.getPcBangScore())
{
player.sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));
return;
}
}
}
_ingredientsList.clear();
_ingredientsList = null;
FastList<L2Augmentation> augmentation = new FastList<L2Augmentation>();
/** All ok, remove items and add final product */
for (MultiSellIngredient e : entry.getIngredients())
{
if (e.getItemId() != 65336 && e.getItemId() != 65436)
{
for (MultiSellIngredient a : entry.getProducts())
{
if (player.getInventoryLimit() < inv.getSize() + _amount && !ItemTable.getInstance().createDummyItem(a.getItemId()).isStackable())
{
player.sendPacket(new SystemMessage(SystemMessageId.SLOTS_FULL));
return;
}
if (player.getInventoryLimit() < inv.getSize() && ItemTable.getInstance().createDummyItem(a.getItemId()).isStackable())
{
player.sendPacket(new SystemMessage(SystemMessageId.SLOTS_FULL));
return;
}
}
L2ItemInstance itemToTake = inv.getItemByItemId(e.getItemId()); // initialize and initial guess for the item to take.
//this is a cheat, transaction will be aborted and if any items already tanken will not be returned back to inventory!
if (itemToTake == null)
{
_log.severe("Character: " + player.getName() + " is trying to cheat in multisell, merchatnt id:" + (merchant!=null?merchant.getNpcId():0));
return;
}
if (itemToTake.fireEvent("MULTISELL", (Object[]) null) != null)
return;
if (itemToTake.isWear())
{
_log.severe("Character: " + player.getName() + " is trying to cheat in multisell with weared item");
return;
}
if (Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMantainIngredient())
{
// if it's a stackable item, just reduce the amount from the first (only) instance that is found in the inventory
if (itemToTake.isStackable())
{
if (!player.destroyItem("Multisell", itemToTake.getObjectId(), (e.getItemCount() * _amount), player.getTarget(), true))
return;
}
else
{
// for non-stackable items, one of two scenaria are possible:
// a) list maintains enchantment: get the instances that exactly match the requested enchantment level
// b) list does not maintain enchantment: get the instances with the LOWEST enchantment level
// a) if enchantment is maintained, then get a list of items that exactly match this enchantment
if (maintainEnchantment)
{
// loop through this list and remove (one by one) each item until the required amount is taken.
L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId(), e.getEnchantmentLevel());
for (int i = 0; i < e.getItemCount() * _amount; i++)
{
if (inventoryContents[i].isAugmented())
{
augmentation.add(inventoryContents[i].getAugmentation());
}
if (inventoryContents[i].isEquipped())
{
if (inventoryContents[i].isAugmented())
{
inventoryContents[i].getAugmentation().removeBoni(player);
}
}
if (!player.destroyItem("Multisell", inventoryContents[i].getObjectId(), 1, player.getTarget(), true))
return;
}
}
else
// b) enchantment is not maintained. Get the instances with the LOWEST enchantment level
{
/* NOTE: There are 2 ways to achieve the above goal.
* 1) Get all items that have the correct itemId, loop through them until the lowest enchantment
* level is found. Repeat all this for the next item until proper count of items is reached.
* 2) Get all items that have the correct itemId, sort them once based on enchantment level,
* and get the range of items that is necessary.
* Method 1 is faster for a small number of items to be exchanged.
* Method 2 is faster for large amounts.
*
* EXPLANATION:
* Worst case scenario for algorithm 1 will make it run in a number of cycles given by:
* m*(2n-m+1)/2 where m is the number of items to be exchanged and n is the total
* number of inventory items that have a matching id.
* With algorithm 2 (sort), sorting takes n*log(n) time and the choice is done in a single cycle
* for case b (just grab the m first items) or in linear time for case a (find the beginning of items
* with correct enchantment, index x, and take all items from x to x+m).
* Basically, whenever m > log(n) we have: m*(2n-m+1)/2 = (2nm-m*m+m)/2 >
* (2nlogn-logn*logn+logn)/2 = nlog(n) - log(n*n) + log(n) = nlog(n) + log(n/n*n) =
* nlog(n) + log(1/n) = nlog(n) - log(n) = (n-1)log(n)
* So for m < log(n) then m*(2n-m+1)/2 > (n-1)log(n) and m*(2n-m+1)/2 > nlog(n)
*
* IDEALLY:
* In order to best optimize the performance, choose which algorithm to run, based on whether 2^m > n
* if ( (2<<(e.getItemCount() * _amount)) < inventoryContents.length )
* // do Algorithm 1, no sorting
* else
* // do Algorithm 2, sorting
*
* CURRENT IMPLEMENTATION:
* In general, it is going to be very rare for a person to do a massive exchange of non-stackable items
* For this reason, we assume that algorithm 1 will always suffice and we keep things simple.
* If, in the future, it becomes necessary that we optimize, the above discussion should make it clear
* what optimization exactly is necessary (based on the comments under "IDEALLY").
*/
// choice 1. Small number of items exchanged. No sorting.
for (int i = 1; i <= e.getItemCount() * _amount; i++)
{
L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId());
itemToTake = inventoryContents[0];
// get item with the LOWEST enchantment level from the inventory...
// +0 is lowest by default...
if (itemToTake.getEnchantLevel() > 0)
{
for (L2ItemInstance inventoryContent : inventoryContents)
{
if (inventoryContent.getEnchantLevel() < itemToTake.getEnchantLevel())
{
itemToTake = inventoryContent;
// nothing will have enchantment less than 0. If a zero-enchanted
// item is found, just take it
if (itemToTake.getEnchantLevel() == 0)
{
break;
}
}
}
}
if (itemToTake.isEquipped())
{
if (itemToTake.isAugmented())
{
itemToTake.getAugmentation().removeBoni(player);
}
}
if (!player.destroyItem("Multisell", itemToTake.getObjectId(), 1, player.getTarget(), true))
return;
}
}
}
}
}
else
{
if (e.getItemId() == 65336)
{
int repCost = player.getClan().getReputationScore() - e.getItemCount();
player.getClan().setReputationScore(repCost, true);
player.sendPacket(new SystemMessage(SystemMessageId.S1_DEDUCTED_FROM_CLAN_REP).addNumber(e.getItemCount()));
player.getClan().broadcastToOnlineMembers(new PledgeShowInfoUpdate(player.getClan()));
}
else
{
player.reducePcBangScore(e.getItemCount() * _amount);
player.sendPacket(new SystemMessage(SystemMessageId.USING_S1_PCPOINT).addNumber(e.getItemCount()));
}
}
}
// Generate the appropriate items
for (MultiSellIngredient e : entry.getProducts())
{
if (ItemTable.getInstance().createDummyItem(e.getItemId()).isStackable())
{
inv.addItem("Multisell["+_listId+"]" , e.getItemId(), (e.getItemCount() * _amount), player, player.getTarget());
}
else
{
L2ItemInstance product = null;
for (int i = 0; i < e.getItemCount() * _amount; i++)
{
product = inv.addItem("Multisell["+_listId+"]", e.getItemId(), 1, player, player.getTarget());
if (maintainEnchantment && (product != null))
{
if (i < augmentation.size())
{
product.setAugmentation(new L2Augmentation(product, augmentation.get(i).getAugmentationId(), augmentation.get(i).getSkill(), true));