/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "item.h" #include "container.h" #include "teleport.h" #include "mailbox.h" #include "house.h" #include "game.h" #include "bed.h" #include "actions.h" #include "spells.h" extern Game g_game; extern Spells* g_spells; extern Vocations g_vocations; Items Item::items; Item* Item::CreateItem(const uint16_t type, uint16_t count /*= 0*/) { Item* newItem = nullptr; const ItemType& it = Item::items[type]; if (it.group == ITEM_GROUP_DEPRECATED) { return nullptr; } if (it.stackable && count == 0) { count = 1; } if (it.id != 0) { if (it.isDepot()) { newItem = new DepotLocker(type); } else if (it.isContainer() || it.isChest()) { newItem = new Container(type); } else if (it.isTeleport()) { newItem = new Teleport(type); } else if (it.isMagicField()) { newItem = new MagicField(type); } else if (it.isDoor()) { newItem = new Door(type); } else if (it.isMailbox()) { newItem = new Mailbox(type); } else if (it.isBed()) { newItem = new BedItem(type); } else { newItem = new Item(type, count); } newItem->incrementReferenceCounter(); } return newItem; } Container* Item::CreateItemAsContainer(const uint16_t type, uint16_t size) { const ItemType& it = Item::items[type]; if (it.id == 0 || it.group == ITEM_GROUP_DEPRECATED || it.stackable || it.useable || it.moveable || it.pickupable || it.isDepot() || it.isSplash() || it.isDoor()) { return nullptr; } Container* newItem = new Container(type, size); newItem->incrementReferenceCounter(); return newItem; } Item* Item::CreateItem(PropStream& propStream) { uint16_t id; if (!propStream.read(id)) { return nullptr; } switch (id) { case ITEM_FIREFIELD_PVP_FULL: id = ITEM_FIREFIELD_PERSISTENT_FULL; break; case ITEM_FIREFIELD_PVP_MEDIUM: id = ITEM_FIREFIELD_PERSISTENT_MEDIUM; break; case ITEM_FIREFIELD_PVP_SMALL: id = ITEM_FIREFIELD_PERSISTENT_SMALL; break; case ITEM_ENERGYFIELD_PVP: id = ITEM_ENERGYFIELD_PERSISTENT; break; case ITEM_POISONFIELD_PVP: id = ITEM_POISONFIELD_PERSISTENT; break; case ITEM_MAGICWALL: id = ITEM_MAGICWALL_PERSISTENT; break; case ITEM_WILDGROWTH: id = ITEM_WILDGROWTH_PERSISTENT; break; default: break; } return Item::CreateItem(id, 0); } Item::Item(const uint16_t type, uint16_t count /*= 0*/) : id(type) { const ItemType& it = items[id]; if (it.isFluidContainer() || it.isSplash()) { setFluidType(count); } else if (it.stackable) { if (count != 0) { setItemCount(count); } else if (it.charges != 0) { setItemCount(it.charges); } } else if (it.charges != 0) { if (count != 0) { setCharges(count); } else { setCharges(it.charges); } } else if (it.isKey()) { setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, count); } setDefaultDuration(); } Item::Item(const Item& i) : Thing(), id(i.id), count(i.count), loadedFromMap(i.loadedFromMap) { if (i.attributes) { attributes.reset(new ItemAttributes(*i.attributes)); } } Item* Item::clone() const { Item* item = Item::CreateItem(id, count); if (attributes) { item->attributes.reset(new ItemAttributes(*attributes)); } return item; } bool Item::equals(const Item* otherItem) const { if (!otherItem || id != otherItem->id) { return false; } if (!attributes) { return !otherItem->attributes; } const auto& otherAttributes = otherItem->attributes; if (!otherAttributes || attributes->attributeBits != otherAttributes->attributeBits) { return false; } const auto& attributeList = attributes->attributes; const auto& otherAttributeList = otherAttributes->attributes; for (const auto& attribute : attributeList) { if (ItemAttributes::isStrAttrType(attribute.type)) { for (const auto& otherAttribute : otherAttributeList) { if (attribute.type == otherAttribute.type && *attribute.value.string != *otherAttribute.value.string) { return false; } } } else { for (const auto& otherAttribute : otherAttributeList) { if (attribute.type == otherAttribute.type && attribute.value.integer != otherAttribute.value.integer) { return false; } } } } return true; } void Item::setDefaultSubtype() { const ItemType& it = items[id]; setItemCount(1); if (it.charges != 0) { if (it.stackable) { setItemCount(it.charges); } else { setCharges(it.charges); } } } void Item::onRemoved() { ScriptEnvironment::removeTempItem(this); } void Item::setID(uint16_t newid) { const ItemType& prevIt = Item::items[id]; id = newid; const ItemType& it = Item::items[newid]; uint32_t newDuration = it.decayTime * 1000; if (newDuration == 0 && !it.stopTime && it.decayTo < 0) { removeAttribute(ITEM_ATTRIBUTE_DECAYSTATE); removeAttribute(ITEM_ATTRIBUTE_DURATION); } removeAttribute(ITEM_ATTRIBUTE_CORPSEOWNER); if (newDuration > 0 && (!prevIt.stopTime || !hasAttribute(ITEM_ATTRIBUTE_DURATION))) { setDecaying(DECAYING_FALSE); setDuration(newDuration); } } Cylinder* Item::getTopParent() { Cylinder* aux = getParent(); Cylinder* prevaux = dynamic_cast(this); if (!aux) { return prevaux; } while (aux->getParent() != nullptr) { prevaux = aux; aux = aux->getParent(); } if (prevaux) { return prevaux; } return aux; } const Cylinder* Item::getTopParent() const { const Cylinder* aux = getParent(); const Cylinder* prevaux = dynamic_cast(this); if (!aux) { return prevaux; } while (aux->getParent() != nullptr) { prevaux = aux; aux = aux->getParent(); } if (prevaux) { return prevaux; } return aux; } Tile* Item::getTile() { Cylinder* cylinder = getTopParent(); //get root cylinder if (cylinder && cylinder->getParent()) { cylinder = cylinder->getParent(); } return dynamic_cast(cylinder); } const Tile* Item::getTile() const { const Cylinder* cylinder = getTopParent(); //get root cylinder if (cylinder && cylinder->getParent()) { cylinder = cylinder->getParent(); } return dynamic_cast(cylinder); } uint16_t Item::getSubType() const { const ItemType& it = items[id]; if (it.isFluidContainer() || it.isSplash()) { return getFluidType(); } else if (it.stackable) { return count; } else if (it.charges != 0) { return getCharges(); } return count; } Player* Item::getHoldingPlayer() const { Cylinder* p = getParent(); while (p) { if (p->getCreature()) { return p->getCreature()->getPlayer(); } p = p->getParent(); } return nullptr; } void Item::setSubType(uint16_t n) { const ItemType& it = items[id]; if (it.isFluidContainer() || it.isSplash()) { setFluidType(n); } else if (it.stackable) { setItemCount(n); } else if (it.charges != 0) { setCharges(n); } else { setItemCount(n); } } Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream) { switch (attr) { case ATTR_COUNT: case ATTR_RUNE_CHARGES: { uint8_t count; if (!propStream.read(count)) { return ATTR_READ_ERROR; } setSubType(count); break; } case ATTR_ACTION_ID: { uint16_t actionId; if (!propStream.read(actionId)) { return ATTR_READ_ERROR; } setActionId(actionId); break; } case ATTR_MOVEMENT_ID: { uint16_t movementId; if (!propStream.read(movementId)) { return ATTR_READ_ERROR; } setMovementID(movementId); break; } case ATTR_TEXT: { std::string text; if (!propStream.readString(text)) { return ATTR_READ_ERROR; } setText(text); break; } case ATTR_WRITTENDATE: { uint32_t writtenDate; if (!propStream.read(writtenDate)) { return ATTR_READ_ERROR; } setDate(writtenDate); break; } case ATTR_WRITTENBY: { std::string writer; if (!propStream.readString(writer)) { return ATTR_READ_ERROR; } setWriter(writer); break; } case ATTR_DESC: { std::string text; if (!propStream.readString(text)) { return ATTR_READ_ERROR; } setSpecialDescription(text); break; } case ATTR_CHARGES: { uint16_t charges; if (!propStream.read(charges)) { return ATTR_READ_ERROR; } setSubType(charges); break; } case ATTR_DURATION: { int32_t duration; if (!propStream.read(duration)) { return ATTR_READ_ERROR; } setDuration(std::max(0, duration)); break; } case ATTR_DECAYING_STATE: { uint8_t state; if (!propStream.read(state)) { return ATTR_READ_ERROR; } if (state != DECAYING_FALSE) { setDecaying(DECAYING_PENDING); } break; } case ATTR_NAME: { std::string name; if (!propStream.readString(name)) { return ATTR_READ_ERROR; } setStrAttr(ITEM_ATTRIBUTE_NAME, name); break; } case ATTR_ARTICLE: { std::string article; if (!propStream.readString(article)) { return ATTR_READ_ERROR; } setStrAttr(ITEM_ATTRIBUTE_ARTICLE, article); break; } case ATTR_PLURALNAME: { std::string pluralName; if (!propStream.readString(pluralName)) { return ATTR_READ_ERROR; } setStrAttr(ITEM_ATTRIBUTE_PLURALNAME, pluralName); break; } case ATTR_WEIGHT: { uint32_t weight; if (!propStream.read(weight)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_WEIGHT, weight); break; } case ATTR_ATTACK: { int32_t attack; if (!propStream.read(attack)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_ATTACK, attack); break; } case ATTR_DEFENSE: { int32_t defense; if (!propStream.read(defense)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_DEFENSE, defense); break; } case ATTR_ARMOR: { int32_t armor; if (!propStream.read(armor)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_ARMOR, armor); break; } case ATTR_SHOOTRANGE: { uint8_t shootRange; if (!propStream.read(shootRange)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE, shootRange); break; } case ATTR_KEYNUMBER: { uint16_t keyNumber; if (!propStream.read(keyNumber)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber); break; } case ATTR_KEYHOLENUMBER: { uint16_t keyHoleNumber; if (!propStream.read(keyHoleNumber)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber); break; } case ATTR_DOORLEVEL: { uint16_t doorLevel; if (!propStream.read(doorLevel)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel); break; } case ATTR_DOORQUESTNUMBER: { uint16_t doorQuestNumber; if (!propStream.read(doorQuestNumber)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber); break; } case ATTR_DOORQUESTVALUE: { uint16_t doorQuestValue; if (!propStream.read(doorQuestValue)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue); break; } case ATTR_CHESTQUESTNUMBER: { uint16_t chestQuestNumber; if (!propStream.read(chestQuestNumber)) { return ATTR_READ_ERROR; } setIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER, chestQuestNumber); break; } //these should be handled through derived classes //If these are called then something has changed in the items.xml since the map was saved //just read the values //Depot class case ATTR_DEPOT_ID: { if (!propStream.skip(2)) { return ATTR_READ_ERROR; } break; } //Door class case ATTR_HOUSEDOORID: { if (!propStream.skip(1)) { return ATTR_READ_ERROR; } break; } //Bed class case ATTR_SLEEPERGUID: { if (!propStream.skip(4)) { return ATTR_READ_ERROR; } break; } case ATTR_SLEEPSTART: { if (!propStream.skip(4)) { return ATTR_READ_ERROR; } break; } //Teleport class case ATTR_TELE_DEST: { if (!propStream.skip(5)) { return ATTR_READ_ERROR; } break; } //Container class case ATTR_CONTAINER_ITEMS: { return ATTR_READ_ERROR; } default: return ATTR_READ_ERROR; } return ATTR_READ_CONTINUE; } bool Item::unserializeAttr(PropStream& propStream) { uint8_t attr_type; while (propStream.read(attr_type) && attr_type != 0) { Attr_ReadValue ret = readAttr(static_cast(attr_type), propStream); if (ret == ATTR_READ_ERROR) { return false; } else if (ret == ATTR_READ_END) { return true; } } return true; } bool Item::unserializeItemNode(FileLoader&, NODE, PropStream& propStream) { return unserializeAttr(propStream); } void Item::serializeAttr(PropWriteStream& propWriteStream) const { const ItemType& it = items[id]; if (it.stackable || it.isFluidContainer() || it.isSplash()) { propWriteStream.write(ATTR_COUNT); propWriteStream.write(getSubType()); } uint16_t charges = getCharges(); if (charges != 0) { propWriteStream.write(ATTR_CHARGES); propWriteStream.write(charges); } if (it.moveable) { uint16_t actionId = getActionId(); if (actionId != 0) { propWriteStream.write(ATTR_ACTION_ID); propWriteStream.write(actionId); } } const std::string& text = getText(); if (!text.empty()) { propWriteStream.write(ATTR_TEXT); propWriteStream.writeString(text); } const time_t writtenDate = getDate(); if (writtenDate != 0) { propWriteStream.write(ATTR_WRITTENDATE); propWriteStream.write(writtenDate); } const std::string& writer = getWriter(); if (!writer.empty()) { propWriteStream.write(ATTR_WRITTENBY); propWriteStream.writeString(writer); } const std::string& specialDesc = getSpecialDescription(); if (!specialDesc.empty()) { propWriteStream.write(ATTR_DESC); propWriteStream.writeString(specialDesc); } if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) { propWriteStream.write(ATTR_DURATION); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DURATION)); } ItemDecayState_t decayState = getDecaying(); if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) { propWriteStream.write(ATTR_DECAYING_STATE); propWriteStream.write(decayState); } if (hasAttribute(ITEM_ATTRIBUTE_NAME)) { propWriteStream.write(ATTR_NAME); propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_NAME)); } if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) { propWriteStream.write(ATTR_ARTICLE); propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_ARTICLE)); } if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) { propWriteStream.write(ATTR_PLURALNAME); propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_PLURALNAME)); } if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) { propWriteStream.write(ATTR_WEIGHT); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_WEIGHT)); } if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) { propWriteStream.write(ATTR_ATTACK); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ATTACK)); } if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) { propWriteStream.write(ATTR_DEFENSE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DEFENSE)); } if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) { propWriteStream.write(ATTR_ARMOR); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_ARMOR)); } if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) { propWriteStream.write(ATTR_SHOOTRANGE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE)); } if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) { propWriteStream.write(ATTR_KEYNUMBER); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)); } if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) { propWriteStream.write(ATTR_KEYHOLENUMBER); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER)); } if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { propWriteStream.write(ATTR_DOORLEVEL); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL)); } if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) { propWriteStream.write(ATTR_DOORQUESTNUMBER); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER)); } if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) { propWriteStream.write(ATTR_DOORQUESTVALUE); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE)); } if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) { propWriteStream.write(ATTR_CHESTQUESTNUMBER); propWriteStream.write(getIntAttr(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)); } } bool Item::hasProperty(ITEMPROPERTY prop) const { const ItemType& it = items[id]; switch (prop) { case CONST_PROP_BLOCKSOLID: return it.blockSolid; case CONST_PROP_MOVEABLE: return it.moveable; case CONST_PROP_HASHEIGHT: return it.hasHeight; case CONST_PROP_BLOCKPROJECTILE: return it.blockProjectile; case CONST_PROP_BLOCKPATH: return it.blockPathFind; case CONST_PROP_ISVERTICAL: return it.isVertical; case CONST_PROP_ISHORIZONTAL: return it.isHorizontal; case CONST_PROP_IMMOVABLEBLOCKSOLID: return it.blockSolid && !it.moveable; case CONST_PROP_IMMOVABLEBLOCKPATH: return it.blockPathFind && !it.moveable; case CONST_PROP_IMMOVABLENOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind && !it.moveable; case CONST_PROP_NOFIELDBLOCKPATH: return !it.isMagicField() && it.blockPathFind; case CONST_PROP_SUPPORTHANGABLE: return it.isHorizontal || it.isVertical; case CONST_PROP_UNLAY: return !it.allowPickupable; default: return false; } } uint32_t Item::getWeight() const { uint32_t weight = getBaseWeight(); if (isStackable()) { return weight * std::max(1, getItemCount()); } return weight; } std::string Item::getDescription(const ItemType& it, int32_t lookDistance, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) { std::ostringstream s; s << getNameDescription(it, item, subType, addArticle); if (item) { subType = item->getSubType(); } if (it.isRune()) { uint32_t charges = std::max(static_cast(1), static_cast(item == nullptr ? it.charges : item->getCharges())); if (it.runeLevel > 0) { s << " for level " << it.runeLevel; } if (it.runeLevel > 0) { s << " and"; } s << " for magic level " << it.runeMagLevel; s << ". It's an \"" << it.runeSpellName << "\"-spell (" << charges << "x). "; } else if (it.isDoor() && item) { if (item->hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) { s << " for level " << item->getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL); } s << "."; } else if (it.weaponType != WEAPON_NONE) { if (it.weaponType == WEAPON_DISTANCE && it.ammoType != AMMO_NONE) { if (it.attack != 0) { s << ", Atk" << std::showpos << it.attack << std::noshowpos; } } else if (it.weaponType != WEAPON_AMMO && it.weaponType != WEAPON_WAND && (it.attack != 0 || it.defense != 0)) { s << " ("; if (it.attack != 0) { s << "Atk:" << static_cast(it.attack); } if (it.defense != 0) { if (it.attack != 0) s << " "; s << "Def:" << static_cast(it.defense); } s << ")"; } s << "."; } else if (it.armor != 0) { if (it.charges > 0) { if (subType > 1) { s << " that has " << static_cast(subType) << " charges left"; } else { s << " that has " << it.charges << " charge left"; } } s << " (Arm:" << it.armor << ")."; } else if (it.isFluidContainer()) { if (item && item->getFluidType() != 0) { s << " of " << items[item->getFluidType()].name << "."; } else { s << ". It is empty."; } } else if (it.isSplash()) { s << " of "; if (item && item->getFluidType() != 0) { s << items[item->getFluidType()].name; } else { s << items[1].name; } s << "."; } else if (it.isContainer() && !it.isChest()) { s << " (Vol:" << static_cast(it.maxItems) << ")."; } else if (it.isKey()) { if (item) { s << " (Key:" << static_cast(item->getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER)) << ")."; } else { s << " (Key:0)."; } } else if (it.allowDistRead) { s << "."; s << std::endl; if (item && item->getText() != "") { if (lookDistance <= 4) { const std::string& writer = item->getWriter(); if (!writer.empty()) { s << writer << " wrote"; time_t date = item->getDate(); if (date != 0) { s << " on " << formatDateShort(date); } s << ": "; } else { s << "You read: "; } s << item->getText(); } else { s << "You are too far away to read it."; } } else { s << "Nothing is written on it."; } } else if (it.charges > 0) { uint32_t charges = (item == nullptr ? it.charges : item->getCharges()); if (charges > 1) { s << " that has " << static_cast(charges) << " charges left."; } else { s << " that has 1 charge left."; } } else if (it.showDuration) { if (item && item->hasAttribute(ITEM_ATTRIBUTE_DURATION)) { int32_t duration = item->getDuration() / 1000; s << " that has energy for "; if (duration >= 120) { s << duration / 60 << " minutes left."; } else if (duration > 60) { s << "1 minute left."; } else { s << "less than a minute left."; } } else { s << " that is brand-new."; } } else { s << "."; } if (it.wieldInfo != 0) { s << std::endl << "It can only be wielded properly by "; if (it.wieldInfo & WIELDINFO_PREMIUM) { s << "premium "; } if (it.wieldInfo & WIELDINFO_VOCREQ) { s << it.vocationString; } else { s << "players"; } if (it.wieldInfo & WIELDINFO_LEVEL) { s << " of level " << static_cast(it.minReqLevel) << " or higher"; } if (it.wieldInfo & WIELDINFO_MAGLV) { if (it.wieldInfo & WIELDINFO_LEVEL) { s << " and"; } else { s << " of"; } s << " magic level " << static_cast(it.minReqMagicLevel) << " or higher"; } s << "."; } if (lookDistance <= 1 && !it.isChest() && it.pickupable) { double weight = (item == nullptr ? it.weight : item->getWeight()); if (weight > 0) { s << std::endl << getWeightDescription(it, weight); } } if (item && item->getSpecialDescription() != "") { s << std::endl << item->getSpecialDescription().c_str(); } else if (it.description.length() && lookDistance <= 1) { s << std::endl << it.description << "."; } return s.str(); } std::string Item::getDescription(int32_t lookDistance) const { const ItemType& it = items[id]; return getDescription(it, lookDistance, this); } std::string Item::getNameDescription(const ItemType& it, const Item* item /*= nullptr*/, int32_t subType /*= -1*/, bool addArticle /*= true*/) { if (item) { subType = item->getSubType(); } std::ostringstream s; const std::string& name = (item ? item->getName() : it.name); if (!name.empty()) { if (it.stackable && subType > 1) { if (it.showCount) { s << subType << ' '; } s << (item ? item->getPluralName() : it.getPluralName()); } else { if (addArticle) { const std::string& article = (item ? item->getArticle() : it.article); if (!article.empty()) { s << article << ' '; } } s << name; } } else { s << "an item of type " << it.id; } return s.str(); } std::string Item::getNameDescription() const { const ItemType& it = items[id]; return getNameDescription(it, this); } std::string Item::getWeightDescription(const ItemType& it, uint32_t weight, uint32_t count /*= 1*/) { std::ostringstream ss; if (it.stackable && count > 1 && it.showCount != 0) { ss << "They weigh "; } else { ss << "It weighs "; } if (weight < 10) { ss << "0.0" << weight; } else if (weight < 100) { ss << "0." << weight; } else { std::string weightString = std::to_string(weight); weightString.insert(weightString.end() - 2, '.'); ss << weightString; } ss << " oz."; return ss.str(); } std::string Item::getWeightDescription(uint32_t weight) const { const ItemType& it = Item::items[id]; return getWeightDescription(it, weight, getItemCount()); } std::string Item::getWeightDescription() const { uint32_t weight = getWeight(); if (weight == 0) { return std::string(); } return getWeightDescription(weight); } bool Item::canDecay() const { if (isRemoved()) { return false; } const ItemType& it = Item::items[id]; if (it.decayTo < 0 || it.decayTime == 0) { return false; } return true; } uint32_t Item::getWorth() const { switch (id) { case ITEM_GOLD_COIN: return count; case ITEM_PLATINUM_COIN: return count * 100; case ITEM_CRYSTAL_COIN: return count * 10000; default: return 0; } } void Item::getLight(LightInfo& lightInfo) const { const ItemType& it = items[id]; lightInfo.color = it.lightColor; lightInfo.level = it.lightLevel; } std::string ItemAttributes::emptyString; const std::string& ItemAttributes::getStrAttr(itemAttrTypes type) const { if (!isStrAttrType(type)) { return emptyString; } const Attribute* attr = getExistingAttr(type); if (!attr) { return emptyString; } return *attr->value.string; } void ItemAttributes::setStrAttr(itemAttrTypes type, const std::string& value) { if (!isStrAttrType(type)) { return; } if (value.empty()) { return; } Attribute& attr = getAttr(type); delete attr.value.string; attr.value.string = new std::string(value); } void ItemAttributes::removeAttribute(itemAttrTypes type) { if (!hasAttribute(type)) { return; } auto prev_it = attributes.cbegin(); if ((*prev_it).type == type) { attributes.pop_front(); } else { auto it = prev_it, end = attributes.cend(); while (++it != end) { if ((*it).type == type) { attributes.erase_after(prev_it); break; } prev_it = it; } } attributeBits &= ~type; } int64_t ItemAttributes::getIntAttr(itemAttrTypes type) const { if (!isIntAttrType(type)) { return 0; } const Attribute* attr = getExistingAttr(type); if (!attr) { return 0; } return attr->value.integer; } void ItemAttributes::setIntAttr(itemAttrTypes type, int64_t value) { if (!isIntAttrType(type)) { return; } getAttr(type).value.integer = value; } void ItemAttributes::increaseIntAttr(itemAttrTypes type, int64_t value) { if (!isIntAttrType(type)) { return; } getAttr(type).value.integer += value; } const ItemAttributes::Attribute* ItemAttributes::getExistingAttr(itemAttrTypes type) const { if (hasAttribute(type)) { for (const Attribute& attribute : attributes) { if (attribute.type == type) { return &attribute; } } } return nullptr; } ItemAttributes::Attribute& ItemAttributes::getAttr(itemAttrTypes type) { if (hasAttribute(type)) { for (Attribute& attribute : attributes) { if (attribute.type == type) { return attribute; } } } attributeBits |= type; attributes.emplace_front(type); return attributes.front(); } void Item::startDecaying() { g_game.startDecay(this); }