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 "minion_equipment.h"
19 #include "item.h"
20 #include "creature.h"
21 #include "effect.h"
22 #include "equipment.h"
23 #include "creature_attributes.h"
24 #include "effect.h"
25 #include "body.h"
26 #include "item_class.h"
27 #include "corpse_info.h"
28 
isCombatConsumable(Effect type)29 static bool isCombatConsumable(Effect type) {
30   return type.visit(
31       [&](const Effect::Teleport&) { return true; },
32       [&](const Effect::Lasting& e) {
33         switch (e.lastingEffect) {
34           case LastingEffect::SPEED:
35           case LastingEffect::SLOWED:
36           case LastingEffect::SLEEP:
37           case LastingEffect::POISON:
38           case LastingEffect::BLIND:
39           case LastingEffect::INVISIBLE:
40           case LastingEffect::DAM_BONUS:
41           case LastingEffect::DEF_BONUS:
42           case LastingEffect::POISON_RESISTANT:
43           case LastingEffect::REGENERATION:
44             return true;
45           default:
46             return false;
47         }
48       },
49       [&](const auto&) { return false; }
50   );
51 }
52 
53 template <class Archive>
serialize(Archive & ar,const unsigned int)54 void MinionEquipment::serialize(Archive& ar, const unsigned int) {
55   ar(owners, locked, myItems);
56 }
57 
58 SERIALIZABLE(MinionEquipment);
59 
getEquipmentLimit(EquipmentType type) const60 optional<int> MinionEquipment::getEquipmentLimit(EquipmentType type) const {
61   switch (type) {
62     case MinionEquipment::COMBAT_ITEM:
63     case MinionEquipment::HEALING:
64       return 6;
65     default:
66       return none;
67   }
68 }
69 
getEquipmentType(WConstItem it)70 optional<MinionEquipment::EquipmentType> MinionEquipment::getEquipmentType(WConstItem it) {
71   if (it->canEquip())
72     return MinionEquipment::ARMOR;
73   if (auto& effect = it->getEffect()) {
74     if (effect->isType<Effect::Heal>())
75       return MinionEquipment::HEALING;
76     if (isCombatConsumable(*effect))
77       return MinionEquipment::COMBAT_ITEM;
78   }
79   return none;
80 }
81 
isItemUseful(WConstItem it)82 bool MinionEquipment::isItemUseful(WConstItem it) {
83   static EnumSet<ItemClass> usefulItems {ItemClass::GOLD, ItemClass::POTION, ItemClass::SCROLL};
84   return getEquipmentType(it) || usefulItems.contains(it->getClass())
85       || (it->getClass() == ItemClass::FOOD && !it->getCorpseInfo());
86 }
87 
needsItem(WConstCreature c,WConstItem it,bool noLimit) const88 bool MinionEquipment::needsItem(WConstCreature c, WConstItem it, bool noLimit) const {
89   if (optional<EquipmentType> type = getEquipmentType(it)) {
90     if (!noLimit) {
91       auto itemValue = getItemValue(c, it);
92       if (auto limit = getEquipmentLimit(*type)) {
93         auto pred = [=](WConstItem ownedItem) {
94           return getEquipmentType(ownedItem) == *type &&
95               (getItemValue(c, ownedItem) >= itemValue || isLocked(c, ownedItem->getUniqueId())) &&
96               ownedItem != it;
97         };
98         if (getItemsOwnedBy(c, pred).size() >= *limit)
99           return false;
100       }
101       if (it->canEquip()) {
102         auto slot = it->getEquipmentSlot();
103         int limit = c->getEquipment().getMaxItems(slot);
104         auto pred = [=](WConstItem ownedItem) {
105           return ownedItem->canEquip() &&
106               ownedItem->getEquipmentSlot() == slot &&
107               (getItemValue(c, ownedItem) >= itemValue || isLocked(c, ownedItem->getUniqueId())) &&
108               ownedItem != it;
109         };
110         if (getItemsOwnedBy(c, pred).size() >= limit)
111           return false;
112       }
113     }
114     return (c->canEquipIfEmptySlot(it))
115       || (type == HEALING && c->getBody().hasHealth())
116       || type == COMBAT_ITEM;
117   } else
118     return false;
119 }
120 
getOwner(WConstItem it) const121 optional<Creature::Id> MinionEquipment::getOwner(WConstItem it) const {
122   if (auto creature = owners.getMaybe(it))
123     return *creature;
124   else
125     return none;
126 }
127 
isOwner(WConstItem it,WConstCreature c) const128 bool MinionEquipment::isOwner(WConstItem it, WConstCreature c) const {
129   return getOwner(it) == c->getUniqueId();
130 }
131 
132 const static vector<WItem> emptyItems;
133 
updateOwners(const vector<WCreature> & creatures)134 void MinionEquipment::updateOwners(const vector<WCreature>& creatures) {
135   auto oldItemMap = myItems;
136   myItems.clear();
137   owners.clear();
138   for (auto c : creatures)
139     for (auto item : oldItemMap.getOrElse(c, emptyItems))
140       if (item) {
141         owners.set(item, c->getUniqueId());
142         myItems.getOrInit(c).push_back(item);
143       }
144   for (auto c : creatures)
145     for (auto item : getItemsOwnedBy(c))
146       if (!needsItem(c, item))
147         discard(item);
148 }
149 
updateItems(const vector<WItem> & items)150 void MinionEquipment::updateItems(const vector<WItem>& items) {
151   auto oldOwners = owners;
152   myItems.clear();
153   owners.clear();
154   for (auto it : items)
155     if (auto owner = oldOwners.getMaybe(it)) {
156       myItems.getOrInit(*owner).push_back(it);
157       owners.set(it->getUniqueId(), *owner);
158     }
159 }
160 
getItemsOwnedBy(WConstCreature c,ItemPredicate predicate) const161 vector<WItem> MinionEquipment::getItemsOwnedBy(WConstCreature c, ItemPredicate predicate) const {
162   vector<WItem> ret;
163   for (auto& item : myItems.getOrElse(c, emptyItems))
164     if (item)
165       if (!predicate || predicate(item))
166         ret.push_back(item);
167   return ret;
168 }
169 
discard(WConstItem it)170 void MinionEquipment::discard(WConstItem it) {
171   discard(it->getUniqueId());
172 }
173 
discard(UniqueEntity<Item>::Id id)174 void MinionEquipment::discard(UniqueEntity<Item>::Id id) {
175   if (auto owner = owners.getMaybe(id)) {
176     locked.erase(make_pair(*owner, id));
177     owners.erase(id);
178     auto& items = myItems.getOrFail(*owner);
179     for (int i : All(items))
180       if (items[i])
181         if (items[i]->getUniqueId() == id) {
182           items.removeIndex(i);
183           break;
184         }
185   }
186 }
187 
sortByEquipmentValue(WConstCreature c,vector<WItem> & items) const188 void MinionEquipment::sortByEquipmentValue(WConstCreature c, vector<WItem>& items) const {
189   sort(items.begin(), items.end(), [this, c](WConstItem it1, WConstItem it2) {
190       int diff = getItemValue(c, it1) - getItemValue(c, it2);
191       if (diff == 0)
192         return it1->getUniqueId() < it2->getUniqueId();
193       else
194         return diff > 0;
195     });
196 }
197 
tryToOwn(WConstCreature c,WItem it)198 bool MinionEquipment::tryToOwn(WConstCreature c, WItem it) {
199   if (it->canEquip()) {
200     auto slot = it->getEquipmentSlot();
201     vector<WItem> contesting;
202     int slotSize = c->getEquipment().getMaxItems(slot);
203     for (auto& item : myItems.getOrElse(c, emptyItems))
204       if (item)
205         if (item->canEquip() && item->getEquipmentSlot() == slot) {
206           if (!isLocked(c, item->getUniqueId()))
207             contesting.push_back(item);
208           else if (--slotSize <= 0)
209             return false;
210         }
211     if (contesting.size() >= slotSize) {
212       sortByEquipmentValue(c, contesting);
213       for (int i = slotSize - 1; i < contesting.size(); ++i)
214         discard(contesting[i]);
215     }
216   }
217   discard(it);
218   owners.set(it, c->getUniqueId());
219   myItems.getOrInit(c).push_back(it);
220   return true;
221 }
222 
getWorstItem(WConstCreature c,vector<WItem> items) const223 WItem MinionEquipment::getWorstItem(WConstCreature c, vector<WItem> items) const {
224   WItem ret = nullptr;
225   for (WItem it : items)
226     if (!isLocked(c, it->getUniqueId()) &&
227         (!ret || getItemValue(c, it) < getItemValue(c, ret)))
228       ret = it;
229   return ret;
230 }
231 
autoAssign(WConstCreature creature,vector<WItem> possibleItems)232 void MinionEquipment::autoAssign(WConstCreature creature, vector<WItem> possibleItems) {
233   map<EquipmentSlot, vector<WItem>> slots;
234   for (WItem it : getItemsOwnedBy(creature))
235     if (it->canEquip()) {
236       EquipmentSlot slot = it->getEquipmentSlot();
237       slots[slot].push_back(it);
238     }
239   sortByEquipmentValue(creature, possibleItems);
240   for (WItem it : possibleItems)
241     if (!getOwner(it) && needsItem(creature, it)) {
242       if (!it->canEquip()) {
243         CHECK(tryToOwn(creature, it));
244         continue;
245       }
246       WItem replacedItem = getWorstItem(creature, slots[it->getEquipmentSlot()]);
247       int slotSize = creature->getEquipment().getMaxItems(it->getEquipmentSlot());
248       int numInSlot = slots[it->getEquipmentSlot()].size();
249       if (numInSlot < slotSize ||
250           (replacedItem && getItemValue(creature, replacedItem) < getItemValue(creature, it))) {
251         if (numInSlot == slotSize) {
252           discard(replacedItem);
253           slots[it->getEquipmentSlot()].removeElement(replacedItem);
254         }
255         CHECK(tryToOwn(creature, it));
256         slots[it->getEquipmentSlot()].push_back(it);
257         break;
258       }
259   }
260 }
261 
getItemValue(WConstCreature c,WConstItem it) const262 int MinionEquipment::getItemValue(WConstCreature c, WConstItem it) const {
263   int sum = 0;
264   for (auto attr : ENUM_ALL(AttrType))
265     switch (attr) {
266       case AttrType::SPEED:
267         sum += 10 * it->getModifier(attr);
268         break;
269       case AttrType::DAMAGE:
270       case AttrType::SPELL_DAMAGE:
271         if (it->getClass() != ItemClass::WEAPON || attr == it->getMeleeAttackAttr())
272           sum += c->getAttributes().getRawAttr(attr) + it->getModifier(attr);
273         break;
274       default:
275         sum += it->getModifier(attr);
276         break;
277     }
278   return sum;
279 }
280 
setLocked(WConstCreature c,UniqueEntity<Item>::Id it,bool lock)281 void MinionEquipment::setLocked(WConstCreature c, UniqueEntity<Item>::Id it, bool lock) {
282   if (lock)
283     locked.insert(make_pair(c->getUniqueId(), it));
284   else
285     locked.erase(make_pair(c->getUniqueId(), it));
286 }
287 
isLocked(WConstCreature c,UniqueEntity<Item>::Id it) const288 bool MinionEquipment::isLocked(WConstCreature c, UniqueEntity<Item>::Id it) const {
289   return locked.count(make_pair(c->getUniqueId(), it));
290 }
291 
292