1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
8
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 *
19 */
20
21 //This class represents the inventory of stores (.sto), area containers (.are)
22 //or actors (.cre).
23
24 #include "Inventory.h"
25
26 #include "strrefs.h"
27
28 #include "CharAnimations.h"
29 #include "DisplayMessage.h"
30 #include "Game.h"
31 #include "GameData.h"
32 #include "GameScript/GSUtils.h"
33 #include "Interface.h"
34 #include "Item.h"
35 #include "Map.h"
36 #include "ScriptEngine.h"
37 #include "Scriptable/Actor.h"
38 #include "System/StringBuffer.h"
39
40 #include <cstdio>
41
42 namespace GemRB {
43
44 static int SLOT_HEAD = -1;
45 static int SLOT_MAGIC = -1;
46 static int SLOT_FIST = -1;
47 static int SLOT_MELEE = -1;
48 static int LAST_MELEE = -1;
49 static int SLOT_RANGED = -1;
50 static int LAST_RANGED = -1;
51 static int SLOT_QUICK = -1;
52 static int LAST_QUICK = -1;
53 static int SLOT_INV = -1;
54 static int LAST_INV = -1;
55 static int SLOT_LEFT = -1;
56 static int SLOT_ARMOR = -1;
57
58 //IWD2 style slots
59 static bool IWD2 = false;
60
61 [[noreturn]]
InvalidSlot(int slot)62 static void InvalidSlot(int slot)
63 {
64 error("Inventory", "Invalid slot: %d!\n", slot);
65 }
66
67 //This inline function returns both an item pointer and the slot data.
68 //slot is a dynamic slot number (SLOT_*)
GetItemPointer(ieDword slot,CREItem * & item) const69 inline Item *Inventory::GetItemPointer(ieDword slot, CREItem *&item) const
70 {
71 item = GetSlotItem(slot);
72 if (!item) return NULL;
73 if (!item->ItemResRef[0]) return NULL;
74 return gamedata->GetItem(item->ItemResRef);
75 }
76
Init()77 void Inventory::Init()
78 {
79 SLOT_MAGIC=-1;
80 SLOT_FIST=-1;
81 SLOT_MELEE=-1;
82 LAST_MELEE=-1;
83 SLOT_RANGED=-1;
84 LAST_RANGED=-1;
85 SLOT_QUICK=-1;
86 LAST_QUICK=-1;
87 SLOT_LEFT=-1;
88 SLOT_ARMOR=-1;
89 //TODO: set this correctly
90 IWD2 = false;
91 }
92
Inventory()93 Inventory::Inventory()
94 {
95 Owner = NULL;
96 InventoryType = INVENTORY_HEAP;
97 Weight = 0;
98 Equipped = IW_NO_EQUIPPED;
99 EquippedHeader = 0;
100 ItemExcl = 0;
101 memset(ItemTypes, 0, sizeof(ItemTypes));
102 }
103
~Inventory()104 Inventory::~Inventory()
105 {
106 for (size_t i = 0; i < Slots.size(); i++) {
107 delete Slots[i];
108 Slots[i] = NULL;
109 }
110 }
111
112 // duplicates the source inventory into the current one
113 // also changes the items to not drop, so simulacrum and similar don't become factories
CopyFrom(const Actor * source)114 void Inventory::CopyFrom(const Actor *source)
115 {
116 if (!source) {
117 return;
118 }
119
120 SetSlotCount(source->inventory.GetSlotCount());
121
122 // allocate the items and mark them undroppable
123 CREItem *tmp, *item;
124 for (size_t i = 0; i < source->inventory.Slots.size(); i++) {
125 item = source->inventory.Slots[i];
126 if (item) {
127 tmp = new CREItem();
128 memcpy(tmp, item, sizeof(CREItem));
129 tmp->Flags |= IE_INV_ITEM_UNDROPPABLE;
130 int ret = AddSlotItem(tmp, i);
131 if (ret != ASI_SUCCESS) {
132 delete tmp;
133 }
134 }
135 }
136
137 // preserve the equipped status
138 Equipped = source->inventory.GetEquipped();
139 EquippedHeader = source->inventory.GetEquippedHeader();
140
141 CalculateWeight();
142 }
143
GetItem(unsigned int slot)144 CREItem *Inventory::GetItem(unsigned int slot)
145 {
146 if (slot >= Slots.size() ) {
147 InvalidSlot(slot);
148 }
149 CREItem *item = Slots[slot];
150 Slots.erase(Slots.begin()+slot);
151 CalculateWeight();
152 return item;
153 }
154
AddItem(CREItem * item)155 void Inventory::AddItem(CREItem *item)
156 {
157 if (!item) return; //invalid items get no slot
158 Slots.push_back(item);
159 CalculateWeight();
160 }
161
CalculateWeight()162 void Inventory::CalculateWeight()
163 {
164 Weight = 0;
165 for (size_t i = 0; i < Slots.size(); i++) {
166 CREItem *slot = Slots[i];
167 if (!slot) {
168 continue;
169 }
170 if (slot->Weight == -1) {
171 Item *itm = gamedata->GetItem(slot->ItemResRef, true);
172 if (itm) {
173 slot->Weight = itm->Weight;
174 gamedata->FreeItem( itm, slot->ItemResRef, false );
175
176 // some items can't be dropped once they've been picked up,
177 // e.g. the portal key in BG2
178 if (!(slot->Flags & IE_INV_ITEM_MOVABLE)) {
179 slot->Flags |= IE_INV_ITEM_UNDROPPABLE;
180 }
181 } else {
182 Log(ERROR, "Inventory", "Invalid item: %s!", slot->ItemResRef);
183 slot->Weight = 0;
184 }
185 } else {
186 slot->Flags &= ~IE_INV_ITEM_ACQUIRED;
187 }
188 if (slot->Weight > 0) {
189 Weight += slot->Weight * ((slot->Usages[0] && slot->MaxStackAmount) ? slot->Usages[0] : 1);
190 }
191 }
192
193 if (Owner) {
194 Owner->SetBase(IE_ENCUMBRANCE, Weight);
195 }
196 }
197
AddSlotEffects(ieDword index)198 void Inventory::AddSlotEffects(ieDword index)
199 {
200 CREItem* slot;
201
202 const Item *itm = GetItemPointer(index, slot);
203 if (!itm) {
204 Log(ERROR, "Inventory", "Invalid item equipped...");
205 return;
206 }
207 ItemExcl|=itm->ItemExcl;
208 ieDword pos = itm->ItemType/32;
209 ieDword bit = itm->ItemType%32;
210 if (pos<8) {
211 ItemTypes[pos]|=1<<bit;
212 }
213
214 ieWord gradient = itm->GetWieldedGradient();
215 if (gradient!=0xffff) {
216 Owner->SetBase(IE_COLORS, gradient);
217 }
218
219 //get the equipping effects
220 EffectQueue *eqfx = itm->GetEffectBlock(Owner, Owner->Pos, -1, index, 0);
221 gamedata->FreeItem( itm, slot->ItemResRef, false );
222
223 // always refresh, as even if eqfx is null, other effects may have been selfapplied from the block
224 Owner->RefreshEffects(eqfx);
225 //call gui for possible paperdoll animation changes
226 if (Owner->InParty) {
227 core->SetEventFlag(EF_UPDATEANIM);
228 }
229 }
230
231 //no need to know the item effects 'personally', the equipping slot
232 //is stored in them
RemoveSlotEffects(ieDword index)233 void Inventory::RemoveSlotEffects(ieDword index)
234 {
235 if (Owner->fxqueue.RemoveEquippingEffects(index)) {
236 Owner->RefreshEffects(NULL);
237 //call gui for possible paperdoll animation changes
238 if (Owner->InParty) {
239 core->SetEventFlag(EF_UPDATEANIM);
240 }
241 }
242 }
243
SetInventoryType(int arg)244 void Inventory::SetInventoryType(int arg)
245 {
246 InventoryType = arg;
247 }
248
SetSlotCount(unsigned int size)249 void Inventory::SetSlotCount(unsigned int size)
250 {
251 if (Slots.size()) {
252 error("Core", "Inventory size changed???\n");
253 //we don't allow reassignment,
254 //if you want this, delete the previous Slots here
255 }
256 Slots.assign((size_t) size, NULL);
257 }
258
259 /** if you supply a "" string, then it checks if the slot is empty */
HasItemInSlot(const char * resref,unsigned int slot) const260 bool Inventory::HasItemInSlot(const char *resref, unsigned int slot) const
261 {
262 if (slot >= Slots.size()) {
263 return false;
264 }
265 const CREItem *item = Slots[slot];
266 if (!item) {
267 return false;
268 }
269 if (!resref[0]) {
270 return true;
271 }
272 if (strnicmp( item->ItemResRef, resref, 8 )==0) {
273 return true;
274 }
275 return false;
276 }
277
HasItemType(ieDword type) const278 bool Inventory::HasItemType(ieDword type) const
279 {
280 if (type>255) return false;
281 int idx = type/32;
282 int bit = type%32;
283 return (ItemTypes[idx] & (1<<bit) )!=0;
284 }
285
286 /** counts the items in the inventory, if stacks == 1 then stacks are
287 accounted for their heap size */
CountItems(const char * resref,bool stacks) const288 int Inventory::CountItems(const char *resref, bool stacks) const
289 {
290 int count = 0;
291 size_t slot = Slots.size();
292 while(slot--) {
293 const CREItem *item = Slots[slot];
294 if (!item) {
295 continue;
296 }
297 if (resref && resref[0]) {
298 if (strnicmp(resref, item->ItemResRef, 8) )
299 continue;
300 }
301 if (stacks && (item->Flags&IE_INV_ITEM_STACKED) ) {
302 count+=item->Usages[0];
303 assert(count!=0);
304 }
305 else {
306 count++;
307 }
308 }
309 return count;
310 }
311
312 /** this function can look for stolen, equipped, identified, destructible
313 etc, items. You just have to specify the flags in the bitmask
314 specifying 1 in a bit signifies a requirement */
HasItem(const char * resref,ieDword flags) const315 bool Inventory::HasItem(const char *resref, ieDword flags) const
316 {
317 size_t slot = Slots.size();
318 while(slot--) {
319 const CREItem *item = Slots[slot];
320 if (!item) {
321 continue;
322 }
323 if ( (flags&item->Flags)!=flags) {
324 continue;
325 }
326 if (resref[0] && strnicmp(item->ItemResRef, resref,8) ) {
327 continue;
328 }
329 return true;
330 }
331 return false;
332 }
333
KillSlot(unsigned int index)334 void Inventory::KillSlot(unsigned int index)
335 {
336 if (InventoryType==INVENTORY_HEAP) {
337 Slots.erase(Slots.begin()+index);
338 return;
339 }
340 const CREItem *item = Slots[index];
341 if (!item) {
342 return;
343 }
344
345 //the used up item vanishes from the quickslot bar
346 if (Owner->IsSelected()) {
347 core->SetEventFlag( EF_ACTION );
348 }
349
350 Slots[index] = NULL;
351 CalculateWeight();
352
353 int effect = core->QuerySlotEffects( index );
354 if (!effect) {
355 return;
356 }
357 RemoveSlotEffects( index );
358 const Item *itm = gamedata->GetItem(item->ItemResRef, true);
359 //this cannot happen, but stuff happens!
360 if (!itm) {
361 error("Inventory", "Invalid item: %s!", item->ItemResRef);
362 }
363 ItemExcl &= ~itm->ItemExcl;
364 int eqslot = GetEquippedSlot();
365 ieDword equip;
366
367 switch (effect) {
368 case SLOT_EFFECT_LEFT:
369 UpdateShieldAnimation(nullptr);
370 break;
371 case SLOT_EFFECT_MISSILE:
372 //getting a new projectile of the same type
373 if (eqslot == (int) index) {
374 if (Equipped < 0) {
375 //always get the projectile weapon header (this quiver was equipped)
376 const ITMExtHeader *header = itm->GetWeaponHeader(true);
377 //remove potential launcher effects too
378 RemoveSlotEffects(FindTypedRangedWeapon(header->ProjectileQualifier));
379 equip = FindRangedProjectile(header->ProjectileQualifier);
380 if (equip != IW_NO_EQUIPPED) {
381 EquipItem(GetWeaponSlot(equip));
382 } else {
383 EquipBestWeapon(EQUIP_MELEE);
384 }
385 }
386 }
387 UpdateWeaponAnimation();
388 break;
389 case SLOT_EFFECT_MAGIC:
390 case SLOT_EFFECT_MELEE:
391 // reset Equipped if it was the removed item
392 if (eqslot == (int)index) {
393 SetEquippedSlot(IW_NO_EQUIPPED, 0);
394 } else if (Equipped < 0) {
395 //always get the projectile weapon header (this is a bow, because Equipped is negative)
396 const ITMExtHeader *header = itm->GetWeaponHeader(true);
397 if (header) {
398 //find the equipped type
399 int type = header->ProjectileQualifier;
400 int weaponslot = FindTypedRangedWeapon(type);
401 CREItem *item2 = Slots[weaponslot];
402 if (weaponslot == SLOT_FIST) { // a ranged weapon was not found - freshly unequipped
403 EquipBestWeapon(EQUIP_MELEE);
404 } else if (item2) {
405 const Item *itm2 = gamedata->GetItem(item2->ItemResRef, true);
406 if (itm2) {
407 if (type == header->ProjectileQualifier) {
408 equip = FindRangedProjectile(header->ProjectileQualifier);
409 if (equip != IW_NO_EQUIPPED) {
410 EquipItem(GetWeaponSlot(equip));
411 } else {
412 EquipBestWeapon(EQUIP_MELEE);
413 }
414 }
415 gamedata->FreeItem(itm2, item2->ItemResRef, false);
416 }
417 }
418 }
419 }
420 // reset Equipped if it is a ranged weapon slot
421 // but not magic weapon slot!
422
423 UpdateWeaponAnimation();
424 break;
425 case SLOT_EFFECT_HEAD:
426 Owner->SetUsedHelmet("\0");
427 break;
428 case SLOT_EFFECT_ITEM:
429 //remove the armor type only if this item is responsible for it
430 if ((ieDword) (itm->AnimationType[0]-'1') == Owner->GetBase(IE_ARMOR_TYPE)) {
431 Owner->SetBase(IE_ARMOR_TYPE, 0);
432 }
433 break;
434 }
435 gamedata->FreeItem(itm, item->ItemResRef, false);
436 }
437 /** if resref is "", then destroy ALL items
438 this function can look for stolen, equipped, identified, destructible
439 etc, items. You just have to specify the flags in the bitmask
440 specifying 1 in a bit signifies a requirement */
DestroyItem(const char * resref,ieDword flags,ieDword count)441 unsigned int Inventory::DestroyItem(const char *resref, ieDword flags, ieDword count)
442 {
443 unsigned int destructed = 0;
444 size_t slot = Slots.size();
445
446 while(slot--) {
447 //ignore the fist slot
448 if (slot == (unsigned int)SLOT_FIST) {
449 continue;
450 }
451
452 CREItem *item = Slots[slot];
453 if (!item) {
454 continue;
455 }
456 // here you can simply destroy all items of a specific type
457 if ( (flags&item->Flags)!=flags) {
458 continue;
459 }
460 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
461 continue;
462 }
463 //we need to acknowledge that the item was destroyed
464 //use unequip stuff etc,
465 //until that, we simply erase it
466 ieDword removed;
467
468 if (item->Flags&IE_INV_ITEM_STACKED) {
469 removed=item->Usages[0];
470 if (count && (removed + destructed > count) ) {
471 removed = count - destructed;
472 item = RemoveItem( (unsigned int) slot, removed );
473 }
474 else {
475 KillSlot( (unsigned int) slot);
476 }
477 } else {
478 removed=1;
479 KillSlot( (unsigned int) slot);
480 }
481 delete item;
482 destructed+=removed;
483 if (count && (destructed>=count) )
484 break;
485 }
486 if (destructed && Owner && Owner->InParty) displaymsg->DisplayConstantString(STR_LOSTITEM, DMC_BG2XPGREEN);
487
488 return destructed;
489 }
490
RemoveItem(unsigned int slot,unsigned int count)491 CREItem *Inventory::RemoveItem(unsigned int slot, unsigned int count)
492 {
493 CREItem *item;
494
495 if (slot >= Slots.size() ) {
496 InvalidSlot(slot);
497 }
498 item = Slots[slot];
499
500 if (!item) {
501 return NULL;
502 }
503
504 if (!count || !(item->Flags & IE_INV_ITEM_STACKED) || (count >= item->Usages[0])) {
505 KillSlot(slot);
506 return item;
507 }
508
509 CREItem *returned = new CREItem(*item);
510 item->Usages[0]-=count;
511 returned->Usages[0]=(ieWord) count;
512 CalculateWeight();
513 return returned;
514 }
515
516 //flags set disable item transfer
517 //except for undroppable which is opposite (and shouldn't be set)
RemoveItem(const char * resref,unsigned int flags,CREItem ** res_item,int count)518 int Inventory::RemoveItem(const char *resref, unsigned int flags, CREItem **res_item, int count)
519 {
520 size_t slot = Slots.size();
521 unsigned int mask = (flags^IE_INV_ITEM_UNDROPPABLE);
522 if (core->HasFeature(GF_NO_DROP_CAN_MOVE) ) {
523 mask &= ~IE_INV_ITEM_UNDROPPABLE;
524 }
525 while(slot--) {
526 CREItem *item = Slots[slot];
527 if (!item) {
528 continue;
529 }
530
531 if (flags && (mask&item->Flags)==flags) {
532 continue;
533 }
534 if (!flags && (mask&item->Flags)!=0) {
535 continue;
536 }
537 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
538 continue;
539 }
540 *res_item=RemoveItem( (unsigned int) slot, count);
541 return (int) slot;
542 }
543 *res_item = NULL;
544 return -1;
545 }
546
SetSlotItem(CREItem * item,unsigned int slot)547 void Inventory::SetSlotItem(CREItem* item, unsigned int slot)
548 {
549 if (slot >= Slots.size() ) {
550 InvalidSlot(slot);
551 }
552
553 delete Slots[slot];
554 Slots[slot] = item;
555
556 CalculateWeight();
557
558 //update the action bar next time
559 if (Owner->IsSelected()) {
560 core->SetEventFlag( EF_ACTION );
561 }
562 }
563
AddSlotItem(CREItem * item,int slot,int slottype,bool ranged)564 int Inventory::AddSlotItem(CREItem* item, int slot, int slottype, bool ranged)
565 {
566 int twohanded = item->Flags&IE_INV_ITEM_TWOHANDED;
567 if (slot >= 0) {
568 if ((unsigned)slot >= Slots.size()) {
569 InvalidSlot(slot);
570 }
571
572 //check for equipping weapons
573 if (WhyCantEquip(slot, twohanded, ranged)) {
574 return ASI_FAILED;
575 }
576
577 if (!Slots[slot]) {
578 item->Flags |= IE_INV_ITEM_ACQUIRED;
579 SetSlotItem(item, slot);
580 EquipItem(slot);
581 return ASI_SUCCESS;
582 }
583
584 return MergeItems(slot, item);
585 }
586
587 bool which = (slot == SLOT_AUTOEQUIP);
588 int res = ASI_FAILED;
589 int max = (int) Slots.size();
590 for (int i = 0;i<max;i++) {
591 //never autoequip in the magic slot!
592 if (i==SLOT_MAGIC)
593 continue;
594 if ((i<SLOT_INV || i>LAST_INV)!=which)
595 continue;
596 if (!(core->QuerySlotType(i)&slottype))
597 continue;
598 //the slot has been disabled for this actor
599 if (i>=SLOT_MELEE && i<=LAST_MELEE) {
600 if (Owner->GetQuickSlot(i-SLOT_MELEE)==0xffff) {
601 continue;
602 }
603 }
604 int part_res = AddSlotItem (item, i);
605 if (part_res == ASI_SUCCESS) return ASI_SUCCESS;
606 else if (part_res == ASI_PARTIAL) res = ASI_PARTIAL;
607 }
608
609 return res;
610 }
611
612 //Used by FillSlot
TryEquipAll(int slot)613 void Inventory::TryEquipAll(int slot)
614 {
615 for(int i=SLOT_INV;i<=LAST_INV;i++) {
616 CREItem *item = Slots[i];
617 if (!item) {
618 continue;
619 }
620
621 Slots[i]=NULL;
622 if (AddSlotItem(item, slot) == ASI_SUCCESS) {
623 return;
624 }
625 //try to stuff it back, it should work
626 if (AddSlotItem(item, i) != ASI_SUCCESS) {
627 delete item;
628 }
629 }
630 }
631
AddStoreItem(STOItem * item,int action)632 int Inventory::AddStoreItem(STOItem* item, int action)
633 {
634 CREItem *temp;
635 int ret = -1;
636
637 // item->PurchasedAmount is the number of items bought
638 // (you can still add grouped objects in a single step,
639 // just set up STOItem)
640 while (item->PurchasedAmount) {
641 //the first part of a STOItem is essentially a CREItem
642 temp = new CREItem(item);
643
644 //except the Expired flag
645 temp->Expired=0;
646 if (action==STA_STEAL && !core->HasFeature(GF_PST_STATE_FLAGS)) {
647 temp->Flags |= IE_INV_ITEM_STOLEN; // "steel" in pst
648 }
649 temp->Flags &= ~IE_INV_ITEM_SELECTED;
650
651 ret = AddSlotItem( temp, SLOT_ONLYINVENTORY );
652 if (ret != ASI_SUCCESS) {
653 delete temp;
654 break;
655 }
656 if (item->InfiniteSupply!=-1) {
657 if (!item->AmountInStock) {
658 break;
659 }
660 item->AmountInStock--;
661 }
662 item->PurchasedAmount--;
663 }
664 return ret;
665 }
666
667 /* could the source item be dropped on the target item to merge them */
ItemsAreCompatible(const CREItem * target,const CREItem * source) const668 bool Inventory::ItemsAreCompatible(const CREItem* target, const CREItem* source) const
669 {
670 if (!target) {
671 //this isn't always ok, please check!
672 Log(WARNING, "Inventory", "Null item encountered by ItemsAreCompatible()");
673 return true;
674 }
675
676 if (!(source->Flags&IE_INV_ITEM_STACKED) ) {
677 return false;
678 }
679
680 if (!strnicmp( target->ItemResRef, source->ItemResRef,8 )) {
681 return true;
682 }
683 return false;
684 }
685
686 //depletes a magical item
687 //if flags==0 then magical weapons are not harmed
DepleteItem(ieDword flags)688 int Inventory::DepleteItem(ieDword flags)
689 {
690 for (size_t i = 0; i < Slots.size(); i++) {
691 CREItem *item = Slots[i];
692 if (!item) {
693 continue;
694 }
695
696 //don't harm critical items
697 //don't harm nonmagical items
698 //don't harm indestructible items
699 if ( (item->Flags&(IE_INV_ITEM_CRITICAL|IE_INV_DEPLETABLE)) != IE_INV_DEPLETABLE) {
700 continue;
701 }
702
703 //if flags = 0 then weapons are not depleted
704 if (!flags) {
705 Item *itm = gamedata->GetItem(item->ItemResRef, true);
706 if (!itm) {
707 Log(WARNING, "Inventory", "Invalid item to deplete: %s!", item->ItemResRef);
708 continue;
709 }
710 //if the item is usable in weapon slot, then it is weapon
711 int weapon = core->CanUseItemType( SLOT_WEAPON, itm );
712 gamedata->FreeItem( itm, item->ItemResRef, false );
713 if (weapon)
714 continue;
715 }
716 //deplete item
717 item->Usages[0]=0;
718 item->Usages[1]=0;
719 item->Usages[2]=0;
720 }
721 return -1;
722 }
723
724 // if flags is 0, skips undroppable items
725 // if flags is IE_INV_ITEM_UNDROPPABLE, doesn't skip undroppable items
726 // TODO: once all callers have been checked, this can be reversed to make more sense
FindItem(const char * resref,unsigned int flags,unsigned int skip) const727 int Inventory::FindItem(const char *resref, unsigned int flags, unsigned int skip) const
728 {
729 unsigned int mask = (flags^IE_INV_ITEM_UNDROPPABLE);
730 if (core->HasFeature(GF_NO_DROP_CAN_MOVE) ) {
731 mask &= ~IE_INV_ITEM_UNDROPPABLE;
732 }
733 for (size_t i = 0; i < Slots.size(); i++) {
734 const CREItem *item = Slots[i];
735 if (!item) {
736 continue;
737 }
738 if ( mask & item->Flags ) {
739 continue;
740 }
741 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
742 continue;
743 }
744 if (skip) {
745 skip--;
746 } else {
747 return (int) i;
748 }
749 }
750 return -1;
751 }
752
DropItemAtLocation(unsigned int slot,unsigned int flags,Map * map,const Point & loc)753 bool Inventory::DropItemAtLocation(unsigned int slot, unsigned int flags, Map *map, const Point &loc)
754 {
755 if (slot >= Slots.size()) {
756 return false;
757 }
758 //these slots will never 'drop' the item
759 if ((slot==(unsigned int) SLOT_FIST) || (slot==(unsigned int) SLOT_MAGIC)) {
760 return false;
761 }
762
763 CREItem *item = Slots[slot];
764 if (!item) {
765 return false;
766 }
767 //if you want to drop undoppable items, simply set IE_INV_UNDROPPABLE
768 //by default, it won't drop them
769 if ( ((flags^IE_INV_ITEM_UNDROPPABLE)&item->Flags)!=flags) {
770 return false;
771 }
772 if (!map) {
773 return false;
774 }
775 map->AddItemToLocation(loc, item);
776 KillSlot(slot);
777 return true;
778 }
779
DropItemAtLocation(const char * resref,unsigned int flags,Map * map,const Point & loc)780 bool Inventory::DropItemAtLocation(const char *resref, unsigned int flags, Map *map, const Point &loc)
781 {
782 bool dropped = false;
783
784 if (!map) {
785 return false;
786 }
787
788 //this loop is going from start
789 for (size_t i = 0; i < Slots.size(); i++) {
790 //these slots will never 'drop' the item
791 if ((i==(unsigned int) SLOT_FIST) || (i==(unsigned int) SLOT_MAGIC)) {
792 continue;
793 }
794 CREItem *item = Slots[i];
795 if (!item) {
796 continue;
797 }
798 //if you want to drop undroppable items, simply set IE_INV_UNDROPPABLE
799 //by default, it won't drop them
800 if ( ((flags^IE_INV_ITEM_UNDROPPABLE)&item->Flags)!=flags) {
801 continue;
802 }
803 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
804 continue;
805 }
806 // mark it as unequipped, so it doesn't cause problems in stores
807 item->Flags &= ~ IE_INV_ITEM_EQUIPPED;
808 map->AddItemToLocation(loc, item);
809 dropped = true;
810 KillSlot((unsigned int) i);
811 //if it isn't all items then we stop here
812 if (resref[0])
813 break;
814 }
815
816 //dropping gold too
817 if (!resref[0]) {
818 if (!Owner->GetBase(IE_GOLD)) {
819 return dropped;
820 }
821 Owner->BaseStats[IE_GOLD] = 0;
822 CREItem *gold = new CREItem();
823 if (CreateItemCore(gold, core->GoldResRef, static_cast<int>(Owner->BaseStats[IE_GOLD]), 0, 0)) {
824 map->AddItemToLocation(loc, gold);
825 } else {
826 delete gold;
827 }
828 }
829 return dropped;
830 }
831
GetSlotItem(ieDword slot) const832 CREItem *Inventory::GetSlotItem(ieDword slot) const
833 {
834 if (slot >= Slots.size() ) {
835 InvalidSlot(slot);
836 }
837 return Slots[slot];
838 }
839
GetItemFlag(unsigned int slot) const840 ieDword Inventory::GetItemFlag(unsigned int slot) const
841 {
842 const CREItem *item = GetSlotItem(slot);
843 if (!item) {
844 return 0;
845 }
846 return item->Flags;
847 }
848
ChangeItemFlag(ieDword slot,ieDword arg,int op)849 bool Inventory::ChangeItemFlag(ieDword slot, ieDword arg, int op)
850 {
851 CREItem *item = GetSlotItem(slot);
852 if (!item) {
853 return false;
854 }
855 SetBits(item->Flags, arg, op);
856 return true;
857 }
858
859 //this is the low level equipping
860 //all checks have been made previously
EquipItem(ieDword slot)861 bool Inventory::EquipItem(ieDword slot)
862 {
863 const ITMExtHeader *header;
864
865 if (!Owner) {
866 //maybe assertion too?
867 return false;
868 }
869 const CREItem *item = GetSlotItem(slot);
870 if (!item) {
871 return false;
872 }
873
874 int weaponslot;
875
876 // add effects of an item just being equipped to actor's effect queue
877 int effect = core->QuerySlotEffects( slot );
878 const Item *itm = gamedata->GetItem(item->ItemResRef, true);
879 if (!itm) {
880 print("Invalid item Equipped: %s Slot: %d", item->ItemResRef, slot);
881 return false;
882 }
883
884 Owner->ClearCurrentStanceAnims();
885
886 switch (effect) {
887 case SLOT_EFFECT_FIST:
888 SetEquippedSlot(IW_NO_EQUIPPED, 0);
889 break;
890 case SLOT_EFFECT_LEFT:
891 //no idea if the offhand weapon has style, or simply the right
892 //hand style is dominant
893 UpdateShieldAnimation(itm);
894 break;
895 case SLOT_EFFECT_MELEE:
896 //if weapon is bow, then find quarrel for it and equip that
897 weaponslot = GetWeaponQuickSlot(slot);
898 EquippedHeader = 0;
899 if (Owner->PCStats) {
900 int eheader = Owner->PCStats->GetHeaderForSlot(slot);
901 if (eheader >= 0) {
902 EquippedHeader = eheader;
903 }
904 }
905 header = itm->GetExtHeader(EquippedHeader);
906 if (header) {
907 ieDword equip;
908 if (header->AttackType == ITEM_AT_BOW) {
909 //find the ranged projectile associated with it, this returns equipped code
910 equip = FindRangedProjectile(header->ProjectileQualifier);
911 //this is the real item slot of the quarrel
912 slot = equip + SLOT_MELEE;
913 } else {
914 //this is always 0-3
915 equip = weaponslot;
916 slot = GetWeaponSlot(weaponslot);
917 }
918 if (equip != IW_NO_EQUIPPED) {
919 Owner->SetupQuickSlot(ACT_WEAPON1+weaponslot, slot, EquippedHeader);
920 }
921 SetEquippedSlot(equip, EquippedHeader);
922 effect = 0; // SetEquippedSlot will already call AddSlotEffects
923 }
924 break;
925 case SLOT_EFFECT_MISSILE:
926 //Get the ranged header of the projectile (so we theoretically allow shooting of daggers)
927 EquippedHeader = itm->GetWeaponHeaderNumber(true);
928 header = itm->GetExtHeader(EquippedHeader);
929 if (header) {
930 weaponslot = FindTypedRangedWeapon(header->ProjectileQualifier);
931 if (weaponslot != SLOT_FIST) {
932 weaponslot -= SLOT_MELEE;
933 SetEquippedSlot((ieWordSigned) (slot-SLOT_MELEE), EquippedHeader);
934 //It is unsure if we can have multiple equipping headers for bows/arrow
935 //It is unclear which item's header index should go there
936 Owner->SetupQuickSlot(ACT_WEAPON1+weaponslot, slot, 0);
937 }
938 UpdateWeaponAnimation();
939 }
940 break;
941 case SLOT_EFFECT_HEAD:
942 Owner->SetUsedHelmet(itm->AnimationType);
943 break;
944 case SLOT_EFFECT_ITEM:
945 //adjusting armour level if needed
946 {
947 int l = itm->AnimationType[0]-'1';
948 if (l >= IE_ANI_NO_ARMOR && l <= IE_ANI_HEAVY_ARMOR) {
949 Owner->SetBase(IE_ARMOR_TYPE, l);
950 } else {
951 UpdateShieldAnimation(itm);
952 }
953 }
954 break;
955 }
956 gamedata->FreeItem(itm, item->ItemResRef, false);
957 if (effect) {
958 AddSlotEffects( slot );
959 }
960 return true;
961 }
962
963 //the removecurse flag will check if it is possible to move the item to the inventory
964 //after a remove curse spell
UnEquipItem(ieDword slot,bool removecurse) const965 bool Inventory::UnEquipItem(ieDword slot, bool removecurse) const
966 {
967 CREItem *item = GetSlotItem(slot);
968 if (!item) {
969 return false;
970 }
971 if (item->Flags & IE_INV_ITEM_UNDROPPABLE && !core->HasFeature(GF_NO_DROP_CAN_MOVE)) {
972 return false;
973 }
974
975 if (!removecurse && item->Flags & IE_INV_ITEM_CURSED && core->QuerySlotEffects(slot)) {
976 return false;
977 }
978
979 Owner->ClearCurrentStanceAnims();
980 item->Flags &= ~IE_INV_ITEM_EQUIPPED; //no idea if this is needed, won't hurt
981 return true;
982 }
983
984 // find the projectile
985 // type = 1 - bow
986 // 2 - xbow
987 // 4 - sling
988 //returns equipped code
FindRangedProjectile(unsigned int type) const989 int Inventory::FindRangedProjectile(unsigned int type) const
990 {
991 for(int i=SLOT_RANGED;i<=LAST_RANGED;i++) {
992 CREItem *Slot;
993
994 const Item *itm = GetItemPointer(i, Slot);
995 if (!itm) continue;
996 ITMExtHeader *ext_header = itm->GetExtHeader(0);
997 unsigned int weapontype = 0;
998 if (ext_header) {
999 weapontype = ext_header->ProjectileQualifier;
1000 }
1001 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1002 if (weapontype & type) {
1003 return i-SLOT_MELEE;
1004 }
1005 }
1006 return IW_NO_EQUIPPED;
1007 }
1008
1009 // find which bow is attached to the projectile marked by 'Equipped'
1010 // returns slotcode
FindRangedWeapon() const1011 int Inventory::FindRangedWeapon() const
1012 {
1013 if (Equipped>=0) return SLOT_FIST;
1014 return FindSlotRangedWeapon(GetEquippedSlot());
1015 }
1016
FindSlotRangedWeapon(ieDword slot) const1017 int Inventory::FindSlotRangedWeapon(ieDword slot) const
1018 {
1019 if ((int)slot >= SLOT_MELEE) return SLOT_FIST;
1020 CREItem *Slot;
1021 Item *itm = GetItemPointer(slot, Slot);
1022 if (!itm) return SLOT_FIST;
1023
1024 //always look for a ranged header when looking for a projectile/projector
1025 ITMExtHeader *ext_header = itm->GetWeaponHeader(true);
1026 unsigned int type = 0;
1027 if (ext_header) {
1028 type = ext_header->ProjectileQualifier;
1029 }
1030 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1031 return FindTypedRangedWeapon(type);
1032 }
1033
1034
1035 // find bow for a specific projectile type
FindTypedRangedWeapon(unsigned int type) const1036 int Inventory::FindTypedRangedWeapon(unsigned int type) const
1037 {
1038 if (!type) {
1039 return SLOT_FIST;
1040 }
1041 for(int i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1042 CREItem *Slot;
1043
1044 const Item *itm = GetItemPointer(i, Slot);
1045 if (!itm) continue;
1046 //always look for a ranged header when looking for a projectile/projector
1047 ITMExtHeader *ext_header = itm->GetWeaponHeader(true);
1048 int weapontype = 0;
1049 if (ext_header && (ext_header->AttackType == ITEM_AT_BOW)) {
1050 weapontype = ext_header->ProjectileQualifier;
1051 }
1052 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1053 if (weapontype & type) {
1054 return i;
1055 }
1056 }
1057 return SLOT_FIST;
1058 }
1059
SetHeadSlot(int arg)1060 void Inventory::SetHeadSlot(int arg) { SLOT_HEAD=arg; }
SetFistSlot(int arg)1061 void Inventory::SetFistSlot(int arg) { SLOT_FIST=arg; }
SetMagicSlot(int arg)1062 void Inventory::SetMagicSlot(int arg) { SLOT_MAGIC=arg; }
SetWeaponSlot(int arg)1063 void Inventory::SetWeaponSlot(int arg)
1064 {
1065 if (SLOT_MELEE==-1) {
1066 SLOT_MELEE=arg;
1067 }
1068 LAST_MELEE=arg;
1069 }
1070
1071 //ranged slots should be before MELEE slots
SetRangedSlot(int arg)1072 void Inventory::SetRangedSlot(int arg)
1073 {
1074 assert(SLOT_MELEE!=-1);
1075 if (SLOT_RANGED==-1) {
1076 SLOT_RANGED=arg;
1077 }
1078 LAST_RANGED=arg;
1079 }
1080
SetQuickSlot(int arg)1081 void Inventory::SetQuickSlot(int arg)
1082 {
1083 if (SLOT_QUICK==-1) {
1084 SLOT_QUICK=arg;
1085 }
1086 LAST_QUICK=arg;
1087 }
1088
SetInventorySlot(int arg)1089 void Inventory::SetInventorySlot(int arg)
1090 {
1091 if (SLOT_INV==-1) {
1092 SLOT_INV=arg;
1093 }
1094 LAST_INV=arg;
1095 }
1096
SetArmorSlot(int arg)1097 void Inventory::SetArmorSlot(int arg)
1098 {
1099 if (SLOT_ARMOR==-1) {
1100 SLOT_ARMOR=arg;
1101 }
1102 }
1103
1104 //multiple shield slots are allowed
1105 //but in this case they should be interspersed with melee slots
SetShieldSlot(int arg)1106 void Inventory::SetShieldSlot(int arg)
1107 {
1108 if (SLOT_LEFT!=-1) {
1109 assert(SLOT_MELEE+1==SLOT_LEFT);
1110 IWD2=true;
1111 return;
1112 }
1113 SLOT_LEFT=arg;
1114 }
1115
GetHeadSlot()1116 int Inventory::GetHeadSlot()
1117 {
1118 return SLOT_HEAD;
1119 }
1120
GetFistSlot()1121 int Inventory::GetFistSlot()
1122 {
1123 return SLOT_FIST;
1124 }
1125
GetMagicSlot()1126 int Inventory::GetMagicSlot()
1127 {
1128 return SLOT_MAGIC;
1129 }
1130
GetWeaponSlot()1131 int Inventory::GetWeaponSlot()
1132 {
1133 return SLOT_MELEE;
1134 }
1135
GetWeaponQuickSlot(int weaponslot)1136 int Inventory::GetWeaponQuickSlot(int weaponslot)
1137 {
1138 int slot = weaponslot-SLOT_MELEE;
1139 if (IWD2 && (slot>=0 && slot<=7) ) slot/=2;
1140 return slot;
1141 }
1142
GetWeaponSlot(int quickslot)1143 int Inventory::GetWeaponSlot(int quickslot)
1144 {
1145 if (IWD2 && (quickslot>=0 && quickslot<=3) ) quickslot*=2;
1146 return quickslot+SLOT_MELEE;
1147 }
1148
GetQuickSlot()1149 int Inventory::GetQuickSlot()
1150 {
1151 return SLOT_QUICK;
1152 }
1153
GetInventorySlot()1154 int Inventory::GetInventorySlot()
1155 {
1156 return SLOT_INV;
1157 }
1158
GetArmorSlot()1159 int Inventory::GetArmorSlot()
1160 {
1161 return SLOT_ARMOR;
1162 }
1163
1164 //if shield slot is empty, call again for fist slot!
GetShieldSlot() const1165 int Inventory::GetShieldSlot() const
1166 {
1167 if (IWD2) {
1168 //actually, in IWD2, the equipped slot never becomes IW_NO_EQUIPPED, it is always 0-3
1169 //this is just a hack to prevent invalid shots from happening
1170 if (Equipped == IW_NO_EQUIPPED) return SLOT_MELEE+1;
1171
1172 if (Equipped>=0 && Equipped<=3) {
1173 return Equipped*2+SLOT_MELEE+1;
1174 }
1175 //still, what about magic weapons...
1176 return -1;
1177 }
1178 return SLOT_LEFT;
1179 }
1180
GetEquippedSlot() const1181 int Inventory::GetEquippedSlot() const
1182 {
1183 if (Equipped == IW_NO_EQUIPPED) {
1184 return SLOT_FIST;
1185 }
1186 if (IWD2 && Equipped>=0) {
1187 //Equipped should never become IW_NO_EQUIPPED, this is just a hack to cover the bug
1188 //about it still becoming invalid
1189 if (Equipped >= 4) {
1190 return SLOT_MELEE;
1191 }
1192 return Equipped*2+SLOT_MELEE;
1193 }
1194 return Equipped+SLOT_MELEE;
1195 }
1196
SetEquippedSlot(ieWordSigned slotcode,ieWord header,bool noFX)1197 bool Inventory::SetEquippedSlot(ieWordSigned slotcode, ieWord header, bool noFX)
1198 {
1199 EquippedHeader = header;
1200
1201 //doesn't work if magic slot is used, refresh the magic slot just in case
1202 if (MagicSlotEquipped() && (slotcode!=SLOT_MAGIC-SLOT_MELEE)) {
1203 Equipped = SLOT_MAGIC-SLOT_MELEE;
1204 UpdateWeaponAnimation();
1205 return false;
1206 }
1207
1208 //if it is an illegal code, make it fist
1209 if ((size_t) (GetWeaponSlot(slotcode))>Slots.size()) {
1210 slotcode=IW_NO_EQUIPPED;
1211 }
1212
1213 int oldslot = GetEquippedSlot();
1214 int newslot = GetWeaponSlot(slotcode);
1215
1216 //remove previous slot effects
1217 if (Equipped != IW_NO_EQUIPPED) {
1218 RemoveSlotEffects(oldslot);
1219 //for projectiles we may need to remove the launcher effects too
1220 int oldeffects = core->QuerySlotEffects(oldslot);
1221 if (oldeffects == SLOT_EFFECT_MISSILE) {
1222 int launcher = FindSlotRangedWeapon(oldslot);
1223 if (launcher != SLOT_FIST) {
1224 RemoveSlotEffects(launcher);
1225 }
1226 }
1227 }
1228
1229 //unequipping (fist slot will be used now)
1230 if (slotcode == IW_NO_EQUIPPED || !HasItemInSlot("", newslot)) {
1231 Equipped = IW_NO_EQUIPPED;
1232 //fist slot equipping effects
1233 AddSlotEffects(SLOT_FIST);
1234 UpdateWeaponAnimation();
1235 return true;
1236 }
1237
1238 //equipping a weapon
1239 Equipped = slotcode;
1240 int effects = core->QuerySlotEffects( newslot);
1241 if (effects) {
1242 CREItem* item = GetSlotItem(newslot);
1243 item->Flags|=IE_INV_ITEM_EQUIPPED;
1244 if (!noFX) {
1245 AddSlotEffects(newslot);
1246
1247 //in case of missiles also look for an appropriate launcher
1248 if (effects == SLOT_EFFECT_MISSILE) {
1249 newslot = FindRangedWeapon();
1250 AddSlotEffects(newslot);
1251 }
1252 }
1253 }
1254 UpdateWeaponAnimation();
1255 return true;
1256 }
1257
GetEquipped() const1258 int Inventory::GetEquipped() const
1259 {
1260 return Equipped;
1261 }
1262
GetEquippedHeader() const1263 int Inventory::GetEquippedHeader() const
1264 {
1265 return EquippedHeader;
1266 }
1267
1268 // store this internally just like Equipped/EquippedHeader if it turns into a hot path
GetEquippedExtHeader(int header) const1269 ITMExtHeader *Inventory::GetEquippedExtHeader(int header) const
1270 {
1271 int slot; // Equipped holds the projectile, not the weapon
1272 CREItem *itm = GetUsedWeapon(false, slot); // check the main hand only
1273 if (!itm) return NULL;
1274 Item *item = gamedata->GetItem(itm->ItemResRef, true);
1275 if (!item) return NULL;
1276 return item->GetExtHeader(header);
1277 }
1278
SetEquipped(ieWordSigned slot,ieWord header)1279 void Inventory::SetEquipped(ieWordSigned slot, ieWord header)
1280 {
1281 Equipped = slot;
1282 EquippedHeader = header;
1283 }
1284
FistsEquipped() const1285 bool Inventory::FistsEquipped() const
1286 {
1287 return Equipped == IW_NO_EQUIPPED;
1288 }
1289
MagicSlotEquipped() const1290 bool Inventory::MagicSlotEquipped() const
1291 {
1292 if (SLOT_MAGIC != -1) {
1293 return Slots[SLOT_MAGIC] != NULL;
1294 }
1295 return false;
1296 }
1297
1298 //returns the fist weapon if there is nothing else
1299 //This will return the actual weapon, I mean the bow in the case of bow+arrow combination
GetUsedWeapon(bool leftorright,int & slot) const1300 CREItem *Inventory::GetUsedWeapon(bool leftorright, int &slot) const
1301 {
1302 CREItem *ret;
1303
1304 if (SLOT_MAGIC!=-1) {
1305 slot = SLOT_MAGIC;
1306 ret = GetSlotItem(slot);
1307 if (ret && ret->ItemResRef[0]) {
1308 return ret;
1309 }
1310 }
1311 if (leftorright) {
1312 //no shield slot
1313 slot = GetShieldSlot();
1314 if (slot>=0) {
1315 ret = GetSlotItem(slot);
1316 if (ret) {
1317 return ret;
1318 } else {
1319 //we don't want to return fist for shield slot
1320 return NULL;
1321 }
1322 } else {
1323 // nothing in the shield slot, so nothing in the right hand, so just quit
1324 return NULL;
1325 }
1326 }
1327 slot = GetEquippedSlot();
1328 if((core->QuerySlotEffects(slot) & SLOT_EFFECT_MISSILE) == SLOT_EFFECT_MISSILE) {
1329 slot = FindRangedWeapon();
1330 }
1331 ret = GetSlotItem(slot);
1332 if (!ret) {
1333 //return fist weapon
1334 slot = SLOT_FIST;
1335 ret = GetSlotItem(slot);
1336 }
1337 return ret;
1338 }
1339
1340 // Returns index of first empty slot or slot with the same
1341 // item and not full stack. On fail returns -1
1342 // Can be used to check for full inventory
FindCandidateSlot(int slottype,size_t first_slot,const char * resref) const1343 int Inventory::FindCandidateSlot(int slottype, size_t first_slot, const char *resref) const
1344 {
1345 if (first_slot >= Slots.size())
1346 return -1;
1347
1348 for (size_t i = first_slot; i < Slots.size(); i++) {
1349 if (!(core->QuerySlotType( (unsigned int) i) & slottype) ) {
1350 continue;
1351 }
1352
1353 CREItem *item = Slots[i];
1354
1355 if (!item) {
1356 return (int) i; //this is a good empty slot
1357 }
1358 if (!resref) {
1359 continue;
1360 }
1361 if (!(item->Flags&IE_INV_ITEM_STACKED) ) {
1362 continue;
1363 }
1364 if (strnicmp( item->ItemResRef, resref, 8 )!=0) {
1365 continue;
1366 }
1367 // check if the item fits in this slot, we use the cached
1368 // stackamount value
1369 if (item->Usages[0]<item->MaxStackAmount) {
1370 return (int) i;
1371 }
1372 }
1373
1374 return -1;
1375 }
1376
AddSlotItemRes(const ieResRef ItemResRef,int SlotID,int Charge0,int Charge1,int Charge2)1377 void Inventory::AddSlotItemRes(const ieResRef ItemResRef, int SlotID, int Charge0, int Charge1, int Charge2)
1378 {
1379 CREItem *TmpItem = new CREItem();
1380 if (CreateItemCore(TmpItem, ItemResRef, Charge0, Charge1, Charge2)) {
1381 int ret = AddSlotItem( TmpItem, SlotID );
1382 if (ret != ASI_SUCCESS) {
1383 // put the remainder on the ground
1384 Map *area = core->GetGame()->GetCurrentArea();
1385 if (area) {
1386 // create or reuse the existing pile
1387 area->AddItemToLocation(Owner->Pos, TmpItem);
1388 } else {
1389 Log(ERROR, "Inventory", "AddSlotItemRes: argh, no area and the inventory is full, bailing out!");
1390 delete TmpItem;
1391 }
1392 }
1393 } else {
1394 delete TmpItem;
1395 }
1396 }
1397
SetSlotItemRes(const ieResRef ItemResRef,int SlotID,int Charge0,int Charge1,int Charge2)1398 void Inventory::SetSlotItemRes(const ieResRef ItemResRef, int SlotID, int Charge0, int Charge1, int Charge2)
1399 {
1400 if(ItemResRef[0]) {
1401 CREItem *TmpItem = new CREItem();
1402 if (CreateItemCore(TmpItem, ItemResRef, Charge0, Charge1, Charge2)) {
1403 SetSlotItem( TmpItem, SlotID );
1404 } else {
1405 delete TmpItem;
1406 }
1407 } else {
1408 //if the item isn't creatable, we still destroy the old item
1409 KillSlot( SlotID );
1410 }
1411 }
1412
GetShieldItemType() const1413 ieWord Inventory::GetShieldItemType() const
1414 {
1415 ieWord ret;
1416 CREItem *Slot;
1417 int slotNum = GetShieldSlot();
1418
1419 if (slotNum < 0) {
1420 return 0xffff;
1421 }
1422 const Item *itm = GetItemPointer(slotNum, Slot);
1423 if (!itm) return 0xffff;
1424 ret = itm->ItemType;
1425 gamedata->FreeItem(itm, Slot->ItemResRef);
1426 return ret;
1427 }
1428
GetArmorItemType() const1429 ieWord Inventory::GetArmorItemType() const
1430 {
1431 ieWord ret;
1432 CREItem *Slot;
1433 int slotNum = GetArmorSlot();
1434
1435 if (slotNum < 0) {
1436 return 0xffff;
1437 }
1438 const Item *itm = GetItemPointer(slotNum, Slot);
1439 if (!itm) return 0xffff;
1440 ret = itm->ItemType;
1441 gamedata->FreeItem(itm, Slot->ItemResRef);
1442 return ret;
1443 }
1444
BreakItemSlot(ieDword slot)1445 void Inventory::BreakItemSlot(ieDword slot)
1446 {
1447 ieResRef newItem;
1448 CREItem *Slot;
1449
1450 const Item *itm = GetItemPointer(slot, Slot);
1451 if (!itm) return;
1452 //if it is the magic weapon slot, don't break it, just remove it, because it couldn't be removed
1453 //or for pst, just remove it as there is no breaking (the replacement item is a sound)
1454 if (slot == (unsigned int) SLOT_MAGIC || core->HasFeature(GF_HAS_PICK_SOUND)) {
1455 newItem[0]=0;
1456 } else {
1457 memcpy(newItem, itm->ReplacementItem,sizeof(newItem) );
1458 }
1459 gamedata->FreeItem( itm, Slot->ItemResRef, true );
1460 //this depends on setslotitemres using setslotitem
1461 SetSlotItemRes(newItem, slot, 0,0,0);
1462 }
1463
dump() const1464 void Inventory::dump() const
1465 {
1466 StringBuffer buffer;
1467 dump(buffer);
1468 Log(DEBUG, "Inventory", buffer);
1469 }
1470
dump(StringBuffer & buffer) const1471 void Inventory::dump(StringBuffer& buffer) const
1472 {
1473 buffer.append( "INVENTORY:\n" );
1474 for (unsigned int i = 0; i < Slots.size(); i++) {
1475 CREItem* itm = Slots[i];
1476
1477 if (!itm) {
1478 continue;
1479 }
1480
1481 buffer.appendFormatted( "%2u: %8.8s - (%d %d %d) Fl:0x%x Wt: %d x %dLb\n", i, itm->ItemResRef, itm->Usages[0], itm->Usages[1], itm->Usages[2], itm->Flags, itm->MaxStackAmount, itm->Weight );
1482 }
1483
1484 buffer.appendFormatted("Equipped: %d EquippedHeader: %d\n", Equipped, EquippedHeader);
1485 buffer.appendFormatted( "Total weight: %d\n", Weight );
1486 }
1487
EquipBestWeapon(int flags)1488 void Inventory::EquipBestWeapon(int flags)
1489 {
1490 int i;
1491 int damage = -1;
1492 ieDword best_slot = SLOT_FIST;
1493 ITMExtHeader *header;
1494 CREItem *Slot;
1495 char AnimationType[2]={0,0};
1496 ieWord MeleeAnimation[3]={100,0,0};
1497
1498 //cannot change equipment when holding magic weapons
1499 if (Equipped == SLOT_MAGIC-SLOT_MELEE) {
1500 return;
1501 }
1502
1503 if (flags&EQUIP_RANGED) {
1504 for(i=SLOT_RANGED;i<LAST_RANGED;i++) {
1505 const Item *itm = GetItemPointer(i, Slot);
1506 if (!itm) continue;
1507 //cannot change equipment when holding a cursed weapon
1508 if (Slot->Flags & IE_INV_ITEM_CURSED) {
1509 return;
1510 }
1511 //best ranged
1512 int tmp = itm->GetDamagePotential(true, header);
1513 if (tmp>damage) {
1514 best_slot = i;
1515 damage = tmp;
1516 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1517 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1518 }
1519 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1520 }
1521
1522 //ranged melee weapons like throwing daggers (not bows!)
1523 for(i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1524 const Item *itm = GetItemPointer(i, Slot);
1525 if (!itm) continue;
1526 //cannot change equipment when holding a cursed weapon
1527 if (Slot->Flags & IE_INV_ITEM_CURSED) {
1528 return;
1529 }
1530 //best ranged
1531 int tmp = itm->GetDamagePotential(true, header);
1532 if (tmp>damage) {
1533 best_slot = i;
1534 damage = tmp;
1535 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1536 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1537 }
1538 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1539 }
1540 }
1541
1542 if (flags&EQUIP_MELEE) {
1543 for(i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1544 const Item *itm = GetItemPointer(i, Slot);
1545 if (!itm) continue;
1546 //cannot change equipment when holding a cursed weapon
1547 if (Slot->Flags & IE_INV_ITEM_CURSED) {
1548 return;
1549 }
1550 //the Slot flag is enough for this
1551 //though we need animation type/damagepotential anyway
1552 if (Slot->Flags&IE_INV_ITEM_BOW) continue;
1553 //best melee
1554 int tmp = itm->GetDamagePotential(false, header);
1555 if (tmp>damage) {
1556 best_slot = i;
1557 damage = tmp;
1558 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1559 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1560 }
1561 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1562 }
1563 }
1564
1565 EquipItem(best_slot);
1566 UpdateWeaponAnimation();
1567 }
1568
1569 #define ID_NONEED 0 //id is not important
1570 #define ID_NEED 1 //id is important
1571 #define ID_NO 2 //shouldn't id
1572
1573 /* returns true if there are more item usages not fitting in given array */
GetEquipmentInfo(ItemExtHeader * array,int startindex,int count)1574 bool Inventory::GetEquipmentInfo(ItemExtHeader *array, int startindex, int count)
1575 {
1576 int pos = 0;
1577 int actual = 0;
1578 memset(array, 0, count * sizeof(ItemExtHeader) );
1579 for(unsigned int idx=0;idx<Slots.size();idx++) {
1580 if (!core->QuerySlotEffects(idx)) {
1581 continue;
1582 }
1583 CREItem *slot;
1584
1585 const Item *itm = GetItemPointer(idx, slot);
1586 if (!itm) {
1587 continue;
1588 }
1589 for(int ehc=0;ehc<itm->ExtHeaderCount;ehc++) {
1590 ITMExtHeader *ext_header = itm->ext_headers+ehc;
1591 if (ext_header->Location!=ITEM_LOC_EQUIPMENT) {
1592 continue;
1593 }
1594 //skipping if we cannot use the item
1595 int idreq1 = (slot->Flags&IE_INV_ITEM_IDENTIFIED);
1596 int idreq2 = ext_header->IDReq;
1597 switch (idreq2) {
1598 case ID_NO:
1599 if (idreq1) continue;
1600 break;
1601 case ID_NEED:
1602 if (!idreq1) continue;
1603 break;
1604 default:;
1605 }
1606
1607 actual++;
1608 if (actual>startindex) {
1609
1610 //store the item, return if we can't store more
1611 if (!count) {
1612 gamedata->FreeItem(itm, slot->ItemResRef, false);
1613 return true;
1614 }
1615 count--;
1616 memcpy(array[pos].itemname, slot->ItemResRef, sizeof(ieResRef) );
1617 array[pos].slot = idx;
1618 array[pos].headerindex = ehc;
1619 array[pos].Tooltip = ext_header->Tooltip;
1620 int slen = ((char *) &(array[pos].itemname)) -((char *) &(array[pos].AttackType));
1621 memcpy(&(array[pos].AttackType), &(ext_header->AttackType), slen);
1622 if (ext_header->Charges) {
1623 //don't modify ehc, it is a counter
1624 if (ehc>=CHARGE_COUNTERS) {
1625 array[pos].Charges=slot->Usages[0];
1626 } else {
1627 array[pos].Charges=slot->Usages[ehc];
1628 }
1629 } else {
1630 array[pos].Charges=0xffff;
1631 }
1632 pos++;
1633 }
1634 }
1635 gamedata->FreeItem(itm, slot->ItemResRef, false);
1636 }
1637
1638 return false;
1639 }
1640
1641 //The slot index value is optional, if you supply it,
1642 // then ItemExcl will be returned as if the item was already unequipped
GetEquipExclusion(int index) const1643 ieDword Inventory::GetEquipExclusion(int index) const
1644 {
1645 if (index<0) {
1646 return ItemExcl;
1647 }
1648 CREItem *slot;
1649 const Item *itm = GetItemPointer(index, slot);
1650 if (!itm) {
1651 return ItemExcl;
1652 }
1653 ieDword ret = ItemExcl&~itm->ItemExcl;
1654 gamedata->FreeItem(itm, slot->ItemResRef, false);
1655 return ret;
1656 }
1657
UpdateShieldAnimation(const Item * it)1658 void Inventory::UpdateShieldAnimation(const Item *it)
1659 {
1660 char AnimationType[2]={0,0};
1661 int WeaponType;
1662
1663 if (it) {
1664 memcpy(AnimationType, it->AnimationType, 2);
1665 if (core->CanUseItemType(SLOT_WEAPON, it))
1666 WeaponType = IE_ANI_WEAPON_2W;
1667 else
1668 WeaponType = IE_ANI_WEAPON_1H;
1669 } else {
1670 WeaponType = IE_ANI_WEAPON_1H;
1671 }
1672 Owner->SetUsedShield(AnimationType, WeaponType);
1673 }
1674
UpdateWeaponAnimation()1675 void Inventory::UpdateWeaponAnimation()
1676 {
1677 int slot = GetEquippedSlot();
1678 int effect = core->QuerySlotEffects( slot );
1679 if (effect == SLOT_EFFECT_MISSILE) {
1680 // ranged weapon
1681 slot = FindRangedWeapon();
1682 }
1683 int WeaponType = -1;
1684
1685 char AnimationType[2]={0,0};
1686 ieWord MeleeAnimation[3]={100,0,0};
1687 CREItem *Slot;
1688
1689 // TODO: fix bows?
1690
1691 ITMExtHeader *header = 0;
1692 const Item *itm = GetItemPointer(slot, Slot);
1693 if (itm) {
1694 itm->GetDamagePotential(false, header);
1695 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1696 //for twohanded flag, you don't need itm
1697 if (Slot->Flags & IE_INV_ITEM_TWOHANDED)
1698 WeaponType = IE_ANI_WEAPON_2H;
1699 else {
1700
1701 // Examine shield slot to check if we're using two weapons
1702 // TODO: for consistency, use same Item* access method as above
1703 bool twoweapon = false;
1704 int slot = GetShieldSlot();
1705 CREItem* si = NULL;
1706 if (slot>0) {
1707 si = GetSlotItem( (ieDword) slot );
1708 }
1709 if (si) {
1710 Item* it = gamedata->GetItem(si->ItemResRef, true);
1711 assert(it);
1712 if (core->CanUseItemType(SLOT_WEAPON, it))
1713 twoweapon = true;
1714 gamedata->FreeItem(it, si->ItemResRef, false);
1715 }
1716
1717 if (twoweapon)
1718 WeaponType = IE_ANI_WEAPON_2W;
1719 else
1720 WeaponType = IE_ANI_WEAPON_1H;
1721 }
1722 }
1723
1724 if (header)
1725 memcpy(MeleeAnimation,header->MeleeAnimation, sizeof(MeleeAnimation) );
1726 if (itm)
1727 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1728 Owner->SetUsedWeapon(AnimationType, MeleeAnimation, WeaponType);
1729 }
1730
1731 //this function will also check disabled slots (if that feature will be imped)
IsSlotBlocked(int slot) const1732 bool Inventory::IsSlotBlocked(int slot) const
1733 {
1734 if (slot<SLOT_MELEE) return false;
1735 if (slot>LAST_MELEE) return false;
1736 int otherslot;
1737 if (IWD2) {
1738 otherslot = slot+1;
1739 } else {
1740 otherslot = SLOT_LEFT;
1741 }
1742 return HasItemInSlot("",otherslot);
1743 }
1744
TwoHandedInSlot(int slot) const1745 inline bool Inventory::TwoHandedInSlot(int slot) const
1746 {
1747 CREItem *item;
1748
1749 item = GetSlotItem(slot);
1750 if (!item) return false;
1751 if (item->Flags&IE_INV_ITEM_TWOHANDED) {
1752 return true;
1753 }
1754 return false;
1755 }
1756
WhyCantEquip(int slot,int twohanded,bool ranged) const1757 int Inventory::WhyCantEquip(int slot, int twohanded, bool ranged) const
1758 {
1759 // check only for hand slots
1760 if ((slot<SLOT_MELEE || slot>LAST_MELEE) && (slot != SLOT_LEFT) ) {
1761 return 0;
1762 }
1763
1764 //magic items have the highest priority
1765 if (MagicSlotEquipped()) {
1766 //magic weapon is in use
1767 return STR_MAGICWEAPON;
1768 }
1769
1770 //can't equip in shield slot if a weapon slot is twohanded or ranged
1771 for (int i=SLOT_MELEE; i<=LAST_MELEE;i++) {
1772 //see GetShieldSlot
1773 int otherslot;
1774 if (IWD2) {
1775 otherslot = i+1;
1776 } else {
1777 otherslot = SLOT_LEFT;
1778 }
1779 if (slot==otherslot) {
1780 if (TwoHandedInSlot(i)) {
1781 return STR_TWOHANDED_USED;
1782 }
1783 if (ranged) {
1784 return STR_NO_RANGED_OFFHAND;
1785 }
1786 }
1787 }
1788
1789 if (twohanded) {
1790 if (IWD2) {
1791 if (slot>=SLOT_MELEE&&slot<=LAST_MELEE && (slot-SLOT_MELEE)&1) {
1792 return STR_NOT_IN_OFFHAND;
1793 }
1794 } else if (slot == SLOT_LEFT) {
1795 return STR_NOT_IN_OFFHAND;
1796 }
1797 if (IsSlotBlocked(slot)) {
1798 //cannot equip two handed while shield slot is in use?
1799 return STR_OFFHAND_USED;
1800 }
1801 }
1802 return 0;
1803 }
1804
1805 //recharge items on rest, if rest was partial, recharge only 'hours'
1806 //if this latter functionality is unwanted, then simply don't recharge if
1807 //hours != 0
ChargeAllItems(int hours)1808 void Inventory::ChargeAllItems(int hours)
1809 {
1810 //this loop is going from start
1811 for (size_t i = 0; i < Slots.size(); i++) {
1812 CREItem *item = Slots[i];
1813 if (!item) {
1814 continue;
1815 }
1816
1817 Item *itm = gamedata->GetItem(item->ItemResRef, true);
1818 if (!itm)
1819 continue;
1820 for(int h=0;h<CHARGE_COUNTERS;h++) {
1821 ITMExtHeader *header = itm->GetExtHeader(h);
1822 if (header && (header->RechargeFlags&IE_ITEM_RECHARGE)) {
1823 unsigned short add = header->Charges;
1824 if (hours && add>hours) add=hours;
1825 add+=item->Usages[h];
1826 if(add>header->Charges) add=header->Charges;
1827 item->Usages[h]=add;
1828 }
1829 }
1830 gamedata->FreeItem( itm, item->ItemResRef, false );
1831 }
1832 }
1833
1834 #define ITM_STEALING (IE_INV_ITEM_UNSTEALABLE | IE_INV_ITEM_MOVABLE | IE_INV_ITEM_EQUIPPED) //0x442
FindStealableItem()1835 int Inventory::FindStealableItem()
1836 {
1837 unsigned int slotcnt = Slots.size();
1838 unsigned int start = core->Roll(1, slotcnt, -1);
1839 int inc = start & 1 ? 1 : -1;
1840
1841 Log(DEBUG, "Inventory", "Start Slot: %d, increment: %d", start, inc);
1842 for (unsigned int i = 0; i < slotcnt; ++i) {
1843 int slot = (slotcnt - 1 + start + i * inc) % slotcnt;
1844 CREItem *item = Slots[slot];
1845 //can't steal empty slot
1846 if (!item) continue;
1847 //bit 1 is stealable slot
1848 if (!(core->QuerySlotFlags(slot)&1) ) continue;
1849 //can't steal equipped weapon
1850 int realslot = core->QuerySlot(slot);
1851 if (GetEquippedSlot() == realslot) continue;
1852 if (GetShieldSlot() == realslot) continue;
1853 //can't steal flagged items
1854 if ((item->Flags & ITM_STEALING) != IE_INV_ITEM_MOVABLE) continue;
1855 return slot;
1856 }
1857 return -1;
1858 }
1859
1860 // extension to allow more or less than head gear to avert critical hits:
1861 // If an item with bit 25 set is equipped in a non-helmet slot, aversion is enabled
1862 // If an item with bit 25 set is equipped in a helmet slot, aversion is disabled
ProvidesCriticalAversion()1863 bool Inventory::ProvidesCriticalAversion()
1864 {
1865 int maxSlot = (int) Slots.size();
1866 for (int i = 0; i < maxSlot; i++) {
1867 CREItem *item = Slots[i];
1868 if (!item || ((i>=SLOT_INV) && (i<=LAST_INV))) { // ignore items in the backpack
1869 continue;
1870 }
1871 // weapon, but not equipped
1872 if (!((i == SLOT_ARMOR) || (i == SLOT_HEAD)) && !(item->Flags & IE_INV_ITEM_EQUIPPED)) {
1873 continue;
1874 }
1875
1876 Item *itm = gamedata->GetItem(item->ItemResRef, true);
1877 if (!itm) {
1878 continue;
1879 }
1880 //if the item is worn on head, toggle crits must be 0, otherwise it must be 1
1881 //this flag is only stored in the item header, so we need to make some efforts
1882 //to get to it (TODO convince ToBEx to move this bit into the accessible range?) - low 24 bits
1883 ieDword flag = itm->Flags;
1884 gamedata->FreeItem( itm, item->ItemResRef, false );
1885 bool togglesCrits = (flag&IE_ITEM_TOGGLE_CRITS);
1886 bool isHelmet = (i == SLOT_HEAD);
1887 if (togglesCrits ^ isHelmet) return true;
1888 }
1889 return false;
1890 }
1891
MergeItems(int slot,CREItem * item)1892 int Inventory::MergeItems(int slot, CREItem *item)
1893 {
1894 CREItem *slotitem = Slots[slot];
1895 if (slotitem->MaxStackAmount && ItemsAreCompatible(slotitem, item)) {
1896 //calculate with the max movable stock
1897 int chunk = item->Usages[0];
1898 if (slotitem->Usages[0] + chunk > slotitem->MaxStackAmount) {
1899 chunk = slotitem->MaxStackAmount - slotitem->Usages[0];
1900 }
1901 if (chunk<=0) {
1902 return ASI_FAILED;
1903 }
1904
1905 slotitem->Flags |= IE_INV_ITEM_ACQUIRED;
1906 slotitem->Usages[0] = (ieWord) (slotitem->Usages[0] + chunk);
1907 item->Usages[0] = (ieWord) (item->Usages[0] - chunk);
1908 EquipItem(slot);
1909 CalculateWeight();
1910 if (item->Usages[0] == 0) {
1911 delete item;
1912 return ASI_SUCCESS;
1913 }
1914 return ASI_PARTIAL;
1915 }
1916 return ASI_FAILED;
1917 }
1918
1919 }
1920