MultiSellChoose.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*
  2. * This program is free software: you can redistribute it and/or modify it under
  3. * the terms of the GNU General Public License as published by the Free Software
  4. * Foundation, either version 3 of the License, or (at your option) any later
  5. * version.
  6. *
  7. * This program is distributed in the hope that it will be useful, but WITHOUT
  8. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  9. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  10. * details.
  11. *
  12. * You should have received a copy of the GNU General Public License along with
  13. * this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. package com.l2jserver.gameserver.network.clientpackets;
  16. import java.util.ArrayList;
  17. import java.util.logging.Logger;
  18. import javolution.util.FastList;
  19. import com.l2jserver.Config;
  20. import com.l2jserver.gameserver.datatables.MultiSell;
  21. import com.l2jserver.gameserver.model.Elementals;
  22. import com.l2jserver.gameserver.model.L2Augmentation;
  23. import com.l2jserver.gameserver.model.L2ItemInstance;
  24. import com.l2jserver.gameserver.model.actor.L2Npc;
  25. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  26. import com.l2jserver.gameserver.model.itemcontainer.PcInventory;
  27. import com.l2jserver.gameserver.model.multisell.Entry;
  28. import com.l2jserver.gameserver.model.multisell.Ingredient;
  29. import com.l2jserver.gameserver.model.multisell.PreparedListContainer;
  30. import com.l2jserver.gameserver.network.SystemMessageId;
  31. import com.l2jserver.gameserver.network.serverpackets.ItemList;
  32. import com.l2jserver.gameserver.network.serverpackets.StatusUpdate;
  33. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  34. /**
  35. * The Class MultiSellChoose.
  36. */
  37. public class MultiSellChoose extends L2GameClientPacket
  38. {
  39. private static final String _C__A7_MULTISELLCHOOSE = "[C] A7 MultiSellChoose";
  40. private static Logger _log = Logger.getLogger(MultiSellChoose.class.getName());
  41. private int _listId;
  42. private int _entryId;
  43. private long _amount;
  44. /*@SuppressWarnings("unused")
  45. private int _unk1;
  46. @SuppressWarnings("unused")
  47. private int _unk2;
  48. @SuppressWarnings("unused")
  49. private int _unk3;
  50. @SuppressWarnings("unused")
  51. private int _unk7;
  52. @SuppressWarnings("unused")
  53. private int _unk4;
  54. @SuppressWarnings("unused")
  55. private int _unk5;
  56. @SuppressWarnings("unused")
  57. private int _unk6;
  58. @SuppressWarnings("unused")
  59. private int _unk8;
  60. @SuppressWarnings("unused")
  61. private int _unk9;
  62. @SuppressWarnings("unused")
  63. private int _unk10;
  64. @SuppressWarnings("unused")
  65. private int _unk11;*/
  66. /* (non-Javadoc)
  67. * @see com.l2jserver.gameserver.network.clientpackets.L2GameClientPacket#readImpl()
  68. */
  69. @Override
  70. protected void readImpl()
  71. {
  72. _listId = readD();
  73. _entryId = readD();
  74. _amount = readQ();
  75. /*_unk1 = readH();
  76. _unk2 = readD();
  77. _unk3 = readD();
  78. _unk4 = readH(); // elemental attributes
  79. _unk5 = readH(); // elemental attributes
  80. _unk6 = readH(); // elemental attributes
  81. _unk7 = readH(); // elemental attributes
  82. _unk8 = readH(); // elemental attributes
  83. _unk9 = readH(); // elemental attributes
  84. _unk10 = readH(); // elemental attributes
  85. _unk11 = readH(); // elemental attributes*/
  86. }
  87. /* (non-Javadoc)
  88. * @see com.l2jserver.gameserver.network.clientpackets.L2GameClientPacket#runImpl()
  89. */
  90. @Override
  91. public void runImpl()
  92. {
  93. final L2PcInstance player = getClient().getActiveChar();
  94. if (player == null)
  95. return;
  96. if (!getClient().getFloodProtectors().getMultiSell().tryPerformAction("multisell choose"))
  97. {
  98. player.setMultiSell(null);
  99. return;
  100. }
  101. if (_amount < 1 || _amount > 5000)
  102. {
  103. player.setMultiSell(null);
  104. return;
  105. }
  106. PreparedListContainer list = player.getMultiSell();
  107. if (list == null || list.getListId() != _listId)
  108. {
  109. player.setMultiSell(null);
  110. return;
  111. }
  112. L2Npc target = player.getLastFolkNPC();
  113. if (!player.isGM() && (target == null
  114. || !list.checkNpcObjectId(target.getObjectId())
  115. || !target.canInteract(player)))
  116. {
  117. player.setMultiSell(null);
  118. return;
  119. }
  120. for (Entry entry : list.getEntries())
  121. {
  122. if (entry.getEntryId() == _entryId)
  123. {
  124. if (!entry.isStackable() && _amount > 1)
  125. {
  126. _log.severe("Character: " + player.getName() + " is trying to set amount > 1 on non-stackable multisell, id:" + _listId + ":" + _entryId);
  127. player.setMultiSell(null);
  128. return;
  129. }
  130. final PcInventory inv = player.getInventory();
  131. int slots = 0;
  132. int weight = 0;
  133. for (Ingredient e : entry.getProducts())
  134. {
  135. if (e.getItemId() < 0) // special
  136. continue;
  137. if (!e.isStackable())
  138. slots += e.getItemCount() * _amount;
  139. else if (player.getInventory().getItemByItemId(e.getItemId()) == null)
  140. slots++;
  141. weight += e.getItemCount() * _amount * e.getWeight();
  142. }
  143. if (!inv.validateWeight(weight))
  144. {
  145. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.WEIGHT_LIMIT_EXCEEDED));
  146. return;
  147. }
  148. if (!inv.validateCapacity(slots))
  149. {
  150. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.SLOTS_FULL));
  151. return;
  152. }
  153. ArrayList<Ingredient> ingredientsList = new ArrayList<Ingredient>(entry.getIngredients().size());
  154. // Generate a list of distinct ingredients and counts in order to check if the correct item-counts
  155. // are possessed by the player
  156. boolean newIng;
  157. for (Ingredient e : entry.getIngredients())
  158. {
  159. newIng = true;
  160. // at this point, the template has already been modified so that enchantments are properly included
  161. // whenever they need to be applied. Uniqueness of items is thus judged by item id AND enchantment level
  162. for (int i = ingredientsList.size(); --i >= 0;)
  163. {
  164. Ingredient ex = ingredientsList.get(i);
  165. // if the item was already added in the list, merely increment the count
  166. // this happens if 1 list entry has the same ingredient twice (example 2 swords = 1 dual)
  167. if ((ex.getItemId() == e.getItemId()) && (ex.getEnchantLevel() == e.getEnchantLevel()))
  168. {
  169. if (ex.getItemCount() + e.getItemCount() > Integer.MAX_VALUE)
  170. {
  171. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
  172. return;
  173. }
  174. // two same ingredients, merge into one and replace old
  175. final Ingredient ing = ex.clone();
  176. ing.setItemCount(ex.getItemCount() + e.getItemCount());
  177. ingredientsList.set(i, ing);
  178. newIng = false;
  179. break;
  180. }
  181. }
  182. if (newIng)
  183. {
  184. // if it's a new ingredient, just store its info directly (item id, count, enchantment)
  185. ingredientsList.add(e);
  186. }
  187. }
  188. // now check if the player has sufficient items in the inventory to cover the ingredients' expences
  189. for (Ingredient e : ingredientsList)
  190. {
  191. if (e.getItemCount() * _amount > Integer.MAX_VALUE)
  192. {
  193. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
  194. return;
  195. }
  196. if (e.getItemId() < 0)
  197. {
  198. if (!MultiSell.checkSpecialIngredient(e.getItemId(), e.getItemCount() * _amount, player))
  199. return;
  200. }
  201. else
  202. {
  203. // if this is not a list that maintains enchantment, check the count of all items that have the given id.
  204. // otherwise, check only the count of items with exactly the needed enchantment level
  205. final long required = ((Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMaintainIngredient()) ? (e.getItemCount() * _amount) : e.getItemCount());
  206. if (inv.getInventoryItemCount(e.getItemId(), list.getMaintainEnchantment() ? e.getEnchantLevel() : -1, false) < required)
  207. {
  208. SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_UNIT_OF_THE_ITEM_S1_REQUIRED);
  209. sm.addItemName(e.getTemplate());
  210. sm.addNumber((int)required);
  211. player.sendPacket(sm);
  212. return;
  213. }
  214. }
  215. }
  216. FastList<L2Augmentation> augmentation = FastList.newInstance();
  217. Elementals[] elemental = null;
  218. /** All ok, remove items and add final product */
  219. try
  220. {
  221. for (Ingredient e : entry.getIngredients())
  222. {
  223. if (e.getItemId() < 0)
  224. {
  225. if (!MultiSell.getSpecialIngredient(e.getItemId(), e.getItemCount() * _amount, player))
  226. return;
  227. }
  228. else
  229. {
  230. L2ItemInstance itemToTake = inv.getItemByItemId(e.getItemId()); // initialize and initial guess for the item to take.
  231. if (itemToTake == null)
  232. { //this is a cheat, transaction will be aborted and if any items already taken will not be returned back to inventory!
  233. _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, id:" + _listId + ":" + _entryId);
  234. player.setMultiSell(null);
  235. return;
  236. }
  237. /*if (itemToTake.isEquipped())
  238. { //this is a cheat, transaction will be aborted and if any items already taken will not be returned back to inventory!
  239. _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, exchanging equipped item, merchatnt id:" + merchant.getNpcId());
  240. player.setMultiSell(null);
  241. return;
  242. }*/
  243. if (Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMaintainIngredient())
  244. {
  245. // if it's a stackable item, just reduce the amount from the first (only) instance that is found in the inventory
  246. if (itemToTake.isStackable())
  247. {
  248. if (!player.destroyItem("Multisell", itemToTake.getObjectId(), (e.getItemCount() * _amount), player.getTarget(), true))
  249. {
  250. player.setMultiSell(null);
  251. return;
  252. }
  253. }
  254. else
  255. {
  256. // for non-stackable items, one of two scenaria are possible:
  257. // a) list maintains enchantment: get the instances that exactly match the requested enchantment level
  258. // b) list does not maintain enchantment: get the instances with the LOWEST enchantment level
  259. // a) if enchantment is maintained, then get a list of items that exactly match this enchantment
  260. if (list.getMaintainEnchantment())
  261. {
  262. // loop through this list and remove (one by one) each item until the required amount is taken.
  263. L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId(), e.getEnchantLevel(), false);
  264. for (int i = 0; i < (e.getItemCount() * _amount); i++)
  265. {
  266. if (inventoryContents[i].isAugmented())
  267. augmentation.add(inventoryContents[i].getAugmentation());
  268. if(inventoryContents[i].getElementals() != null)
  269. elemental = inventoryContents[i].getElementals();
  270. if (!player.destroyItem("Multisell", inventoryContents[i].getObjectId(), 1, player.getTarget(), true))
  271. {
  272. player.setMultiSell(null);
  273. return;
  274. }
  275. }
  276. }
  277. else
  278. // b) enchantment is not maintained. Get the instances with the LOWEST enchantment level
  279. {
  280. /* NOTE: There are 2 ways to achieve the above goal.
  281. * 1) Get all items that have the correct itemId, loop through them until the lowest enchantment
  282. * level is found. Repeat all this for the next item until proper count of items is reached.
  283. * 2) Get all items that have the correct itemId, sort them once based on enchantment level,
  284. * and get the range of items that is necessary.
  285. * Method 1 is faster for a small number of items to be exchanged.
  286. * Method 2 is faster for large amounts.
  287. *
  288. * EXPLANATION:
  289. * Worst case scenario for algorithm 1 will make it run in a number of cycles given by:
  290. * m*(2n-m+1)/2 where m is the number of items to be exchanged and n is the total
  291. * number of inventory items that have a matching id.
  292. * With algorithm 2 (sort), sorting takes n*log(n) time and the choice is done in a single cycle
  293. * for case b (just grab the m first items) or in linear time for case a (find the beginning of items
  294. * with correct enchantment, index x, and take all items from x to x+m).
  295. * Basically, whenever m > log(n) we have: m*(2n-m+1)/2 = (2nm-m*m+m)/2 >
  296. * (2nlogn-logn*logn+logn)/2 = nlog(n) - log(n*n) + log(n) = nlog(n) + log(n/n*n) =
  297. * nlog(n) + log(1/n) = nlog(n) - log(n) = (n-1)log(n)
  298. * So for m < log(n) then m*(2n-m+1)/2 > (n-1)log(n) and m*(2n-m+1)/2 > nlog(n)
  299. *
  300. * IDEALLY:
  301. * In order to best optimize the performance, choose which algorithm to run, based on whether 2^m > n
  302. * if ( (2<<(e.getItemCount() * _amount)) < inventoryContents.length )
  303. * // do Algorithm 1, no sorting
  304. * else
  305. * // do Algorithm 2, sorting
  306. *
  307. * CURRENT IMPLEMENTATION:
  308. * In general, it is going to be very rare for a person to do a massive exchange of non-stackable items
  309. * For this reason, we assume that algorithm 1 will always suffice and we keep things simple.
  310. * If, in the future, it becomes necessary that we optimize, the above discussion should make it clear
  311. * what optimization exactly is necessary (based on the comments under "IDEALLY").
  312. */
  313. // choice 1. Small number of items exchanged. No sorting.
  314. for (int i = 1; i <= (e.getItemCount() * _amount); i++)
  315. {
  316. L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId(), false);
  317. itemToTake = inventoryContents[0];
  318. // get item with the LOWEST enchantment level from the inventory...
  319. // +0 is lowest by default...
  320. if (itemToTake.getEnchantLevel() > 0)
  321. {
  322. for (L2ItemInstance item : inventoryContents)
  323. {
  324. if (item.getEnchantLevel() < itemToTake.getEnchantLevel())
  325. {
  326. itemToTake = item;
  327. // nothing will have enchantment less than 0. If a zero-enchanted
  328. // item is found, just take it
  329. if (itemToTake.getEnchantLevel() == 0)
  330. break;
  331. }
  332. }
  333. }
  334. if (!player.destroyItem("Multisell", itemToTake.getObjectId(), 1, player.getTarget(), true))
  335. {
  336. player.setMultiSell(null);
  337. return;
  338. }
  339. }
  340. }
  341. }
  342. }
  343. }
  344. }
  345. // Generate the appropriate items
  346. for (Ingredient e : entry.getProducts())
  347. {
  348. if (e.getItemId() < 0)
  349. {
  350. MultiSell.addSpecialProduct(e.getItemId(), e.getItemCount() * _amount, player);
  351. }
  352. else
  353. {
  354. if (e.isStackable())
  355. inv.addItem("Multisell", e.getItemId(), e.getItemCount() * _amount, player, player.getTarget());
  356. else
  357. {
  358. L2ItemInstance product = null;
  359. for (int i = 0; i < (e.getItemCount() * _amount); i++)
  360. {
  361. product = inv.addItem("Multisell", e.getItemId(), 1, player, player.getTarget());
  362. if (list.getMaintainEnchantment())
  363. {
  364. if (i < augmentation.size())
  365. product.setAugmentation(new L2Augmentation(augmentation.get(i).getAugmentationId(), augmentation.get(i).getSkill()));
  366. if (elemental != null)
  367. for (Elementals elm : elemental)
  368. product.setElementAttr(elm.getElement(), elm.getValue());
  369. product.setEnchantLevel(e.getEnchantLevel());
  370. product.updateDatabase();
  371. }
  372. }
  373. }
  374. // msg part
  375. SystemMessage sm;
  376. if (e.getItemCount() * _amount > 1)
  377. {
  378. sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_S2_S1_S);
  379. sm.addItemName(e.getItemId());
  380. sm.addItemNumber(e.getItemCount() * _amount);
  381. player.sendPacket(sm);
  382. sm = null;
  383. }
  384. else
  385. {
  386. if (list.getMaintainEnchantment() && e.getEnchantLevel() > 0)
  387. {
  388. sm = SystemMessage.getSystemMessage(SystemMessageId.ACQUIRED_S1_S2);
  389. sm.addItemNumber(e.getEnchantLevel());
  390. sm.addItemName(e.getItemId());
  391. }
  392. else
  393. {
  394. sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1);
  395. sm.addItemName(e.getItemId());
  396. }
  397. player.sendPacket(sm);
  398. sm = null;
  399. }
  400. }
  401. }
  402. player.sendPacket(new ItemList(player, false));
  403. StatusUpdate su = new StatusUpdate(player);
  404. su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad());
  405. player.sendPacket(su);
  406. su = null;
  407. }
  408. finally
  409. {
  410. FastList.recycle(augmentation);
  411. }
  412. // finally, give the tax to the castle...
  413. if (entry.getTaxAmount() > 0)
  414. target.getCastle().addToTreasury(entry.getTaxAmount() * _amount);
  415. break;
  416. }
  417. }
  418. }
  419. @Override
  420. public String getType()
  421. {
  422. return _C__A7_MULTISELLCHOOSE;
  423. }
  424. }