RecipeController.java 31 KB

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