2021-04-14 22:52:30 +03:00

1305 lines
27 KiB
C++

/**
* Tibia GIMUD Server - a free and open-source MMORPG server emulator
* Copyright (C) 2019 Sabrehaven and Mark Samman <mark.samman@gmail.com>
*
* 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<uint16_t>(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<Cylinder*>(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<const Cylinder*>(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<Tile*>(cylinder);
}
const Tile* Item::getTile() const
{
const Cylinder* cylinder = getTopParent();
//get root cylinder
if (cylinder && cylinder->getParent()) {
cylinder = cylinder->getParent();
}
return dynamic_cast<const Tile*>(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<uint8_t>(count)) {
return ATTR_READ_ERROR;
}
setSubType(count);
break;
}
case ATTR_ACTION_ID: {
uint16_t actionId;
if (!propStream.read<uint16_t>(actionId)) {
return ATTR_READ_ERROR;
}
setActionId(actionId);
break;
}
case ATTR_MOVEMENT_ID: {
uint16_t movementId;
if (!propStream.read<uint16_t>(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<uint32_t>(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<uint16_t>(charges)) {
return ATTR_READ_ERROR;
}
setSubType(charges);
break;
}
case ATTR_DURATION: {
int32_t duration;
if (!propStream.read<int32_t>(duration)) {
return ATTR_READ_ERROR;
}
setDuration(std::max<int32_t>(0, duration));
break;
}
case ATTR_DECAYING_STATE: {
uint8_t state;
if (!propStream.read<uint8_t>(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<uint32_t>(weight)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_WEIGHT, weight);
break;
}
case ATTR_ATTACK: {
int32_t attack;
if (!propStream.read<int32_t>(attack)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_ATTACK, attack);
break;
}
case ATTR_DEFENSE: {
int32_t defense;
if (!propStream.read<int32_t>(defense)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_DEFENSE, defense);
break;
}
case ATTR_ARMOR: {
int32_t armor;
if (!propStream.read<int32_t>(armor)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_ARMOR, armor);
break;
}
case ATTR_SHOOTRANGE: {
uint8_t shootRange;
if (!propStream.read<uint8_t>(shootRange)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE, shootRange);
break;
}
case ATTR_KEYNUMBER: {
uint16_t keyNumber;
if (!propStream.read<uint16_t>(keyNumber)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_KEYNUMBER, keyNumber);
break;
}
case ATTR_KEYHOLENUMBER:
{
uint16_t keyHoleNumber;
if (!propStream.read<uint16_t>(keyHoleNumber)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER, keyHoleNumber);
break;
}
case ATTR_DOORLEVEL:
{
uint16_t doorLevel;
if (!propStream.read<uint16_t>(doorLevel)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_DOORLEVEL, doorLevel);
break;
}
case ATTR_DOORQUESTNUMBER:
{
uint16_t doorQuestNumber;
if (!propStream.read<uint16_t>(doorQuestNumber)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER, doorQuestNumber);
break;
}
case ATTR_DOORQUESTVALUE:
{
uint16_t doorQuestValue;
if (!propStream.read<uint16_t>(doorQuestValue)) {
return ATTR_READ_ERROR;
}
setIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE, doorQuestValue);
break;
}
case ATTR_CHESTQUESTNUMBER:
{
uint16_t chestQuestNumber;
if (!propStream.read<uint16_t>(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<uint8_t>(attr_type) && attr_type != 0) {
Attr_ReadValue ret = readAttr(static_cast<AttrTypes_t>(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<uint8_t>(ATTR_COUNT);
propWriteStream.write<uint8_t>(getSubType());
}
uint16_t charges = getCharges();
if (charges != 0) {
propWriteStream.write<uint8_t>(ATTR_CHARGES);
propWriteStream.write<uint16_t>(charges);
}
if (it.moveable) {
uint16_t actionId = getActionId();
if (actionId != 0) {
propWriteStream.write<uint8_t>(ATTR_ACTION_ID);
propWriteStream.write<uint16_t>(actionId);
}
}
const std::string& text = getText();
if (!text.empty()) {
propWriteStream.write<uint8_t>(ATTR_TEXT);
propWriteStream.writeString(text);
}
const time_t writtenDate = getDate();
if (writtenDate != 0) {
propWriteStream.write<uint8_t>(ATTR_WRITTENDATE);
propWriteStream.write<uint32_t>(writtenDate);
}
const std::string& writer = getWriter();
if (!writer.empty()) {
propWriteStream.write<uint8_t>(ATTR_WRITTENBY);
propWriteStream.writeString(writer);
}
const std::string& specialDesc = getSpecialDescription();
if (!specialDesc.empty()) {
propWriteStream.write<uint8_t>(ATTR_DESC);
propWriteStream.writeString(specialDesc);
}
if (hasAttribute(ITEM_ATTRIBUTE_DURATION)) {
propWriteStream.write<uint8_t>(ATTR_DURATION);
propWriteStream.write<uint32_t>(getIntAttr(ITEM_ATTRIBUTE_DURATION));
}
ItemDecayState_t decayState = getDecaying();
if (decayState == DECAYING_TRUE || decayState == DECAYING_PENDING) {
propWriteStream.write<uint8_t>(ATTR_DECAYING_STATE);
propWriteStream.write<uint8_t>(decayState);
}
if (hasAttribute(ITEM_ATTRIBUTE_NAME)) {
propWriteStream.write<uint8_t>(ATTR_NAME);
propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_NAME));
}
if (hasAttribute(ITEM_ATTRIBUTE_ARTICLE)) {
propWriteStream.write<uint8_t>(ATTR_ARTICLE);
propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_ARTICLE));
}
if (hasAttribute(ITEM_ATTRIBUTE_PLURALNAME)) {
propWriteStream.write<uint8_t>(ATTR_PLURALNAME);
propWriteStream.writeString(getStrAttr(ITEM_ATTRIBUTE_PLURALNAME));
}
if (hasAttribute(ITEM_ATTRIBUTE_WEIGHT)) {
propWriteStream.write<uint8_t>(ATTR_WEIGHT);
propWriteStream.write<uint32_t>(getIntAttr(ITEM_ATTRIBUTE_WEIGHT));
}
if (hasAttribute(ITEM_ATTRIBUTE_ATTACK)) {
propWriteStream.write<uint8_t>(ATTR_ATTACK);
propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_ATTACK));
}
if (hasAttribute(ITEM_ATTRIBUTE_DEFENSE)) {
propWriteStream.write<uint8_t>(ATTR_DEFENSE);
propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_DEFENSE));
}
if (hasAttribute(ITEM_ATTRIBUTE_ARMOR)) {
propWriteStream.write<uint8_t>(ATTR_ARMOR);
propWriteStream.write<int32_t>(getIntAttr(ITEM_ATTRIBUTE_ARMOR));
}
if (hasAttribute(ITEM_ATTRIBUTE_SHOOTRANGE)) {
propWriteStream.write<uint8_t>(ATTR_SHOOTRANGE);
propWriteStream.write<uint8_t>(getIntAttr(ITEM_ATTRIBUTE_SHOOTRANGE));
}
if (hasAttribute(ITEM_ATTRIBUTE_KEYNUMBER)) {
propWriteStream.write<uint8_t>(ATTR_KEYNUMBER);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYNUMBER));
}
if (hasAttribute(ITEM_ATTRIBUTE_KEYHOLENUMBER)) {
propWriteStream.write<uint8_t>(ATTR_KEYHOLENUMBER);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_KEYHOLENUMBER));
}
if (hasAttribute(ITEM_ATTRIBUTE_DOORLEVEL)) {
propWriteStream.write<uint8_t>(ATTR_DOORLEVEL);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORLEVEL));
}
if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTNUMBER)) {
propWriteStream.write<uint8_t>(ATTR_DOORQUESTNUMBER);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTNUMBER));
}
if (hasAttribute(ITEM_ATTRIBUTE_DOORQUESTVALUE)) {
propWriteStream.write<uint8_t>(ATTR_DOORQUESTVALUE);
propWriteStream.write<uint16_t>(getIntAttr(ITEM_ATTRIBUTE_DOORQUESTVALUE));
}
if (hasAttribute(ITEM_ATTRIBUTE_CHESTQUESTNUMBER)) {
propWriteStream.write<uint8_t>(ATTR_CHESTQUESTNUMBER);
propWriteStream.write<uint16_t>(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<uint32_t>(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<uint32_t>(1), static_cast<uint32_t>(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 (item->getAttack() != 0) {
s << ", Atk" << std::showpos << item->getAttack() << std::noshowpos;
}
}
else if (it.weaponType != WEAPON_WAND && (item->getAttack() != 0 || item->getDefense() != 0)) {
s << " (";
if (item->getAttack() != 0) {
s << "Atk:" << static_cast<int>(item->getAttack());
}
if (item->getDefense() != 0) {
if (item->getAttack() != 0)
s << " ";
s << "Def:" << static_cast<int>(item->getDefense());
}
s << ")";
}
s << ".";
}
else if (item->getArmor() != 0 || (it.abilities && it.abilities->speed != 0)) {
if (it.charges > 0) {
if (subType > 1) {
s << " that has " << static_cast<int32_t>(subType) << " charges left";
}
else {
s << " that has " << it.charges << " charge left";
}
}
s << " (";
if (item->getArmor() > 0) {
s << "Arm:" << item->getArmor();
}
if (it.abilities && it.abilities->speed > 0) {
if (item->getArmor() > 0) {
s << ", ";
}
s << "Speed +" << it.abilities->speed;
}
s << ").";
}
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<int>(it.maxItems) << ").";
}
else if (it.isKey()) {
if (item) {
s << " (Key:" << static_cast<int>(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<int>(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<int>(it.minReqLevel) << " or higher";
}
if (it.wieldInfo & WIELDINFO_MAGLV) {
if (it.wieldInfo & WIELDINFO_LEVEL) {
s << " and";
}
else {
s << " of";
}
s << " magic level " << static_cast<int>(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);
}