MultiSellChoose.java 17 KB

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