2
0

RecipeController.java 31 KB

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