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