inventorymanager.cpp 28 KB

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