MultiSell.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. /*
  2. * Copyright (C) 2004-2013 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.datatables;
  20. import java.io.File;
  21. import java.util.HashMap;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.logging.Level;
  26. import java.util.logging.Logger;
  27. import javax.xml.parsers.DocumentBuilderFactory;
  28. import javolution.util.FastList;
  29. import org.w3c.dom.DOMException;
  30. import org.w3c.dom.Document;
  31. import org.w3c.dom.Node;
  32. import com.l2jserver.Config;
  33. import com.l2jserver.gameserver.model.actor.L2Npc;
  34. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  35. import com.l2jserver.gameserver.model.multisell.Entry;
  36. import com.l2jserver.gameserver.model.multisell.Ingredient;
  37. import com.l2jserver.gameserver.model.multisell.ListContainer;
  38. import com.l2jserver.gameserver.model.multisell.PreparedListContainer;
  39. import com.l2jserver.gameserver.network.SystemMessageId;
  40. import com.l2jserver.gameserver.network.serverpackets.ExBrExtraUserInfo;
  41. import com.l2jserver.gameserver.network.serverpackets.MultiSellList;
  42. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  43. import com.l2jserver.gameserver.network.serverpackets.UserInfo;
  44. import com.l2jserver.util.file.filter.XMLFilter;
  45. public class MultiSell
  46. {
  47. private static final Logger _log = Logger.getLogger(MultiSell.class.getName());
  48. public static final int PAGE_SIZE = 40;
  49. public static final int PC_BANG_POINTS = -100;
  50. public static final int CLAN_REPUTATION = -200;
  51. public static final int FAME = -300;
  52. private final Map<Integer, ListContainer> _entries = new HashMap<>();
  53. protected MultiSell()
  54. {
  55. load();
  56. }
  57. public final void reload()
  58. {
  59. _entries.clear();
  60. load();
  61. }
  62. /**
  63. * This will generate the multisell list for the items.<br>
  64. * There exist various parameters in multisells that affect the way they will appear:
  65. * <ol>
  66. * <li>Inventory only:
  67. * <ul>
  68. * <li>If true, only show items of the multisell for which the "primary" ingredients are already in the player's inventory. By "primary" ingredients we mean weapon and armor.</li>
  69. * <li>If false, show the entire list.</li>
  70. * </ul>
  71. * </li>
  72. * <li>Maintain enchantment: presumably, only lists with "inventory only" set to true should sometimes have this as true. This makes no sense otherwise...
  73. * <ul>
  74. * <li>If true, then the product will match the enchantment level of the ingredient.<br>
  75. * If the player has multiple items that match the ingredient list but the enchantment levels differ, then the entries need to be duplicated to show the products and ingredients for each enchantment level.<br>
  76. * For example: If the player has a crystal staff +1 and a crystal staff +3 and goes to exchange it at the mammon, the list should have all exchange possibilities for the +1 staff, followed by all possibilities for the +3 staff.</li>
  77. * <li>If false, then any level ingredient will be considered equal and product will always be at +0</li>
  78. * </ul>
  79. * </li>
  80. * <li>Apply taxes: Uses the "taxIngredient" entry in order to add a certain amount of adena to the ingredients.
  81. * <li>
  82. * <li>Additional product and ingredient multipliers.</li>
  83. * </ol>
  84. * @param listId
  85. * @param player
  86. * @param npc
  87. * @param inventoryOnly
  88. * @param productMultiplier
  89. * @param ingredientMultiplier
  90. */
  91. public final void separateAndSend(int listId, L2PcInstance player, L2Npc npc, boolean inventoryOnly, double productMultiplier, double ingredientMultiplier)
  92. {
  93. ListContainer template = _entries.get(listId);
  94. if (template == null)
  95. {
  96. _log.warning(getClass().getSimpleName() + ": can't find list id: " + listId + " requested by player: " + player.getName() + ", npcId:" + (npc != null ? npc.getNpcId() : 0));
  97. return;
  98. }
  99. final PreparedListContainer list = new PreparedListContainer(template, inventoryOnly, player, npc);
  100. // Pass through this only when multipliers are different from 1
  101. if ((productMultiplier != 1) || (ingredientMultiplier != 1))
  102. {
  103. for (Entry entry : list.getEntries())
  104. {
  105. for (Ingredient product : entry.getProducts())
  106. {
  107. // Math.max used here to avoid dropping count to 0
  108. product.setItemCount((long) Math.max(product.getItemCount() * productMultiplier, 1));
  109. }
  110. for (Ingredient ingredient : entry.getIngredients())
  111. {
  112. // Math.max used here to avoid dropping count to 0
  113. ingredient.setItemCount((long) Math.max(ingredient.getItemCount() * ingredientMultiplier, 1));
  114. }
  115. }
  116. }
  117. int index = 0;
  118. do
  119. {
  120. // send list at least once even if size = 0
  121. player.sendPacket(new MultiSellList(list, index));
  122. index += PAGE_SIZE;
  123. }
  124. while (index < list.getEntries().size());
  125. player.setMultiSell(list);
  126. }
  127. public final void separateAndSend(int listId, L2PcInstance player, L2Npc npc, boolean inventoryOnly)
  128. {
  129. separateAndSend(listId, player, npc, inventoryOnly, 1, 1);
  130. }
  131. public static final boolean checkSpecialIngredient(int id, long amount, L2PcInstance player)
  132. {
  133. switch (id)
  134. {
  135. case CLAN_REPUTATION:
  136. if (player.getClan() == null)
  137. {
  138. player.sendPacket(SystemMessageId.YOU_ARE_NOT_A_CLAN_MEMBER);
  139. break;
  140. }
  141. if (!player.isClanLeader())
  142. {
  143. player.sendPacket(SystemMessageId.ONLY_THE_CLAN_LEADER_IS_ENABLED);
  144. break;
  145. }
  146. if (player.getClan().getReputationScore() < amount)
  147. {
  148. player.sendPacket(SystemMessageId.THE_CLAN_REPUTATION_SCORE_IS_TOO_LOW);
  149. break;
  150. }
  151. return true;
  152. case FAME:
  153. if (player.getFame() < amount)
  154. {
  155. player.sendPacket(SystemMessageId.NOT_ENOUGH_FAME_POINTS);
  156. break;
  157. }
  158. return true;
  159. }
  160. return false;
  161. }
  162. public static final boolean getSpecialIngredient(int id, long amount, L2PcInstance player)
  163. {
  164. switch (id)
  165. {
  166. case CLAN_REPUTATION:
  167. player.getClan().takeReputationScore((int) amount, true);
  168. SystemMessage smsg = SystemMessage.getSystemMessage(SystemMessageId.S1_DEDUCTED_FROM_CLAN_REP);
  169. smsg.addItemNumber(amount);
  170. player.sendPacket(smsg);
  171. return true;
  172. case FAME:
  173. player.setFame(player.getFame() - (int) amount);
  174. player.sendPacket(new UserInfo(player));
  175. player.sendPacket(new ExBrExtraUserInfo(player));
  176. return true;
  177. }
  178. return false;
  179. }
  180. public static final void addSpecialProduct(int id, long amount, L2PcInstance player)
  181. {
  182. switch (id)
  183. {
  184. case CLAN_REPUTATION:
  185. player.getClan().addReputationScore((int) amount, true);
  186. break;
  187. case FAME:
  188. player.setFame((int) (player.getFame() + amount));
  189. player.sendPacket(new UserInfo(player));
  190. player.sendPacket(new ExBrExtraUserInfo(player));
  191. break;
  192. }
  193. }
  194. private final void load()
  195. {
  196. Document doc = null;
  197. int id = 0;
  198. List<File> files = new FastList<>();
  199. hashFiles("data/multisell", files);
  200. if (Config.CUSTOM_MULTISELL_LOAD)
  201. {
  202. hashFiles("data/multisell/custom", files);
  203. }
  204. for (File f : files)
  205. {
  206. try
  207. {
  208. id = Integer.parseInt(f.getName().replaceAll(".xml", ""));
  209. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  210. factory.setValidating(false);
  211. factory.setIgnoringComments(true);
  212. doc = factory.newDocumentBuilder().parse(f);
  213. }
  214. catch (Exception e)
  215. {
  216. _log.log(Level.SEVERE, getClass().getSimpleName() + ": Error loading file " + f, e);
  217. continue;
  218. }
  219. try
  220. {
  221. ListContainer list = parseDocument(doc);
  222. list.setListId(id);
  223. _entries.put(id, list);
  224. }
  225. catch (Exception e)
  226. {
  227. _log.log(Level.SEVERE, getClass().getSimpleName() + ": Error in file " + f, e);
  228. }
  229. }
  230. verify();
  231. _log.log(Level.INFO, getClass().getSimpleName() + ": Loaded " + _entries.size() + " lists.");
  232. }
  233. private final ListContainer parseDocument(Document doc)
  234. {
  235. int entryId = 1;
  236. Node attribute;
  237. ListContainer list = new ListContainer();
  238. for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
  239. {
  240. if ("list".equalsIgnoreCase(n.getNodeName()))
  241. {
  242. attribute = n.getAttributes().getNamedItem("applyTaxes");
  243. if (attribute == null)
  244. {
  245. list.setApplyTaxes(false);
  246. }
  247. else
  248. {
  249. list.setApplyTaxes(Boolean.parseBoolean(attribute.getNodeValue()));
  250. }
  251. attribute = n.getAttributes().getNamedItem("useRate");
  252. if (attribute != null)
  253. {
  254. try
  255. {
  256. list.setUseRate(Double.valueOf(attribute.getNodeValue()));
  257. if (list.getUseRate() <= 1e-6)
  258. {
  259. throw new NumberFormatException("The value cannot be 0"); // threat 0 as invalid value
  260. }
  261. }
  262. catch (NumberFormatException e)
  263. {
  264. try
  265. {
  266. list.setUseRate(Config.class.getField(attribute.getNodeValue()).getDouble(Config.class));
  267. }
  268. catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException | DOMException e1)
  269. {
  270. _log.warning(e.getMessage() + doc.getLocalName());
  271. list.setUseRate(1.0);
  272. }
  273. }
  274. catch (DOMException e)
  275. {
  276. _log.warning(e.getMessage() + doc.getLocalName());
  277. }
  278. }
  279. attribute = n.getAttributes().getNamedItem("maintainEnchantment");
  280. if (attribute == null)
  281. {
  282. list.setMaintainEnchantment(false);
  283. }
  284. else
  285. {
  286. list.setMaintainEnchantment(Boolean.parseBoolean(attribute.getNodeValue()));
  287. }
  288. for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  289. {
  290. if ("item".equalsIgnoreCase(d.getNodeName()))
  291. {
  292. Entry e = parseEntry(d, entryId++, list);
  293. list.getEntries().add(e);
  294. }
  295. }
  296. }
  297. else if ("item".equalsIgnoreCase(n.getNodeName()))
  298. {
  299. Entry e = parseEntry(n, entryId++, list);
  300. list.getEntries().add(e);
  301. }
  302. }
  303. return list;
  304. }
  305. private final Entry parseEntry(Node n, int entryId, ListContainer list)
  306. {
  307. Node attribute;
  308. Node first = n.getFirstChild();
  309. final Entry entry = new Entry(entryId);
  310. for (n = first; n != null; n = n.getNextSibling())
  311. {
  312. if ("ingredient".equalsIgnoreCase(n.getNodeName()))
  313. {
  314. int id = Integer.parseInt(n.getAttributes().getNamedItem("id").getNodeValue());
  315. long count = Long.parseLong(n.getAttributes().getNamedItem("count").getNodeValue());
  316. boolean isTaxIngredient, mantainIngredient;
  317. attribute = n.getAttributes().getNamedItem("isTaxIngredient");
  318. if (attribute != null)
  319. {
  320. isTaxIngredient = Boolean.parseBoolean(attribute.getNodeValue());
  321. }
  322. else
  323. {
  324. isTaxIngredient = false;
  325. }
  326. attribute = n.getAttributes().getNamedItem("maintainIngredient");
  327. if (attribute != null)
  328. {
  329. mantainIngredient = Boolean.parseBoolean(attribute.getNodeValue());
  330. }
  331. else
  332. {
  333. mantainIngredient = false;
  334. }
  335. entry.addIngredient(new Ingredient(id, count, isTaxIngredient, mantainIngredient));
  336. }
  337. else if ("production".equalsIgnoreCase(n.getNodeName()))
  338. {
  339. int id = Integer.parseInt(n.getAttributes().getNamedItem("id").getNodeValue());
  340. long count = (long) (Long.parseLong(n.getAttributes().getNamedItem("count").getNodeValue()) * list.getUseRate());
  341. entry.addProduct(new Ingredient(id, count, false, false));
  342. }
  343. }
  344. return entry;
  345. }
  346. private final void hashFiles(String dirname, List<File> hash)
  347. {
  348. File dir = new File(Config.DATAPACK_ROOT, dirname);
  349. if (!dir.exists())
  350. {
  351. _log.log(Level.WARNING, getClass().getSimpleName() + ": Dir " + dir.getAbsolutePath() + " not exists");
  352. return;
  353. }
  354. File[] files = dir.listFiles(new XMLFilter());
  355. for (File f : files)
  356. {
  357. hash.add(f);
  358. }
  359. }
  360. private final void verify()
  361. {
  362. ListContainer list;
  363. final Iterator<ListContainer> iter = _entries.values().iterator();
  364. while (iter.hasNext())
  365. {
  366. list = iter.next();
  367. for (Entry ent : list.getEntries())
  368. {
  369. for (Ingredient ing : ent.getIngredients())
  370. {
  371. if (!verifyIngredient(ing))
  372. {
  373. _log.warning(getClass().getSimpleName() + ": can't find ingredient with itemId: " + ing.getItemId() + " in list: " + list.getListId());
  374. }
  375. }
  376. for (Ingredient ing : ent.getProducts())
  377. {
  378. if (!verifyIngredient(ing))
  379. {
  380. _log.warning(getClass().getSimpleName() + ": can't find product with itemId: " + ing.getItemId() + " in list: " + list.getListId());
  381. }
  382. }
  383. }
  384. }
  385. }
  386. private final boolean verifyIngredient(Ingredient ing)
  387. {
  388. switch (ing.getItemId())
  389. {
  390. case CLAN_REPUTATION:
  391. case FAME:
  392. return true;
  393. default:
  394. if (ing.getTemplate() != null)
  395. {
  396. return true;
  397. }
  398. }
  399. return false;
  400. }
  401. public static MultiSell getInstance()
  402. {
  403. return SingletonHolder._instance;
  404. }
  405. private static class SingletonHolder
  406. {
  407. protected static final MultiSell _instance = new MultiSell();
  408. }
  409. }