MultiSellChoose.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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 net.sf.l2j.gameserver.clientpackets;
  16. import java.util.logging.Logger;
  17. import javolution.util.FastList;
  18. import net.sf.l2j.Config;
  19. import net.sf.l2j.gameserver.datatables.ItemTable;
  20. import net.sf.l2j.gameserver.model.L2Augmentation;
  21. import net.sf.l2j.gameserver.model.L2ItemInstance;
  22. import net.sf.l2j.gameserver.model.L2Multisell;
  23. import net.sf.l2j.gameserver.model.PcInventory;
  24. import net.sf.l2j.gameserver.model.L2Multisell.MultiSellEntry;
  25. import net.sf.l2j.gameserver.model.L2Multisell.MultiSellIngredient;
  26. import net.sf.l2j.gameserver.model.L2Multisell.MultiSellListContainer;
  27. import net.sf.l2j.gameserver.model.actor.instance.L2NpcInstance;
  28. import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
  29. import net.sf.l2j.gameserver.network.SystemMessageId;
  30. import net.sf.l2j.gameserver.serverpackets.ItemList;
  31. import net.sf.l2j.gameserver.serverpackets.PledgeShowInfoUpdate;
  32. import net.sf.l2j.gameserver.serverpackets.StatusUpdate;
  33. import net.sf.l2j.gameserver.serverpackets.SystemMessage;
  34. import net.sf.l2j.gameserver.templates.L2Armor;
  35. import net.sf.l2j.gameserver.templates.L2Item;
  36. import net.sf.l2j.gameserver.templates.L2Weapon;
  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 int _amount;
  44. private int _enchantment;
  45. private int _transactionTax; // local handling of taxation
  46. @Override
  47. protected void readImpl()
  48. {
  49. _listId = readD();
  50. _entryId = readD();
  51. _amount = readD();
  52. // _enchantment = readH(); // <---commented this line because it did NOT work!
  53. _enchantment = _entryId % 100000;
  54. _entryId = _entryId / 100000;
  55. _transactionTax = 0; // initialize tax amount to 0...
  56. }
  57. @Override
  58. public void runImpl()
  59. {
  60. if(_amount < 1 || _amount > 5000)
  61. return;
  62. MultiSellListContainer list = L2Multisell.getInstance().getList(_listId);
  63. if(list == null) return;
  64. L2PcInstance player = getClient().getActiveChar();
  65. if(player == null) return;
  66. for(MultiSellEntry entry : list.getEntries())
  67. {
  68. if(entry.getEntryId() == _entryId)
  69. {
  70. doExchange(player,entry,list.getApplyTaxes(), list.getMaintainEnchantment(), _enchantment);
  71. return;
  72. }
  73. }
  74. }
  75. private void doExchange(L2PcInstance player, MultiSellEntry templateEntry, boolean applyTaxes, boolean maintainEnchantment, int enchantment)
  76. {
  77. PcInventory inv = player.getInventory();
  78. // given the template entry and information about maintaining enchantment and applying taxes
  79. // re-create the instance of the entry that will be used for this exchange
  80. // i.e. change the enchantment level of select ingredient/products and adena amount appropriately.
  81. L2NpcInstance merchant = (player.getTarget() instanceof L2NpcInstance)? (L2NpcInstance) player.getTarget() : null;
  82. if (merchant == null) return;
  83. MultiSellEntry entry = prepareEntry(merchant, templateEntry, applyTaxes, maintainEnchantment, enchantment);
  84. // Generate a list of distinct ingredients and counts in order to check if the correct item-counts
  85. // are possessed by the player
  86. FastList<MultiSellIngredient> _ingredientsList = new FastList<MultiSellIngredient>();
  87. boolean newIng = true;
  88. for(MultiSellIngredient e: entry.getIngredients())
  89. {
  90. newIng = true;
  91. // at this point, the template has already been modified so that enchantments are properly included
  92. // whenever they need to be applied. Uniqueness of items is thus judged by item id AND enchantment level
  93. for(MultiSellIngredient ex: _ingredientsList)
  94. {
  95. // if the item was already added in the list, merely increment the count
  96. // this happens if 1 list entry has the same ingredient twice (example 2 swords = 1 dual)
  97. if( (ex.getItemId() == e.getItemId()) && (ex.getEnchantmentLevel() == e.getEnchantmentLevel()) )
  98. {
  99. if ((double)ex.getItemCount() + e.getItemCount() > Integer.MAX_VALUE) {
  100. player.sendPacket(new SystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
  101. _ingredientsList.clear();
  102. _ingredientsList = null;
  103. return;
  104. }
  105. ex.setItemCount(ex.getItemCount() + e.getItemCount());
  106. newIng = false;
  107. }
  108. }
  109. if(newIng)
  110. {
  111. // if it's a new ingredient, just store its info directly (item id, count, enchantment)
  112. _ingredientsList.add(L2Multisell.getInstance().new MultiSellIngredient(e));
  113. }
  114. }
  115. // now check if the player has sufficient items in the inventory to cover the ingredients' expences
  116. for(MultiSellIngredient e : _ingredientsList)
  117. {
  118. if((double)e.getItemCount() * _amount > Integer.MAX_VALUE )
  119. {
  120. player.sendPacket(new SystemMessage(SystemMessageId.YOU_HAVE_EXCEEDED_QUANTITY_THAT_CAN_BE_INPUTTED));
  121. _ingredientsList.clear();
  122. _ingredientsList = null;
  123. return;
  124. }
  125. if(e.getItemId() != -200)
  126. {
  127. // if this is not a list that maintains enchantment, check the count of all items that have the given id.
  128. // otherwise, check only the count of items with exactly the needed enchantment level
  129. if( inv.getInventoryItemCount(e.getItemId(), maintainEnchantment? e.getEnchantmentLevel() : -1) < ((Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMantainIngredient()) ? (e.getItemCount() * _amount) : e.getItemCount()) )
  130. {
  131. player.sendPacket(new SystemMessage(SystemMessageId.NOT_ENOUGH_ITEMS));
  132. _ingredientsList.clear();
  133. _ingredientsList = null;
  134. return;
  135. }
  136. }
  137. else
  138. {
  139. if(player.getClan() == null)
  140. {
  141. player.sendPacket(new SystemMessage(SystemMessageId.YOU_ARE_NOT_A_CLAN_MEMBER));
  142. return;
  143. }
  144. if(!player.isClanLeader())
  145. {
  146. player.sendPacket(new SystemMessage(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED));
  147. return;
  148. }
  149. if(player.getClan().getReputationScore() < e.getItemCount())
  150. {
  151. player.sendPacket(new SystemMessage(SystemMessageId.THE_CLAN_REPUTATION_SCORE_IS_TOO_LOW));
  152. return;
  153. }
  154. }
  155. }
  156. _ingredientsList.clear();
  157. _ingredientsList = null;
  158. FastList<L2Augmentation> augmentation = new FastList<L2Augmentation>();
  159. /** All ok, remove items and add final product */
  160. for(MultiSellIngredient e : entry.getIngredients())
  161. {
  162. if(e.getItemId()!= -200)
  163. {
  164. L2ItemInstance itemToTake = inv.getItemByItemId(e.getItemId()); // initialize and initial guess for the item to take.
  165. if (itemToTake == null)
  166. { //this is a cheat, transaction will be aborted and if any items already taken will not be returned back to inventory!
  167. _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, merchatnt id:" + merchant.getNpcId());
  168. return;
  169. }
  170. if(itemToTake.isWear())
  171. {//Player trying to buy something from the Multisell store with an item that's just being used from the Wear option from merchants.
  172. _log.severe("Character: " + player.getName() + " is trying to cheat in multisell, merchatnt id:" + merchant.getNpcId());
  173. return;
  174. }
  175. if (Config.ALT_BLACKSMITH_USE_RECIPES || !e.getMantainIngredient())
  176. {
  177. // if it's a stackable item, just reduce the amount from the first (only) instance that is found in the inventory
  178. if (itemToTake.isStackable())
  179. {
  180. if (!player.destroyItem("Multisell", itemToTake.getObjectId(), (e.getItemCount() * _amount), player.getTarget(), true))
  181. return;
  182. }
  183. else
  184. {
  185. // for non-stackable items, one of two scenaria are possible:
  186. // a) list maintains enchantment: get the instances that exactly match the requested enchantment level
  187. // b) list does not maintain enchantment: get the instances with the LOWEST enchantment level
  188. // a) if enchantment is maintained, then get a list of items that exactly match this enchantment
  189. if (maintainEnchantment)
  190. {
  191. // loop through this list and remove (one by one) each item until the required amount is taken.
  192. L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId(), e.getEnchantmentLevel());
  193. for (int i = 0; i < (e.getItemCount() * _amount); i++)
  194. {
  195. if (inventoryContents[i].isAugmented())
  196. augmentation.add(inventoryContents[i].getAugmentation());
  197. if (!player.destroyItem("Multisell", inventoryContents[i].getObjectId(), 1, player.getTarget(), true))
  198. return;
  199. }
  200. }
  201. else // b) enchantment is not maintained. Get the instances with the LOWEST enchantment level
  202. {
  203. /* NOTE: There are 2 ways to achieve the above goal.
  204. * 1) Get all items that have the correct itemId, loop through them until the lowest enchantment
  205. * level is found. Repeat all this for the next item until proper count of items is reached.
  206. * 2) Get all items that have the correct itemId, sort them once based on enchantment level,
  207. * and get the range of items that is necessary.
  208. * Method 1 is faster for a small number of items to be exchanged.
  209. * Method 2 is faster for large amounts.
  210. *
  211. * EXPLANATION:
  212. * Worst case scenario for algorithm 1 will make it run in a number of cycles given by:
  213. * m*(2n-m+1)/2 where m is the number of items to be exchanged and n is the total
  214. * number of inventory items that have a matching id.
  215. * With algorithm 2 (sort), sorting takes n*log(n) time and the choice is done in a single cycle
  216. * for case b (just grab the m first items) or in linear time for case a (find the beginning of items
  217. * with correct enchantment, index x, and take all items from x to x+m).
  218. * Basically, whenever m > log(n) we have: m*(2n-m+1)/2 = (2nm-m*m+m)/2 >
  219. * (2nlogn-logn*logn+logn)/2 = nlog(n) - log(n*n) + log(n) = nlog(n) + log(n/n*n) =
  220. * nlog(n) + log(1/n) = nlog(n) - log(n) = (n-1)log(n)
  221. * So for m < log(n) then m*(2n-m+1)/2 > (n-1)log(n) and m*(2n-m+1)/2 > nlog(n)
  222. *
  223. * IDEALLY:
  224. * In order to best optimize the performance, choose which algorithm to run, based on whether 2^m > n
  225. * if ( (2<<(e.getItemCount() * _amount)) < inventoryContents.length )
  226. * // do Algorithm 1, no sorting
  227. * else
  228. * // do Algorithm 2, sorting
  229. *
  230. * CURRENT IMPLEMENTATION:
  231. * In general, it is going to be very rare for a person to do a massive exchange of non-stackable items
  232. * For this reason, we assume that algorithm 1 will always suffice and we keep things simple.
  233. * If, in the future, it becomes necessary that we optimize, the above discussion should make it clear
  234. * what optimization exactly is necessary (based on the comments under "IDEALLY").
  235. */
  236. // choice 1. Small number of items exchanged. No sorting.
  237. for (int i = 1; i <= (e.getItemCount() * _amount); i++)
  238. {
  239. L2ItemInstance[] inventoryContents = inv.getAllItemsByItemId(e.getItemId());
  240. itemToTake = inventoryContents[0];
  241. // get item with the LOWEST enchantment level from the inventory...
  242. // +0 is lowest by default...
  243. if (itemToTake.getEnchantLevel() > 0)
  244. {
  245. for (int j = 0; j < inventoryContents.length; j++)
  246. {
  247. if (inventoryContents[j].getEnchantLevel() < itemToTake.getEnchantLevel())
  248. {
  249. itemToTake = inventoryContents[j];
  250. // nothing will have enchantment less than 0. If a zero-enchanted
  251. // item is found, just take it
  252. if (itemToTake.getEnchantLevel() == 0)
  253. break;
  254. }
  255. }
  256. }
  257. if (!player.destroyItem("Multisell", itemToTake.getObjectId(), 1, player.getTarget(), true))
  258. return;
  259. }
  260. }
  261. }
  262. }
  263. }
  264. else
  265. {
  266. int repCost = player.getClan().getReputationScore() - e.getItemCount();
  267. player.getClan().setReputationScore(repCost, true);
  268. SystemMessage smsg = new SystemMessage(SystemMessageId.S1_DEDUCTED_FROM_CLAN_REP);
  269. smsg.addNumber(e.getItemCount());
  270. player.sendPacket(smsg);
  271. player.getClan().broadcastToOnlineMembers(new PledgeShowInfoUpdate(player.getClan()));
  272. }
  273. }
  274. // Generate the appropriate items
  275. for(MultiSellIngredient e : entry.getProducts())
  276. {
  277. if (ItemTable.getInstance().createDummyItem(e.getItemId()).isStackable())
  278. {
  279. inv.addItem("Multisell", e.getItemId(), (e.getItemCount() * _amount), player, player.getTarget());
  280. } else
  281. {
  282. L2ItemInstance product = null;
  283. for (int i = 0; i < (e.getItemCount() * _amount); i++)
  284. {
  285. product = inv.addItem("Multisell", e.getItemId(), 1, player, player.getTarget());
  286. if (maintainEnchantment)
  287. {
  288. if (i < augmentation.size())
  289. {
  290. product.setAugmentation(new L2Augmentation(product, augmentation.get(i).getAugmentationId(), augmentation.get(i).getSkill(), true));
  291. }
  292. product.setEnchantLevel(e.getEnchantmentLevel());
  293. }
  294. }
  295. }
  296. // msg part
  297. SystemMessage sm;
  298. if (e.getItemCount() * _amount > 1)
  299. {
  300. sm = new SystemMessage(SystemMessageId.EARNED_S2_S1_S);
  301. sm.addItemName(e.getItemId());
  302. sm.addNumber(e.getItemCount() * _amount);
  303. player.sendPacket(sm);
  304. sm = null;
  305. }
  306. else
  307. {
  308. if(maintainEnchantment && _enchantment > 0)
  309. {
  310. sm = new SystemMessage(SystemMessageId.ACQUIRED);
  311. sm.addNumber(_enchantment);
  312. sm.addItemName(e.getItemId());
  313. }
  314. else
  315. {
  316. sm = new SystemMessage(SystemMessageId.EARNED_ITEM);
  317. sm.addItemName(e.getItemId());
  318. }
  319. player.sendPacket(sm);
  320. sm = null;
  321. }
  322. }
  323. player.sendPacket(new ItemList(player, false));
  324. StatusUpdate su = new StatusUpdate(player.getObjectId());
  325. su.addAttribute(StatusUpdate.CUR_LOAD, player.getCurrentLoad());
  326. player.sendPacket(su);
  327. su = null;
  328. // finally, give the tax to the castle...
  329. if (merchant != null && merchant.getIsInTown() && merchant.getCastle().getOwnerId() > 0)
  330. merchant.getCastle().addToTreasury(_transactionTax * _amount);
  331. }
  332. // Regarding taxation, the following appears to be the case:
  333. // a) The count of aa remains unchanged (taxes do not affect aa directly).
  334. // b) 5/6 of the amount of aa is taxed by the normal tax rate.
  335. // c) the resulting taxes are added as normal adena value.
  336. // d) normal adena are taxed fully.
  337. // e) Items other than adena and ancient adena are not taxed even when the list is taxable.
  338. // example: If the template has an item worth 120aa, and the tax is 10%,
  339. // then from 120aa, take 5/6 so that is 100aa, apply the 10% tax in adena (10a)
  340. // so the final price will be 120aa and 10a!
  341. private MultiSellEntry prepareEntry(L2NpcInstance merchant, MultiSellEntry templateEntry, boolean applyTaxes, boolean maintainEnchantment, int enchantLevel)
  342. {
  343. MultiSellEntry newEntry = L2Multisell.getInstance().new MultiSellEntry();
  344. newEntry.setEntryId(templateEntry.getEntryId());
  345. int totalAdenaCount = 0;
  346. for (MultiSellIngredient ing : templateEntry.getIngredients())
  347. {
  348. // load the ingredient from the template
  349. MultiSellIngredient newIngredient = L2Multisell.getInstance().new MultiSellIngredient(ing);
  350. if (newIngredient.getItemId() == 57 && newIngredient.isTaxIngredient())
  351. {
  352. double taxRate = 0.0;
  353. if (applyTaxes)
  354. {
  355. if (merchant != null && merchant.getIsInTown())
  356. taxRate = merchant.getCastle().getTaxRate();
  357. }
  358. _transactionTax = (int)Math.round(newIngredient.getItemCount()*taxRate);
  359. totalAdenaCount += _transactionTax;
  360. continue; // do not yet add this adena amount to the list as non-taxIngredient adena might be entered later (order not guaranteed)
  361. }
  362. else if (ing.getItemId() == 57) // && !ing.isTaxIngredient()
  363. {
  364. totalAdenaCount += newIngredient.getItemCount();
  365. continue; // do not yet add this adena amount to the list as taxIngredient adena might be entered later (order not guaranteed)
  366. }
  367. // if it is an armor/weapon, modify the enchantment level appropriately, if necessary
  368. else if (maintainEnchantment)
  369. {
  370. L2Item tempItem = ItemTable.getInstance().createDummyItem(newIngredient.getItemId()).getItem();
  371. if ((tempItem instanceof L2Armor) || (tempItem instanceof L2Weapon))
  372. newIngredient.setEnchantmentLevel(enchantLevel);
  373. }
  374. // finally, add this ingredient to the entry
  375. newEntry.addIngredient(newIngredient);
  376. }
  377. // Next add the adena amount, if any
  378. if (totalAdenaCount > 0)
  379. newEntry.addIngredient(L2Multisell.getInstance().new MultiSellIngredient(57, totalAdenaCount, false, false));
  380. // Now modify the enchantment level of products, if necessary
  381. for (MultiSellIngredient ing : templateEntry.getProducts())
  382. {
  383. // load the ingredient from the template
  384. MultiSellIngredient newIngredient = L2Multisell.getInstance().new MultiSellIngredient(ing);
  385. if (maintainEnchantment)
  386. {
  387. // if it is an armor/weapon, modify the enchantment level appropriately
  388. // (note, if maintain enchantment is "false" this modification will result to a +0)
  389. L2Item tempItem = ItemTable.getInstance().createDummyItem(newIngredient.getItemId()).getItem();
  390. if ((tempItem instanceof L2Armor) || (tempItem instanceof L2Weapon))
  391. newIngredient.setEnchantmentLevel(enchantLevel);
  392. }
  393. newEntry.addProduct(newIngredient);
  394. }
  395. return newEntry;
  396. }
  397. @Override
  398. public String getType()
  399. {
  400. return _C__A7_MULTISELLCHOOSE;
  401. }
  402. }