inventorymanager.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. /*
  2. Minetest
  3. Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "inventorymanager.h"
  17. #include "debug.h"
  18. #include "log.h"
  19. #include "serverenvironment.h"
  20. #include "scripting_server.h"
  21. #include "serverobject.h"
  22. #include "settings.h"
  23. #include "craftdef.h"
  24. #include "rollback_interface.h"
  25. #include "util/strfnd.h"
  26. #include "util/basic_macros.h"
  27. #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface()
  28. /*
  29. InventoryLocation
  30. */
  31. std::string InventoryLocation::dump() const
  32. {
  33. std::ostringstream os(std::ios::binary);
  34. serialize(os);
  35. return os.str();
  36. }
  37. void InventoryLocation::serialize(std::ostream &os) const
  38. {
  39. switch (type) {
  40. case InventoryLocation::UNDEFINED:
  41. os<<"undefined";
  42. break;
  43. case InventoryLocation::CURRENT_PLAYER:
  44. os<<"current_player";
  45. break;
  46. case InventoryLocation::PLAYER:
  47. os<<"player:"<<name;
  48. break;
  49. case InventoryLocation::NODEMETA:
  50. os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
  51. break;
  52. case InventoryLocation::DETACHED:
  53. os<<"detached:"<<name;
  54. break;
  55. default:
  56. FATAL_ERROR("Unhandled inventory location type");
  57. }
  58. }
  59. void InventoryLocation::deSerialize(std::istream &is)
  60. {
  61. std::string tname;
  62. std::getline(is, tname, ':');
  63. if (tname == "undefined") {
  64. type = InventoryLocation::UNDEFINED;
  65. } else if (tname == "current_player") {
  66. type = InventoryLocation::CURRENT_PLAYER;
  67. } else if (tname == "player") {
  68. type = InventoryLocation::PLAYER;
  69. std::getline(is, name, '\n');
  70. } else if (tname == "nodemeta") {
  71. type = InventoryLocation::NODEMETA;
  72. std::string pos;
  73. std::getline(is, pos, '\n');
  74. Strfnd fn(pos);
  75. p.X = stoi(fn.next(","));
  76. p.Y = stoi(fn.next(","));
  77. p.Z = stoi(fn.next(","));
  78. } else if (tname == "detached") {
  79. type = InventoryLocation::DETACHED;
  80. std::getline(is, name, '\n');
  81. } else {
  82. infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
  83. throw SerializationError("Unknown InventoryLocation type");
  84. }
  85. }
  86. void InventoryLocation::deSerialize(std::string s)
  87. {
  88. std::istringstream is(s, std::ios::binary);
  89. deSerialize(is);
  90. }
  91. /*
  92. InventoryAction
  93. */
  94. InventoryAction *InventoryAction::deSerialize(std::istream &is)
  95. {
  96. std::string type;
  97. std::getline(is, type, ' ');
  98. InventoryAction *a = nullptr;
  99. if (type == "Move") {
  100. a = new IMoveAction(is, false);
  101. } else if (type == "MoveSomewhere") {
  102. a = new IMoveAction(is, true);
  103. } else if (type == "Drop") {
  104. a = new IDropAction(is);
  105. } else if (type == "Craft") {
  106. a = new ICraftAction(is);
  107. }
  108. return a;
  109. }
  110. /*
  111. IMoveAction
  112. */
  113. IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
  114. move_somewhere(somewhere)
  115. {
  116. std::string ts;
  117. std::getline(is, ts, ' ');
  118. count = stoi(ts);
  119. std::getline(is, ts, ' ');
  120. from_inv.deSerialize(ts);
  121. std::getline(is, from_list, ' ');
  122. std::getline(is, ts, ' ');
  123. from_i = stoi(ts);
  124. std::getline(is, ts, ' ');
  125. to_inv.deSerialize(ts);
  126. std::getline(is, to_list, ' ');
  127. if (!somewhere) {
  128. std::getline(is, ts, ' ');
  129. to_i = stoi(ts);
  130. }
  131. }
  132. void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
  133. {
  134. Inventory *inv_from = mgr->getInventory(from_inv);
  135. Inventory *inv_to = mgr->getInventory(to_inv);
  136. if (!inv_from) {
  137. infostream << "IMoveAction::apply(): FAIL: source inventory not found: "
  138. << "from_inv=\""<<from_inv.dump() << "\""
  139. << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
  140. return;
  141. }
  142. if (!inv_to) {
  143. infostream << "IMoveAction::apply(): FAIL: destination inventory not found: "
  144. << "from_inv=\"" << from_inv.dump() << "\""
  145. << ", to_inv=\"" << to_inv.dump() << "\"" << std::endl;
  146. return;
  147. }
  148. InventoryList *list_from = inv_from->getList(from_list);
  149. InventoryList *list_to = inv_to->getList(to_list);
  150. /*
  151. If a list doesn't exist or the source item doesn't exist
  152. */
  153. if (!list_from) {
  154. infostream << "IMoveAction::apply(): FAIL: source list not found: "
  155. << "from_inv=\"" << from_inv.dump() << "\""
  156. << ", from_list=\"" << from_list << "\"" << std::endl;
  157. return;
  158. }
  159. if (!list_to) {
  160. infostream << "IMoveAction::apply(): FAIL: destination list not found: "
  161. << "to_inv=\""<<to_inv.dump() << "\""
  162. << ", to_list=\"" << to_list << "\"" << std::endl;
  163. return;
  164. }
  165. if (move_somewhere) {
  166. s16 old_to_i = to_i;
  167. u16 old_count = count;
  168. caused_by_move_somewhere = true;
  169. move_somewhere = false;
  170. infostream << "IMoveAction::apply(): moving item somewhere"
  171. << " msom=" << move_somewhere
  172. << " count=" << count
  173. << " from inv=\"" << from_inv.dump() << "\""
  174. << " list=\"" << from_list << "\""
  175. << " i=" << from_i
  176. << " to inv=\"" << to_inv.dump() << "\""
  177. << " list=\"" << to_list << "\""
  178. << std::endl;
  179. // Try to add the item to destination list
  180. s16 dest_size = list_to->getSize();
  181. // First try all the non-empty slots
  182. for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
  183. if (!list_to->getItem(dest_i).empty()) {
  184. to_i = dest_i;
  185. apply(mgr, player, gamedef);
  186. count -= move_count;
  187. }
  188. }
  189. // Then try all the empty ones
  190. for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
  191. if (list_to->getItem(dest_i).empty()) {
  192. to_i = dest_i;
  193. apply(mgr, player, gamedef);
  194. count -= move_count;
  195. }
  196. }
  197. to_i = old_to_i;
  198. count = old_count;
  199. caused_by_move_somewhere = false;
  200. move_somewhere = true;
  201. return;
  202. }
  203. if ((u16)to_i > list_to->getSize()) {
  204. infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
  205. << "to_i=" << to_i
  206. << ", size=" << list_to->getSize() << std::endl;
  207. return;
  208. }
  209. /*
  210. Do not handle rollback if both inventories are that of the same player
  211. */
  212. bool ignore_rollback = (
  213. from_inv.type == InventoryLocation::PLAYER &&
  214. to_inv.type == InventoryLocation::PLAYER &&
  215. from_inv.name == to_inv.name);
  216. /*
  217. Collect information of endpoints
  218. */
  219. int try_take_count = count;
  220. if (try_take_count == 0)
  221. try_take_count = list_from->getItem(from_i).count;
  222. int src_can_take_count = 0xffff;
  223. int dst_can_put_count = 0xffff;
  224. /* Query detached inventories */
  225. // Move occurs in the same detached inventory
  226. if (from_inv.type == InventoryLocation::DETACHED &&
  227. to_inv.type == InventoryLocation::DETACHED &&
  228. from_inv.name == to_inv.name) {
  229. src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowMove(
  230. from_inv.name, from_list, from_i,
  231. to_list, to_i, try_take_count, player);
  232. dst_can_put_count = src_can_take_count;
  233. } else {
  234. // Destination is detached
  235. if (to_inv.type == InventoryLocation::DETACHED) {
  236. ItemStack src_item = list_from->getItem(from_i);
  237. src_item.count = try_take_count;
  238. dst_can_put_count = PLAYER_TO_SA(player)->detached_inventory_AllowPut(
  239. to_inv.name, to_list, to_i, src_item, player);
  240. }
  241. // Source is detached
  242. if (from_inv.type == InventoryLocation::DETACHED) {
  243. ItemStack src_item = list_from->getItem(from_i);
  244. src_item.count = try_take_count;
  245. src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
  246. from_inv.name, from_list, from_i, src_item, player);
  247. }
  248. }
  249. /* Query node metadata inventories */
  250. // Both endpoints are nodemeta
  251. // Move occurs in the same nodemeta inventory
  252. if (from_inv.type == InventoryLocation::NODEMETA &&
  253. to_inv.type == InventoryLocation::NODEMETA &&
  254. from_inv.p == to_inv.p) {
  255. src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowMove(
  256. from_inv.p, from_list, from_i,
  257. to_list, to_i, try_take_count, player);
  258. dst_can_put_count = src_can_take_count;
  259. } else {
  260. // Destination is nodemeta
  261. if (to_inv.type == InventoryLocation::NODEMETA) {
  262. ItemStack src_item = list_from->getItem(from_i);
  263. src_item.count = try_take_count;
  264. dst_can_put_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowPut(
  265. to_inv.p, to_list, to_i, src_item, player);
  266. }
  267. // Source is nodemeta
  268. if (from_inv.type == InventoryLocation::NODEMETA) {
  269. ItemStack src_item = list_from->getItem(from_i);
  270. src_item.count = try_take_count;
  271. src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
  272. from_inv.p, from_list, from_i, src_item, player);
  273. }
  274. }
  275. int old_count = count;
  276. /* Modify count according to collected data */
  277. count = try_take_count;
  278. if (src_can_take_count != -1 && count > src_can_take_count)
  279. count = src_can_take_count;
  280. if (dst_can_put_count != -1 && count > dst_can_put_count)
  281. count = dst_can_put_count;
  282. /* Limit according to source item count */
  283. if (count > list_from->getItem(from_i).count)
  284. count = list_from->getItem(from_i).count;
  285. /* If no items will be moved, don't go further */
  286. if (count == 0) {
  287. infostream<<"IMoveAction::apply(): move was completely disallowed:"
  288. <<" count="<<old_count
  289. <<" from inv=\""<<from_inv.dump()<<"\""
  290. <<" list=\""<<from_list<<"\""
  291. <<" i="<<from_i
  292. <<" to inv=\""<<to_inv.dump()<<"\""
  293. <<" list=\""<<to_list<<"\""
  294. <<" i="<<to_i
  295. <<std::endl;
  296. return;
  297. }
  298. ItemStack src_item = list_from->getItem(from_i);
  299. src_item.count = count;
  300. ItemStack from_stack_was = list_from->getItem(from_i);
  301. ItemStack to_stack_was = list_to->getItem(to_i);
  302. /*
  303. Perform actual move
  304. If something is wrong (source item is empty, destination is the
  305. same as source), nothing happens
  306. */
  307. bool did_swap = false;
  308. move_count = list_from->moveItem(from_i,
  309. list_to, to_i, count, !caused_by_move_somewhere, &did_swap);
  310. // If source is infinite, reset it's stack
  311. if (src_can_take_count == -1) {
  312. // For the caused_by_move_somewhere == true case we didn't force-put the item,
  313. // which guarantees there is no leftover, and code below would duplicate the
  314. // (not replaced) to_stack_was item.
  315. if (!caused_by_move_somewhere) {
  316. // If destination stack is of different type and there are leftover
  317. // items, attempt to put the leftover items to a different place in the
  318. // destination inventory.
  319. // The client-side GUI will try to guess if this happens.
  320. if (from_stack_was.name != to_stack_was.name) {
  321. for (u32 i = 0; i < list_to->getSize(); i++) {
  322. if (list_to->getItem(i).empty()) {
  323. list_to->changeItem(i, to_stack_was);
  324. break;
  325. }
  326. }
  327. }
  328. }
  329. if (move_count > 0 || did_swap) {
  330. list_from->deleteItem(from_i);
  331. list_from->addItem(from_i, from_stack_was);
  332. }
  333. }
  334. // If destination is infinite, reset it's stack and take count from source
  335. if (dst_can_put_count == -1) {
  336. list_to->deleteItem(to_i);
  337. list_to->addItem(to_i, to_stack_was);
  338. list_from->deleteItem(from_i);
  339. list_from->addItem(from_i, from_stack_was);
  340. list_from->takeItem(from_i, count);
  341. }
  342. infostream << "IMoveAction::apply(): moved"
  343. << " msom=" << move_somewhere
  344. << " caused=" << caused_by_move_somewhere
  345. << " count=" << count
  346. << " from inv=\"" << from_inv.dump() << "\""
  347. << " list=\"" << from_list << "\""
  348. << " i=" << from_i
  349. << " to inv=\"" << to_inv.dump() << "\""
  350. << " list=\"" << to_list << "\""
  351. << " i=" << to_i
  352. << std::endl;
  353. // If we are inside the move somewhere loop, we don't need to report
  354. // anything if nothing happened (perhaps we don't need to report
  355. // anything for caused_by_move_somewhere == true, but this way its safer)
  356. if (caused_by_move_somewhere && move_count == 0)
  357. return;
  358. /*
  359. Record rollback information
  360. */
  361. if (!ignore_rollback && gamedef->rollback()) {
  362. IRollbackManager *rollback = gamedef->rollback();
  363. // If source is not infinite, record item take
  364. if (src_can_take_count != -1) {
  365. RollbackAction action;
  366. std::string loc;
  367. {
  368. std::ostringstream os(std::ios::binary);
  369. from_inv.serialize(os);
  370. loc = os.str();
  371. }
  372. action.setModifyInventoryStack(loc, from_list, from_i, false,
  373. src_item);
  374. rollback->reportAction(action);
  375. }
  376. // If destination is not infinite, record item put
  377. if (dst_can_put_count != -1) {
  378. RollbackAction action;
  379. std::string loc;
  380. {
  381. std::ostringstream os(std::ios::binary);
  382. to_inv.serialize(os);
  383. loc = os.str();
  384. }
  385. action.setModifyInventoryStack(loc, to_list, to_i, true,
  386. src_item);
  387. rollback->reportAction(action);
  388. }
  389. }
  390. /*
  391. Report move to endpoints
  392. */
  393. /* Detached inventories */
  394. // Both endpoints are same detached
  395. if (from_inv.type == InventoryLocation::DETACHED &&
  396. to_inv.type == InventoryLocation::DETACHED &&
  397. from_inv.name == to_inv.name) {
  398. PLAYER_TO_SA(player)->detached_inventory_OnMove(
  399. from_inv.name, from_list, from_i,
  400. to_list, to_i, count, player);
  401. } else {
  402. // Destination is detached
  403. if (to_inv.type == InventoryLocation::DETACHED) {
  404. PLAYER_TO_SA(player)->detached_inventory_OnPut(
  405. to_inv.name, to_list, to_i, src_item, player);
  406. }
  407. // Source is detached
  408. if (from_inv.type == InventoryLocation::DETACHED) {
  409. PLAYER_TO_SA(player)->detached_inventory_OnTake(
  410. from_inv.name, from_list, from_i, src_item, player);
  411. }
  412. }
  413. /* Node metadata inventories */
  414. // Both endpoints are same nodemeta
  415. if (from_inv.type == InventoryLocation::NODEMETA &&
  416. to_inv.type == InventoryLocation::NODEMETA &&
  417. from_inv.p == to_inv.p) {
  418. PLAYER_TO_SA(player)->nodemeta_inventory_OnMove(
  419. from_inv.p, from_list, from_i,
  420. to_list, to_i, count, player);
  421. } else {
  422. // Destination is nodemeta
  423. if (to_inv.type == InventoryLocation::NODEMETA) {
  424. PLAYER_TO_SA(player)->nodemeta_inventory_OnPut(
  425. to_inv.p, to_list, to_i, src_item, player);
  426. }
  427. // Source is nodemeta
  428. else if (from_inv.type == InventoryLocation::NODEMETA) {
  429. PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
  430. from_inv.p, from_list, from_i, src_item, player);
  431. }
  432. }
  433. mgr->setInventoryModified(from_inv, false);
  434. if (inv_from != inv_to)
  435. mgr->setInventoryModified(to_inv, false);
  436. }
  437. void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  438. {
  439. // Optional InventoryAction operation that is run on the client
  440. // to make lag less apparent.
  441. Inventory *inv_from = mgr->getInventory(from_inv);
  442. Inventory *inv_to = mgr->getInventory(to_inv);
  443. if (!inv_from || !inv_to)
  444. return;
  445. InventoryLocation current_player;
  446. current_player.setCurrentPlayer();
  447. Inventory *inv_player = mgr->getInventory(current_player);
  448. if (inv_from != inv_player || inv_to != inv_player)
  449. return;
  450. InventoryList *list_from = inv_from->getList(from_list);
  451. InventoryList *list_to = inv_to->getList(to_list);
  452. if (!list_from || !list_to)
  453. return;
  454. if (!move_somewhere)
  455. list_from->moveItem(from_i, list_to, to_i, count);
  456. else
  457. list_from->moveItemSomewhere(from_i, list_to, count);
  458. mgr->setInventoryModified(from_inv);
  459. if (inv_from != inv_to)
  460. mgr->setInventoryModified(to_inv);
  461. }
  462. /*
  463. IDropAction
  464. */
  465. IDropAction::IDropAction(std::istream &is)
  466. {
  467. std::string ts;
  468. std::getline(is, ts, ' ');
  469. count = stoi(ts);
  470. std::getline(is, ts, ' ');
  471. from_inv.deSerialize(ts);
  472. std::getline(is, from_list, ' ');
  473. std::getline(is, ts, ' ');
  474. from_i = stoi(ts);
  475. }
  476. void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
  477. {
  478. Inventory *inv_from = mgr->getInventory(from_inv);
  479. if (!inv_from) {
  480. infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
  481. <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
  482. return;
  483. }
  484. InventoryList *list_from = inv_from->getList(from_list);
  485. /*
  486. If a list doesn't exist or the source item doesn't exist
  487. */
  488. if (!list_from) {
  489. infostream<<"IDropAction::apply(): FAIL: source list not found: "
  490. <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
  491. return;
  492. }
  493. if (list_from->getItem(from_i).empty()) {
  494. infostream<<"IDropAction::apply(): FAIL: source item not found: "
  495. <<"from_inv=\""<<from_inv.dump()<<"\""
  496. <<", from_list=\""<<from_list<<"\""
  497. <<" from_i="<<from_i<<std::endl;
  498. return;
  499. }
  500. /*
  501. Do not handle rollback if inventory is player's
  502. */
  503. bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
  504. /*
  505. Collect information of endpoints
  506. */
  507. int take_count = list_from->getItem(from_i).count;
  508. if (count != 0 && count < take_count)
  509. take_count = count;
  510. int src_can_take_count = take_count;
  511. // Source is detached
  512. if (from_inv.type == InventoryLocation::DETACHED) {
  513. ItemStack src_item = list_from->getItem(from_i);
  514. src_item.count = take_count;
  515. src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
  516. from_inv.name, from_list, from_i, src_item, player);
  517. }
  518. // Source is nodemeta
  519. if (from_inv.type == InventoryLocation::NODEMETA) {
  520. ItemStack src_item = list_from->getItem(from_i);
  521. src_item.count = take_count;
  522. src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
  523. from_inv.p, from_list, from_i, src_item, player);
  524. }
  525. if (src_can_take_count != -1 && src_can_take_count < take_count)
  526. take_count = src_can_take_count;
  527. int actually_dropped_count = 0;
  528. ItemStack src_item = list_from->getItem(from_i);
  529. // Drop the item
  530. ItemStack item1 = list_from->getItem(from_i);
  531. item1.count = take_count;
  532. if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
  533. player->getBasePosition())) {
  534. actually_dropped_count = take_count - item1.count;
  535. if (actually_dropped_count == 0) {
  536. infostream<<"Actually dropped no items"<<std::endl;
  537. return;
  538. }
  539. // If source isn't infinite
  540. if (src_can_take_count != -1) {
  541. // Take item from source list
  542. ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
  543. if (item2.count != actually_dropped_count)
  544. errorstream<<"Could not take dropped count of items"<<std::endl;
  545. mgr->setInventoryModified(from_inv, false);
  546. }
  547. }
  548. infostream<<"IDropAction::apply(): dropped "
  549. <<" from inv=\""<<from_inv.dump()<<"\""
  550. <<" list=\""<<from_list<<"\""
  551. <<" i="<<from_i
  552. <<std::endl;
  553. src_item.count = actually_dropped_count;
  554. /*
  555. Report drop to endpoints
  556. */
  557. // Source is detached
  558. if (from_inv.type == InventoryLocation::DETACHED) {
  559. PLAYER_TO_SA(player)->detached_inventory_OnTake(
  560. from_inv.name, from_list, from_i, src_item, player);
  561. }
  562. // Source is nodemeta
  563. if (from_inv.type == InventoryLocation::NODEMETA) {
  564. PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
  565. from_inv.p, from_list, from_i, src_item, player);
  566. }
  567. /*
  568. Record rollback information
  569. */
  570. if (!ignore_src_rollback && gamedef->rollback()) {
  571. IRollbackManager *rollback = gamedef->rollback();
  572. // If source is not infinite, record item take
  573. if (src_can_take_count != -1) {
  574. RollbackAction action;
  575. std::string loc;
  576. {
  577. std::ostringstream os(std::ios::binary);
  578. from_inv.serialize(os);
  579. loc = os.str();
  580. }
  581. action.setModifyInventoryStack(loc, from_list, from_i,
  582. false, src_item);
  583. rollback->reportAction(action);
  584. }
  585. }
  586. }
  587. void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  588. {
  589. // Optional InventoryAction operation that is run on the client
  590. // to make lag less apparent.
  591. Inventory *inv_from = mgr->getInventory(from_inv);
  592. if (!inv_from)
  593. return;
  594. InventoryLocation current_player;
  595. current_player.setCurrentPlayer();
  596. Inventory *inv_player = mgr->getInventory(current_player);
  597. if (inv_from != inv_player)
  598. return;
  599. InventoryList *list_from = inv_from->getList(from_list);
  600. if (!list_from)
  601. return;
  602. if (count == 0)
  603. list_from->changeItem(from_i, ItemStack());
  604. else
  605. list_from->takeItem(from_i, count);
  606. mgr->setInventoryModified(from_inv);
  607. }
  608. /*
  609. ICraftAction
  610. */
  611. ICraftAction::ICraftAction(std::istream &is)
  612. {
  613. std::string ts;
  614. std::getline(is, ts, ' ');
  615. count = stoi(ts);
  616. std::getline(is, ts, ' ');
  617. craft_inv.deSerialize(ts);
  618. }
  619. void ICraftAction::apply(InventoryManager *mgr,
  620. ServerActiveObject *player, IGameDef *gamedef)
  621. {
  622. Inventory *inv_craft = mgr->getInventory(craft_inv);
  623. if (!inv_craft) {
  624. infostream << "ICraftAction::apply(): FAIL: inventory not found: "
  625. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  626. return;
  627. }
  628. InventoryList *list_craft = inv_craft->getList("craft");
  629. InventoryList *list_craftresult = inv_craft->getList("craftresult");
  630. InventoryList *list_main = inv_craft->getList("main");
  631. /*
  632. If a list doesn't exist or the source item doesn't exist
  633. */
  634. if (!list_craft) {
  635. infostream << "ICraftAction::apply(): FAIL: craft list not found: "
  636. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  637. return;
  638. }
  639. if (!list_craftresult) {
  640. infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
  641. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  642. return;
  643. }
  644. if (list_craftresult->getSize() < 1) {
  645. infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
  646. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  647. return;
  648. }
  649. ItemStack crafted;
  650. ItemStack craftresultitem;
  651. int count_remaining = count;
  652. std::vector<ItemStack> output_replacements;
  653. getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
  654. PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
  655. bool found = !crafted.empty();
  656. while (found && list_craftresult->itemFits(0, crafted)) {
  657. InventoryList saved_craft_list = *list_craft;
  658. std::vector<ItemStack> temp;
  659. // Decrement input and add crafting output
  660. getCraftingResult(inv_craft, crafted, temp, true, gamedef);
  661. PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
  662. list_craftresult->addItem(0, crafted);
  663. mgr->setInventoryModified(craft_inv);
  664. // Add the new replacements to the list
  665. IItemDefManager *itemdef = gamedef->getItemDefManager();
  666. for (auto &itemstack : temp) {
  667. for (auto &output_replacement : output_replacements) {
  668. if (itemstack.name == output_replacement.name) {
  669. itemstack = output_replacement.addItem(itemstack, itemdef);
  670. if (itemstack.empty())
  671. continue;
  672. }
  673. }
  674. output_replacements.push_back(itemstack);
  675. }
  676. actionstream << player->getDescription()
  677. << " crafts "
  678. << crafted.getItemString()
  679. << std::endl;
  680. // Decrement counter
  681. if (count_remaining == 1)
  682. break;
  683. if (count_remaining > 1)
  684. count_remaining--;
  685. // Get next crafting result
  686. found = getCraftingResult(inv_craft, crafted, temp, false, gamedef);
  687. PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
  688. found = !crafted.empty();
  689. }
  690. // Put the replacements in the inventory or drop them on the floor, if
  691. // the invenotry is full
  692. for (auto &output_replacement : output_replacements) {
  693. if (list_main)
  694. output_replacement = list_main->addItem(output_replacement);
  695. if (output_replacement.empty())
  696. continue;
  697. u16 count = output_replacement.count;
  698. do {
  699. PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
  700. player->getBasePosition());
  701. if (count >= output_replacement.count) {
  702. errorstream << "Couldn't drop replacement stack " <<
  703. output_replacement.getItemString() << " because drop loop didn't "
  704. "decrease count." << std::endl;
  705. break;
  706. }
  707. } while (!output_replacement.empty());
  708. }
  709. infostream<<"ICraftAction::apply(): crafted "
  710. <<" craft_inv=\""<<craft_inv.dump()<<"\""
  711. <<std::endl;
  712. }
  713. void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  714. {
  715. // Optional InventoryAction operation that is run on the client
  716. // to make lag less apparent.
  717. }
  718. // Crafting helper
  719. bool getCraftingResult(Inventory *inv, ItemStack &result,
  720. std::vector<ItemStack> &output_replacements,
  721. bool decrementInput, IGameDef *gamedef)
  722. {
  723. result.clear();
  724. // Get the InventoryList in which we will operate
  725. InventoryList *clist = inv->getList("craft");
  726. if (!clist)
  727. return false;
  728. // Mangle crafting grid to an another format
  729. CraftInput ci;
  730. ci.method = CRAFT_METHOD_NORMAL;
  731. ci.width = clist->getWidth() ? clist->getWidth() : 3;
  732. for (u16 i=0; i < clist->getSize(); i++)
  733. ci.items.push_back(clist->getItem(i));
  734. // Find out what is crafted and add it to result item slot
  735. CraftOutput co;
  736. bool found = gamedef->getCraftDefManager()->getCraftResult(
  737. ci, co, output_replacements, decrementInput, gamedef);
  738. if (found)
  739. result.deSerialize(co.item, gamedef->getItemDefManager());
  740. if (found && decrementInput) {
  741. // CraftInput has been changed, apply changes in clist
  742. for (u16 i=0; i < clist->getSize(); i++) {
  743. clist->changeItem(i, ci.items[i]);
  744. }
  745. }
  746. return found;
  747. }