inventorymanager.cpp 25 KB

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