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