inventorymanager.cpp 28 KB

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