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