1 /* Copyright (C) 2013-2014 Michal Brzozowski (rusolis@poczta.fm)
2 
3    This file is part of KeeperRL.
4 
5    KeeperRL is free software; you can redistribute it and/or modify it under the terms of the
6    GNU General Public License as published by the Free Software Foundation; either version 2
7    of the License, or (at your option) any later version.
8 
9    KeeperRL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
10    even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11    GNU General Public License for more details.
12 
13    You should have received a copy of the GNU General Public License along with this program.
14    If not, see http://www.gnu.org/licenses/ . */
15 
16 #include "stdafx.h"
17 
18 #include "item.h"
19 #include "creature.h"
20 #include "level.h"
21 #include "statistics.h"
22 #include "effect.h"
23 #include "view_object.h"
24 #include "game.h"
25 #include "player_message.h"
26 #include "fire.h"
27 #include "item_attributes.h"
28 #include "view.h"
29 #include "sound.h"
30 #include "item_class.h"
31 #include "corpse_info.h"
32 #include "equipment.h"
33 #include "attr_type.h"
34 #include "attack.h"
35 #include "lasting_effect.h"
36 
37 template <class Archive>
serialize(Archive & ar,const unsigned int version)38 void Item::serialize(Archive& ar, const unsigned int version) {
39   ar & SUBCLASS(OwnedObject<Item>) & SUBCLASS(UniqueEntity) & SUBCLASS(Renderable);
40   ar(attributes, discarded, shopkeeper, fire, classCache, canEquipCache);
41 }
42 
43 SERIALIZABLE(Item)
SERIALIZATION_CONSTRUCTOR_IMPL(Item)44 SERIALIZATION_CONSTRUCTOR_IMPL(Item)
45 
46 Item::Item(const ItemAttributes& attr) : Renderable(ViewObject(*attr.viewId, ViewLayer::ITEM, capitalFirst(*attr.name))),
47     attributes(attr), fire(*attr.weight, attr.flamability), canEquipCache(!!attributes->equipmentSlot),
48     classCache(*attributes->itemClass) {
49 }
50 
~Item()51 Item::~Item() {
52 }
53 
effectPredicate(Effect type)54 ItemPredicate Item::effectPredicate(Effect type) {
55   return [type](WConstItem item) { return item->getEffect() == type; };
56 }
57 
classPredicate(ItemClass cl)58 ItemPredicate Item::classPredicate(ItemClass cl) {
59   return [cl](WConstItem item) { return item->getClass() == cl; };
60 }
61 
equipmentSlotPredicate(EquipmentSlot slot)62 ItemPredicate Item::equipmentSlotPredicate(EquipmentSlot slot) {
63   return [slot](WConstItem item) { return item->canEquip() && item->getEquipmentSlot() == slot; };
64 }
65 
classPredicate(vector<ItemClass> cl)66 ItemPredicate Item::classPredicate(vector<ItemClass> cl) {
67   return [cl](WConstItem item) { return cl.contains(item->getClass()); };
68 }
69 
namePredicate(const string & name)70 ItemPredicate Item::namePredicate(const string& name) {
71   return [name](WConstItem item) { return item->getName() == name; };
72 }
73 
isRangedWeaponPredicate()74 ItemPredicate Item::isRangedWeaponPredicate() {
75  return [](WConstItem it) { return it->canEquip() && it->getEquipmentSlot() == EquipmentSlot::RANGED_WEAPON;};
76 }
77 
stackItems(vector<WItem> items,function<string (WConstItem)> suffix)78 vector<vector<WItem>> Item::stackItems(vector<WItem> items, function<string(WConstItem)> suffix) {
79   map<string, vector<WItem>> stacks = groupBy<WItem, string>(items, [suffix](WConstItem item) {
80         return item->getNameAndModifiers() + suffix(item);
81       });
82   vector<vector<WItem>> ret;
83   for (auto& elem : stacks)
84     ret.push_back(elem.second);
85   return ret;
86 }
87 
onEquip(WCreature c)88 void Item::onEquip(WCreature c) {
89   onEquipSpecial(c);
90   if (attributes->equipedEffect)
91     c->addPermanentEffect(*attributes->equipedEffect);
92 }
93 
onUnequip(WCreature c)94 void Item::onUnequip(WCreature c) {
95   onUnequipSpecial(c);
96   if (attributes->equipedEffect)
97     c->removePermanentEffect(*attributes->equipedEffect);
98 }
99 
fireDamage(double amount,Position position)100 void Item::fireDamage(double amount, Position position) {
101   bool burning = fire->isBurning();
102   string noBurningName = getTheName();
103   fire->set(amount);
104   if (!burning && fire->isBurning()) {
105     position.globalMessage(noBurningName + " catches fire");
106     modViewObject().setAttribute(ViewObject::Attribute::BURNING, fire->getSize());
107   }
108 }
109 
getFireSize() const110 double Item::getFireSize() const {
111   return fire->getSize();
112 }
113 
tick(Position position)114 void Item::tick(Position position) {
115   if (fire->isBurning()) {
116     INFO << getName() << " burning " << fire->getSize();
117     position.fireDamage(fire->getSize());
118     modViewObject().setAttribute(ViewObject::Attribute::BURNING, fire->getSize());
119     fire->tick();
120     if (!fire->isBurning()) {
121       position.globalMessage(getTheName() + " burns out");
122       discarded = true;
123     }
124   }
125   specialTick(position);
126 }
127 
onHitSquareMessage(Position pos,int numItems)128 void Item::onHitSquareMessage(Position pos, int numItems) {
129   if (attributes->fragile) {
130     pos.globalMessage(getPluralTheNameAndVerb(numItems, "crashes", "crash") + " on the " + pos.getName());
131     pos.unseenMessage("You hear a crash");
132     discarded = true;
133   } else
134     pos.globalMessage(getPluralTheNameAndVerb(numItems, "hits", "hit") + " the " + pos.getName());
135 }
136 
onHitCreature(WCreature c,const Attack & attack,int numItems)137 void Item::onHitCreature(WCreature c, const Attack& attack, int numItems) {
138   if (attributes->fragile) {
139     c->you(numItems > 1 ? MsgType::ITEM_CRASHES_PLURAL : MsgType::ITEM_CRASHES, getPluralTheName(numItems));
140     discarded = true;
141   } else
142     c->you(numItems > 1 ? MsgType::HIT_THROWN_ITEM_PLURAL : MsgType::HIT_THROWN_ITEM, getPluralTheName(numItems));
143   if (c->takeDamage(attack))
144     return;
145   if (attributes->effect && getClass() == ItemClass::POTION) {
146     attributes->effect->applyToCreature(c, attack.attacker);
147   }
148 }
149 
getApplyTime() const150 double Item::getApplyTime() const {
151   return attributes->applyTime;
152 }
153 
getWeight() const154 double Item::getWeight() const {
155   return *attributes->weight;
156 }
157 
getDescription() const158 string Item::getDescription() const {
159   return attributes->description;
160 }
161 
getClass() const162 ItemClass Item::getClass() const {
163   return classCache;
164 }
165 
getPrice() const166 int Item::getPrice() const {
167   return attributes->price;
168 }
169 
isShopkeeper(WConstCreature c) const170 bool Item::isShopkeeper(WConstCreature c) const {
171   return shopkeeper == c->getUniqueId();
172 }
173 
setShopkeeper(WConstCreature s)174 void Item::setShopkeeper(WConstCreature s) {
175   if (s)
176     shopkeeper = s->getUniqueId();
177   else
178     shopkeeper = none;
179 }
180 
isOrWasForSale() const181 bool Item::isOrWasForSale() const {
182   return !!shopkeeper;
183 }
184 
getTrapType() const185 optional<TrapType> Item::getTrapType() const {
186   return attributes->trapType;
187 }
188 
getResourceId() const189 optional<CollectiveResourceId> Item::getResourceId() const {
190   return attributes->resourceId;
191 }
192 
apply(WCreature c,bool noSound)193 void Item::apply(WCreature c, bool noSound) {
194   if (attributes->applySound && !noSound)
195     c->addSound(*attributes->applySound);
196   applySpecial(c);
197 }
198 
applySpecial(WCreature c)199 void Item::applySpecial(WCreature c) {
200   if (attributes->itemClass == ItemClass::SCROLL)
201     c->getGame()->getStatistics().add(StatId::SCROLL_READ);
202   if (attributes->effect)
203     attributes->effect->applyToCreature(c);
204   if (attributes->uses > -1 && --attributes->uses == 0) {
205     discarded = true;
206     if (attributes->usedUpMsg)
207       c->privateMessage(getTheName() + " is used up.");
208   }
209 }
210 
getApplyMsgThirdPerson(WConstCreature owner) const211 string Item::getApplyMsgThirdPerson(WConstCreature owner) const {
212   if (attributes->applyMsgThirdPerson)
213     return *attributes->applyMsgThirdPerson;
214   switch (getClass()) {
215     case ItemClass::SCROLL: return "reads " + getAName(false, owner);
216     case ItemClass::POTION: return "drinks " + getAName(false, owner);
217     case ItemClass::BOOK: return "reads " + getAName(false, owner);
218     case ItemClass::TOOL: return "applies " + getAName(false, owner);
219     case ItemClass::FOOD: return "eats " + getAName(false, owner);
220     default: FATAL << "Bad type for applying " << (int)getClass();
221   }
222   return "";
223 }
224 
getApplyMsgFirstPerson(WConstCreature owner) const225 string Item::getApplyMsgFirstPerson(WConstCreature owner) const {
226   if (attributes->applyMsgFirstPerson)
227     return *attributes->applyMsgFirstPerson;
228   switch (getClass()) {
229     case ItemClass::SCROLL: return "read " + getAName(false, owner);
230     case ItemClass::POTION: return "drink " + getAName(false, owner);
231     case ItemClass::BOOK: return "read " + getAName(false, owner);
232     case ItemClass::TOOL: return "apply " + getAName(false, owner);
233     case ItemClass::FOOD: return "eat " + getAName(false, owner);
234     default: FATAL << "Bad type for applying " << (int)getClass();
235   }
236   return "";
237 }
238 
getNoSeeApplyMsg() const239 string Item::getNoSeeApplyMsg() const {
240   switch (getClass()) {
241     case ItemClass::SCROLL: return "You hear someone reading";
242     case ItemClass::POTION: return "";
243     case ItemClass::BOOK: return "You hear someone reading ";
244     case ItemClass::TOOL: return "";
245     case ItemClass::FOOD: return "";
246     default: FATAL << "Bad type for applying " << (int)getClass();
247   }
248   return "";
249 }
250 
setName(const string & n)251 void Item::setName(const string& n) {
252   attributes->name = n;
253 }
254 
getShopkeeper(WConstCreature owner) const255 WCreature Item::getShopkeeper(WConstCreature owner) const {
256   if (shopkeeper)
257     for (WCreature c : owner->getVisibleCreatures())
258       if (c->getUniqueId() == *shopkeeper)
259         return c;
260   return nullptr;
261 }
262 
getName(bool plural,WConstCreature owner) const263 string Item::getName(bool plural, WConstCreature owner) const {
264   string suff;
265   if (fire->isBurning())
266     suff.append(" (burning)");
267   if (owner && getShopkeeper(owner))
268     suff += " (" + toString(getPrice()) + (plural ? " gold each)" : " gold)");
269   if (owner && owner->isAffected(LastingEffect::BLIND))
270     return getBlindName(plural);
271   return getVisibleName(plural) + suff;
272 }
273 
getAName(bool getPlural,WConstCreature owner) const274 string Item::getAName(bool getPlural, WConstCreature owner) const {
275   if (attributes->noArticle || getPlural)
276     return getName(getPlural, owner);
277   else
278     return addAParticle(getName(getPlural, owner));
279 }
280 
getTheName(bool getPlural,WConstCreature owner) const281 string Item::getTheName(bool getPlural, WConstCreature owner) const {
282   string the = (attributes->noArticle || getPlural) ? "" : "the ";
283   return the + getName(getPlural, owner);
284 }
285 
getPluralName(int count) const286 string Item::getPluralName(int count) const {
287   if (count > 1)
288     return toString(count) + " " + getName(true);
289   else
290     return getName(false);
291 }
292 
getPluralTheName(int count) const293 string Item::getPluralTheName(int count) const {
294   if (count > 1)
295     return toString(count) + " " + getTheName(true);
296   else
297     return getTheName(false);
298 }
299 
getPluralTheNameAndVerb(int count,const string & verbSingle,const string & verbPlural) const300 string Item::getPluralTheNameAndVerb(int count, const string& verbSingle, const string& verbPlural) const {
301   return getPluralTheName(count) + " " + (count > 1 ? verbPlural : verbSingle);
302 }
303 
getVisibleName(bool getPlural) const304 string Item::getVisibleName(bool getPlural) const {
305   if (!getPlural)
306     return *attributes->name;
307   else {
308     if (attributes->plural)
309       return *attributes->plural;
310     else
311       return *attributes->name + "s";
312   }
313 }
314 
withSign(int a)315 static string withSign(int a) {
316   if (a >= 0)
317     return "+" + toString(a);
318   else
319     return toString(a);
320 }
321 
getArtifactName() const322 const optional<string>& Item::getArtifactName() const {
323   return attributes->artifactName;
324 }
325 
setArtifactName(const string & s)326 void Item::setArtifactName(const string& s) {
327   attributes->artifactName = s;
328 }
329 
getModifiers(bool shorten) const330 string Item::getModifiers(bool shorten) const {
331   string artStr;
332   if (attributes->artifactName) {
333     artStr = *attributes->artifactName;
334     if (!shorten)
335       artStr = " named " + artStr;
336   }
337   EnumSet<AttrType> printAttr;
338   if (!shorten) {
339     for (auto attr : ENUM_ALL(AttrType))
340       if (attributes->modifiers[attr] != 0)
341         printAttr.insert(attr);
342   } else
343     switch (getClass()) {
344       case ItemClass::RANGED_WEAPON:
345         printAttr.insert(getRangedWeapon()->getDamageAttr());
346         break;
347       case ItemClass::WEAPON:
348         printAttr.insert(attributes->meleeAttackAttr);
349         break;
350       case ItemClass::ARMOR:
351         printAttr.insert(AttrType::DEFENSE);
352         break;
353       default: break;
354     }
355   vector<string> attrStrings;
356   for (auto attr : printAttr)
357     attrStrings.push_back(withSign(attributes->modifiers[attr]) + (shorten ? "" : " " + ::getName(attr)));
358   string attrString = combine(attrStrings, true);
359   if (!attrString.empty())
360     attrString = " (" + attrString + ")";
361   if (attributes->uses > -1 && attributes->displayUses)
362     attrString += " (" + toString(attributes->uses) + " uses left)";
363   return artStr + attrString;
364 }
365 
getShortName(WConstCreature owner,bool noSuffix) const366 string Item::getShortName(WConstCreature owner, bool noSuffix) const {
367   if (owner && owner->isAffected(LastingEffect::BLIND) && attributes->blindName)
368     return getBlindName(false);
369   string name = getModifiers(true);
370   if (attributes->shortName && !attributes->artifactName)
371     name = *attributes->shortName + " " + name;
372   if (fire->isBurning() && !noSuffix)
373     name.append(" (burning)");
374   return name;
375 }
376 
getNameAndModifiers(bool getPlural,WConstCreature owner) const377 string Item::getNameAndModifiers(bool getPlural, WConstCreature owner) const {
378   return getName(getPlural, owner) + getModifiers();
379 }
380 
getBlindName(bool plural) const381 string Item::getBlindName(bool plural) const {
382   if (attributes->blindName)
383     return *attributes->blindName + (plural ? "s" : "");
384   else
385     return getName(plural);
386 }
387 
isDiscarded()388 bool Item::isDiscarded() {
389   return discarded;
390 }
391 
getEffect() const392 const optional<Effect>& Item::getEffect() const {
393   return attributes->effect;
394 }
395 
getAttackEffect() const396 optional<Effect> Item::getAttackEffect() const {
397   return attributes->attackEffect;
398 }
399 
canEquip() const400 bool Item::canEquip() const {
401   return canEquipCache;
402 }
403 
getEquipmentSlot() const404 EquipmentSlot Item::getEquipmentSlot() const {
405   CHECK(canEquip());
406   return *attributes->equipmentSlot;
407 }
408 
addModifier(AttrType type,int value)409 void Item::addModifier(AttrType type, int value) {
410   attributes->modifiers[type] += value;
411 }
412 
getModifier(AttrType type) const413 int Item::getModifier(AttrType type) const {
414   CHECK(abs(attributes->modifiers[type]) < 10000) << EnumInfo<AttrType>::getString(type) << " "
415       << attributes->modifiers[type] << " " << getName();
416   return attributes->modifiers[type];
417 }
418 
getRangedWeapon() const419 const optional<RangedWeapon>& Item::getRangedWeapon() const {
420   return attributes->rangedWeapon;
421 }
422 
getMeleeAttackAttr() const423 AttrType Item::getMeleeAttackAttr() const {
424   return attributes->meleeAttackAttr;
425 }
426 
getAttackType() const427 AttackType Item::getAttackType() const {
428   return attributes->attackType;
429 }
430 
isWieldedTwoHanded() const431 bool Item::isWieldedTwoHanded() const {
432   return attributes->twoHanded;
433 }
434 
getMinStrength() const435 int Item::getMinStrength() const {
436   return 10 + getWeight();
437 }
438 
getCorpseInfo() const439 optional<CorpseInfo> Item::getCorpseInfo() const {
440   return none;
441 }
442 
443