inventorymanager.cpp 29 KB

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