RecipeController.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  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;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.util.Arrays;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.logging.Level;
  22. import java.util.logging.Logger;
  23. import javax.xml.parsers.DocumentBuilderFactory;
  24. import javax.xml.parsers.ParserConfigurationException;
  25. import javolution.util.FastList;
  26. import javolution.util.FastMap;
  27. import org.w3c.dom.Document;
  28. import org.w3c.dom.NamedNodeMap;
  29. import org.w3c.dom.Node;
  30. import org.xml.sax.SAXException;
  31. import com.l2jserver.Config;
  32. import com.l2jserver.gameserver.datatables.ItemTable;
  33. import com.l2jserver.gameserver.model.L2ItemInstance;
  34. import com.l2jserver.gameserver.model.L2ManufactureItem;
  35. import com.l2jserver.gameserver.model.L2RecipeInstance;
  36. import com.l2jserver.gameserver.model.L2RecipeList;
  37. import com.l2jserver.gameserver.model.L2RecipeStatInstance;
  38. import com.l2jserver.gameserver.model.L2Skill;
  39. import com.l2jserver.gameserver.model.actor.instance.L2PcInstance;
  40. import com.l2jserver.gameserver.model.itemcontainer.Inventory;
  41. import com.l2jserver.gameserver.network.SystemMessageId;
  42. import com.l2jserver.gameserver.network.serverpackets.ActionFailed;
  43. import com.l2jserver.gameserver.network.serverpackets.ItemList;
  44. import com.l2jserver.gameserver.network.serverpackets.MagicSkillUse;
  45. import com.l2jserver.gameserver.network.serverpackets.RecipeBookItemList;
  46. import com.l2jserver.gameserver.network.serverpackets.RecipeItemMakeInfo;
  47. import com.l2jserver.gameserver.network.serverpackets.RecipeShopItemInfo;
  48. import com.l2jserver.gameserver.network.serverpackets.SetupGauge;
  49. import com.l2jserver.gameserver.network.serverpackets.StatusUpdate;
  50. import com.l2jserver.gameserver.network.serverpackets.SystemMessage;
  51. import com.l2jserver.gameserver.skills.Stats;
  52. import com.l2jserver.gameserver.taskmanager.AttackStanceTaskManager;
  53. import com.l2jserver.gameserver.templates.StatsSet;
  54. import com.l2jserver.gameserver.templates.item.L2Item;
  55. import com.l2jserver.gameserver.util.Util;
  56. import com.l2jserver.util.Rnd;
  57. public class RecipeController
  58. {
  59. protected static final Logger _log = Logger.getLogger(RecipeController.class.getName());
  60. private Map<Integer, L2RecipeList> _lists;
  61. private static final Map<Integer, RecipeItemMaker> _activeMakers = new FastMap<Integer, RecipeItemMaker>();
  62. private static final String RECIPES_FILE = "recipes.xml";
  63. public static RecipeController getInstance()
  64. {
  65. return SingletonHolder._instance;
  66. }
  67. private RecipeController()
  68. {
  69. _lists = new FastMap<Integer, L2RecipeList>();
  70. try
  71. {
  72. this.loadFromXML();
  73. _log.info("RecipeController: Loaded " + _lists.size() + " recipes.");
  74. }
  75. catch (Exception e)
  76. {
  77. _log.log(Level.SEVERE, "Failed loading recipe list", e);
  78. }
  79. }
  80. public int getRecipesCount()
  81. {
  82. return _lists.size();
  83. }
  84. public L2RecipeList getRecipeList(int listId)
  85. {
  86. return _lists.get(listId);
  87. }
  88. public L2RecipeList getRecipeByItemId(int itemId)
  89. {
  90. for (L2RecipeList find : _lists.values())
  91. {
  92. if (find.getRecipeId() == itemId)
  93. return find;
  94. }
  95. return null;
  96. }
  97. public int[] getAllItemIds()
  98. {
  99. int[] idList = new int[_lists.size()];
  100. int i = 0;
  101. for (L2RecipeList rec : _lists.values())
  102. {
  103. idList[i++] = rec.getRecipeId();
  104. }
  105. return idList;
  106. }
  107. public synchronized void requestBookOpen(L2PcInstance player, boolean isDwarvenCraft)
  108. {
  109. RecipeItemMaker maker = null;
  110. if (Config.ALT_GAME_CREATION)
  111. maker = _activeMakers.get(player.getObjectId());
  112. if (maker == null)
  113. {
  114. RecipeBookItemList response = new RecipeBookItemList(isDwarvenCraft, player.getMaxMp());
  115. response.addRecipes(isDwarvenCraft ? player.getDwarvenRecipeBook() : player.getCommonRecipeBook());
  116. player.sendPacket(response);
  117. return;
  118. }
  119. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANT_ALTER_RECIPEBOOK_WHILE_CRAFTING));
  120. }
  121. public synchronized void requestMakeItemAbort(L2PcInstance player)
  122. {
  123. _activeMakers.remove(player.getObjectId()); // TODO: anything else here?
  124. }
  125. public synchronized void requestManufactureItem(L2PcInstance manufacturer, int recipeListId, L2PcInstance player)
  126. {
  127. L2RecipeList recipeList = getValidRecipeList(player, recipeListId);
  128. if (recipeList == null)
  129. return;
  130. List<L2RecipeList> dwarfRecipes = Arrays.asList(manufacturer.getDwarvenRecipeBook());
  131. List<L2RecipeList> commonRecipes = Arrays.asList(manufacturer.getCommonRecipeBook());
  132. if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
  133. {
  134. Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName()
  135. + " sent a false recipe id.", Config.DEFAULT_PUNISH);
  136. return;
  137. }
  138. RecipeItemMaker maker;
  139. if (Config.ALT_GAME_CREATION && (maker = _activeMakers.get(manufacturer.getObjectId())) != null) // check if busy
  140. {
  141. player.sendMessage("Manufacturer is busy, please try later.");
  142. return;
  143. }
  144. maker = new RecipeItemMaker(manufacturer, recipeList, player);
  145. if (maker._isValid)
  146. {
  147. if (Config.ALT_GAME_CREATION)
  148. {
  149. _activeMakers.put(manufacturer.getObjectId(), maker);
  150. ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
  151. }
  152. else
  153. maker.run();
  154. }
  155. }
  156. public synchronized void requestMakeItem(L2PcInstance player, int recipeListId)
  157. {
  158. if (AttackStanceTaskManager.getInstance().getAttackStanceTask(player) || player.isInDuel())
  159. {
  160. player.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.CANT_OPERATE_PRIVATE_STORE_DURING_COMBAT));
  161. return;
  162. }
  163. L2RecipeList recipeList = getValidRecipeList(player, recipeListId);
  164. if (recipeList == null)
  165. return;
  166. List<L2RecipeList> dwarfRecipes = Arrays.asList(player.getDwarvenRecipeBook());
  167. List<L2RecipeList> commonRecipes = Arrays.asList(player.getCommonRecipeBook());
  168. if (!dwarfRecipes.contains(recipeList) && !commonRecipes.contains(recipeList))
  169. {
  170. Util.handleIllegalPlayerAction(player, "Warning!! Character " + player.getName() + " of account " + player.getAccountName()
  171. + " sent a false recipe id.", Config.DEFAULT_PUNISH);
  172. return;
  173. }
  174. RecipeItemMaker maker;
  175. // check if already busy (possible in alt mode only)
  176. if (Config.ALT_GAME_CREATION && ((maker = _activeMakers.get(player.getObjectId())) != null))
  177. {
  178. SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1);
  179. sm.addItemName(recipeList.getItemId());
  180. sm.addString("You are busy creating");
  181. player.sendPacket(sm);
  182. return;
  183. }
  184. maker = new RecipeItemMaker(player, recipeList, player);
  185. if (maker._isValid)
  186. {
  187. if (Config.ALT_GAME_CREATION)
  188. {
  189. _activeMakers.put(player.getObjectId(), maker);
  190. ThreadPoolManager.getInstance().scheduleGeneral(maker, 100);
  191. }
  192. else
  193. maker.run();
  194. }
  195. }
  196. private void loadFromXML() throws SAXException, IOException, ParserConfigurationException
  197. {
  198. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  199. factory.setValidating(false);
  200. factory.setIgnoringComments(true);
  201. File file = new File(Config.DATAPACK_ROOT + "/data/" + RECIPES_FILE);
  202. if (file.exists())
  203. {
  204. Document doc = factory.newDocumentBuilder().parse(file);
  205. List<L2RecipeInstance> recipePartList = new FastList<L2RecipeInstance>();
  206. List<L2RecipeStatInstance> recipeStatUseList = new FastList<L2RecipeStatInstance>();
  207. List<L2RecipeStatInstance> recipeAltStatChangeList = new FastList<L2RecipeStatInstance>();
  208. for (Node n = doc.getFirstChild(); n != null; n = n.getNextSibling())
  209. {
  210. if ("list".equalsIgnoreCase(n.getNodeName()))
  211. {
  212. recipesFile: for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling())
  213. {
  214. if ("item".equalsIgnoreCase(d.getNodeName()))
  215. {
  216. recipePartList.clear();
  217. recipeStatUseList.clear();
  218. recipeAltStatChangeList.clear();
  219. NamedNodeMap attrs = d.getAttributes();
  220. Node att;
  221. int id = -1;
  222. boolean haveRare = false;
  223. StatsSet set = new StatsSet();
  224. att = attrs.getNamedItem("id");
  225. if (att == null)
  226. {
  227. _log.severe("Missing id for recipe item, skipping");
  228. continue;
  229. }
  230. id = Integer.parseInt(att.getNodeValue());
  231. set.set("id", id);
  232. att = attrs.getNamedItem("recipeId");
  233. if (att == null)
  234. {
  235. _log.severe("Missing recipeId for recipe item id: " + id + ", skipping");
  236. continue;
  237. }
  238. set.set("recipeId", Integer.parseInt(att.getNodeValue()));
  239. att = attrs.getNamedItem("name");
  240. if (att == null)
  241. {
  242. _log.severe("Missing name for recipe item id: " + id + ", skipping");
  243. continue;
  244. }
  245. set.set("recipeName", att.getNodeValue());
  246. att = attrs.getNamedItem("craftLevel");
  247. if (att == null)
  248. {
  249. _log.severe("Missing level for recipe item id: " + id + ", skipping");
  250. continue;
  251. }
  252. set.set("craftLevel", Integer.parseInt(att.getNodeValue()));
  253. att = attrs.getNamedItem("type");
  254. if (att == null)
  255. {
  256. _log.severe("Missing type for recipe item id: " + id + ", skipping");
  257. continue;
  258. }
  259. set.set("isDwarvenRecipe", att.getNodeValue().equalsIgnoreCase("dwarven"));
  260. att = attrs.getNamedItem("successRate");
  261. if (att == null)
  262. {
  263. _log.severe("Missing successRate for recipe item id: " + id + ", skipping");
  264. continue;
  265. }
  266. set.set("successRate", Integer.parseInt(att.getNodeValue()));
  267. for (Node c = d.getFirstChild(); c != null; c = c.getNextSibling())
  268. {
  269. if ("statUse".equalsIgnoreCase(c.getNodeName()))
  270. {
  271. String statName = c.getAttributes().getNamedItem("name").getNodeValue();
  272. int value = Integer.parseInt(c.getAttributes().getNamedItem("value").getNodeValue());
  273. try
  274. {
  275. recipeStatUseList.add(new L2RecipeStatInstance(statName, value));
  276. }
  277. catch (Exception e)
  278. {
  279. _log.severe("Error in StatUse parameter for recipe item id: " + id + ", skipping");
  280. continue recipesFile;
  281. }
  282. }
  283. else if ("altStatChange".equalsIgnoreCase(c.getNodeName()))
  284. {
  285. String statName = c.getAttributes().getNamedItem("name").getNodeValue();
  286. int value = Integer.parseInt(c.getAttributes().getNamedItem("value").getNodeValue());
  287. try
  288. {
  289. recipeAltStatChangeList.add(new L2RecipeStatInstance(statName, value));
  290. }
  291. catch (Exception e)
  292. {
  293. _log.severe("Error in AltStatChange parameter for recipe item id: " + id + ", skipping");
  294. continue recipesFile;
  295. }
  296. }
  297. else if ("ingredient".equalsIgnoreCase(c.getNodeName()))
  298. {
  299. int ingId = Integer.parseInt(c.getAttributes().getNamedItem("id").getNodeValue());
  300. int ingCount = Integer.parseInt(c.getAttributes().getNamedItem("count").getNodeValue());
  301. recipePartList.add(new L2RecipeInstance(ingId, ingCount));
  302. }
  303. else if ("production".equalsIgnoreCase(c.getNodeName()))
  304. {
  305. set.set("itemId", Integer.parseInt(c.getAttributes().getNamedItem("id").getNodeValue()));
  306. set.set("count", Integer.parseInt(c.getAttributes().getNamedItem("count").getNodeValue()));
  307. }
  308. else if ("productionRare".equalsIgnoreCase(c.getNodeName()))
  309. {
  310. set.set("rareItemId", Integer.parseInt(c.getAttributes().getNamedItem("id").getNodeValue()));
  311. set.set("rareCount", Integer.parseInt(c.getAttributes().getNamedItem("count").getNodeValue()));
  312. set.set("rarity", Integer.parseInt(c.getAttributes().getNamedItem("rarity").getNodeValue()));
  313. haveRare = true;
  314. }
  315. }
  316. L2RecipeList recipeList = new L2RecipeList(set, haveRare);
  317. for (L2RecipeInstance recipePart : recipePartList)
  318. {
  319. recipeList.addRecipe(recipePart);
  320. }
  321. for (L2RecipeStatInstance recipeStatUse : recipeStatUseList)
  322. {
  323. recipeList.addStatUse(recipeStatUse);
  324. }
  325. for (L2RecipeStatInstance recipeAltStatChange : recipeAltStatChangeList)
  326. {
  327. recipeList.addAltStatChange(recipeAltStatChange);
  328. }
  329. _lists.put(id, recipeList);
  330. }
  331. }
  332. }
  333. }
  334. }
  335. else
  336. {
  337. _log.severe("Recipes file (" + file.getAbsolutePath() + ") doesnt exists.");
  338. }
  339. }
  340. private static class RecipeItemMaker implements Runnable
  341. {
  342. protected boolean _isValid;
  343. protected List<TempItem> _items = null;
  344. protected final L2RecipeList _recipeList;
  345. protected final L2PcInstance _player; // "crafter"
  346. protected final L2PcInstance _target; // "customer"
  347. protected final L2Skill _skill;
  348. protected final int _skillId;
  349. protected final int _skillLevel;
  350. protected int _creationPasses = 1;
  351. protected int _itemGrab;
  352. protected int _exp = -1;
  353. protected int _sp = -1;
  354. protected long _price;
  355. protected int _totalItems;
  356. protected int _materialsRefPrice;
  357. protected int _delay;
  358. public RecipeItemMaker(L2PcInstance pPlayer, L2RecipeList pRecipeList, L2PcInstance pTarget)
  359. {
  360. _player = pPlayer;
  361. _target = pTarget;
  362. _recipeList = pRecipeList;
  363. _isValid = false;
  364. _skillId = _recipeList.isDwarvenRecipe() ? L2Skill.SKILL_CREATE_DWARVEN : L2Skill.SKILL_CREATE_COMMON;
  365. _skillLevel = _player.getSkillLevel(_skillId);
  366. _skill = _player.getKnownSkill(_skillId);
  367. _player.isInCraftMode(true);
  368. if (_player.isAlikeDead())
  369. {
  370. _player.sendPacket(ActionFailed.STATIC_PACKET);
  371. abort();
  372. return;
  373. }
  374. if (_target.isAlikeDead())
  375. {
  376. _target.sendPacket(ActionFailed.STATIC_PACKET);
  377. abort();
  378. return;
  379. }
  380. if (_target.isProcessingTransaction())
  381. {
  382. _target.sendPacket(ActionFailed.STATIC_PACKET);
  383. abort();
  384. return;
  385. }
  386. if (_player.isProcessingTransaction())
  387. {
  388. _player.sendPacket(ActionFailed.STATIC_PACKET);
  389. abort();
  390. return;
  391. }
  392. // validate recipe list
  393. if (_recipeList.getRecipes().length == 0)
  394. {
  395. _player.sendPacket(ActionFailed.STATIC_PACKET);
  396. abort();
  397. return;
  398. }
  399. // validate skill level
  400. if (_recipeList.getLevel() > _skillLevel)
  401. {
  402. _player.sendPacket(ActionFailed.STATIC_PACKET);
  403. abort();
  404. return;
  405. }
  406. // check that customer can afford to pay for creation services
  407. if (_player != _target)
  408. {
  409. for (L2ManufactureItem temp : _player.getCreateList().getList())
  410. {
  411. if (temp.getRecipeId() == _recipeList.getId()) // find recipe for item we want manufactured
  412. {
  413. _price = temp.getCost();
  414. if (_target.getAdena() < _price) // check price
  415. {
  416. _target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_NOT_ENOUGH_ADENA));
  417. abort();
  418. return;
  419. }
  420. break;
  421. }
  422. }
  423. }
  424. // make temporary items
  425. if ((_items = listItems(false)) == null)
  426. {
  427. abort();
  428. return;
  429. }
  430. // calculate reference price
  431. for (TempItem i : _items)
  432. {
  433. _materialsRefPrice += i.getReferencePrice() * i.getQuantity();
  434. _totalItems += i.getQuantity();
  435. }
  436. // initial statUse checks
  437. if (!calculateStatUse(false, false))
  438. {
  439. abort();
  440. return;
  441. }
  442. // initial AltStatChange checks
  443. if (Config.ALT_GAME_CREATION)
  444. calculateAltStatChange();
  445. updateMakeInfo(true);
  446. updateCurMp();
  447. updateCurLoad();
  448. _player.isInCraftMode(false);
  449. _isValid = true;
  450. }
  451. public void run()
  452. {
  453. if (!Config.IS_CRAFTING_ENABLED)
  454. {
  455. _target.sendMessage("Item creation is currently disabled.");
  456. abort();
  457. return;
  458. }
  459. if (_player == null || _target == null)
  460. {
  461. _log.warning("player or target == null (disconnected?), aborting" + _target + _player);
  462. abort();
  463. return;
  464. }
  465. if (!_player.isOnline() || !_target.isOnline())
  466. {
  467. _log.warning("player or target is not online, aborting " + _target + _player);
  468. abort();
  469. return;
  470. }
  471. if (Config.ALT_GAME_CREATION && _activeMakers.get(_player.getObjectId()) == null)
  472. {
  473. if (_target != _player)
  474. {
  475. _target.sendMessage("Manufacture aborted");
  476. _player.sendMessage("Manufacture aborted");
  477. }
  478. else
  479. {
  480. _player.sendMessage("Item creation aborted");
  481. }
  482. abort();
  483. return;
  484. }
  485. if (Config.ALT_GAME_CREATION && !_items.isEmpty())
  486. {
  487. if (!calculateStatUse(true, true))
  488. return; // check stat use
  489. updateCurMp(); // update craft window mp bar
  490. grabSomeItems(); // grab (equip) some more items with a nice msg to player
  491. // if still not empty, schedule another pass
  492. if (!_items.isEmpty())
  493. {
  494. // divided by RATE_CONSUMABLES_COST to remove craft time increase on higher consumables rates
  495. _delay = (int) (Config.ALT_GAME_CREATION_SPEED * _player.getMReuseRate(_skill) * GameTimeController.TICKS_PER_SECOND / Config.RATE_CONSUMABLE_COST)
  496. * GameTimeController.MILLIS_IN_TICK;
  497. // FIXME: please fix this packet to show crafting animation (somebody)
  498. MagicSkillUse msk = new MagicSkillUse(_player, _skillId, _skillLevel, _delay, 0);
  499. _player.broadcastPacket(msk);
  500. _player.sendPacket(new SetupGauge(0, _delay));
  501. ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
  502. }
  503. else
  504. {
  505. // for alt mode, sleep delay msec before finishing
  506. _player.sendPacket(new SetupGauge(0, _delay));
  507. try
  508. {
  509. Thread.sleep(_delay);
  510. }
  511. catch (InterruptedException e)
  512. {
  513. }
  514. finally
  515. {
  516. finishCrafting();
  517. }
  518. }
  519. } // for old craft mode just finish
  520. else
  521. finishCrafting();
  522. }
  523. private void finishCrafting()
  524. {
  525. if (!Config.ALT_GAME_CREATION)
  526. calculateStatUse(false, true);
  527. // first take adena for manufacture
  528. if ((_target != _player) && _price > 0) // customer must pay for services
  529. {
  530. // attempt to pay for item
  531. L2ItemInstance adenatransfer = _target.transferItem("PayManufacture", _target.getInventory().getAdenaInstance().getObjectId(), _price, _player.getInventory(), _player);
  532. if (adenatransfer == null)
  533. {
  534. _target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.YOU_NOT_ENOUGH_ADENA));
  535. abort();
  536. return;
  537. }
  538. }
  539. if ((_items = listItems(true)) == null) // this line actually takes materials from inventory
  540. { // handle possible cheaters here
  541. // (they click craft then try to get rid of items in order to get free craft)
  542. }
  543. else if (Rnd.get(100) < _recipeList.getSuccessRate())
  544. {
  545. rewardPlayer(); // and immediately puts created item in its place
  546. updateMakeInfo(true);
  547. }
  548. else
  549. {
  550. if (_target != _player)
  551. {
  552. SystemMessage msg = SystemMessage.getSystemMessage(SystemMessageId.CREATION_OF_S2_FOR_C1_AT_S3_ADENA_FAILED);
  553. msg.addString(_target.getName());
  554. msg.addItemName(_recipeList.getItemId());
  555. msg.addItemNumber(_price);
  556. _player.sendPacket(msg);
  557. msg = SystemMessage.getSystemMessage(SystemMessageId.C1_FAILED_TO_CREATE_S2_FOR_S3_ADENA);
  558. msg.addString(_player.getName());
  559. msg.addItemName(_recipeList.getItemId());
  560. msg.addItemNumber(_price);
  561. _target.sendPacket(msg);
  562. }
  563. else
  564. _target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.ITEM_MIXING_FAILED));
  565. updateMakeInfo(false);
  566. }
  567. // update load and mana bar of craft window
  568. updateCurMp();
  569. updateCurLoad();
  570. _activeMakers.remove(_player.getObjectId());
  571. _player.isInCraftMode(false);
  572. _target.sendPacket(new ItemList(_target, false));
  573. }
  574. private void updateMakeInfo(boolean success)
  575. {
  576. if (_target == _player)
  577. _target.sendPacket(new RecipeItemMakeInfo(_recipeList.getId(), _target, success));
  578. else
  579. _target.sendPacket(new RecipeShopItemInfo(_player, _recipeList.getId()));
  580. }
  581. private void updateCurLoad()
  582. {
  583. StatusUpdate su = new StatusUpdate(_target);
  584. su.addAttribute(StatusUpdate.CUR_LOAD, _target.getCurrentLoad());
  585. _target.sendPacket(su);
  586. }
  587. private void updateCurMp()
  588. {
  589. StatusUpdate su = new StatusUpdate(_target);
  590. su.addAttribute(StatusUpdate.CUR_MP, (int) _target.getCurrentMp());
  591. _target.sendPacket(su);
  592. }
  593. private void grabSomeItems()
  594. {
  595. int grabItems = _itemGrab;
  596. while (grabItems > 0 && !_items.isEmpty())
  597. {
  598. TempItem item = _items.get(0);
  599. int count = item.getQuantity();
  600. if (count >= grabItems)
  601. count = grabItems;
  602. item.setQuantity(item.getQuantity() - count);
  603. if (item.getQuantity() <= 0)
  604. _items.remove(0);
  605. else
  606. _items.set(0, item);
  607. grabItems -= count;
  608. if (_target == _player)
  609. {
  610. SystemMessage sm = SystemMessage.getSystemMessage(SystemMessageId.S1_S2_EQUIPPED); // you equipped ...
  611. sm.addItemNumber(count);
  612. sm.addItemName(item.getItemId());
  613. _player.sendPacket(sm);
  614. }
  615. else
  616. _target.sendMessage("Manufacturer " + _player.getName() + " used " + count + " " + item.getItemName());
  617. }
  618. }
  619. // AltStatChange parameters make their effect here
  620. private void calculateAltStatChange()
  621. {
  622. _itemGrab = _skillLevel;
  623. for (L2RecipeStatInstance altStatChange : _recipeList.getAltStatChange())
  624. {
  625. if (altStatChange.getType() == L2RecipeStatInstance.StatType.XP)
  626. {
  627. _exp = altStatChange.getValue();
  628. }
  629. else if (altStatChange.getType() == L2RecipeStatInstance.StatType.SP)
  630. {
  631. _sp = altStatChange.getValue();
  632. }
  633. else if (altStatChange.getType() == L2RecipeStatInstance.StatType.GIM)
  634. {
  635. _itemGrab *= altStatChange.getValue();
  636. }
  637. }
  638. // determine number of creation passes needed
  639. _creationPasses = (_totalItems / _itemGrab) + ((_totalItems % _itemGrab) != 0 ? 1 : 0);
  640. if (_creationPasses < 1)
  641. _creationPasses = 1;
  642. }
  643. // StatUse
  644. private boolean calculateStatUse(boolean isWait, boolean isReduce)
  645. {
  646. boolean ret = true;
  647. for (L2RecipeStatInstance statUse : _recipeList.getStatUse())
  648. {
  649. double modifiedValue = statUse.getValue() / _creationPasses;
  650. if (statUse.getType() == L2RecipeStatInstance.StatType.HP)
  651. {
  652. // we do not want to kill the player, so its CurrentHP must be greater than the reduce value
  653. if (_player.getCurrentHp() <= modifiedValue)
  654. {
  655. // rest (wait for HP)
  656. if (Config.ALT_GAME_CREATION && isWait)
  657. {
  658. _player.sendPacket(new SetupGauge(0, _delay));
  659. ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
  660. }
  661. else
  662. // no rest - report no hp
  663. {
  664. _target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_HP));
  665. abort();
  666. }
  667. ret = false;
  668. }
  669. else if (isReduce)
  670. _player.reduceCurrentHp(modifiedValue, _player, _skill);
  671. }
  672. else if (statUse.getType() == L2RecipeStatInstance.StatType.MP)
  673. {
  674. if (_player.getCurrentMp() < modifiedValue)
  675. {
  676. // rest (wait for MP)
  677. if (Config.ALT_GAME_CREATION && isWait)
  678. {
  679. _player.sendPacket(new SetupGauge(0, _delay));
  680. ThreadPoolManager.getInstance().scheduleGeneral(this, 100 + _delay);
  681. }
  682. else
  683. // no rest - report no mana
  684. {
  685. _target.sendPacket(SystemMessage.getSystemMessage(SystemMessageId.NOT_ENOUGH_MP));
  686. abort();
  687. }
  688. ret = false;
  689. }
  690. else if (isReduce)
  691. _player.reduceCurrentMp(modifiedValue);
  692. }
  693. else
  694. {
  695. // there is an unknown StatUse value
  696. _target.sendMessage("Recipe error!!!, please tell this to your GM.");
  697. ret = false;
  698. abort();
  699. }
  700. }
  701. return ret;
  702. }
  703. private List<TempItem> listItems(boolean remove)
  704. {
  705. L2RecipeInstance[] recipes = _recipeList.getRecipes();
  706. Inventory inv = _target.getInventory();
  707. List<TempItem> materials = new FastList<TempItem>();
  708. SystemMessage sm;
  709. for (L2RecipeInstance recipe : recipes)
  710. {
  711. int quantity = _recipeList.isConsumable() ? (int) (recipe.getQuantity() * Config.RATE_CONSUMABLE_COST) : recipe.getQuantity();
  712. if (quantity > 0)
  713. {
  714. L2ItemInstance item = inv.getItemByItemId(recipe.getItemId());
  715. long itemQuantityAmount = item == null ? 0 : item.getCount();
  716. // check materials
  717. if (itemQuantityAmount < quantity)
  718. {
  719. sm = SystemMessage.getSystemMessage(SystemMessageId.MISSING_S2_S1_TO_CREATE);
  720. sm.addItemName(recipe.getItemId());
  721. sm.addItemNumber(quantity - itemQuantityAmount);
  722. _target.sendPacket(sm);
  723. abort();
  724. return null;
  725. }
  726. // make new temporary object, just for counting purposes
  727. TempItem temp = new TempItem(item, quantity);
  728. materials.add(temp);
  729. }
  730. }
  731. if (remove)
  732. {
  733. for (TempItem tmp : materials)
  734. {
  735. inv.destroyItemByItemId("Manufacture", tmp.getItemId(), tmp.getQuantity(), _target, _player);
  736. if (tmp.getQuantity() > 1)
  737. {
  738. sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S1_DISAPPEARED);
  739. sm.addItemName(tmp.getItemId());
  740. sm.addItemNumber(tmp.getQuantity());
  741. _target.sendPacket(sm);
  742. }
  743. else
  744. {
  745. sm = SystemMessage.getSystemMessage(SystemMessageId.S1_DISAPPEARED);
  746. sm.addItemName(tmp.getItemId());
  747. _target.sendPacket(sm);
  748. }
  749. }
  750. }
  751. return materials;
  752. }
  753. private void abort()
  754. {
  755. updateMakeInfo(false);
  756. _player.isInCraftMode(false);
  757. _activeMakers.remove(_player.getObjectId());
  758. }
  759. /**
  760. * FIXME: This class should be in some other file, but I don't know where
  761. *
  762. * Class explanation:
  763. * For item counting or checking purposes. When you don't want to modify inventory
  764. * class contains itemId, quantity, ownerId, referencePrice, but not objectId
  765. */
  766. private class TempItem
  767. { // no object id stored, this will be only "list" of items with it's owner
  768. private int _itemId;
  769. private int _quantity;
  770. private int _referencePrice;
  771. private String _itemName;
  772. /**
  773. * @param item
  774. * @param quantity of that item
  775. */
  776. public TempItem(L2ItemInstance item, int quantity)
  777. {
  778. super();
  779. _itemId = item.getItemId();
  780. _quantity = quantity;
  781. _itemName = item.getItem().getName();
  782. _referencePrice = item.getReferencePrice();
  783. }
  784. /**
  785. * @return Returns the quantity.
  786. */
  787. public int getQuantity()
  788. {
  789. return _quantity;
  790. }
  791. /**
  792. * @param quantity The quantity to set.
  793. */
  794. public void setQuantity(int quantity)
  795. {
  796. _quantity = quantity;
  797. }
  798. public int getReferencePrice()
  799. {
  800. return _referencePrice;
  801. }
  802. /**
  803. * @return Returns the itemId.
  804. */
  805. public int getItemId()
  806. {
  807. return _itemId;
  808. }
  809. /**
  810. * @return Returns the itemName.
  811. */
  812. public String getItemName()
  813. {
  814. return _itemName;
  815. }
  816. }
  817. private void rewardPlayer()
  818. {
  819. int rareProdId = _recipeList.getRareItemId();
  820. int itemId = _recipeList.getItemId();
  821. int itemCount = _recipeList.getCount();
  822. L2Item template = ItemTable.getInstance().getTemplate(itemId);
  823. // check that the current recipe has a rare production or not
  824. if (rareProdId != -1 && (rareProdId == itemId || Config.CRAFT_MASTERWORK))
  825. {
  826. if (Rnd.get(100) < _recipeList.getRarity())
  827. {
  828. itemId = rareProdId;
  829. itemCount = _recipeList.getRareCount();
  830. }
  831. }
  832. _target.getInventory().addItem("Manufacture", itemId, itemCount, _target, _player);
  833. // inform customer of earned item
  834. SystemMessage sm = null;
  835. if (_target != _player)
  836. {
  837. // inform manufacturer of earned profit
  838. if (itemCount == 1)
  839. {
  840. sm = SystemMessage.getSystemMessage(SystemMessageId.S2_CREATED_FOR_C1_FOR_S3_ADENA);
  841. sm.addString(_target.getName());
  842. sm.addItemName(itemId);
  843. sm.addItemNumber(_price);
  844. _player.sendPacket(sm);
  845. sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_FOR_S3_ADENA);
  846. sm.addString(_player.getName());
  847. sm.addItemName(itemId);
  848. sm.addItemNumber(_price);
  849. _target.sendPacket(sm);
  850. }
  851. else
  852. {
  853. sm = SystemMessage.getSystemMessage(SystemMessageId.S2_S3_S_CREATED_FOR_C1_FOR_S4_ADENA);
  854. sm.addString(_target.getName());
  855. sm.addNumber(itemCount);
  856. sm.addItemName(itemId);
  857. sm.addItemNumber(_price);
  858. _player.sendPacket(sm);
  859. sm = SystemMessage.getSystemMessage(SystemMessageId.C1_CREATED_S2_S3_S_FOR_S4_ADENA);
  860. sm.addString(_player.getName());
  861. sm.addNumber(itemCount);
  862. sm.addItemName(itemId);
  863. sm.addItemNumber(_price);
  864. _target.sendPacket(sm);
  865. }
  866. }
  867. if (itemCount > 1)
  868. {
  869. sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_S2_S1_S);
  870. sm.addItemName(itemId);
  871. sm.addItemNumber(itemCount);
  872. _target.sendPacket(sm);
  873. }
  874. else
  875. {
  876. sm = SystemMessage.getSystemMessage(SystemMessageId.EARNED_ITEM_S1);
  877. sm.addItemName(itemId);
  878. _target.sendPacket(sm);
  879. }
  880. if (Config.ALT_GAME_CREATION)
  881. {
  882. int recipeLevel = _recipeList.getLevel();
  883. if (_exp < 0)
  884. {
  885. _exp = template.getReferencePrice() * itemCount;
  886. _exp /= recipeLevel;
  887. }
  888. if (_sp < 0)
  889. _sp = _exp / 10;
  890. if (itemId == rareProdId)
  891. {
  892. _exp *= Config.ALT_GAME_CREATION_RARE_XPSP_RATE;
  893. _sp *= Config.ALT_GAME_CREATION_RARE_XPSP_RATE;
  894. }
  895. // one variation
  896. // exp -= materialsRefPrice; // mat. ref. price is not accurate so other method is better
  897. if (_exp < 0)
  898. _exp = 0;
  899. if (_sp < 0)
  900. _sp = 0;
  901. for (int i = _skillLevel; i > recipeLevel; i--)
  902. {
  903. _exp /= 4;
  904. _sp /= 4;
  905. }
  906. // Added multiplication of Creation speed with XP/SP gain
  907. // slower crafting -> more XP, faster crafting -> less XP
  908. // you can use ALT_GAME_CREATION_XP_RATE/SP to
  909. // modify XP/SP gained (default = 1)
  910. _player.addExpAndSp((int) _player.calcStat(Stats.EXPSP_RATE, _exp * Config.ALT_GAME_CREATION_XP_RATE
  911. * Config.ALT_GAME_CREATION_SPEED, null, null), (int) _player.calcStat(Stats.EXPSP_RATE, _sp
  912. * Config.ALT_GAME_CREATION_SP_RATE * Config.ALT_GAME_CREATION_SPEED, null, null));
  913. }
  914. updateMakeInfo(true); // success
  915. }
  916. }
  917. private L2RecipeList getValidRecipeList(L2PcInstance player, int id)
  918. {
  919. L2RecipeList recipeList = getRecipeList(id);
  920. if ((recipeList == null) || (recipeList.getRecipes().length == 0))
  921. {
  922. player.sendMessage("No recipe for: " + id);
  923. player.isInCraftMode(false);
  924. return null;
  925. }
  926. return recipeList;
  927. }
  928. @SuppressWarnings("synthetic-access")
  929. private static class SingletonHolder
  930. {
  931. protected static final RecipeController _instance = new RecipeController();
  932. }
  933. }