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 "item.h"
23#include "container.h"
24#include "teleport.h"
25#include "trashholder.h"
26#include "mailbox.h"
27#include "house.h"
28#include "game.h"
29#include "bed.h"
30
31#include "actions.h"
32#include "spells.h"
33
34extern Game g_game;
35extern Spells* g_spells;
36extern Vocations g_vocations;
37
38Items Item::items;
39
40Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/)
41{
42 Item* newItem = nullptr;
43
44 const ItemType& it = Item::items[type];
45 if (it.group == ITEM_GROUP_DEPRECATED) {
46 return nullptr;
47 }
48
49 if (it.stackable && count == 0) {
50 count = 1;
51 }
52
53 if (it.id != 0) {
54 if (it.isDepot()) {
55 newItem = new DepotLocker(type);
56 } else if (it.isContainer()) {
57 newItem = new Container(type);
58 } else if (it.isTeleport()) {
59 newItem = new Teleport(type);
60 } else if (it.isMagicField()) {
61 newItem = new MagicField(type);
62 } else if (it.isDoor()) {
63 newItem = new Door(type);
64 } else if (it.isTrashHolder()) {
65 newItem = new TrashHolder(type);
66 } else if (it.isMailbox()) {
67 newItem = new Mailbox(type);
68 } else if (it.isBed()) {
69 newItem = new BedItem(type);
70 } else if (it.id >= 2210 && it.id <= 2212) {
71 newItem = new Item(type - 3, count);
72 } else if (it.id == 2215 || it.id == 2216) {
73 newItem = new Item(type - 2, count);
74 } else if (it.id >= 2202 && it.id <= 2206) {
75 newItem = new Item(type - 37, count);
76 } else if (it.id == 2640) {
77 newItem = new Item(6132, count);
78 } else if (it.id == 6301) {
79 newItem = new Item(6300, count);
80 } else if (it.id == 18528) {
81 newItem = new Item(18408, count);
82 } else {
83 newItem = new Item(type, count);
84 }
85
86 newItem->incrementReferenceCounter();
87 }
88
89 return newItem;
90}
91
92Container* Item::CreateItemAsContainer(const uint16_t type, uint16_t size)
93{
94 const ItemType& it = Item::items[type];
95 if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || it.isDepot() || it.isSplash() || it.isDoor()) {
96 return nullptr;
97 }
98
99 Container* newItem = new Container(type, size);
100 newItem->incrementReferenceCounter();
101 return newItem;
102}
103
104Item* Item::CreateItem(PropStream& propStream)
105{
106 uint16_t id;
107 if (!propStream.read<uint16_t>(id)) {
108 return nullptr;
109 }
110
111 switch (id) {
112 case ITEM_FIREFIELD_PVP_FULL:
113 id = ITEM_FIREFIELD_PERSISTENT_FULL;
114 break;
115
116 case ITEM_FIREFIELD_PVP_MEDIUM:
117 id = ITEM_FIREFIELD_PERSISTENT_MEDIUM;
118 break;
119
120 case ITEM_FIREFIELD_PVP_SMALL:
121 id = ITEM_FIREFIELD_PERSISTENT_SMALL;
122 break;
123
124 case ITEM_ENERGYFIELD_PVP:
125 id = ITEM_ENERGYFIELD_PERSISTENT;
126 break;
127
128 case ITEM_POISONFIELD_PVP:
129 id = ITEM_POISONFIELD_PERSISTENT;
130 break;
131
132 case ITEM_MAGICWALL:
133 id = ITEM_MAGICWALL_PERSISTENT;
134 break;
135
136 case ITEM_WILDGROWTH:
137 id = ITEM_WILDGROWTH_PERSISTENT;
138 break;
139
140 default:
141 break;
142 }
143
144 return Item::CreateItem(id, 0);
145}
146
147Item::Item(const uint16_t type, uint16_t count /*= 0*/) :
148 id(type)
149{
150 const ItemType& it = items[id];
151
152 if (it.isFluidContainer() || it.isSplash()) {
153 setFluidType(count);
154 } else if (it.stackable) {
155 if (count != 0) {
156 setItemCount(count);
157 } else if (it.charges != 0) {
158 setItemCount(it.charges);
159 }
160 } else if (it.charges != 0) {
161 if (count != 0) {
162 setCharges(count);
163 } else {
164 setCharges(it.charges);
165 }
166 }
167
168 setDefaultDuration();
169}
170
171Item::Item(const Item& i) :
172 Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap)
173{
174 if (i.attributes) {
175 attributes.reset(new ItemAttributes(*i.attributes));
176 }
177}
178
179Item* Item::clone() const
180{
181 Item* item = Item::CreateItem(id, count);
182 if (attributes) {
183 item->attributes.reset(new ItemAttributes(*attributes));
184 if (item->getDuration() > 0) {
185 item->incrementReferenceCounter();
186 item->setDecaying(DECAYING_TRUE);
187 g_game.toDecayItems.push_front(item);
188 }
189 }
190 return item;
191}
192
193bool Item::equals(const Item* otherItem) const
194{
195 if (!otherItem || id != otherItem->id) {
196 return false;
197 }
198
199 const auto& otherAttributes = otherItem->attributes;
200 if (!attributes) {
201 return !otherAttributes || (otherAttributes->attributeBits == 0);
202 } else if (!otherAttributes) {
203 return (attributes->attributeBits == 0);
204 }
205
206 if (attributes->attributeBits != otherAttributes->attributeBits) {
207 return false;
208 }
209
210 const auto& attributeList = attributes->attributes;
211 const auto& otherAttributeList = otherAttributes->attributes;
212 for (const auto& attribute : attributeList) {
213 if (ItemAttributes::isStrAttrType(attribute.type)) {
214 for (const auto& otherAttribute : otherAttributeList) {
215 if (attribute.type == otherAttribute.type && *attribute.value.string != *otherAttribute.value.string) {
216 return false;
217 }
218 }
219 } else {
220 for (const auto& otherAttribute : otherAttributeList) {
221 if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) {
222 return false;
223 }
224 }
225 }
226 }
227 return true;
228}
229
230void Item::setDefaultSubtype()
231{
232 const ItemType& it = items[id];
233
234 setItemCount(1);
235
236 if (it.charges != 0) {
237 if (it.stackable) {
238 setItemCount(it.charges);
239 } else {
240 setCharges(it.charges);
241 }
242 }
243}
244
245void Item::onRemoved()
246{
247 ScriptEnvironment::removeTempItem(this);
248
249 if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
250 g_game.removeUniqueItem(getUniqueId());
251 }
252}
253
254void Item::setID(uint16_t newid)
255{
256 const ItemType& prevIt = Item::items[id];
257 id = newid;
258
259 const ItemType& it = Item::items[newid];
260 uint32_t newDuration = it.decayTime * 1000;
261
262 if (newDuration == 0 && !it.stopTime && it.decayTo < 0) {
263 removeAttribute(ITEM_ATTRIBUTE_DECAYSTATE);
264 removeAttribute(ITEM_ATTRIBUTE_DURATION);
265 }
266
267 removeAttribute(ITEM_ATTRIBUTE_CORPSEOWNER);
268
269 if (newDuration > 0 && (!prevIt.stopTime || !hasAttribute(ITEM_ATTRIBUTE_DURATION))) {
270 setDecaying(DECAYING_FALSE);
271 setDuration(newDuration);
272 }
273}
274
275Cylinder* Item::getTopParent()
276{
277 Cylinder* aux = getParent();
278 Cylinder* prevaux = dynamic_cast<Cylinder*>(this);
279 if (!aux) {
280 return prevaux;
281 }
282
283 while (aux->getParent() != nullptr) {
284 prevaux = aux;
285 aux = aux->getParent();
286 }
287
288 if (prevaux) {
289 return prevaux;
290 }
291 return aux;
292}
293
294const Cylinder* Item::getTopParent() const
295{
296 const Cylinder* aux = getParent();
297 const Cylinder* prevaux = dynamic_cast<const Cylinder*>(this);
298 if (!aux) {
299 return prevaux;
300 }
301
302 while (aux->getParent() != nullptr) {
303 prevaux = aux;
304 aux = aux->getParent();
305 }
306
307 if (prevaux) {
308 return prevaux;
309 }
310 return aux;
311}
312
313Tile* Item::getTile()
314{
315 Cylinder* cylinder = getTopParent();
316 //get root cylinder
317 if (cylinder && cylinder->getParent()) {
318 cylinder = cylinder->getParent();
319 }
320 return dynamic_cast<Tile*>(cylinder);
321}
322
323const Tile* Item::getTile() const
324{
325 const Cylinder* cylinder = getTopParent();
326 //get root cylinder
327 if (cylinder && cylinder->getParent()) {
328 cylinder = cylinder->getParent();
329 }
330 return dynamic_cast<const Tile*>(cylinder);
331}
332
333uint16_t Item::getSubType() const
334{
335 const ItemType& it = items[id];
336 if (it.isFluidContainer() || it.isSplash()) {
337 return getFluidType();
338 } else if (it.stackable) {
339 return count;
340 } else if (it.charges != 0) {
341 return getCharges();
342 }
343 return count;
344}
345
346Player* Item::getHoldingPlayer() const
347{
348 Cylinder* p = getParent();
349 while (p) {
350 if (p->getCreature()) {
351 return p->getCreature()->getPlayer();
352 }
353
354 p = p->getParent();
355 }
356 return nullptr;
357}
358
359void Item::setSubType(uint16_t n)
360{
361 const ItemType& it = items[id];
362 if (it.isFluidContainer() || it.isSplash()) {
363 setFluidType(n);
364 } else if (it.stackable) {
365 setItemCount(n);
366 } else if (it.charges != 0) {
367 setCharges(n);
368 } else {
369 setItemCount(n);
370 }
371}
372
373Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream)
374{
375 switch (attr) {
376 case ATTR_COUNT:
377 case ATTR_RUNE_CHARGES: {
378 uint8_t count;
379 if (!propStream.read<uint8_t>(count)) {
380 return ATTR_READ_ERROR;
381 }
382
383 setSubType(count);
384 break;
385 }
386
387 case ATTR_ACTION_ID: {
388 uint16_t actionId;
389 if (!propStream.read<uint16_t>(actionId)) {
390 return ATTR_READ_ERROR;
391 }
392
393 setActionId(actionId);
394 break;
395 }
396
397 case ATTR_UNIQUE_ID: {
398 uint16_t uniqueId;
399 if (!propStream.read<uint16_t>(uniqueId)) {
400 return ATTR_READ_ERROR;
401 }
402
403 setUniqueId(uniqueId);
404 break;
405 }
406
407 case ATTR_TEXT: {
408 std::string text;
409 if (!propStream.readString(text)) {
410 return ATTR_READ_ERROR;
411 }
412
413 setText(text);
414 break;
415 }
416
417 case ATTR_WRITTENDATE: {
418 uint32_t writtenDate;
419 if (!propStream.read<uint32_t>(writtenDate)) {
420 return ATTR_READ_ERROR;
421 }
422
423 setDate(writtenDate);
424 break;
425 }
426
427 case ATTR_WRITTENBY: {
428 std::string writer;
429 if (!propStream.readString(writer)) {
430 return ATTR_READ_ERROR;
431 }
432
433 setWriter(writer);
434 break;
435 }
436
437 case ATTR_DESC: {
438 std::string text;
439 if (!propStream.readString(text)) {
440 return ATTR_READ_ERROR;
441 }
442
443 setSpecialDescription(text);
444 break;
445 }
446
447 case ATTR_CHARGES: {
448 uint16_t charges;
449 if (!propStream.read<uint16_t>(charges)) {
450 return ATTR_READ_ERROR;
451 }
452
453 setSubType(charges);
454 break;
455 }
456
457 case ATTR_DURATION: {
458 int32_t duration;
459 if (!propStream.read<int32_t>(duration)) {
460 return ATTR_READ_ERROR;
461 }
462
463 setDuration(std::max<int32_t>(0, duration));
464 break;
465 }
466
467 case ATTR_DECAYING_STATE: {
468 uint8_t state;
469 if (!propStream.read<uint8_t>(state)) {
470 return ATTR_READ_ERROR;
471 }
472
473 if (state != DECAYING_FALSE) {
474 setDecaying(DECAYING_PENDING);
475 }
476 break;
477 }
478
479 case ATTR_NAME: {
480 std::string name;
481 if (!propStream.readString(name)) {
482 return ATTR_READ_ERROR;
483 }
484
485 setStrAttr(ITEM_ATTRIBUTE_NAME, name);
486 break;
487 }
488
489 case ATTR_ARTICLE: {
490 std::string article;
491 if (!propStream.readString(article)) {
492 return ATTR_READ_ERROR;
493 }
494
495 setStrAttr(ITEM_ATTRIBUTE_ARTICLE, article);
496 break;
497 }
498
499 case ATTR_PLURALNAME: {
500 std::string pluralName;
501 if (!propStream.readString(pluralName)) {
502 return ATTR_READ_ERROR;
503 }
504
505 setStrAttr(ITEM_ATTRIBUTE_PLURALNAME, pluralName);
506 break;
507 }
508
509 case ATTR_WEIGHT: {
510 uint32_t weight;
511 if (!propStream.read<uint32_t>(weight)) {
512 return ATTR_READ_ERROR;
513 }
514
515 setIntAttr(ITEM_ATTRIBUTE_WEIGHT, weight);
516 break;
517 }
518
519 case ATTR_ATTACK: {
520 int32_t attack;
521 if (!propStream.read<int32_t>(attack)) {
522 return ATTR_READ_ERROR;
523 }
524
525 setIntAttr(ITEM_ATTRIBUTE_ATTACK, attack);
526 break;
527 }
528
529 case ATTR_DEFENSE: {
530 int32_t defense;
531 if (!propStream.read<int32_t>(defense)) {
532 return ATTR_READ_ERROR;
533 }
534
535 setIntAttr(ITEM_ATTRIBUTE_DEFENSE, defense);
536 break;
537 }
538
539 case ATTR_EXTRADEFENSE: {
540 int32_t extraDefense;
541 if (!propStream.read<int32_t>(extraDefense)) {
542 return ATTR_READ_ERROR;
543 }
544
545 setIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE, extraDefense);
546 break;
547 }
548
549 case ATTR_ARMOR: {
550 int32_t armor;
551 if (!propStream.read<int32_t>(armor)) {
552 return ATTR_READ_ERROR;
553 }
554
555 setIntAttr(ITEM_ATTRIBUTE_ARMOR, armor);
556 break;
557 }
558
559 case ATTR_HITCHANCE: {
560 int8_t hitChance;
561 if (!propStream.read<int8_t>(hitChance)) {
562 return ATTR_READ_ERROR;
563 }
564
565 setIntAttr(ITEM_ATTRIBUTE_HITCHANCE, hitChance);
566 break;
567 }
568
569 case ATTR_SHOOTRANGE: {
570 uint8_t shootRange;
571 if (!propStream.read<uint8_t>(shootRange)) {
572 return ATTR_READ_ERROR;
573 }
574
575 setIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE, shootRange);
576 break;
577 }
578
579 case ATTR_DECAYTO: {
580 int32_t decayTo;
581 if (!propStream.read<int32_t>(decayTo)) {
582 return ATTR_READ_ERROR;
583 }
584
585 setIntAttr(ITEM_ATTRIBUTE_DECAYTO, decayTo);
586 break;
587 }
588
589 case ATTR_WRAPID: {
590 uint16_t wrapId;
591 if (!propStream.read<uint16_t>(wrapId)) {
592 return ATTR_READ_ERROR;
593 }
594
595 setIntAttr(ITEM_ATTRIBUTE_WRAPID, wrapId);
596 break;
597 }
598
599 //these should be handled through derived classes
600 //If these are called then something has changed in the items.xml since the map was saved
601 //just read the values
602
603 //Depot class
604 case ATTR_DEPOT_ID: {
605 if (!propStream.skip(2)) {
606 return ATTR_READ_ERROR;
607 }
608 break;
609 }
610
611 //Door class
612 case ATTR_HOUSEDOORID: {
613 if (!propStream.skip(1)) {
614 return ATTR_READ_ERROR;
615 }
616 break;
617 }
618
619 //Bed class
620 case ATTR_SLEEPERGUID: {
621 if (!propStream.skip(4)) {
622 return ATTR_READ_ERROR;
623 }
624 break;
625 }
626
627 case ATTR_SLEEPSTART: {
628 if (!propStream.skip(4)) {
629 return ATTR_READ_ERROR;
630 }
631 break;
632 }
633
634 //Teleport class
635 case ATTR_TELE_DEST: {
636 if (!propStream.skip(5)) {
637 return ATTR_READ_ERROR;
638 }
639 break;
640 }
641
642 //Container class
643 case ATTR_CONTAINER_ITEMS: {
644 return ATTR_READ_ERROR;
645 }
646
647 case ATTR_CUSTOM_ATTRIBUTES: {
648 uint64_t size;
649 if (!propStream.read<uint64_t>(size)) {
650 return ATTR_READ_ERROR;
651 }
652
653 for (uint64_t i = 0; i < size; i++) {
654 // Unserialize key type and value
655 std::string key;
656 if (!propStream.readString(key)) {
657 return ATTR_READ_ERROR;
658 };
659
660 // Unserialize value type and value
661 ItemAttributes::CustomAttribute val;
662 if (!val.unserialize(propStream)) {
663 return ATTR_READ_ERROR;
664 }
665
666 setCustomAttribute(key, val);
667 }
668 break;
669 }
670
671 default:
672 return ATTR_READ_ERROR;
673 }
674
675 return ATTR_READ_CONTINUE;
676}
677
678bool Item::unserializeAttr(PropStream& propStream)
679{
680 uint8_t attr_type;
681 while (propStream.read<uint8_t>(attr_type) && attr_type != 0) {
682 Attr_ReadValue ret = readAttr(static_cast<AttrTypes_t>(attr_type), propStream);
683 if (ret == ATTR_READ_ERROR) {
684 return false;
685 } else if (ret == ATTR_READ_END) {
686 return true;
687 }
688 }
689 return true;
690}
691
692bool Item::unserializeItemNode(OTB::Loader&, const OTB::Node&, PropStream& propStream)
693{
694 return unserializeAttr(propStream);
695}
696
697void Item::serializeAttr(PropWriteStream& propWriteStream) const
698{
699 const ItemType& it = items[id];
700 if (it.stackable || it.isFluidContainer() || it.isSplash()) {
701 propWriteStream.write<uint8_t>(ATTR_COUNT);
702 propWriteStream.write<uint8_t>(getSubType());
703 }
704
705 uint16_t charges = getCharges();
706 if (charges != 0) {
707 propWriteStream.write<uint8_t>(ATTR_CHARGES);
708 propWriteStream.write<uint16_t>(charges);
709 }
710
711 if (it.moveable) {
712 uint16_t actionId = getActionId();
713 if (actionId != 0) {
714 propWriteStream.write<uint8_t>(ATTR_ACTION_ID);
715 propWriteStream.write<uint16_t>(actionId);
716 }
717 }
718
719 const std::string& text = getText();
720 if (!text.empty()) {
721 propWriteStream.write<uint8_t>(ATTR_TEXT);
722 propWriteStream.writeString(text);
723 }
724
725 const time_t writtenDate = getDate();
726 if (writtenDate != 0) {
727 propWriteStream.write<uint8_t>(ATTR_WRITTENDATE);
728 propWriteStream.write<uint32_t>(writtenDate);
729 }
730
731 const std::string& writer = getWriter();
732 if (!writer.empty()) {
733 propWriteStream.write<uint8_t>(ATTR_WRITTENBY);
734 propWriteStream.writeString(writer);
735 }
736
737 const std::string& specialDesc = getSpecialDescription();
738 if (!specialDesc.empty()) {
739 propWriteStream.write<uint8_t>(ATTR_DESC);
740 propWriteStream.writeString(specialDesc);
741 }
742
743 if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) {
744 propWriteStream.write<uint8_t>(ATTR_DURATION);
745 propWriteStream.write<uint32_t>(getIntAttr(ITEM_ATTRIBUTE_DURATION));
746 }
747
748 ItemDecayState_t decayState = getDecaying();
749 if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) {
750 propWriteStream.write<uint8_t>(ATTR_DECAYING_STATE);
751 propWriteStream.write<uint8_t>(decayState);
752 }
753
754 if (hasAttribute(ITEM_ATTRIBUTE_NAME)) {
755 propWriteStream.write<uint8_t>(ATTR_NAME);
756 propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_NAME));
757 }
758
759 if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) {
760 propWriteStream.write<uint8_t>(ATTR_ARTICLE);
761 propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_ARTICLE));
762 }
763
764 if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) {
765 propWriteStream.write<uint8_t>(ATTR_PLURALNAME);
766 propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_PLURALNAME));
767 }
768
769 if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) {
770 propWriteStream.write<uint8_t>(ATTR_WEIGHT);
771 propWriteStream.write<uint32_t>(getIntAttr(ITEM_ATTRIBUTE_WEIGHT));
772 }
773
774 if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) {
775 propWriteStream.write<uint8_t>(ATTR_ATTACK);
776 propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_ATTACK));
777 }
778
779 if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) {
780 propWriteStream.write<uint8_t>(ATTR_DEFENSE);
781 propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_DEFENSE));
782 }
783
784 if (hasAttribute(ITEM_ATTRIBUTE_EXTRADEFENSE)) {
785 propWriteStream.write<uint8_t>(ATTR_EXTRADEFENSE);
786 propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_EXTRADEFENSE));
787 }
788
789 if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) {
790 propWriteStream.write<uint8_t>(ATTR_ARMOR);
791 propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_ARMOR));
792 }
793
794 if (hasAttribute(ITEM_ATTRIBUTE_HITCHANCE)) {
795 propWriteStream.write<uint8_t>(ATTR_HITCHANCE);
796 propWriteStream.write<int8_t>(getIntAttr(ITEM_ATTRIBUTE_HITCHANCE));
797 }
798
799 if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) {
800 propWriteStream.write<uint8_t>(ATTR_SHOOTRANGE);
801 propWriteStream.write<uint8_t>(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE));
802 }
803
804 if (hasAttribute(ITEM_ATTRIBUTE_DECAYTO)) {
805 propWriteStream.write<uint8_t>(ATTR_DECAYTO);
806 propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_DECAYTO));
807 }
808
809 if (hasAttribute(ITEM_ATTRIBUTE_WRAPID)) {
810 propWriteStream.write<uint8_t>(ATTR_WRAPID);
811 propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_WRAPID));
812 }
813
814 if (hasAttribute(ITEM_ATTRIBUTE_CUSTOM)) {
815 const ItemAttributes::CustomAttributeMap* customAttrMap = attributes->getCustomAttributeMap();
816 propWriteStream.write<uint8_t>(ATTR_CUSTOM_ATTRIBUTES);
817 propWriteStream.write<uint64_t>(static_cast<uint64_t>(customAttrMap->size()));
818 for (const auto &entry : *customAttrMap) {
819 // Serializing key type and value
820 propWriteStream.writeString(entry.first);
821
822 // Serializing value type and value
823 entry.second.serialize(propWriteStream);
824 }
825 }
826}
827
828bool Item::hasProperty(ITEMPROPERTY prop) const
829{
830 const ItemType& it = items[id];
831 switch (prop) {
832 case CONST_PROP_BLOCKSOLID: return it.blockSolid;
833 case CONST_PROP_MOVEABLE: return it.moveable && !hasAttribute(ITEM_ATTRIBUTE_UNIQUEID);
834 case CONST_PROP_HASHEIGHT: return it.hasHeight;
835 case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile;
836 case CONST_PROP_BLOCKPATH: return it.blockPathFind;
837 case CONST_PROP_ISVERTICAL: return it.isVertical;
838 case CONST_PROP_ISHORIZONTAL: return it.isHorizontal;
839 case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID));
840 case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID));
841 case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && (!it.moveable || hasAttribute(ITEM_ATTRIBUTE_UNIQUEID));
842 case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind;
843 case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical;
844 default: return false;
845 }
846}
847
848uint32_t Item::getWeight() const
849{
850 uint32_t weight = getBaseWeight();
851 if (isStackable()) {
852 return weight * std::max<uint32_t>(1, getItemCount());
853 }
854 return weight;
855}
856
857std::string Item::getDescription(const ItemType& it, int32_t lookDistance,
858 const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/)
859{
860 const std::string* text = nullptr;
861
862 std::ostringstream s;
863 s << getNameDescription(it, item, subType, addArticle);
864
865 if (item) {
866 subType = item->getSubType();
867 }
868
869 if (it.isRune()) {
870 if (it.runeLevel > 0 || it.runeMagLevel > 0) {
871 if (RuneSpell* rune = g_spells->getRuneSpell(it.id)) {
872 int32_t tmpSubType = subType;
873 if (item) {
874 tmpSubType = item->getSubType();
875 }
876 s << ". " << (it.stackable && tmpSubType > 1 ? "They" : "It") << " can only be used by ";
877
878 const VocSpellMap& vocMap = rune->getVocMap();
879 std::vector<Vocation*> showVocMap;
880
881 // vocations are usually listed with the unpromoted and promoted version, the latter being
882 // hidden from description, so `total / 2` is most likely the amount of vocations to be shown.
883 showVocMap.reserve(vocMap.size() / 2);
884 for (const auto& voc : vocMap) {
885 if (voc.second) {
886 showVocMap.push_back(g_vocations.getVocation(voc.first));
887 }
888 }
889
890 if (!showVocMap.empty()) {
891 auto vocIt = showVocMap.begin(), vocLast = (showVocMap.end() - 1);
892 while (vocIt != vocLast) {
893 s << asLowerCaseString((*vocIt)->getVocName()) << "s";
894 if (++vocIt == vocLast) {
895 s << " and ";
896 } else {
897 s << ", ";
898 }
899 }
900 s << asLowerCaseString((*vocLast)->getVocName()) << "s";
901 } else {
902 s << "players";
903 }
904
905 s << " with";
906
907 if (it.runeLevel > 0) {
908 s << " level " << it.runeLevel;
909 }
910
911 if (it.runeMagLevel > 0) {
912 if (it.runeLevel > 0) {
913 s << " and";
914 }
915
916 s << " magic level " << it.runeMagLevel;
917 }
918
919 s << " or higher";
920 }
921 }
922 } else if (it.weaponType != WEAPON_NONE) {
923 bool begin = true;
924 if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) {
925 s << " (Range:" << static_cast<uint16_t>(item ? item->getShootRange() : it.shootRange);
926
927 int32_t attack;
928 int8_t hitChance;
929 if (item) {
930 attack = item->getAttack();
931 hitChance = item->getHitChance();
932 } else {
933 attack = it.attack;
934 hitChance = it.hitChance;
935 }
936
937 if (attack != 0) {
938 s << ", Atk" << std::showpos << attack << std::noshowpos;
939 }
940
941 if (hitChance != 0) {
942 s << ", Hit%" << std::showpos << static_cast<int16_t>(hitChance) << std::noshowpos;
943 }
944
945 begin = false;
946 } else if (it.weaponType != WEAPON_AMMO) {
947
948 int32_t attack, defense, extraDefense;
949 if (item) {
950 attack = item->getAttack();
951 defense = item->getDefense();
952 extraDefense = item->getExtraDefense();
953 } else {
954 attack = it.attack;
955 defense = it.defense;
956 extraDefense = it.extraDefense;
957 }
958
959 if (attack != 0) {
960 begin = false;
961 s << " (Atk:" << attack;
962
963 if (it.abilities && it.abilities->elementType != COMBAT_NONE && it.abilities->elementDamage != 0) {
964 s << " physical + " << it.abilities->elementDamage << ' ' << getCombatName(it.abilities->elementType);
965 }
966 }
967
968 if (defense != 0 || extraDefense != 0) {
969 if (begin) {
970 begin = false;
971 s << " (";
972 } else {
973 s << ", ";
974 }
975
976 s << "Def:" << defense;
977 if (extraDefense != 0) {
978 s << ' ' << std::showpos << extraDefense << std::noshowpos;
979 }
980 }
981 }
982
983 if (it.abilities) {
984 for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) {
985 if (!it.abilities->skills[i]) {
986 continue;
987 }
988
989 if (begin) {
990 begin = false;
991 s << " (";
992 } else {
993 s << ", ";
994 }
995
996 s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos;
997 }
998
999 for (uint8_t i = SPECIALSKILL_FIRST; i <= SPECIALSKILL_LAST; i++) {
1000 if (!it.abilities->specialSkills[i]) {
1001 continue;
1002 }
1003
1004 if (begin) {
1005 begin = false;
1006 s << " (";
1007 } else {
1008 s << ", ";
1009 }
1010
1011 s << getSpecialSkillName(i) << ' ' << std::showpos << it.abilities->specialSkills[i] << '%' << std::noshowpos;
1012 }
1013
1014 if (it.abilities->stats[STAT_MAGICPOINTS]) {
1015 if (begin) {
1016 begin = false;
1017 s << " (";
1018 } else {
1019 s << ", ";
1020 }
1021
1022 s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
1023 }
1024
1025 int16_t show = it.abilities->absorbPercent[0];
1026 if (show != 0) {
1027 for (size_t i = 1; i < COMBAT_COUNT; ++i) {
1028 if (it.abilities->absorbPercent[i] != show) {
1029 show = 0;
1030 break;
1031 }
1032 }
1033 }
1034
1035 if (show == 0) {
1036 bool tmp = true;
1037
1038 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
1039 if (it.abilities->absorbPercent[i] == 0) {
1040 continue;
1041 }
1042
1043 if (tmp) {
1044 tmp = false;
1045
1046 if (begin) {
1047 begin = false;
1048 s << " (";
1049 } else {
1050 s << ", ";
1051 }
1052
1053 s << "protection ";
1054 } else {
1055 s << ", ";
1056 }
1057
1058 s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%';
1059 }
1060 } else {
1061 if (begin) {
1062 begin = false;
1063 s << " (";
1064 } else {
1065 s << ", ";
1066 }
1067
1068 s << "protection all " << std::showpos << show << std::noshowpos << '%';
1069 }
1070
1071 show = it.abilities->fieldAbsorbPercent[0];
1072 if (show != 0) {
1073 for (size_t i = 1; i < COMBAT_COUNT; ++i) {
1074 if (it.abilities->absorbPercent[i] != show) {
1075 show = 0;
1076 break;
1077 }
1078 }
1079 }
1080
1081 if (show == 0) {
1082 bool tmp = true;
1083
1084 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
1085 if (it.abilities->fieldAbsorbPercent[i] == 0) {
1086 continue;
1087 }
1088
1089 if (tmp) {
1090 tmp = false;
1091
1092 if (begin) {
1093 begin = false;
1094 s << " (";
1095 } else {
1096 s << ", ";
1097 }
1098
1099 s << "protection ";
1100 } else {
1101 s << ", ";
1102 }
1103
1104 s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%';
1105 }
1106 } else {
1107 if (begin) {
1108 begin = false;
1109 s << " (";
1110 } else {
1111 s << ", ";
1112 }
1113
1114 s << "protection all fields " << std::showpos << show << std::noshowpos << '%';
1115 }
1116
1117 if (it.abilities->speed) {
1118 if (begin) {
1119 begin = false;
1120 s << " (";
1121 } else {
1122 s << ", ";
1123 }
1124
1125 s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
1126 }
1127 }
1128
1129 if (!begin) {
1130 s << ')';
1131 }
1132 } else if (it.armor != 0 || (item && item->getArmor() != 0) || it.showAttributes) {
1133 bool begin = true;
1134
1135 int32_t armor = (item ? item->getArmor() : it.armor);
1136 if (armor != 0) {
1137 s << " (Arm:" << armor;
1138 begin = false;
1139 }
1140
1141 if (it.abilities) {
1142 for (uint8_t i = SKILL_FIRST; i <= SKILL_LAST; i++) {
1143 if (!it.abilities->skills[i]) {
1144 continue;
1145 }
1146
1147 if (begin) {
1148 begin = false;
1149 s << " (";
1150 } else {
1151 s << ", ";
1152 }
1153
1154 s << getSkillName(i) << ' ' << std::showpos << it.abilities->skills[i] << std::noshowpos;
1155 }
1156
1157 if (it.abilities->stats[STAT_MAGICPOINTS]) {
1158 if (begin) {
1159 begin = false;
1160 s << " (";
1161 } else {
1162 s << ", ";
1163 }
1164
1165 s << "magic level " << std::showpos << it.abilities->stats[STAT_MAGICPOINTS] << std::noshowpos;
1166 }
1167
1168 int16_t show = it.abilities->absorbPercent[0];
1169 if (show != 0) {
1170 for (size_t i = 1; i < COMBAT_COUNT; ++i) {
1171 if (it.abilities->absorbPercent[i] != show) {
1172 show = 0;
1173 break;
1174 }
1175 }
1176 }
1177
1178 if (!show) {
1179 bool protectionBegin = true;
1180 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
1181 if (it.abilities->absorbPercent[i] == 0) {
1182 continue;
1183 }
1184
1185 if (protectionBegin) {
1186 protectionBegin = false;
1187
1188 if (begin) {
1189 begin = false;
1190 s << " (";
1191 } else {
1192 s << ", ";
1193 }
1194
1195 s << "protection ";
1196 } else {
1197 s << ", ";
1198 }
1199
1200 s << getCombatName(indexToCombatType(i)) << ' ' << std::showpos << it.abilities->absorbPercent[i] << std::noshowpos << '%';
1201 }
1202 } else {
1203 if (begin) {
1204 begin = false;
1205 s << " (";
1206 } else {
1207 s << ", ";
1208 }
1209
1210 s << "protection all " << std::showpos << show << std::noshowpos << '%';
1211 }
1212
1213 show = it.abilities->fieldAbsorbPercent[0];
1214 if (show != 0) {
1215 for (size_t i = 1; i < COMBAT_COUNT; ++i) {
1216 if (it.abilities->absorbPercent[i] != show) {
1217 show = 0;
1218 break;
1219 }
1220 }
1221 }
1222
1223 if (!show) {
1224 bool tmp = true;
1225
1226 for (size_t i = 0; i < COMBAT_COUNT; ++i) {
1227 if (it.abilities->fieldAbsorbPercent[i] == 0) {
1228 continue;
1229 }
1230
1231 if (tmp) {
1232 tmp = false;
1233
1234 if (begin) {
1235 begin = false;
1236 s << " (";
1237 } else {
1238 s << ", ";
1239 }
1240
1241 s << "protection ";
1242 } else {
1243 s << ", ";
1244 }
1245
1246 s << getCombatName(indexToCombatType(i)) << " field " << std::showpos << it.abilities->fieldAbsorbPercent[i] << std::noshowpos << '%';
1247 }
1248 } else {
1249 if (begin) {
1250 begin = false;
1251 s << " (";
1252 } else {
1253 s << ", ";
1254 }
1255
1256 s << "protection all fields " << std::showpos << show << std::noshowpos << '%';
1257 }
1258
1259 if (it.abilities->speed) {
1260 if (begin) {
1261 begin = false;
1262 s << " (";
1263 } else {
1264 s << ", ";
1265 }
1266
1267 s << "speed " << std::showpos << (it.abilities->speed >> 1) << std::noshowpos;
1268 }
1269 }
1270
1271 if (!begin) {
1272 s << ')';
1273 }
1274 } else if (it.isContainer() || (item && item->getContainer())) {
1275 uint32_t volume = 0;
1276 if (!item || !item->hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
1277 if (it.isContainer()) {
1278 volume = it.maxItems;
1279 } else {
1280 volume = item->getContainer()->capacity();
1281 }
1282 }
1283
1284 if (volume != 0) {
1285 s << " (Vol:" << volume << ')';
1286 }
1287 } else {
1288 bool found = true;
1289
1290 if (it.abilities) {
1291 if (it.abilities->speed > 0) {
1292 s << " (speed " << std::showpos << (it.abilities->speed / 2) << std::noshowpos << ')';
1293 } else if (hasBitSet(CONDITION_DRUNK, it.abilities->conditionSuppressions)) {
1294 s << " (hard drinking)";
1295 } else if (it.abilities->invisible) {
1296 s << " (invisibility)";
1297 } else if (it.abilities->regeneration) {
1298 s << " (faster regeneration)";
1299 } else if (it.abilities->manaShield) {
1300 s << " (mana shield)";
1301 } else {
1302 found = false;
1303 }
1304 } else {
1305 found = false;
1306 }
1307
1308 if (!found) {
1309 if (it.isKey()) {
1310 s << " (Key:" << std::setfill('0') << std::setw(4) << (item ? item->getActionId() : 0) << ')';
1311 } else if (it.isFluidContainer()) {
1312 if (subType > 0) {
1313 const std::string& itemName = items[subType].name;
1314 s << " of " << (!itemName.empty() ? itemName : "unknown");
1315 } else {
1316 s << ". It is empty";
1317 }
1318 } else if (it.isSplash()) {
1319 s << " of ";
1320
1321 if (subType > 0 && !items[subType].name.empty()) {
1322 s << items[subType].name;
1323 } else {
1324 s << "unknown";
1325 }
1326 } else if (it.allowDistRead && (it.id < 7369 || it.id > 7371)) {
1327 s << ".\n";
1328
1329 if (lookDistance <= 4) {
1330 if (item) {
1331 text = &item->getText();
1332 if (!text->empty()) {
1333 const std::string& writer = item->getWriter();
1334 if (!writer.empty()) {
1335 s << writer << " wrote";
1336 time_t date = item->getDate();
1337 if (date != 0) {
1338 s << " on " << formatDateShort(date);
1339 }
1340 s << ": ";
1341 } else {
1342 s << "You read: ";
1343 }
1344 s << *text;
1345 } else {
1346 s << "Nothing is written on it";
1347 }
1348 } else {
1349 s << "Nothing is written on it";
1350 }
1351 } else {
1352 s << "You are too far away to read it";
1353 }
1354 } else if (it.levelDoor != 0 && item) {
1355 uint16_t actionId = item->getActionId();
1356 if (actionId >= it.levelDoor) {
1357 s << " for level " << (actionId - it.levelDoor);
1358 }
1359 }
1360 }
1361 }
1362
1363 if (it.showCharges) {
1364 s << " that has " << subType << " charge" << (subType != 1 ? "s" : "") << " left";
1365 }
1366
1367 if (it.showDuration) {
1368 if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) {
1369 uint32_t duration = item->getDuration() / 1000;
1370 s << " that will expire in ";
1371
1372 if (duration >= 86400) {
1373 uint16_t days = duration / 86400;
1374 uint16_t hours = (duration % 86400) / 3600;
1375 s << days << " day" << (days != 1 ? "s" : "");
1376
1377 if (hours > 0) {
1378 s << " and " << hours << " hour" << (hours != 1 ? "s" : "");
1379 }
1380 } else if (duration >= 3600) {
1381 uint16_t hours = duration / 3600;
1382 uint16_t minutes = (duration % 3600) / 60;
1383 s << hours << " hour" << (hours != 1 ? "s" : "");
1384
1385 if (minutes > 0) {
1386 s << " and " << minutes << " minute" << (minutes != 1 ? "s" : "");
1387 }
1388 } else if (duration >= 60) {
1389 uint16_t minutes = duration / 60;
1390 s << minutes << " minute" << (minutes != 1 ? "s" : "");
1391 uint16_t seconds = duration % 60;
1392
1393 if (seconds > 0) {
1394 s << " and " << seconds << " second" << (seconds != 1 ? "s" : "");
1395 }
1396 } else {
1397 s << duration << " second" << (duration != 1 ? "s" : "");
1398 }
1399 } else {
1400 s << " that is brand-new";
1401 }
1402 }
1403
1404 if (!it.allowDistRead || (it.id >= 7369 && it.id <= 7371)) {
1405 s << '.';
1406 } else {
1407 if (!text && item) {
1408 text = &item->getText();
1409 }
1410
1411 if (!text || text->empty()) {
1412 s << '.';
1413 }
1414 }
1415
1416 if (it.wieldInfo != 0) {
1417 s << "\nIt can only be wielded properly by ";
1418
1419 if (it.wieldInfo & WIELDINFO_PREMIUM) {
1420 s << "premium ";
1421 }
1422
1423 if (!it.vocationString.empty()) {
1424 s << it.vocationString;
1425 } else {
1426 s << "players";
1427 }
1428
1429 if (it.wieldInfo & WIELDINFO_LEVEL) {
1430 s << " of level " << it.minReqLevel << " or higher";
1431 }
1432
1433 if (it.wieldInfo & WIELDINFO_MAGLV) {
1434 if (it.wieldInfo & WIELDINFO_LEVEL) {
1435 s << " and";
1436 } else {
1437 s << " of";
1438 }
1439
1440 s << " magic level " << it.minReqMagicLevel << " or higher";
1441 }
1442
1443 s << '.';
1444 }
1445
1446 if (lookDistance <= 1) {
1447 if (item) {
1448 const uint32_t weight = item->getWeight();
1449 if (weight != 0 && it.pickupable) {
1450 s << '\n' << getWeightDescription(it, weight, item->getItemCount());
1451 }
1452 } else if (it.weight != 0 && it.pickupable) {
1453 s << '\n' << getWeightDescription(it, it.weight);
1454 }
1455 }
1456
1457 if (item) {
1458 const std::string& specialDescription = item->getSpecialDescription();
1459 if (!specialDescription.empty()) {
1460 s << '\n' << specialDescription;
1461 } else if (lookDistance <= 1 && !it.description.empty()) {
1462 s << '\n' << it.description;
1463 }
1464 } else if (lookDistance <= 1 && !it.description.empty()) {
1465 s << '\n' << it.description;
1466 }
1467
1468 if (it.allowDistRead && it.id >= 7369 && it.id <= 7371) {
1469 if (!text && item) {
1470 text = &item->getText();
1471 }
1472
1473 if (text && !text->empty()) {
1474 s << '\n' << *text;
1475 }
1476 }
1477 return s.str();
1478}
1479
1480std::string Item::getDescription(int32_t lookDistance) const
1481{
1482 const ItemType& it = items[id];
1483 return getDescription(it, lookDistance, this);
1484}
1485
1486std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/)
1487{
1488 if (item) {
1489 subType = item->getSubType();
1490 }
1491
1492 std::ostringstream s;
1493
1494 const std::string& name = (item ? item->getName() : it.name);
1495 if (!name.empty()) {
1496 if (it.stackable && subType > 1) {
1497 if (it.showCount) {
1498 s << subType << ' ';
1499 }
1500
1501 s << (item ? item->getPluralName() : it.getPluralName());
1502 } else {
1503 if (addArticle) {
1504 const std::string& article = (item ? item->getArticle() : it.article);
1505 if (!article.empty()) {
1506 s << article << ' ';
1507 }
1508 }
1509
1510 s << name;
1511 }
1512 } else {
1513 if (addArticle) {
1514 s << "an ";
1515 }
1516 s << "item of type " << it.id;
1517 }
1518 return s.str();
1519}
1520
1521std::string Item::getNameDescription() const
1522{
1523 const ItemType& it = items[id];
1524 return getNameDescription(it, this);
1525}
1526
1527std::string Item::getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count /*= 1*/)
1528{
1529 std::ostringstream ss;
1530 if (it.stackable && count > 1 && it.showCount != 0) {
1531 ss << "They weigh ";
1532 } else {
1533 ss << "It weighs ";
1534 }
1535
1536 if (weight < 10) {
1537 ss << "0.0" << weight;
1538 } else if (weight < 100) {
1539 ss << "0." << weight;
1540 } else {
1541 std::string weightString = std::to_string(weight);
1542 weightString.insert(weightString.end() - 2, '.');
1543 ss << weightString;
1544 }
1545
1546 ss << " oz.";
1547 return ss.str();
1548}
1549
1550std::string Item::getWeightDescription(uint32_t weight) const
1551{
1552 const ItemType& it = Item::items[id];
1553 return getWeightDescription(it, weight, getItemCount());
1554}
1555
1556std::string Item::getWeightDescription() const
1557{
1558 uint32_t weight = getWeight();
1559 if (weight == 0) {
1560 return std::string();
1561 }
1562 return getWeightDescription(weight);
1563}
1564
1565void Item::setUniqueId(uint16_t n)
1566{
1567 if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
1568 return;
1569 }
1570
1571 if (g_game.addUniqueItem(n, this)) {
1572 getAttributes()->setUniqueId(n);
1573 }
1574}
1575
1576bool Item::canDecay() const
1577{
1578 if (isRemoved()) {
1579 return false;
1580 }
1581
1582 const ItemType& it = Item::items[id];
1583 if (getDecayTo() < 0 || it.decayTime == 0) {
1584 return false;
1585 }
1586
1587 if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
1588 return false;
1589 }
1590
1591 return true;
1592}
1593
1594uint32_t Item::getWorth() const
1595{
1596 switch (id) {
1597 case ITEM_GOLD_COIN:
1598 return count;
1599
1600 case ITEM_PLATINUM_COIN:
1601 return count * 100;
1602
1603 case ITEM_CRYSTAL_COIN:
1604 return count * 10000;
1605
1606 default:
1607 return 0;
1608 }
1609}
1610
1611LightInfo Item::getLightInfo() const
1612{
1613 const ItemType& it = items[id];
1614 return {it.lightLevel, it.lightColor};
1615}
1616
1617std::string ItemAttributes::emptyString;
1618int64_t ItemAttributes::emptyInt;
1619double ItemAttributes::emptyDouble;
1620bool ItemAttributes::emptyBool;
1621
1622const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const
1623{
1624 if (!isStrAttrType(type)) {
1625 return emptyString;
1626 }
1627
1628 const Attribute* attr = getExistingAttr(type);
1629 if (!attr) {
1630 return emptyString;
1631 }
1632 return *attr->value.string;
1633}
1634
1635void ItemAttributes::setStrAttr(itemAttrTypes type, const std::string& value)
1636{
1637 if (!isStrAttrType(type)) {
1638 return;
1639 }
1640
1641 if (value.empty()) {
1642 return;
1643 }
1644
1645 Attribute& attr = getAttr(type);
1646 delete attr.value.string;
1647 attr.value.string = new std::string(value);
1648}
1649
1650void ItemAttributes::removeAttribute(itemAttrTypes type)
1651{
1652 if (!hasAttribute(type)) {
1653 return;
1654 }
1655
1656 auto prev_it = attributes.rbegin();
1657 if ((*prev_it).type == type) {
1658 attributes.pop_back();
1659 } else {
1660 auto it = prev_it, end = attributes.rend();
1661 while (++it != end) {
1662 if ((*it).type == type) {
1663 (*it) = attributes.back();
1664 attributes.pop_back();
1665 break;
1666 }
1667 prev_it = it;
1668 }
1669 }
1670 attributeBits &= ~type;
1671}
1672
1673int64_t ItemAttributes::getIntAttr(itemAttrTypes type) const
1674{
1675 if (!isIntAttrType(type)) {
1676 return 0;
1677 }
1678
1679 const Attribute* attr = getExistingAttr(type);
1680 if (!attr) {
1681 return 0;
1682 }
1683 return attr->value.integer;
1684}
1685
1686void ItemAttributes::setIntAttr(itemAttrTypes type, int64_t value)
1687{
1688 if (!isIntAttrType(type)) {
1689 return;
1690 }
1691
1692 getAttr(type).value.integer = value;
1693}
1694
1695void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value)
1696{
1697 if (!isIntAttrType(type)) {
1698 return;
1699 }
1700
1701 getAttr(type).value.integer += value;
1702}
1703
1704const ItemAttributes::Attribute* ItemAttributes::getExistingAttr(itemAttrTypes type) const
1705{
1706 if (hasAttribute(type)) {
1707 for (const Attribute& attribute : attributes) {
1708 if (attribute.type == type) {
1709 return &attribute;
1710 }
1711 }
1712 }
1713 return nullptr;
1714}
1715
1716ItemAttributes::Attribute& ItemAttributes::getAttr(itemAttrTypes type)
1717{
1718 if (hasAttribute(type)) {
1719 for (Attribute& attribute : attributes) {
1720 if (attribute.type == type) {
1721 return attribute;
1722 }
1723 }
1724 }
1725
1726 attributeBits |= type;
1727 attributes.emplace_back(type);
1728 return attributes.back();
1729}
1730
1731void Item::startDecaying()
1732{
1733 g_game.startDecay(this);
1734}
1735
1736bool Item::hasMarketAttributes() const
1737{
1738 if (attributes == nullptr) {
1739 return true;
1740 }
1741
1742 for (const auto& attr : attributes->getList()) {
1743 if (attr.type == ITEM_ATTRIBUTE_CHARGES) {
1744 uint16_t charges = static_cast<uint16_t>(attr.value.integer);
1745 if (charges != items[id].charges) {
1746 return false;
1747 }
1748 } else if (attr.type == ITEM_ATTRIBUTE_DURATION) {
1749 uint32_t duration = static_cast<uint32_t>(attr.value.integer);
1750 if (duration != getDefaultDuration()) {
1751 return false;
1752 }
1753 } else {
1754 return false;
1755 }
1756 }
1757 return true;
1758}
1759
1760template<>
1761const std::string& ItemAttributes::CustomAttribute::get<std::string>() {
1762 if (value.type() == typeid(std::string)) {
1763 return boost::get<std::string>(value);
1764 }
1765
1766 return emptyString;
1767}
1768
1769template<>
1770const int64_t& ItemAttributes::CustomAttribute::get<int64_t>() {
1771 if (value.type() == typeid(int64_t)) {
1772 return boost::get<int64_t>(value);
1773 }
1774
1775 return emptyInt;
1776}
1777
1778template<>
1779const double& ItemAttributes::CustomAttribute::get<double>() {
1780 if (value.type() == typeid(double)) {
1781 return boost::get<double>(value);
1782 }
1783
1784 return emptyDouble;
1785}
1786
1787template<>
1788const bool& ItemAttributes::CustomAttribute::get<bool>() {
1789 if (value.type() == typeid(bool)) {
1790 return boost::get<bool>(value);
1791 }
1792
1793 return emptyBool;
1794}
1795