123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- /*
- * Copyright (C) 2004-2015 L2J Server
- *
- * This file is part of L2J Server.
- *
- * L2J Server is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * L2J Server is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- package com.l2jserver.gameserver.network.clientpackets;
- import static com.l2jserver.gameserver.model.actor.L2Npc.INTERACTION_DISTANCE;
- import java.util.ArrayList;
- import java.util.List;
- import com.l2jserver.Config;
- import com.l2jserver.gameserver.data.xml.impl.MultisellData;
- import com.l2jserver.gameserver.model.Elementals;
- import com.l2jserver.gameserver.model.L2Augmentation;
- import com.l2jserver.gameserver.model.actor.L2Npc;
- import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
- import com.l2jserver.gameserver.model.itemcontainer.PcInventory;
- import com.l2jserver.gameserver.model.items.instance.L2ItemInstance;
- import com.l2jserver.gameserver.model.multisell.Entry;
- import com.l2jserver.gameserver.model.multisell.Ingredient;
- import com.l2jserver.gameserver.model.multisell.PreparedListContainer;
- import com.l2jserver.gameserver.network.SystemMessageId;
- import com.l2jserver.gameserver.network.serverpackets.ItemList;
- import com.l2jserver.gameserver.network.serverpackets.StatusUpdate;
- import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
- /**
- * The Class MultiSellChoose.
- */
- public class MultiSellChoose extends L2GameClientPacket
- {
- private static final String _C__B0_MULTISELLCHOOSE = "[C] B0 MultiSellChoose";
-
- private int _listId;
- private int _entryId;
- private long _amount;
- @SuppressWarnings("unused")
- private int _unk1;
- @SuppressWarnings("unused")
- private int _unk2;
- @SuppressWarnings("unused")
- private int _unk3;
- @SuppressWarnings("unused")
- private int _unk4;
- @SuppressWarnings("unused")
- private int _unk5;
- @SuppressWarnings("unused")
- private int _unk6;
- @SuppressWarnings("unused")
- private int _unk7;
- @SuppressWarnings("unused")
- private int _unk8;
- @SuppressWarnings("unused")
- private int _unk9;
- @SuppressWarnings("unused")
- private int _unk10;
- @SuppressWarnings("unused")
- private int _unk11;
-
- @Override
- protected void readImpl()
- {
- _listId = readD();
- _entryId = readD();
- _amount = readQ();
- _unk1 = readH();
- _unk2 = readD();
- _unk3 = readD();
- _unk4 = readH(); // elemental attributes
- _unk5 = readH(); // elemental attributes
- _unk6 = readH(); // elemental attributes
- _unk7 = readH(); // elemental attributes
- _unk8 = readH(); // elemental attributes
- _unk9 = readH(); // elemental attributes
- _unk10 = readH(); // elemental attributes
- _unk11 = readH(); // elemental attributes
- }
-
- @Override
- public void runImpl()
- {
- final L2PcInstance player = getClient().getActiveChar();
- if (player == null)
- {
- return;
- }
-
- if (!getClient().getFloodProtectors().getMultiSell().tryPerformAction("multisell choose"))
- {
- player.setMultiSell(null);
- return;
- }
-
- if ((_amount < 1) || (_amount > 5000))
- {
- player.setMultiSell(null);
- return;
- }
-
- PreparedListContainer list = player.getMultiSell();
- if ((list == null) || (list.getListId() != _listId))
- {
- player.setMultiSell(null);
- return;
- }
-
- final L2Npc npc = player.getLastFolkNPC();
- if (((npc != null) && !list.isNpcAllowed(npc.getId())) || ((npc == null) && list.isNpcOnly()))
- {
- player.setMultiSell(null);
- return;
- }
-
- if (!player.isGM() && (npc != null))
- {
- if (!player.isInsideRadius(npc, INTERACTION_DISTANCE, true, false) || (player.getInstanceId() != npc.getInstanceId()))
- {
- player.setMultiSell(null);
- return;
- }
- }
-
- for (Entry entry : list.getEntries())
- {
- if (entry.getEntryId() == _entryId)
- {
- if (!entry.isStackable() && (_amount > 1))
- {
- _log.severe("Character: " + player.getName() + " is trying to set amount > 1 on non-stackable multisell, id:" + _listId + ":" + _entryId);
- player.setMultiSell(null);
- return;
- }
-
- final PcInventory inv = player.getInventory();
-
- int slots = 0;
- int weight = 0;
- for (Ingredient e : entry.getProducts())
- {
- if (e.getItemId() < 0)
- {
- continue;
- }
-
- if (!e.isStackable())
- {
- slots += e.getItemCount() * _amount;
- }
- else if (player.getInventory().getItemByItemId(e.getItemId()) == null)
- {
- slots++;
- }
- weight += e.getItemCount() * _amount * e.getWeight();
- }
-
- if (!inv.validateWeight(weight))
- {
- player.sendPacket(SystemMessageId.WEIGHT_LIMIT_EXCEEDED);
- return;
- }
-
- if (!inv.validateCapacity(slots))
- {
- player.sendPacket(SystemMessageId.SLOTS_FULL);
- return;
- }
-
- ArrayList<Ingredient> ingredientsList = new ArrayList<>(entry.getIngredients().size());
- // Generate a list of distinct ingredients and counts in order to check if the correct item-counts
- // are possessed by the player
- boolean newIng;
- for (Ingredient 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 (int i = ingredientsList.size(); --i >= 0;)
- {
- Ingredient ex = ingredientsList.get(i);
- // 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.getEnchantLevel() == e.getEnchantLevel()))
- {
- if ((ex.getItemCount() + e.getItemCount()) > Integer.MAX_VALUE)
- {
- player.sendPacket(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED);
- return;
- }
- // two same ingredients, merge into one and replace old
- final Ingredient ing = ex.getCopy();
- ing.setItemCount(ex.getItemCount() + e.getItemCount());
- ingredientsList.set(i, ing);
- newIng = false;
- break;
- }
- }
- if (newIng)
- {
- // if it's a new ingredient, just store its info directly (item id, count, enchantment)
- ingredientsList.add(e);
- }
- }
-
- // now check if the player has sufficient items in the inventory to cover the ingredients' expences
- for (Ingredient e : ingredientsList)
- {
- if ((e.getItemCount() * _amount) > Integer.MAX_VALUE)
- {
- player.sendPacket(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED);
- return;
- }
- if (e.getItemId() < 0)
- {
- if (!MultisellData.hasSpecialIngredient(e.getItemId(), e.getItemCount() * _amount, player))
- {
- return;
- }
- }
- else
- {
- // 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
- final long required = ((Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMaintainIngredient()) ? (e.getItemCount() * _amount) : e.getItemCount());
- if (inv.getInventoryItemCount(e.getItemId(), list.getMaintainEnchantment() ? e.getEnchantLevel() : -1, false) < required)
- {
- SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_UNIT_OF_THE_ITEM_S1_REQUIRED);
- sm.addItemName(e.getTemplate());
- sm.addLong(required);
- player.sendPacket(sm);
- return;
- }
- }
- }
-
- List<L2Augmentation> augmentation = new ArrayList<>();
- Elementals[] elemental = null;
- /** All ok, remove items and add final product */
- for (Ingredient e : entry.getIngredients())
- {
- if (e.getItemId() < 0)
- {
- if (!MultisellData.takeSpecialIngredient(e.getItemId(), e.getItemCount() * _amount, player))
- {
- return;
- }
- }
- else
- {
- L2ItemInstance itemToTake = inv.getItemByItemId(e.getItemId()); // initialize and initial guess for the item to take.
- if (itemToTake == null)
- { // this is a cheat, transaction will be aborted and if any items already taken will not be returned back to inventory!
- _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, id:" + _listId + ":" + _entryId);
- player.setMultiSell(null);
- return;
- }
-
- // if (itemToTake.isEquipped())
- // {
- // this is a cheat, transaction will be aborted and if any items already taken will not be returned back to inventory!
- // _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, exchanging equipped item, merchatnt id:" + merchant.getNpcId());
- // player.setMultiSell(null);
- // return;
- // }
-
- if (Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMaintainIngredient())
- {
- // 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))
- {
- player.setMultiSell(null);
- 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 (list.getMaintainEnchantment())
- {
- // loop through this list and remove (one by one) each item until the required amount is taken.
- L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId(), e.getEnchantLevel(), false);
- for (int i = 0; i < (e.getItemCount() * _amount); i++)
- {
- if (inventoryContents[i].isAugmented())
- {
- augmentation.add(inventoryContents[i].getAugmentation());
- }
- if (inventoryContents[i].getElementals() != null)
- {
- elemental = inventoryContents[i].getElementals();
- }
- if (!player.destroyItem("Multisell", inventoryContents[i].getObjectId(), 1, player.getTarget(), true))
- {
- player.setMultiSell(null);
- 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(), false);
-
- itemToTake = inventoryContents[0];
- // get item with the LOWEST enchantment level from the inventory...
- // +0 is lowest by default...
- if (itemToTake.getEnchantLevel() > 0)
- {
- for (L2ItemInstance item : inventoryContents)
- {
- if (item.getEnchantLevel() < itemToTake.getEnchantLevel())
- {
- itemToTake = item;
- // nothing will have enchantment less than 0. If a zero-enchanted
- // item is found, just take it
- if (itemToTake.getEnchantLevel() == 0)
- {
- break;
- }
- }
- }
- }
- if (!player.destroyItem("Multisell", itemToTake.getObjectId(), 1, player.getTarget(), true))
- {
- player.setMultiSell(null);
- return;
- }
- }
- }
- }
- }
- }
- }
-
- // Generate the appropriate items
- for (Ingredient e : entry.getProducts())
- {
- if (e.getItemId() < 0)
- {
- MultisellData.giveSpecialProduct(e.getItemId(), e.getItemCount() * _amount, player);
- }
- else
- {
- if (e.isStackable())
- {
- inv.addItem("Multisell", 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", e.getItemId(), 1, player, player.getTarget());
- if ((product != null) && list.getMaintainEnchantment())
- {
- if (i < augmentation.size())
- {
- product.setAugmentation(new L2Augmentation(augmentation.get(i).getAugmentationId()));
- }
- if (elemental != null)
- {
- for (Elementals elm : elemental)
- {
- product.setElementAttr(elm.getElement(), elm.getValue());
- }
- }
- product.setEnchantLevel(e.getEnchantLevel());
- product.updateDatabase();
- }
- }
- }
- // msg part
- SystemMessage sm;
-
- if ((e.getItemCount() * _amount) > 1)
- {
- sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_S2_S1_S);
- sm.addItemName(e.getItemId());
- sm.addLong(e.getItemCount() * _amount);
- player.sendPacket(sm);
- }
- else
- {
- if (list.getMaintainEnchantment() && (e.getEnchantLevel() > 0))
- {
- sm = SystemMessage.getSystemMessage(SystemMessageId.ACQUIRED_S1_S2);
- sm.addLong(e.getEnchantLevel());
- sm.addItemName(e.getItemId());
- }
- else
- {
- sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1);
- sm.addItemName(e.getItemId());
- }
- player.sendPacket(sm);
- }
- }
- }
- player.sendPacket(new ItemList(player, false));
-
- StatusUpdate su = new StatusUpdate(player);
- su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad());
- player.sendPacket(su);
-
- // finally, give the tax to the castle...
- if ((npc != null) && (entry.getTaxAmount() > 0))
- {
- npc.getCastle().addToTreasury(entry.getTaxAmount() * _amount);
- }
- break;
- }
- }
- }
-
- @Override
- public String getType()
- {
- return _C__B0_MULTISELLCHOOSE;
- }
- }
|