1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU 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 "otpch.h"
21
22#include "container.h"
23#include "iomap.h"
24#include "game.h"
25
26extern Game g_game;
27
28Container::Container(uint16_t type) :
29 Container(type, items[type].maxItems) {}
30
31Container::Container(uint16_t type, uint16_t size, bool unlocked /*= true*/, bool pagination /*= false*/) :
32 Item(type),
33 maxSize(size),
34 unlocked(unlocked),
35 pagination(pagination)
36{}
37
38Container::Container(Tile* tile) : Container(ITEM_BROWSEFIELD, 30, false, true)
39{
40 TileItemVector* itemVector = tile->getItemList();
41 if (itemVector) {
42 for (Item* item : *itemVector) {
43 if ((item->getContainer() || item->hasProperty(CONST_PROP_MOVEABLE)) && !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
44 itemlist.push_front(item);
45 item->setParent(this);
46 }
47 }
48 }
49
50 setParent(tile);
51}
52
53Container::~Container()
54{
55 if (getID() == ITEM_BROWSEFIELD) {
56 g_game.browseFields.erase(getTile());
57
58 for (Item* item : itemlist) {
59 item->setParent(parent);
60 }
61 } else {
62 for (Item* item : itemlist) {
63 item->setParent(nullptr);
64 item->decrementReferenceCounter();
65 }
66 }
67}
68
69Item* Container::clone() const
70{
71 Container* clone = static_cast<Container*>(Item::clone());
72 for (Item* item : itemlist) {
73 clone->addItem(item->clone());
74 }
75 clone->totalWeight = totalWeight;
76 return clone;
77}
78
79Container* Container::getParentContainer()
80{
81 Thing* thing = getParent();
82 if (!thing) {
83 return nullptr;
84 }
85 return thing->getContainer();
86}
87
88std::string Container::getName(bool addArticle /* = false*/) const {
89 const ItemType& it = items[id];
90 return getNameDescription(it, this, -1, addArticle);
91}
92
93bool Container::hasParent() const
94{
95 return getID() != ITEM_BROWSEFIELD && dynamic_cast<const Player*>(getParent()) == nullptr;
96}
97
98void Container::addItem(Item* item)
99{
100 itemlist.push_back(item);
101 item->setParent(this);
102}
103
104Attr_ReadValue Container::readAttr(AttrTypes_t attr, PropStream& propStream)
105{
106 if (attr == ATTR_CONTAINER_ITEMS) {
107 if (!propStream.read<uint32_t>(serializationCount)) {
108 return ATTR_READ_ERROR;
109 }
110 return ATTR_READ_END;
111 }
112 return Item::readAttr(attr, propStream);
113}
114
115bool Container::unserializeItemNode(OTB::Loader& loader, const OTB::Node& node, PropStream& propStream)
116{
117 bool ret = Item::unserializeItemNode(loader, node, propStream);
118 if (!ret) {
119 return false;
120 }
121
122 for (auto& itemNode : node.children) {
123 //load container items
124 if (itemNode.type != OTBM_ITEM) {
125 // unknown type
126 return false;
127 }
128
129 PropStream itemPropStream;
130 if (!loader.getProps(itemNode, itemPropStream)) {
131 return false;
132 }
133
134 Item* item = Item::CreateItem(itemPropStream);
135 if (!item) {
136 return false;
137 }
138
139 if (!item->unserializeItemNode(loader, itemNode, itemPropStream)) {
140 return false;
141 }
142
143 addItem(item);
144 updateItemWeight(item->getWeight());
145 }
146 return true;
147}
148
149void Container::updateItemWeight(int32_t diff)
150{
151 totalWeight += diff;
152 if (Container* parentContainer = getParentContainer()) {
153 parentContainer->updateItemWeight(diff);
154 }
155}
156
157uint32_t Container::getWeight() const
158{
159 return Item::getWeight() + totalWeight;
160}
161
162std::string Container::getContentDescription() const
163{
164 std::ostringstream os;
165 return getContentDescription(os).str();
166}
167
168std::ostringstream& Container::getContentDescription(std::ostringstream& os) const
169{
170 bool firstitem = true;
171 for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
172 Item* item = *it;
173
174 Container* container = item->getContainer();
175 if (container && !container->empty()) {
176 continue;
177 }
178
179 if (firstitem) {
180 firstitem = false;
181 } else {
182 os << ", ";
183 }
184
185 os << item->getNameDescription();
186 }
187
188 if (firstitem) {
189 os << "nothing";
190 }
191 return os;
192}
193
194Item* Container::getItemByIndex(size_t index) const
195{
196 if (index >= size()) {
197 return nullptr;
198 }
199 return itemlist[index];
200}
201
202uint32_t Container::getItemHoldingCount() const
203{
204 uint32_t counter = 0;
205 for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
206 ++counter;
207 }
208 return counter;
209}
210
211bool Container::isHoldingItem(const Item* item) const
212{
213 for (ContainerIterator it = iterator(); it.hasNext(); it.advance()) {
214 if (*it == item) {
215 return true;
216 }
217 }
218 return false;
219}
220
221void Container::onAddContainerItem(Item* item)
222{
223 SpectatorVec spectators;
224 g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2);
225
226 //send to client
227 for (Creature* spectator : spectators) {
228 spectator->getPlayer()->sendAddContainerItem(this, item);
229 }
230
231 //event methods
232 for (Creature* spectator : spectators) {
233 spectator->getPlayer()->onAddContainerItem(item);
234 }
235}
236
237void Container::onUpdateContainerItem(uint32_t index, Item* oldItem, Item* newItem)
238{
239 SpectatorVec spectators;
240 g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2);
241
242 //send to client
243 for (Creature* spectator : spectators) {
244 spectator->getPlayer()->sendUpdateContainerItem(this, index, newItem);
245 }
246
247 //event methods
248 for (Creature* spectator : spectators) {
249 spectator->getPlayer()->onUpdateContainerItem(this, oldItem, newItem);
250 }
251}
252
253void Container::onRemoveContainerItem(uint32_t index, Item* item)
254{
255 SpectatorVec spectators;
256 g_game.map.getSpectators(spectators, getPosition(), false, true, 2, 2, 2, 2);
257
258 //send change to client
259 for (Creature* spectator : spectators) {
260 spectator->getPlayer()->sendRemoveContainerItem(this, index);
261 }
262
263 //event methods
264 for (Creature* spectator : spectators) {
265 spectator->getPlayer()->onRemoveContainerItem(this, item);
266 }
267}
268
269ReturnValue Container::queryAdd(int32_t index, const Thing& thing, uint32_t count,
270 uint32_t flags, Creature* actor/* = nullptr*/) const
271{
272 bool childIsOwner = hasBitSet(FLAG_CHILDISOWNER, flags);
273 if (childIsOwner) {
274 //a child container is querying, since we are the top container (not carried by a player)
275 //just return with no error.
276 return RETURNVALUE_NOERROR;
277 }
278
279 if (!unlocked) {
280 return RETURNVALUE_NOTPOSSIBLE;
281 }
282
283 const Item* item = thing.getItem();
284 if (item == nullptr) {
285 return RETURNVALUE_NOTPOSSIBLE;
286 }
287
288 if (!item->isPickupable()) {
289 return RETURNVALUE_CANNOTPICKUP;
290 }
291
292 if (item == this) {
293 return RETURNVALUE_THISISIMPOSSIBLE;
294 }
295
296 const Cylinder* cylinder = getParent();
297 if (!hasBitSet(FLAG_NOLIMIT, flags)) {
298 while (cylinder) {
299 if (cylinder == &thing) {
300 return RETURNVALUE_THISISIMPOSSIBLE;
301 }
302
303 if (dynamic_cast<const Inbox*>(cylinder)) {
304 return RETURNVALUE_CONTAINERNOTENOUGHROOM;
305 }
306
307 cylinder = cylinder->getParent();
308 }
309
310 if (index == INDEX_WHEREEVER && size() >= capacity()) {
311 return RETURNVALUE_CONTAINERNOTENOUGHROOM;
312 }
313 } else {
314 while (cylinder) {
315 if (cylinder == &thing) {
316 return RETURNVALUE_THISISIMPOSSIBLE;
317 }
318
319 cylinder = cylinder->getParent();
320 }
321 }
322
323 const Cylinder* topParent = getTopParent();
324 if (topParent != this) {
325 return topParent->queryAdd(INDEX_WHEREEVER, *item, count, flags | FLAG_CHILDISOWNER, actor);
326 } else {
327 return RETURNVALUE_NOERROR;
328 }
329}
330
331ReturnValue Container::queryMaxCount(int32_t index, const Thing& thing, uint32_t count,
332 uint32_t& maxQueryCount, uint32_t flags) const
333{
334 const Item* item = thing.getItem();
335 if (item == nullptr) {
336 maxQueryCount = 0;
337 return RETURNVALUE_NOTPOSSIBLE;
338 }
339
340 if (hasBitSet(FLAG_NOLIMIT, flags)) {
341 maxQueryCount = std::max<uint32_t>(1, count);
342 return RETURNVALUE_NOERROR;
343 }
344
345 int32_t freeSlots = std::max<int32_t>(capacity() - size(), 0);
346
347 if (item->isStackable()) {
348 uint32_t n = 0;
349
350 if (index == INDEX_WHEREEVER) {
351 //Iterate through every item and check how much free stackable slots there is.
352 uint32_t slotIndex = 0;
353 for (Item* containerItem : itemlist) {
354 if (containerItem != item && containerItem->equals(item) && containerItem->getItemCount() < 100) {
355 if (queryAdd(slotIndex++, *item, count, flags) == RETURNVALUE_NOERROR) {
356 n += 100 - containerItem->getItemCount();
357 }
358 }
359 }
360 } else {
361 const Item* destItem = getItemByIndex(index);
362 if (item->equals(destItem) && destItem->getItemCount() < 100) {
363 if (queryAdd(index, *item, count, flags) == RETURNVALUE_NOERROR) {
364 n = 100 - destItem->getItemCount();
365 }
366 }
367 }
368
369 maxQueryCount = freeSlots * 100 + n;
370 if (maxQueryCount < count) {
371 return RETURNVALUE_CONTAINERNOTENOUGHROOM;
372 }
373 } else {
374 maxQueryCount = freeSlots;
375 if (maxQueryCount == 0) {
376 return RETURNVALUE_CONTAINERNOTENOUGHROOM;
377 }
378 }
379 return RETURNVALUE_NOERROR;
380}
381
382ReturnValue Container::queryRemove(const Thing& thing, uint32_t count, uint32_t flags) const
383{
384 int32_t index = getThingIndex(&thing);
385 if (index == -1) {
386 return RETURNVALUE_NOTPOSSIBLE;
387 }
388
389 const Item* item = thing.getItem();
390 if (item == nullptr) {
391 return RETURNVALUE_NOTPOSSIBLE;
392 }
393
394 if (count == 0 || (item->isStackable() && count > item->getItemCount())) {
395 return RETURNVALUE_NOTPOSSIBLE;
396 }
397
398 if (!item->isMoveable() && !hasBitSet(FLAG_IGNORENOTMOVEABLE, flags)) {
399 return RETURNVALUE_NOTMOVEABLE;
400 }
401 return RETURNVALUE_NOERROR;
402}
403
404Cylinder* Container::queryDestination(int32_t& index, const Thing& thing, Item** destItem,
405 uint32_t& flags)
406{
407 if (!unlocked) {
408 *destItem = nullptr;
409 return this;
410 }
411
412 if (index == 254 /*move up*/) {
413 index = INDEX_WHEREEVER;
414 *destItem = nullptr;
415
416 Container* parentContainer = dynamic_cast<Container*>(getParent());
417 if (parentContainer) {
418 return parentContainer;
419 }
420 return this;
421 }
422
423 if (index == 255 /*add wherever*/) {
424 index = INDEX_WHEREEVER;
425 *destItem = nullptr;
426 } else if (index >= static_cast<int32_t>(capacity())) {
427 /*
428 if you have a container, maximize it to show all 20 slots
429 then you open a bag that is inside the container you will have a bag with 8 slots
430 and a "grey" area where the other 12 slots where from the container
431 if you drop the item on that grey area
432 the client calculates the slot position as if the bag has 20 slots
433 */
434 index = INDEX_WHEREEVER;
435 *destItem = nullptr;
436 }
437
438 const Item* item = thing.getItem();
439 if (!item) {
440 return this;
441 }
442
443 if (index != INDEX_WHEREEVER) {
444 Item* itemFromIndex = getItemByIndex(index);
445 if (itemFromIndex) {
446 *destItem = itemFromIndex;
447 }
448
449 Cylinder* subCylinder = dynamic_cast<Cylinder*>(*destItem);
450 if (subCylinder) {
451 index = INDEX_WHEREEVER;
452 *destItem = nullptr;
453 return subCylinder;
454 }
455 }
456
457 bool autoStack = !hasBitSet(FLAG_IGNOREAUTOSTACK, flags);
458 if (autoStack && item->isStackable() && item->getParent() != this) {
459 if (*destItem && (*destItem)->equals(item) && (*destItem)->getItemCount() < 100) {
460 return this;
461 }
462
463 //try find a suitable item to stack with
464 uint32_t n = 0;
465 for (Item* listItem : itemlist) {
466 if (listItem != item && listItem->equals(item) && listItem->getItemCount() < 100) {
467 *destItem = listItem;
468 index = n;
469 return this;
470 }
471 ++n;
472 }
473 }
474 return this;
475}
476
477void Container::addThing(Thing* thing)
478{
479 return addThing(0, thing);
480}
481
482void Container::addThing(int32_t index, Thing* thing)
483{
484 if (index >= static_cast<int32_t>(capacity())) {
485 return /*RETURNVALUE_NOTPOSSIBLE*/;
486 }
487
488 Item* item = thing->getItem();
489 if (item == nullptr) {
490 return /*RETURNVALUE_NOTPOSSIBLE*/;
491 }
492
493 item->setParent(this);
494 itemlist.push_front(item);
495 updateItemWeight(item->getWeight());
496
497 //send change to client
498 if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
499 onAddContainerItem(item);
500 }
501}
502
503void Container::addItemBack(Item* item)
504{
505 addItem(item);
506 updateItemWeight(item->getWeight());
507
508 //send change to client
509 if (getParent() && (getParent() != VirtualCylinder::virtualCylinder)) {
510 onAddContainerItem(item);
511 }
512}
513
514void Container::updateThing(Thing* thing, uint16_t itemId, uint32_t count)
515{
516 int32_t index = getThingIndex(thing);
517 if (index == -1) {
518 return /*RETURNVALUE_NOTPOSSIBLE*/;
519 }
520
521 Item* item = thing->getItem();
522 if (item == nullptr) {
523 return /*RETURNVALUE_NOTPOSSIBLE*/;
524 }
525
526 const int32_t oldWeight = item->getWeight();
527 item->setID(itemId);
528 item->setSubType(count);
529 updateItemWeight(-oldWeight + item->getWeight());
530
531 //send change to client
532 if (getParent()) {
533 onUpdateContainerItem(index, item, item);
534 }
535}
536
537void Container::replaceThing(uint32_t index, Thing* thing)
538{
539 Item* item = thing->getItem();
540 if (!item) {
541 return /*RETURNVALUE_NOTPOSSIBLE*/;
542 }
543
544 Item* replacedItem = getItemByIndex(index);
545 if (!replacedItem) {
546 return /*RETURNVALUE_NOTPOSSIBLE*/;
547 }
548
549 itemlist[index] = item;
550 item->setParent(this);
551 updateItemWeight(-static_cast<int32_t>(replacedItem->getWeight()) + item->getWeight());
552
553 //send change to client
554 if (getParent()) {
555 onUpdateContainerItem(index, replacedItem, item);
556 }
557
558 replacedItem->setParent(nullptr);
559}
560
561void Container::removeThing(Thing* thing, uint32_t count)
562{
563 Item* item = thing->getItem();
564 if (item == nullptr) {
565 return /*RETURNVALUE_NOTPOSSIBLE*/;
566 }
567
568 int32_t index = getThingIndex(thing);
569 if (index == -1) {
570 return /*RETURNVALUE_NOTPOSSIBLE*/;
571 }
572
573 if (item->isStackable() && count != item->getItemCount()) {
574 uint8_t newCount = static_cast<uint8_t>(std::max<int32_t>(0, item->getItemCount() - count));
575 const int32_t oldWeight = item->getWeight();
576 item->setItemCount(newCount);
577 updateItemWeight(-oldWeight + item->getWeight());
578
579 //send change to client
580 if (getParent()) {
581 onUpdateContainerItem(index, item, item);
582 }
583 } else {
584 updateItemWeight(-static_cast<int32_t>(item->getWeight()));
585
586 //send change to client
587 if (getParent()) {
588 onRemoveContainerItem(index, item);
589 }
590
591 item->setParent(nullptr);
592 itemlist.erase(itemlist.begin() + index);
593 }
594}
595
596int32_t Container::getThingIndex(const Thing* thing) const
597{
598 int32_t index = 0;
599 for (Item* item : itemlist) {
600 if (item == thing) {
601 return index;
602 }
603 ++index;
604 }
605 return -1;
606}
607
608size_t Container::getFirstIndex() const
609{
610 return 0;
611}
612
613size_t Container::getLastIndex() const
614{
615 return size();
616}
617
618uint32_t Container::getItemTypeCount(uint16_t itemId, int32_t subType/* = -1*/) const
619{
620 uint32_t count = 0;
621 for (Item* item : itemlist) {
622 if (item->getID() == itemId) {
623 count += countByType(item, subType);
624 }
625 }
626 return count;
627}
628
629std::map<uint32_t, uint32_t>& Container::getAllItemTypeCount(std::map<uint32_t, uint32_t>& countMap) const
630{
631 for (Item* item : itemlist) {
632 countMap[item->getID()] += item->getItemCount();
633 }
634 return countMap;
635}
636
637Thing* Container::getThing(size_t index) const
638{
639 return getItemByIndex(index);
640}
641
642void Container::postAddNotification(Thing* thing, const Cylinder* oldParent, int32_t index, cylinderlink_t)
643{
644 Cylinder* topParent = getTopParent();
645 if (topParent->getCreature()) {
646 topParent->postAddNotification(thing, oldParent, index, LINK_TOPPARENT);
647 } else if (topParent == this) {
648 //let the tile class notify surrounding players
649 if (topParent->getParent()) {
650 topParent->getParent()->postAddNotification(thing, oldParent, index, LINK_NEAR);
651 }
652 } else {
653 topParent->postAddNotification(thing, oldParent, index, LINK_PARENT);
654 }
655}
656
657void Container::postRemoveNotification(Thing* thing, const Cylinder* newParent, int32_t index, cylinderlink_t)
658{
659 Cylinder* topParent = getTopParent();
660 if (topParent->getCreature()) {
661 topParent->postRemoveNotification(thing, newParent, index, LINK_TOPPARENT);
662 } else if (topParent == this) {
663 //let the tile class notify surrounding players
664 if (topParent->getParent()) {
665 topParent->getParent()->postRemoveNotification(thing, newParent, index, LINK_NEAR);
666 }
667 } else {
668 topParent->postRemoveNotification(thing, newParent, index, LINK_PARENT);
669 }
670}
671
672void Container::internalAddThing(Thing* thing)
673{
674 internalAddThing(0, thing);
675}
676
677void Container::internalAddThing(uint32_t, Thing* thing)
678{
679 Item* item = thing->getItem();
680 if (item == nullptr) {
681 return;
682 }
683
684 item->setParent(this);
685 itemlist.push_front(item);
686 updateItemWeight(item->getWeight());
687}
688
689void Container::startDecaying()
690{
691 for (Item* item : itemlist) {
692 item->startDecaying();
693 }
694}
695
696ContainerIterator Container::iterator() const
697{
698 ContainerIterator cit;
699 if (!itemlist.empty()) {
700 cit.over.push_back(this);
701 cit.cur = itemlist.begin();
702 }
703 return cit;
704}
705
706Item* ContainerIterator::operator*()
707{
708 return *cur;
709}
710
711void ContainerIterator::advance()
712{
713 if (Item* i = *cur) {
714 if (Container* c = i->getContainer()) {
715 if (!c->empty()) {
716 over.push_back(c);
717 }
718 }
719 }
720
721 ++cur;
722
723 if (cur == over.front()->itemlist.end()) {
724 over.pop_front();
725 if (!over.empty()) {
726 cur = over.front()->itemlist.begin();
727 }
728 }
729}
730