inventorymanager.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030
  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 "server/serveractiveobject.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. #include "inventory.h"
  28. #define PLAYER_TO_SA(p) p->getEnv()->getScriptIface()
  29. /*
  30. InventoryLocation
  31. */
  32. std::string InventoryLocation::dump() const
  33. {
  34. std::ostringstream os(std::ios::binary);
  35. serialize(os);
  36. return os.str();
  37. }
  38. void InventoryLocation::serialize(std::ostream &os) const
  39. {
  40. switch (type) {
  41. case InventoryLocation::UNDEFINED:
  42. os<<"undefined";
  43. break;
  44. case InventoryLocation::CURRENT_PLAYER:
  45. os<<"current_player";
  46. break;
  47. case InventoryLocation::PLAYER:
  48. os<<"player:"<<name;
  49. break;
  50. case InventoryLocation::NODEMETA:
  51. os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
  52. break;
  53. case InventoryLocation::DETACHED:
  54. os<<"detached:"<<name;
  55. break;
  56. default:
  57. FATAL_ERROR("Unhandled inventory location type");
  58. }
  59. }
  60. void InventoryLocation::deSerialize(std::istream &is)
  61. {
  62. std::string tname;
  63. std::getline(is, tname, ':');
  64. if (tname == "undefined") {
  65. type = InventoryLocation::UNDEFINED;
  66. } else if (tname == "current_player") {
  67. type = InventoryLocation::CURRENT_PLAYER;
  68. } else if (tname == "player") {
  69. type = InventoryLocation::PLAYER;
  70. std::getline(is, name, '\n');
  71. } else if (tname == "nodemeta") {
  72. type = InventoryLocation::NODEMETA;
  73. std::string pos;
  74. std::getline(is, pos, '\n');
  75. Strfnd fn(pos);
  76. p.X = stoi(fn.next(","));
  77. p.Y = stoi(fn.next(","));
  78. p.Z = stoi(fn.next(","));
  79. } else if (tname == "detached") {
  80. type = InventoryLocation::DETACHED;
  81. std::getline(is, name, '\n');
  82. } else {
  83. infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
  84. throw SerializationError("Unknown InventoryLocation type");
  85. }
  86. }
  87. void InventoryLocation::deSerialize(const std::string &s)
  88. {
  89. std::istringstream is(s, std::ios::binary);
  90. deSerialize(is);
  91. }
  92. /*
  93. InventoryAction
  94. */
  95. InventoryAction *InventoryAction::deSerialize(std::istream &is)
  96. {
  97. std::string type;
  98. std::getline(is, type, ' ');
  99. InventoryAction *a = nullptr;
  100. if (type == "Move") {
  101. a = new IMoveAction(is, false);
  102. } else if (type == "MoveSomewhere") {
  103. a = new IMoveAction(is, true);
  104. } else if (type == "Drop") {
  105. a = new IDropAction(is);
  106. } else if (type == "Craft") {
  107. a = new ICraftAction(is);
  108. }
  109. return a;
  110. }
  111. /*
  112. IMoveAction
  113. */
  114. IMoveAction::IMoveAction(std::istream &is, bool somewhere) :
  115. move_somewhere(somewhere)
  116. {
  117. std::string ts;
  118. std::getline(is, ts, ' ');
  119. count = stoi(ts);
  120. std::getline(is, ts, ' ');
  121. from_inv.deSerialize(ts);
  122. std::getline(is, from_list, ' ');
  123. std::getline(is, ts, ' ');
  124. from_i = stoi(ts);
  125. std::getline(is, ts, ' ');
  126. to_inv.deSerialize(ts);
  127. std::getline(is, to_list, ' ');
  128. if (!somewhere) {
  129. std::getline(is, ts, ' ');
  130. to_i = stoi(ts);
  131. }
  132. }
  133. void IMoveAction::swapDirections()
  134. {
  135. std::swap(from_inv, to_inv);
  136. std::swap(from_list, to_list);
  137. std::swap(from_i, to_i);
  138. }
  139. void IMoveAction::onPutAndOnTake(const ItemStack &src_item, ServerActiveObject *player) const
  140. {
  141. ServerScripting *sa = PLAYER_TO_SA(player);
  142. if (to_inv.type == InventoryLocation::DETACHED)
  143. sa->detached_inventory_OnPut(*this, src_item, player);
  144. else if (to_inv.type == InventoryLocation::NODEMETA)
  145. sa->nodemeta_inventory_OnPut(*this, src_item, player);
  146. else if (to_inv.type == InventoryLocation::PLAYER)
  147. sa->player_inventory_OnPut(*this, src_item, player);
  148. else
  149. assert(false);
  150. if (from_inv.type == InventoryLocation::DETACHED)
  151. sa->detached_inventory_OnTake(*this, src_item, player);
  152. else if (from_inv.type == InventoryLocation::NODEMETA)
  153. sa->nodemeta_inventory_OnTake(*this, src_item, player);
  154. else if (from_inv.type == InventoryLocation::PLAYER)
  155. sa->player_inventory_OnTake(*this, src_item, player);
  156. else
  157. assert(false);
  158. }
  159. void IMoveAction::onMove(int count, ServerActiveObject *player) const
  160. {
  161. ServerScripting *sa = PLAYER_TO_SA(player);
  162. if (from_inv.type == InventoryLocation::DETACHED)
  163. sa->detached_inventory_OnMove(*this, count, player);
  164. else if (from_inv.type == InventoryLocation::NODEMETA)
  165. sa->nodemeta_inventory_OnMove(*this, count, player);
  166. else if (from_inv.type == InventoryLocation::PLAYER)
  167. sa->player_inventory_OnMove(*this, count, player);
  168. else
  169. assert(false);
  170. }
  171. int IMoveAction::allowPut(const ItemStack &dst_item, ServerActiveObject *player) const
  172. {
  173. ServerScripting *sa = PLAYER_TO_SA(player);
  174. int dst_can_put_count = 0xffff;
  175. if (to_inv.type == InventoryLocation::DETACHED)
  176. dst_can_put_count = sa->detached_inventory_AllowPut(*this, dst_item, player);
  177. else if (to_inv.type == InventoryLocation::NODEMETA)
  178. dst_can_put_count = sa->nodemeta_inventory_AllowPut(*this, dst_item, player);
  179. else if (to_inv.type == InventoryLocation::PLAYER)
  180. dst_can_put_count = sa->player_inventory_AllowPut(*this, dst_item, player);
  181. else
  182. assert(false);
  183. return dst_can_put_count;
  184. }
  185. int IMoveAction::allowTake(const ItemStack &src_item, ServerActiveObject *player) const
  186. {
  187. ServerScripting *sa = PLAYER_TO_SA(player);
  188. int src_can_take_count = 0xffff;
  189. if (from_inv.type == InventoryLocation::DETACHED)
  190. src_can_take_count = sa->detached_inventory_AllowTake(*this, src_item, player);
  191. else if (from_inv.type == InventoryLocation::NODEMETA)
  192. src_can_take_count = sa->nodemeta_inventory_AllowTake(*this, src_item, player);
  193. else if (from_inv.type == InventoryLocation::PLAYER)
  194. src_can_take_count = sa->player_inventory_AllowTake(*this, src_item, player);
  195. else
  196. assert(false);
  197. return src_can_take_count;
  198. }
  199. int IMoveAction::allowMove(int try_take_count, ServerActiveObject *player) const
  200. {
  201. ServerScripting *sa = PLAYER_TO_SA(player);
  202. int src_can_take_count = 0xffff;
  203. if (from_inv.type == InventoryLocation::DETACHED)
  204. src_can_take_count = sa->detached_inventory_AllowMove(*this, try_take_count, player);
  205. else if (from_inv.type == InventoryLocation::NODEMETA)
  206. src_can_take_count = sa->nodemeta_inventory_AllowMove(*this, try_take_count, player);
  207. else if (from_inv.type == InventoryLocation::PLAYER)
  208. src_can_take_count = sa->player_inventory_AllowMove(*this, try_take_count, player);
  209. else
  210. assert(false);
  211. return src_can_take_count;
  212. }
  213. void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
  214. {
  215. auto get_borrow_checked_invlist = [mgr](const InventoryLocation &invloc,
  216. const std::string &listname) -> InventoryList::ResizeLocked
  217. {
  218. Inventory *inv = mgr->getInventory(invloc);
  219. if (!inv)
  220. return nullptr;
  221. InventoryList *list = inv->getList(listname);
  222. if (!list)
  223. return nullptr;
  224. return list->resizeLock();
  225. };
  226. auto list_from = get_borrow_checked_invlist(from_inv, from_list);
  227. auto list_to = get_borrow_checked_invlist(to_inv, to_list);
  228. if (!list_from) {
  229. infostream << "IMoveAction::apply(): FAIL: source inventory or list not found: "
  230. << "from_inv=\"" << from_inv.dump() << "\""
  231. << ", from_list=\"" << from_list << "\""
  232. << ", to_inv=\"" << to_inv.dump() << "\""
  233. << ", to_list=\"" << to_list << "\""
  234. << std::endl;
  235. return;
  236. }
  237. if (!list_to) {
  238. infostream << "IMoveAction::apply(): FAIL: destination inventory or list not found: "
  239. << "from_inv=\"" << from_inv.dump() << "\""
  240. << ", from_list=\"" << from_list << "\""
  241. << ", to_inv=\"" << to_inv.dump() << "\""
  242. << ", to_list=\"" << to_list << "\""
  243. << std::endl;
  244. return;
  245. }
  246. if (move_somewhere) {
  247. list_from.reset();
  248. s16 old_to_i = to_i;
  249. u16 old_count = count;
  250. caused_by_move_somewhere = true;
  251. move_somewhere = false;
  252. infostream << "IMoveAction::apply(): moving item somewhere"
  253. << " msom=" << move_somewhere
  254. << " count=" << count
  255. << " from inv=\"" << from_inv.dump() << "\""
  256. << " list=\"" << from_list << "\""
  257. << " i=" << from_i
  258. << " to inv=\"" << to_inv.dump() << "\""
  259. << " list=\"" << to_list << "\""
  260. << std::endl;
  261. // Try to add the item to destination list
  262. s16 dest_size = list_to->getSize();
  263. auto add_to = [&](s16 dest_i) {
  264. // release resize lock while the callbacks are happening
  265. list_to.reset();
  266. to_i = dest_i;
  267. apply(mgr, player, gamedef);
  268. assert(move_count <= count);
  269. count -= move_count;
  270. list_to = get_borrow_checked_invlist(to_inv, to_list);
  271. if (!list_to) {
  272. // list_to was removed. simulate an empty list
  273. dest_size = 0;
  274. return;
  275. }
  276. dest_size = list_to->getSize();
  277. };
  278. // First try all the non-empty slots
  279. for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
  280. if (!list_to->getItem(dest_i).empty())
  281. add_to(dest_i);
  282. }
  283. // Then try all the empty ones
  284. for (s16 dest_i = 0; dest_i < dest_size && count > 0; dest_i++) {
  285. if (list_to->getItem(dest_i).empty())
  286. add_to(dest_i);
  287. }
  288. to_i = old_to_i;
  289. count = old_count;
  290. caused_by_move_somewhere = false;
  291. move_somewhere = true;
  292. return;
  293. }
  294. if (from_i < 0 || list_from->getSize() <= (u32) from_i) {
  295. infostream << "IMoveAction::apply(): FAIL: source index out of bounds: "
  296. << "size of from_list=\"" << list_from->getSize() << "\""
  297. << ", from_index=\"" << from_i << "\"" << std::endl;
  298. return;
  299. }
  300. if (to_i < 0 || list_to->getSize() <= (u32) to_i) {
  301. infostream << "IMoveAction::apply(): FAIL: destination index out of bounds: "
  302. << "size of to_list=\"" << list_to->getSize() << "\""
  303. << ", to_index=\"" << to_i << "\"" << std::endl;
  304. return;
  305. }
  306. if (list_from.get() == list_to.get() && from_i == to_i)
  307. return; // Same slot
  308. /*
  309. Do not handle rollback if both inventories are that of the same player
  310. */
  311. bool ignore_rollback = (
  312. from_inv.type == InventoryLocation::PLAYER &&
  313. from_inv == to_inv);
  314. /*
  315. Collect information of endpoints
  316. */
  317. ItemStack src_item = list_from->getItem(from_i);
  318. if (count > 0 && count < src_item.count)
  319. src_item.count = count;
  320. if (src_item.empty())
  321. return;
  322. int src_can_take_count = 0xffff;
  323. int dst_can_put_count = 0xffff;
  324. // this is needed for swapping items inside one inventory to work
  325. ItemStack restitem;
  326. bool allow_swap = !list_to->itemFits(to_i, src_item, &restitem)
  327. && restitem.count == src_item.count
  328. && !caused_by_move_somewhere;
  329. move_count = src_item.count - restitem.count;
  330. // Shift-click: Cannot fill this stack, proceed with next slot
  331. if (caused_by_move_somewhere && move_count == 0) {
  332. return;
  333. }
  334. if (allow_swap) {
  335. // Swap will affect the entire stack if it can performed.
  336. src_item = list_from->getItem(from_i);
  337. count = src_item.count;
  338. }
  339. if (from_inv == to_inv) {
  340. // Move action within the same inventory
  341. src_can_take_count = allowMove(src_item.count, player);
  342. bool swap_expected = allow_swap;
  343. allow_swap = allow_swap
  344. && (src_can_take_count == -1 || src_can_take_count >= src_item.count);
  345. if (allow_swap) {
  346. int try_put_count = list_to->getItem(to_i).count;
  347. swapDirections();
  348. dst_can_put_count = allowMove(try_put_count, player);
  349. allow_swap = allow_swap
  350. && (dst_can_put_count == -1 || dst_can_put_count >= try_put_count);
  351. swapDirections();
  352. } else {
  353. dst_can_put_count = src_can_take_count;
  354. }
  355. if (swap_expected != allow_swap)
  356. src_can_take_count = dst_can_put_count = 0;
  357. } else {
  358. // Take from one inventory, put into another
  359. int src_item_count = src_item.count;
  360. if (caused_by_move_somewhere)
  361. // When moving somewhere: temporarily use the actual movable stack
  362. // size to ensure correct callback execution.
  363. src_item.count = move_count;
  364. dst_can_put_count = allowPut(src_item, player);
  365. src_can_take_count = allowTake(src_item, player);
  366. if (caused_by_move_somewhere)
  367. // Reset source item count
  368. src_item.count = src_item_count;
  369. bool swap_expected = allow_swap;
  370. allow_swap = allow_swap
  371. && (src_can_take_count == -1 || src_can_take_count >= src_item.count)
  372. && (dst_can_put_count == -1 || dst_can_put_count >= src_item.count);
  373. // A swap is expected, which means that we have to
  374. // run the "allow" callbacks a second time with swapped inventories
  375. if (allow_swap) {
  376. ItemStack dst_item = list_to->getItem(to_i);
  377. swapDirections();
  378. int src_can_take = allowPut(dst_item, player);
  379. int dst_can_put = allowTake(dst_item, player);
  380. allow_swap = allow_swap
  381. && (src_can_take == -1 || src_can_take >= dst_item.count)
  382. && (dst_can_put == -1 || dst_can_put >= dst_item.count);
  383. swapDirections();
  384. }
  385. if (swap_expected != allow_swap)
  386. src_can_take_count = dst_can_put_count = 0;
  387. }
  388. int old_count = count;
  389. /* Modify count according to collected data */
  390. count = src_item.count;
  391. if (src_can_take_count != -1 && count > src_can_take_count)
  392. count = src_can_take_count;
  393. if (dst_can_put_count != -1 && count > dst_can_put_count)
  394. count = dst_can_put_count;
  395. /* Limit according to source item count */
  396. if (count > list_from->getItem(from_i).count)
  397. count = list_from->getItem(from_i).count;
  398. /* If no items will be moved, don't go further */
  399. if (count == 0) {
  400. if (caused_by_move_somewhere)
  401. // Set move count to zero, as no items have been moved
  402. move_count = 0;
  403. // Undo client prediction. See 'clientApply'
  404. if (from_inv.type == InventoryLocation::PLAYER)
  405. list_from->setModified();
  406. if (to_inv.type == InventoryLocation::PLAYER)
  407. list_to->setModified();
  408. infostream<<"IMoveAction::apply(): move was completely disallowed:"
  409. <<" count="<<old_count
  410. <<" from inv=\""<<from_inv.dump()<<"\""
  411. <<" list=\""<<from_list<<"\""
  412. <<" i="<<from_i
  413. <<" to inv=\""<<to_inv.dump()<<"\""
  414. <<" list=\""<<to_list<<"\""
  415. <<" i="<<to_i
  416. <<std::endl;
  417. return;
  418. }
  419. src_item = list_from->getItem(from_i);
  420. src_item.count = count;
  421. ItemStack from_stack_was = list_from->getItem(from_i);
  422. ItemStack to_stack_was = list_to->getItem(to_i);
  423. /*
  424. Perform actual move
  425. If something is wrong (source item is empty, destination is the
  426. same as source), nothing happens
  427. */
  428. bool did_swap = false;
  429. move_count = list_from->moveItem(from_i,
  430. list_to.get(), to_i, count, allow_swap, &did_swap);
  431. if (caused_by_move_somewhere)
  432. count = old_count;
  433. assert(allow_swap == did_swap);
  434. // If source is infinite, reset it's stack
  435. if (src_can_take_count == -1) {
  436. // For the caused_by_move_somewhere == true case we didn't force-put the item,
  437. // which guarantees there is no leftover, and code below would duplicate the
  438. // (not replaced) to_stack_was item.
  439. if (!caused_by_move_somewhere) {
  440. // If destination stack is of different type and there are leftover
  441. // items, attempt to put the leftover items to a different place in the
  442. // destination inventory.
  443. // The client-side GUI will try to guess if this happens.
  444. if (from_stack_was.name != to_stack_was.name) {
  445. for (u32 i = 0; i < list_to->getSize(); i++) {
  446. if (list_to->getItem(i).empty()) {
  447. list_to->changeItem(i, to_stack_was);
  448. break;
  449. }
  450. }
  451. }
  452. }
  453. if (move_count > 0 || did_swap) {
  454. list_from->deleteItem(from_i);
  455. list_from->addItem(from_i, from_stack_was);
  456. }
  457. }
  458. // If destination is infinite, reset it's stack and take count from source
  459. if (dst_can_put_count == -1) {
  460. list_to->deleteItem(to_i);
  461. list_to->addItem(to_i, to_stack_was);
  462. list_from->deleteItem(from_i);
  463. list_from->addItem(from_i, from_stack_was);
  464. list_from->takeItem(from_i, count);
  465. }
  466. infostream << "IMoveAction::apply(): moved"
  467. << " msom=" << move_somewhere
  468. << " caused=" << caused_by_move_somewhere
  469. << " count=" << count
  470. << " from inv=\"" << from_inv.dump() << "\""
  471. << " list=\"" << from_list << "\""
  472. << " i=" << from_i
  473. << " to inv=\"" << to_inv.dump() << "\""
  474. << " list=\"" << to_list << "\""
  475. << " i=" << to_i
  476. << std::endl;
  477. // If we are inside the move somewhere loop, we don't need to report
  478. // anything if nothing happened
  479. if (caused_by_move_somewhere && move_count == 0)
  480. return;
  481. /*
  482. Record rollback information
  483. */
  484. if (!ignore_rollback && gamedef->rollback()) {
  485. IRollbackManager *rollback = gamedef->rollback();
  486. // If source is not infinite, record item take
  487. if (src_can_take_count != -1) {
  488. RollbackAction action;
  489. std::string loc;
  490. {
  491. std::ostringstream os(std::ios::binary);
  492. from_inv.serialize(os);
  493. loc = os.str();
  494. }
  495. action.setModifyInventoryStack(loc, from_list, from_i, false,
  496. src_item);
  497. rollback->reportAction(action);
  498. }
  499. // If destination is not infinite, record item put
  500. if (dst_can_put_count != -1) {
  501. RollbackAction action;
  502. std::string loc;
  503. {
  504. std::ostringstream os(std::ios::binary);
  505. to_inv.serialize(os);
  506. loc = os.str();
  507. }
  508. action.setModifyInventoryStack(loc, to_list, to_i, true,
  509. src_item);
  510. rollback->reportAction(action);
  511. }
  512. }
  513. /*
  514. Report move to endpoints
  515. */
  516. list_to.reset();
  517. list_from.reset();
  518. // Source = destination => move
  519. if (from_inv == to_inv) {
  520. onMove(count, player);
  521. if (did_swap) {
  522. // Item is now placed in source list
  523. list_from = get_borrow_checked_invlist(from_inv, from_list);
  524. if (list_from) {
  525. src_item = list_from->getItem(from_i);
  526. list_from.reset();
  527. swapDirections();
  528. onMove(src_item.count, player);
  529. swapDirections();
  530. }
  531. }
  532. mgr->setInventoryModified(from_inv);
  533. } else {
  534. int src_item_count = src_item.count;
  535. if (caused_by_move_somewhere)
  536. // When moving somewhere: temporarily use the actual movable stack
  537. // size to ensure correct callback execution.
  538. src_item.count = move_count;
  539. onPutAndOnTake(src_item, player);
  540. if (caused_by_move_somewhere)
  541. // Reset source item count
  542. src_item.count = src_item_count;
  543. if (did_swap) {
  544. // Item is now placed in source list
  545. list_from = get_borrow_checked_invlist(from_inv, from_list);
  546. if (list_from) {
  547. src_item = list_from->getItem(from_i);
  548. list_from.reset();
  549. swapDirections();
  550. onPutAndOnTake(src_item, player);
  551. swapDirections();
  552. }
  553. }
  554. mgr->setInventoryModified(to_inv);
  555. mgr->setInventoryModified(from_inv);
  556. }
  557. }
  558. void IMoveAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  559. {
  560. // Optional InventoryAction operation that is run on the client
  561. // to make lag less apparent.
  562. Inventory *inv_from = mgr->getInventory(from_inv);
  563. Inventory *inv_to = mgr->getInventory(to_inv);
  564. if (!inv_from || !inv_to)
  565. return;
  566. InventoryLocation current_player;
  567. current_player.setCurrentPlayer();
  568. Inventory *inv_player = mgr->getInventory(current_player);
  569. if (inv_from != inv_player || inv_to != inv_player)
  570. return;
  571. InventoryList *list_from = inv_from->getList(from_list);
  572. InventoryList *list_to = inv_to->getList(to_list);
  573. if (!list_from || !list_to)
  574. return;
  575. if (!move_somewhere)
  576. list_from->moveItem(from_i, list_to, to_i, count);
  577. else
  578. list_from->moveItemSomewhere(from_i, list_to, count);
  579. mgr->setInventoryModified(from_inv);
  580. if (inv_from != inv_to)
  581. mgr->setInventoryModified(to_inv);
  582. }
  583. /*
  584. IDropAction
  585. */
  586. IDropAction::IDropAction(std::istream &is)
  587. {
  588. std::string ts;
  589. std::getline(is, ts, ' ');
  590. count = stoi(ts);
  591. std::getline(is, ts, ' ');
  592. from_inv.deSerialize(ts);
  593. std::getline(is, from_list, ' ');
  594. std::getline(is, ts, ' ');
  595. from_i = stoi(ts);
  596. }
  597. void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGameDef *gamedef)
  598. {
  599. Inventory *inv_from = mgr->getInventory(from_inv);
  600. if (!inv_from) {
  601. infostream<<"IDropAction::apply(): FAIL: source inventory not found: "
  602. <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
  603. return;
  604. }
  605. InventoryList *list_from = inv_from->getList(from_list);
  606. /*
  607. If a list doesn't exist or the source item doesn't exist
  608. */
  609. if (!list_from) {
  610. infostream<<"IDropAction::apply(): FAIL: source list not found: "
  611. <<"from_inv=\""<<from_inv.dump()<<"\""<<std::endl;
  612. return;
  613. }
  614. if (list_from->getItem(from_i).empty()) {
  615. infostream<<"IDropAction::apply(): FAIL: source item not found: "
  616. <<"from_inv=\""<<from_inv.dump()<<"\""
  617. <<", from_list=\""<<from_list<<"\""
  618. <<" from_i="<<from_i<<std::endl;
  619. return;
  620. }
  621. auto list_from_lock = list_from->resizeLock();
  622. /*
  623. Do not handle rollback if inventory is player's
  624. */
  625. bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
  626. /*
  627. Collect information of endpoints
  628. */
  629. int take_count = list_from->getItem(from_i).count;
  630. if (count != 0 && count < take_count)
  631. take_count = count;
  632. int src_can_take_count = take_count;
  633. ItemStack src_item = list_from->getItem(from_i);
  634. src_item.count = take_count;
  635. // Run callbacks depending on source inventory
  636. switch (from_inv.type) {
  637. case InventoryLocation::DETACHED:
  638. src_can_take_count = PLAYER_TO_SA(player)->detached_inventory_AllowTake(
  639. *this, src_item, player);
  640. break;
  641. case InventoryLocation::NODEMETA:
  642. src_can_take_count = PLAYER_TO_SA(player)->nodemeta_inventory_AllowTake(
  643. *this, src_item, player);
  644. break;
  645. case InventoryLocation::PLAYER:
  646. src_can_take_count = PLAYER_TO_SA(player)->player_inventory_AllowTake(
  647. *this, src_item, player);
  648. break;
  649. default:
  650. break;
  651. }
  652. if (src_can_take_count != -1 && src_can_take_count < take_count)
  653. take_count = src_can_take_count;
  654. // Update item due executed callbacks
  655. src_item = list_from->getItem(from_i);
  656. // Drop the item
  657. ItemStack item1 = list_from->getItem(from_i);
  658. item1.count = take_count;
  659. if(PLAYER_TO_SA(player)->item_OnDrop(item1, player,
  660. player->getBasePosition())) {
  661. int actually_dropped_count = take_count - item1.count;
  662. if (actually_dropped_count == 0) {
  663. infostream<<"Actually dropped no items"<<std::endl;
  664. // Revert client prediction. See 'clientApply'
  665. if (from_inv.type == InventoryLocation::PLAYER)
  666. list_from->setModified();
  667. return;
  668. }
  669. // If source isn't infinite
  670. if (src_can_take_count != -1) {
  671. // Take item from source list
  672. ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
  673. if (item2.count != actually_dropped_count)
  674. errorstream<<"Could not take dropped count of items"<<std::endl;
  675. }
  676. src_item.count = actually_dropped_count;
  677. mgr->setInventoryModified(from_inv);
  678. }
  679. infostream<<"IDropAction::apply(): dropped "
  680. <<" from inv=\""<<from_inv.dump()<<"\""
  681. <<" list=\""<<from_list<<"\""
  682. <<" i="<<from_i
  683. <<std::endl;
  684. /*
  685. Report drop to endpoints
  686. */
  687. list_from_lock.reset();
  688. switch (from_inv.type) {
  689. case InventoryLocation::DETACHED:
  690. PLAYER_TO_SA(player)->detached_inventory_OnTake(
  691. *this, src_item, player);
  692. break;
  693. case InventoryLocation::NODEMETA:
  694. PLAYER_TO_SA(player)->nodemeta_inventory_OnTake(
  695. *this, src_item, player);
  696. break;
  697. case InventoryLocation::PLAYER:
  698. PLAYER_TO_SA(player)->player_inventory_OnTake(
  699. *this, src_item, player);
  700. break;
  701. default:
  702. break;
  703. }
  704. /*
  705. Record rollback information
  706. */
  707. if (!ignore_src_rollback && gamedef->rollback()) {
  708. IRollbackManager *rollback = gamedef->rollback();
  709. // If source is not infinite, record item take
  710. if (src_can_take_count != -1) {
  711. RollbackAction action;
  712. std::string loc;
  713. {
  714. std::ostringstream os(std::ios::binary);
  715. from_inv.serialize(os);
  716. loc = os.str();
  717. }
  718. action.setModifyInventoryStack(loc, from_list, from_i,
  719. false, src_item);
  720. rollback->reportAction(action);
  721. }
  722. }
  723. }
  724. void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  725. {
  726. // Optional InventoryAction operation that is run on the client
  727. // to make lag less apparent.
  728. Inventory *inv_from = mgr->getInventory(from_inv);
  729. if (!inv_from)
  730. return;
  731. InventoryLocation current_player;
  732. current_player.setCurrentPlayer();
  733. Inventory *inv_player = mgr->getInventory(current_player);
  734. if (inv_from != inv_player)
  735. return;
  736. InventoryList *list_from = inv_from->getList(from_list);
  737. if (!list_from)
  738. return;
  739. if (count == 0)
  740. list_from->changeItem(from_i, ItemStack());
  741. else
  742. list_from->takeItem(from_i, count);
  743. mgr->setInventoryModified(from_inv);
  744. }
  745. /*
  746. ICraftAction
  747. */
  748. ICraftAction::ICraftAction(std::istream &is)
  749. {
  750. std::string ts;
  751. std::getline(is, ts, ' ');
  752. count = stoi(ts);
  753. std::getline(is, ts, ' ');
  754. craft_inv.deSerialize(ts);
  755. }
  756. void ICraftAction::apply(InventoryManager *mgr,
  757. ServerActiveObject *player, IGameDef *gamedef)
  758. {
  759. Inventory *inv_craft = mgr->getInventory(craft_inv);
  760. if (!inv_craft) {
  761. infostream << "ICraftAction::apply(): FAIL: inventory not found: "
  762. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  763. return;
  764. }
  765. InventoryList *list_craft = inv_craft->getList("craft");
  766. InventoryList *list_craftresult = inv_craft->getList("craftresult");
  767. InventoryList *list_main = inv_craft->getList("main");
  768. /*
  769. If a list doesn't exist or the source item doesn't exist
  770. */
  771. if (!list_craft) {
  772. infostream << "ICraftAction::apply(): FAIL: craft list not found: "
  773. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  774. return;
  775. }
  776. if (!list_craftresult) {
  777. infostream << "ICraftAction::apply(): FAIL: craftresult list not found: "
  778. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  779. return;
  780. }
  781. if (list_craftresult->getSize() < 1) {
  782. infostream << "ICraftAction::apply(): FAIL: craftresult list too short: "
  783. << "craft_inv=\"" << craft_inv.dump() << "\"" << std::endl;
  784. return;
  785. }
  786. auto list_craft_lock = list_craft->resizeLock();
  787. auto list_craftresult_lock = list_craftresult->resizeLock();
  788. auto list_main_lock = list_main->resizeLock();
  789. ItemStack crafted;
  790. ItemStack craftresultitem;
  791. int count_remaining = count;
  792. std::vector<ItemStack> output_replacements;
  793. getCraftingResult(inv_craft, crafted, output_replacements, false, gamedef);
  794. PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
  795. bool found = !crafted.empty();
  796. while (found && list_craftresult->itemFits(0, crafted)) {
  797. InventoryList saved_craft_list = *list_craft;
  798. std::vector<ItemStack> temp;
  799. // Decrement input and add crafting output
  800. getCraftingResult(inv_craft, crafted, temp, true, gamedef);
  801. PLAYER_TO_SA(player)->item_OnCraft(crafted, player, &saved_craft_list, craft_inv);
  802. list_craftresult->addItem(0, crafted);
  803. mgr->setInventoryModified(craft_inv);
  804. // Add the new replacements to the list
  805. IItemDefManager *itemdef = gamedef->getItemDefManager();
  806. for (auto &itemstack : temp) {
  807. for (auto &output_replacement : output_replacements) {
  808. if (itemstack.name == output_replacement.name) {
  809. itemstack = output_replacement.addItem(itemstack, itemdef);
  810. if (itemstack.empty())
  811. continue;
  812. }
  813. }
  814. output_replacements.push_back(itemstack);
  815. }
  816. actionstream << player->getDescription()
  817. << " crafts "
  818. << crafted.getItemString()
  819. << std::endl;
  820. // Decrement counter
  821. if (count_remaining == 1)
  822. break;
  823. if (count_remaining > 1)
  824. count_remaining--;
  825. // Get next crafting result
  826. getCraftingResult(inv_craft, crafted, temp, false, gamedef);
  827. PLAYER_TO_SA(player)->item_CraftPredict(crafted, player, list_craft, craft_inv);
  828. found = !crafted.empty();
  829. }
  830. // Put the replacements in the inventory or drop them on the floor, if
  831. // the inventory is full
  832. for (auto &output_replacement : output_replacements) {
  833. if (list_main)
  834. output_replacement = list_main->addItem(output_replacement);
  835. if (output_replacement.empty())
  836. continue;
  837. u16 count = output_replacement.count;
  838. do {
  839. PLAYER_TO_SA(player)->item_OnDrop(output_replacement, player,
  840. player->getBasePosition());
  841. if (count <= output_replacement.count) {
  842. errorstream << "Couldn't drop replacement stack " <<
  843. output_replacement.getItemString() << " because drop loop didn't "
  844. "decrease count." << std::endl;
  845. break;
  846. }
  847. } while (!output_replacement.empty());
  848. }
  849. infostream<<"ICraftAction::apply(): crafted "
  850. <<" craft_inv=\""<<craft_inv.dump()<<"\""
  851. <<std::endl;
  852. }
  853. void ICraftAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
  854. {
  855. // Optional InventoryAction operation that is run on the client
  856. // to make lag less apparent.
  857. }
  858. // Crafting helper
  859. bool getCraftingResult(Inventory *inv, ItemStack &result,
  860. std::vector<ItemStack> &output_replacements,
  861. bool decrementInput, IGameDef *gamedef)
  862. {
  863. result.clear();
  864. // Get the InventoryList in which we will operate
  865. InventoryList *clist = inv->getList("craft");
  866. if (!clist)
  867. return false;
  868. // Mangle crafting grid to another format
  869. CraftInput ci;
  870. ci.method = CRAFT_METHOD_NORMAL;
  871. ci.width = clist->getWidth() ? clist->getWidth() : 3;
  872. for (u16 i=0; i < clist->getSize(); i++)
  873. ci.items.push_back(clist->getItem(i));
  874. // Find out what is crafted and add it to result item slot
  875. CraftOutput co;
  876. bool found = gamedef->getCraftDefManager()->getCraftResult(
  877. ci, co, output_replacements, decrementInput, gamedef);
  878. if (found)
  879. result.deSerialize(co.item, gamedef->getItemDefManager());
  880. if (found && decrementInput) {
  881. // CraftInput has been changed, apply changes in clist
  882. for (u16 i=0; i < clist->getSize(); i++) {
  883. clist->changeItem(i, ci.items[i]);
  884. }
  885. }
  886. return found;
  887. }