1 /* GemRB - Infinity Engine Emulator
2 * Copyright (C) 2003 The GemRB Project
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.
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.
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.
21 //This class represents the inventory of stores (.sto), area containers (.are)
24 #include "Inventory.h"
32 #include "Interface.h"
34 #include "ScriptEngine.h"
38 static int SLOT_HEAD
= -1;
39 static int SLOT_MAGIC
= -1;
40 static int SLOT_FIST
= -1;
41 static int SLOT_MELEE
= -1;
42 static int LAST_MELEE
= -1;
43 static int SLOT_RANGED
= -1;
44 static int LAST_RANGED
= -1;
45 static int SLOT_QUICK
= -1;
46 static int LAST_QUICK
= -1;
47 static int SLOT_INV
= -1;
48 static int LAST_INV
= -1;
49 static int SLOT_LEFT
= -1;
52 static bool IWD2
= false;
53 static int MagicBit
= 0;
55 static void InvalidSlot(int slot
)
57 printMessage("Inventory"," ",LIGHT_RED
);
58 printf("Invalid slot: %d!\n",slot
);
62 //This inline function returns both an item pointer and the slot data.
63 //slot is a dynamic slot number (SLOT_*)
64 inline Item
*Inventory::GetItemPointer(ieDword slot
, CREItem
*&item
) const
66 item
= GetSlotItem(slot
);
67 if (!item
) return NULL
;
68 if (!item
->ItemResRef
[0]) return NULL
;
69 return gamedata
->GetItem(item
->ItemResRef
);
72 void Inventory::Init(int mb
)
83 //TODO: set this correctly
88 Inventory::Inventory()
91 InventoryType
= INVENTORY_HEAP
;
94 Equipped
= IW_NO_EQUIPPED
;
99 Inventory::~Inventory()
101 for (size_t i
= 0; i
< Slots
.size(); i
++) {
109 CREItem
*Inventory::GetItem(unsigned int slot
)
111 if (slot
>= Slots
.size() ) {
115 CREItem
*item
= Slots
[slot
];
116 Slots
.erase(Slots
.begin()+slot
);
120 void Inventory::AddItem(CREItem
*item
)
122 if (!item
) return; //invalid items get no slot
123 Slots
.push_back(item
);
124 //Changed=true; //probably not needed, chests got no encumbrance
127 void Inventory::CalculateWeight()
133 for (size_t i
= 0; i
< Slots
.size(); i
++) {
134 CREItem
*slot
= Slots
[i
];
138 //printf ("%2d: %8s : %d x %d\n", (int) i, slot->ItemResRef, slot->Weight, slot->Usages[0]);
139 if (slot
->Weight
== -1) {
140 Item
*itm
= gamedata
->GetItem( slot
->ItemResRef
);
142 //simply adding the item flags to the slot
143 slot
->Flags
|= (itm
->Flags
<<8);
144 //some slot flags might be affected by the item flags
145 if (!(slot
->Flags
& IE_INV_ITEM_CRITICAL
)) {
146 slot
->Flags
|= IE_INV_ITEM_DESTRUCTIBLE
;
148 //this is for converting IWD items magic flag
150 if (slot
->Flags
&IE_INV_ITEM_UNDROPPABLE
) {
151 slot
->Flags
|=IE_INV_ITEM_MAGICAL
;
152 slot
->Flags
&=~IE_INV_ITEM_UNDROPPABLE
;
156 if (!(slot
->Flags
& IE_INV_ITEM_MOVABLE
)) {
157 slot
->Flags
|= IE_INV_ITEM_UNDROPPABLE
;
160 if (slot
->Flags
& IE_INV_ITEM_STOLEN2
) {
161 slot
->Flags
|= IE_INV_ITEM_STOLEN
;
164 //auto identify basic items
165 if (!itm
->LoreToID
) {
166 slot
->Flags
|= IE_INV_ITEM_IDENTIFIED
;
169 //if item is stacked mark it as so
170 if (itm
->StackAmount
) {
171 slot
->Flags
|= IE_INV_ITEM_STACKED
;
174 slot
->Weight
= itm
->Weight
;
175 slot
->StackAmount
= itm
->StackAmount
;
176 gamedata
->FreeItem( itm
, slot
->ItemResRef
, false );
179 printMessage( "Inventory", " ", LIGHT_RED
);
180 printf("Invalid item: %s!\n", slot
->ItemResRef
);
184 slot
->Flags
&= ~IE_INV_ITEM_ACQUIRED
;
186 if (slot
->Weight
> 0) {
187 Weight
+= slot
->Weight
* ((slot
->Usages
[0] && slot
->StackAmount
> 1) ? slot
->Usages
[0] : 1);
193 void Inventory::AddSlotEffects(ieDword index
)
197 const Item
*itm
= GetItemPointer(index
, slot
);
199 printMessage("Inventory","Invalid item equipped...\n",LIGHT_RED
);
202 ItemExcl
|=itm
->ItemExcl
;
204 ieWord gradient
= itm
->GetWieldedGradient();
205 if (gradient
!=0xffff) {
206 Owner
->SetBase(IE_COLORS
, gradient
);
209 //get the equipping effects
210 EffectQueue
*eqfx
= itm
->GetEffectBlock(-1, index
, 0);
211 gamedata
->FreeItem( itm
, slot
->ItemResRef
, false );
213 Owner
->RefreshEffects(eqfx
);
214 //call gui for possible paperdoll animation changes
215 if (Owner
->InParty
) {
216 core
->SetEventFlag(EF_UPDATEANIM
);
220 //no need to know the item effects 'personally', the equipping slot
222 void Inventory::RemoveSlotEffects(ieDword index
)
224 Owner
->fxqueue
.RemoveEquippingEffects(index
);
225 Owner
->RefreshEffects(NULL
);
226 //call gui for possible paperdoll animation changes
227 if (Owner
->InParty
) {
228 core
->SetEventFlag(EF_UPDATEANIM
);
232 void Inventory::SetInventoryType(int arg
)
237 void Inventory::SetSlotCount(unsigned int size
)
240 printf("Inventory size changed???\n");
241 //we don't allow reassignment,
242 //if you want this, delete the previous Slots here
245 Slots
.assign((size_t) size
, NULL
);
248 /** if you supply a "" string, then it checks if the slot is empty */
249 bool Inventory::HasItemInSlot(const char *resref
, unsigned int slot
) const
251 if (slot
>= Slots
.size()) {
254 const CREItem
*item
= Slots
[slot
];
261 if (strnicmp( item
->ItemResRef
, resref
, 8 )==0) {
267 /** counts the items in the inventory, if stacks == 1 then stacks are
268 accounted for their heap size */
269 int Inventory::CountItems(const char *resref
, bool stacks
) const
272 size_t slot
= Slots
.size();
274 const CREItem
*item
= Slots
[slot
];
278 if (resref
&& resref
[0]) {
279 if (!strnicmp(resref
, item
->ItemResRef
, 8) )
282 if (stacks
&& (item
->Flags
&IE_INV_ITEM_STACKED
) ) {
283 count
+=item
->Usages
[0];
292 /** this function can look for stolen, equipped, identified, destructible
293 etc, items. You just have to specify the flags in the bitmask
294 specifying 1 in a bit signifies a requirement */
295 bool Inventory::HasItem(const char *resref
, ieDword flags
) const
297 size_t slot
= Slots
.size();
299 const CREItem
*item
= Slots
[slot
];
303 if ( (flags
&item
->Flags
)!=flags
) {
306 if (resref
[0] && strnicmp(item
->ItemResRef
, resref
,8) ) {
314 void Inventory::KillSlot(unsigned int index
)
316 if (InventoryType
==INVENTORY_HEAP
) {
317 Slots
.erase(Slots
.begin()+index
);
320 CREItem
*item
= Slots
[index
];
325 //the used up item vanishes from the quickslot bar
326 if (Owner
->InParty
) {
327 core
->SetEventFlag( EF_ACTION
);
331 int effect
= core
->QuerySlotEffects( index
);
335 RemoveSlotEffects( index
);
336 Item
*itm
= gamedata
->GetItem(item
->ItemResRef
);
337 //this cannot happen, but stuff happens!
341 ItemExcl
&= ~itm
->ItemExcl
;
344 case SLOT_EFFECT_LEFT
:
345 UpdateShieldAnimation(0);
347 case SLOT_EFFECT_MISSILE
:
348 //getting a new projectile of the same type
349 if (Equipped
+ SLOT_MELEE
== (int) index
) {
351 //always get the projectile weapon header (this quiver was equipped)
352 ITMExtHeader
*header
= itm
->GetWeaponHeader(true);
353 Equipped
= FindRangedProjectile(header
->ProjectileQualifier
);
354 if (Equipped
!=IW_NO_EQUIPPED
) {
355 EquipItem(Equipped
+SLOT_MELEE
);
357 EquipItem(SLOT_FIST
);
361 UpdateWeaponAnimation();
363 case SLOT_EFFECT_MELEE
:
364 // reset Equipped if it was the removed item
365 if (Equipped
+SLOT_MELEE
== (int)index
)
366 Equipped
= IW_NO_EQUIPPED
;
367 else if (Equipped
< 0) {
368 //always get the projectile weapon header (this is a bow, because Equipped is negative)
369 ITMExtHeader
*header
= itm
->GetWeaponHeader(true);
371 //find the equipped type
372 int type
= header
->ProjectileQualifier
;
373 int weaponslot
= FindTypedRangedWeapon(type
);
374 CREItem
*item2
= Slots
[weaponslot
];
376 Item
*itm2
= gamedata
->GetItem(item2
->ItemResRef
);
378 if (type
== header
->ProjectileQualifier
) {
379 Equipped
= FindRangedProjectile(header
->ProjectileQualifier
);
380 if (Equipped
!=IW_NO_EQUIPPED
) {
381 EquipItem(Equipped
+SLOT_MELEE
);
383 EquipItem(SLOT_FIST
);
386 gamedata
->FreeItem(itm2
, item2
->ItemResRef
, false);
391 // reset Equipped if it is a ranged weapon slot
392 // but not magic weapon slot!
394 UpdateWeaponAnimation();
396 case SLOT_EFFECT_HEAD
:
397 Owner
->SetUsedHelmet("");
399 case SLOT_EFFECT_ITEM
:
400 //remove the armor type only if this item is responsible for it
401 if ((ieDword
) (itm
->AnimationType
[0]-'1') == Owner
->GetBase(IE_ARMOR_TYPE
)) {
402 Owner
->SetBase(IE_ARMOR_TYPE
, 0);
406 gamedata
->FreeItem(itm
, item
->ItemResRef
, false);
408 /** if resref is "", then destroy ALL items
409 this function can look for stolen, equipped, identified, destructible
410 etc, items. You just have to specify the flags in the bitmask
411 specifying 1 in a bit signifies a requirement */
412 unsigned int Inventory::DestroyItem(const char *resref
, ieDword flags
, ieDword count
)
414 unsigned int destructed
= 0;
415 size_t slot
= Slots
.size();
418 //ignore the fist slot
419 if (slot
== (unsigned int)SLOT_FIST
) {
423 CREItem
*item
= Slots
[slot
];
427 // here you can simply destroy all items of a specific type
428 if ( (flags
&item
->Flags
)!=flags
) {
431 if (resref
[0] && strnicmp(item
->ItemResRef
, resref
, 8) ) {
434 //we need to acknowledge that the item was destroyed
435 //use unequip stuff, decrease encumbrance etc,
436 //until that, we simply erase it
439 if (item
->Flags
&IE_INV_ITEM_STACKED
) {
440 removed
=item
->Usages
[0];
441 if (count
&& (removed
+ destructed
> count
) ) {
442 removed
= count
- destructed
;
443 item
= RemoveItem( (unsigned int) slot
, removed
);
446 KillSlot( (unsigned int) slot
);
450 KillSlot( (unsigned int) slot
);
455 if (count
&& (destructed
>=count
) )
458 if (Changed
&& Owner
&& Owner
->InParty
) core
->DisplayConstantString(STR_LOSTITEM
, 0xbcefbc);
463 CREItem
*Inventory::RemoveItem(unsigned int slot
, unsigned int count
)
467 if (slot
>= Slots
.size() ) {
473 if (!count
|| !(item
->Flags
&IE_INV_ITEM_STACKED
) ) {
477 if (count
>= item
->Usages
[0]) {
482 CREItem
*returned
= new CREItem(*item
);
483 item
->Usages
[0]-=count
;
484 returned
->Usages
[0]=(ieWord
) count
;
488 //flags set disable item transfer
489 //except for undroppable and equipped, which are opposite (and shouldn't be set)
490 int Inventory::RemoveItem(const char *resref
, unsigned int flags
, CREItem
**res_item
)
492 size_t slot
= Slots
.size();
494 CREItem
*item
= Slots
[slot
];
498 if ( ((flags
^(IE_INV_ITEM_UNDROPPABLE
|IE_INV_ITEM_EQUIPPED
))&item
->Flags
)!=flags
) {
501 if (resref
[0] && strnicmp(item
->ItemResRef
, resref
, 8) ) {
504 *res_item
=RemoveItem( (unsigned int) slot
, 0);
511 void Inventory::SetSlotItem(CREItem
* item
, unsigned int slot
)
513 if (slot
>= Slots
.size() ) {
523 //update the action bar next time
524 if (Owner
->InParty
) {
525 core
->SetEventFlag( EF_ACTION
);
529 int Inventory::AddSlotItem(CREItem
* item
, int slot
, int slottype
)
531 int twohanded
= item
->Flags
&IE_INV_ITEM_TWOHANDED
;
533 if ((unsigned)slot
>= Slots
.size()) {
538 //check for equipping weapons
539 if (WhyCantEquip(slot
,twohanded
)) {
544 item
->Flags
|= IE_INV_ITEM_ACQUIRED
;
551 CREItem
*myslot
= Slots
[slot
];
552 if (ItemsAreCompatible( myslot
, item
)) {
553 //calculate with the max movable stock
554 int chunk
= item
->Usages
[0];
555 int newamount
= myslot
->Usages
[0]+chunk
;
556 if (newamount
>myslot
->StackAmount
) {
557 newamount
=myslot
->StackAmount
;
558 chunk
= item
->Usages
[0]-newamount
;
563 myslot
->Flags
|= IE_INV_ITEM_ACQUIRED
;
564 myslot
->Usages
[0] = (ieWord
) (myslot
->Usages
[0] + chunk
);
565 item
->Usages
[0] = (ieWord
) (item
->Usages
[0] - chunk
);
568 if (item
->Usages
[0] == 0) {
578 if (slot
==SLOT_AUTOEQUIP
) {
583 int res
= ASI_FAILED
;
584 int max
= (int) Slots
.size();
585 for (int i
= 0;i
<max
;i
++) {
586 //never autoequip in the magic slot!
589 if ((i
<SLOT_INV
|| i
>LAST_INV
)!=which
)
591 if (!(core
->QuerySlotType(i
)&slottype
))
593 //the slot has been disabled for this actor
594 if (i
>=SLOT_MELEE
&& i
<=LAST_MELEE
) {
595 if (Owner
->GetQuickSlot(i
-SLOT_MELEE
)==0xffff) {
599 //this is called by AddSlotItem as well
600 //if (WhyCantEquip(i,twohanded))
602 int part_res
= AddSlotItem (item
, i
);
603 if (part_res
== ASI_SUCCESS
) return ASI_SUCCESS
;
604 else if (part_res
== ASI_PARTIAL
) res
= ASI_PARTIAL
;
610 int Inventory::AddStoreItem(STOItem
* item
, int action
)
615 // item->PurchasedAmount is the number of items bought
616 // (you can still add grouped objects in a single step,
617 // just set up STOItem)
618 while (item
->PurchasedAmount
) {
619 //the first part of a STOItem is essentially a CREItem
620 temp
= new CREItem();
621 memcpy( temp
, item
, sizeof( CREItem
) );
622 //except the Expired flag
624 if (action
==STA_STEAL
) {
625 temp
->Flags
|= IE_INV_ITEM_STOLEN
;
627 temp
->Flags
&= ~IE_INV_ITEM_SELECTED
;
629 ret
= AddSlotItem( temp
, SLOT_ONLYINVENTORY
);
630 if (ret
!= ASI_SUCCESS
) {
634 if (item
->InfiniteSupply
!=-1) {
635 if (!item
->AmountInStock
) {
638 item
->AmountInStock
--;
640 item
->PurchasedAmount
--;
645 /* could the source item be dropped on the target item to merge them */
646 bool Inventory::ItemsAreCompatible(CREItem
* target
, CREItem
* source
) const
649 //this isn't always ok, please check!
650 printMessage("Inventory","Null item encountered by ItemsAreCompatible()",YELLOW
);
654 if (!(source
->Flags
&IE_INV_ITEM_STACKED
) ) {
658 if (!strnicmp( target
->ItemResRef
, source
->ItemResRef
,8 )) {
664 //depletes a magical item
665 //if flags==0 then magical weapons are not harmed
666 int Inventory::DepleteItem(ieDword flags
)
668 for (size_t i
= 0; i
< Slots
.size(); i
++) {
669 CREItem
*item
= Slots
[i
];
674 //don't harm critical items
675 //don't harm nonmagical items
676 //don't harm indestructible items
677 if ( (item
->Flags
&(IE_INV_ITEM_CRITICAL
|IE_INV_DEPLETABLE
)) != IE_INV_DEPLETABLE
) {
681 //if flags = 0 then weapons are not depleted
683 Item
*itm
= gamedata
->GetItem( item
->ItemResRef
);
686 //if the item is usable in weapon slot, then it is weapon
687 int weapon
= core
->CanUseItemType( SLOT_WEAPON
, itm
);
688 gamedata
->FreeItem( itm
, item
->ItemResRef
, false );
700 // if flags is 0, skips undroppable items
701 // if flags is IE_INV_ITEM_UNDROPPABLE, doesn't skip undroppable items
702 // TODO: once all callers have been checked, this can be reversed to make more sense
703 int Inventory::FindItem(const char *resref
, unsigned int flags
) const
705 for (size_t i
= 0; i
< Slots
.size(); i
++) {
706 const CREItem
*item
= Slots
[i
];
710 if ( (flags
^IE_INV_ITEM_UNDROPPABLE
) & item
->Flags
) {
713 if (resref
[0] && strnicmp(item
->ItemResRef
, resref
, 8) ) {
721 bool Inventory::DropItemAtLocation(unsigned int slot
, unsigned int flags
, Map
*map
, const Point
&loc
)
723 if (slot
>= Slots
.size()) {
726 //these slots will never 'drop' the item
727 if ((slot
==(unsigned int) SLOT_FIST
) || (slot
==(unsigned int) SLOT_MAGIC
)) {
731 CREItem
*item
= Slots
[slot
];
735 //if you want to drop undoppable items, simply set IE_INV_UNDROPPABLE
736 //by default, it won't drop them
737 if ( ((flags
^IE_INV_ITEM_UNDROPPABLE
)&item
->Flags
)!=flags
) {
743 map
->AddItemToLocation(loc
, item
);
749 bool Inventory::DropItemAtLocation(const char *resref
, unsigned int flags
, Map
*map
, const Point
&loc
)
751 bool dropped
= false;
757 //this loop is going from start
758 for (size_t i
= 0; i
< Slots
.size(); i
++) {
759 //these slots will never 'drop' the item
760 if ((i
==(unsigned int) SLOT_FIST
) || (i
==(unsigned int) SLOT_MAGIC
)) {
763 CREItem
*item
= Slots
[i
];
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
) {
772 if (resref
[0] && strnicmp(item
->ItemResRef
, resref
, 8) ) {
775 map
->AddItemToLocation(loc
, item
);
778 KillSlot((unsigned int) i
);
779 //if it isn't all items then we stop here
786 CREItem
*Inventory::GetSlotItem(unsigned int slot
) const
788 if (slot
>= Slots
.size() ) {
795 ieDword
Inventory::GetItemFlag(unsigned int slot
) const
797 const CREItem
*item
= GetSlotItem(slot
);
804 bool Inventory::ChangeItemFlag(unsigned int slot
, ieDword arg
, int op
)
806 CREItem
*item
= GetSlotItem(slot
);
811 case BM_SET
: item
->Flags
= arg
; break;
812 case BM_OR
: item
->Flags
|= arg
; break;
813 case BM_NAND
: item
->Flags
&= ~arg
; break;
814 case BM_XOR
: item
->Flags
^= arg
; break;
815 case BM_AND
: item
->Flags
&= arg
; break;
820 //this is the low level equipping
821 //all checks have been made previously
822 bool Inventory::EquipItem(unsigned int slot
)
824 ITMExtHeader
*header
;
827 //maybe assertion too?
830 CREItem
*item
= GetSlotItem(slot
);
837 // add effects of an item just being equipped to actor's effect queue
838 int effect
= core
->QuerySlotEffects( slot
);
839 Item
*itm
= gamedata
->GetItem(item
->ItemResRef
);
841 printf("Invalid item Equipped: %s Slot: %d\n", item
->ItemResRef
, slot
);
845 case SLOT_EFFECT_LEFT
:
846 //no idea if the offhand weapon has style, or simply the right
847 //hand style is dominant
848 UpdateShieldAnimation(itm
);
850 case SLOT_EFFECT_MELEE
:
851 //if weapon is ranged, then find quarrel for it and equip that
855 header
= itm
->GetExtHeader(EquippedHeader
);
856 if (header
&& header
->AttackType
== ITEM_AT_BOW
) {
857 //find the ranged projectile associated with it.
858 slot
= FindRangedProjectile(header
->ProjectileQualifier
);
859 EquippedHeader
= itm
->GetWeaponHeaderNumber(true);
860 } else if (header
&& header
->AttackType
== ITEM_AT_PROJECTILE
) {
861 EquippedHeader
= itm
->GetWeaponHeaderNumber(true);
863 EquippedHeader
= itm
->GetWeaponHeaderNumber(false);
865 header
= itm
->GetExtHeader(EquippedHeader
);
867 SetEquippedSlot(slot
, EquippedHeader
);
868 if (slot
!= IW_NO_EQUIPPED
) {
869 Owner
->SetupQuickSlot(ACT_WEAPON1
+weaponslot
, slot
+SLOT_MELEE
, EquippedHeader
);
871 effect
= 0; // SetEquippedSlot will already call AddSlotEffects
872 UpdateWeaponAnimation();
875 case SLOT_EFFECT_MISSILE
:
876 //Get the ranged header of the projectile (so we theoretically allow shooting of daggers)
877 EquippedHeader
= itm
->GetWeaponHeaderNumber(true);
878 header
= itm
->GetExtHeader(EquippedHeader
);
880 weaponslot
= FindTypedRangedWeapon(header
->ProjectileQualifier
);
881 if (weaponslot
!= SLOT_FIST
) {
882 weaponslot
-= SLOT_MELEE
;
883 SetEquippedSlot((ieWordSigned
) (slot
-SLOT_MELEE
), EquippedHeader
);
884 //It is unsure if we can have multiple equipping headers for bows/arrow
885 //It is unclear which item's header index should go there
886 Owner
->SetupQuickSlot(ACT_WEAPON1
+weaponslot
, slot
, 0);
888 UpdateWeaponAnimation();
891 case SLOT_EFFECT_HEAD
:
892 Owner
->SetUsedHelmet(itm
->AnimationType
);
894 case SLOT_EFFECT_ITEM
:
895 //adjusting armour level if needed
897 int l
= itm
->AnimationType
[0]-'1';
899 Owner
->SetBase(IE_ARMOR_TYPE
, l
);
901 UpdateShieldAnimation(itm
);
906 gamedata
->FreeItem(itm
, item
->ItemResRef
, false);
908 if (item
->Flags
& IE_INV_ITEM_CURSED
) {
909 item
->Flags
|=IE_INV_ITEM_UNDROPPABLE
;
911 AddSlotEffects( slot
);
916 //the removecurse flag will check if it is possible to move the item to the inventory
917 //after a remove curse spell
918 bool Inventory::UnEquipItem(unsigned int slot
, bool removecurse
)
920 CREItem
*item
= GetSlotItem(slot
);
925 if (item
->Flags
& IE_INV_ITEM_MOVABLE
) {
926 item
->Flags
&=~IE_INV_ITEM_UNDROPPABLE
;
928 if (FindCandidateSlot(SLOT_INVENTORY
,0,item
->ItemResRef
)<0) {
932 if (item
->Flags
& IE_INV_ITEM_UNDROPPABLE
) {
935 item
->Flags
&= ~IE_INV_ITEM_EQUIPPED
; //no idea if this is needed, won't hurt
939 // find the projectile
943 //returns equipped code
944 int Inventory::FindRangedProjectile(unsigned int type
) const
946 for(int i
=SLOT_RANGED
;i
<=LAST_RANGED
;i
++) {
949 const Item
*itm
= GetItemPointer(i
, Slot
);
951 ITMExtHeader
*ext_header
= itm
->GetExtHeader(0);
952 unsigned int weapontype
= 0;
954 weapontype
= ext_header
->ProjectileQualifier
;
956 gamedata
->FreeItem(itm
, Slot
->ItemResRef
, false);
957 if (weapontype
& type
) {
961 return IW_NO_EQUIPPED
;
964 // find which bow is attached to the projectile marked by 'Equipped'
966 int Inventory::FindRangedWeapon() const
968 if (Equipped
>=0) return SLOT_FIST
;
969 return FindSlotRangedWeapon(Equipped
+SLOT_MELEE
);
972 int Inventory::FindSlotRangedWeapon(unsigned int slot
) const
974 if ((int)slot
>= SLOT_MELEE
) return SLOT_FIST
;
976 Item
*itm
= GetItemPointer(slot
, Slot
);
977 if (!itm
) return SLOT_FIST
;
979 //always look for a ranged header when looking for a projectile/projector
980 ITMExtHeader
*ext_header
= itm
->GetWeaponHeader(true);
981 unsigned int type
= 0;
983 type
= ext_header
->ProjectileQualifier
;
985 gamedata
->FreeItem(itm
, Slot
->ItemResRef
, false);
986 return FindTypedRangedWeapon(type
);
990 // find bow for a specific projectile type
991 int Inventory::FindTypedRangedWeapon(unsigned int type
) const
996 for(int i
=SLOT_MELEE
;i
<=LAST_MELEE
;i
++) {
999 const Item
*itm
= GetItemPointer(i
, Slot
);
1001 //always look for a ranged header when looking for a projectile/projector
1002 ITMExtHeader
*ext_header
= itm
->GetWeaponHeader(true);
1005 weapontype
= ext_header
->ProjectileQualifier
;
1007 gamedata
->FreeItem(itm
, Slot
->ItemResRef
, false);
1008 if (weapontype
& type
) {
1015 void Inventory::SetHeadSlot(int arg
) { SLOT_HEAD
=arg
; }
1016 void Inventory::SetFistSlot(int arg
) { SLOT_FIST
=arg
; }
1017 void Inventory::SetMagicSlot(int arg
) { SLOT_MAGIC
=arg
; }
1018 void Inventory::SetWeaponSlot(int arg
)
1020 if (SLOT_MELEE
==-1) {
1026 //ranged slots should be before MELEE slots
1027 void Inventory::SetRangedSlot(int arg
)
1029 assert(SLOT_MELEE
!=-1);
1030 if (SLOT_RANGED
==-1) {
1036 void Inventory::SetQuickSlot(int arg
)
1038 if (SLOT_QUICK
==-1) {
1044 void Inventory::SetInventorySlot(int arg
)
1052 //multiple shield slots are allowed
1053 //but in this case they should be interspersed with melee slots
1054 void Inventory::SetShieldSlot(int arg
)
1056 if (SLOT_LEFT
!=-1) {
1057 assert(SLOT_MELEE
+1==SLOT_LEFT
);
1064 int Inventory::GetHeadSlot()
1069 int Inventory::GetFistSlot()
1074 int Inventory::GetMagicSlot()
1079 int Inventory::GetWeaponSlot()
1084 int Inventory::GetQuickSlot()
1089 int Inventory::GetInventorySlot()
1094 //if shield slot is empty, call again for fist slot!
1095 int Inventory::GetShieldSlot() const
1098 if (Equipped
>=0 && Equipped
<=3) {
1099 return Equipped
*2+SLOT_MELEE
+1;
1106 int Inventory::GetEquippedSlot() const
1108 if (Equipped
== IW_NO_EQUIPPED
) {
1111 if (IWD2
&& Equipped
>=0) {
1112 //i've absolutely NO idea what is this 4 (Avenger)
1113 //Equipped should be 0-3 in iWD2, no???
1114 if (Equipped
>= 4) {
1117 return Equipped
*2+SLOT_MELEE
;
1119 return Equipped
+SLOT_MELEE
;
1122 bool Inventory::SetEquippedSlot(ieWordSigned slotcode
, ieWord header
)
1124 EquippedHeader
= header
;
1126 //doesn't work if magic slot is used, refresh the magic slot just in case
1127 if (HasItemInSlot("",SLOT_MAGIC
) && (slotcode
!=SLOT_MAGIC
-SLOT_MELEE
)) {
1128 Equipped
= SLOT_MAGIC
-SLOT_MELEE
;
1129 UpdateWeaponAnimation();
1133 //if it is an illegal code, make it fist
1134 if ((size_t) (slotcode
+SLOT_MELEE
)>Slots
.size()) {
1135 slotcode
=IW_NO_EQUIPPED
;
1138 //unequipping (fist slot will be used now)
1139 if (slotcode
== IW_NO_EQUIPPED
|| !HasItemInSlot("",slotcode
+SLOT_MELEE
)) {
1140 if (Equipped
!= IW_NO_EQUIPPED
) {
1141 RemoveSlotEffects( SLOT_MELEE
+Equipped
);
1143 Equipped
= IW_NO_EQUIPPED
;
1144 //fist slot equipping effects
1145 AddSlotEffects(SLOT_FIST
);
1146 UpdateWeaponAnimation();
1150 //equipping a weapon, but remove its effects first
1151 if (Equipped
!= IW_NO_EQUIPPED
) {
1152 RemoveSlotEffects( SLOT_MELEE
+Equipped
);
1155 Equipped
= slotcode
;
1156 int effects
= core
->QuerySlotEffects( SLOT_MELEE
+Equipped
);
1158 CREItem
* item
= GetSlotItem(SLOT_MELEE
+Equipped
);
1159 item
->Flags
|=IE_INV_ITEM_EQUIPPED
;
1160 if (item
->Flags
& IE_INV_ITEM_CURSED
) {
1161 item
->Flags
|=IE_INV_ITEM_UNDROPPABLE
;
1163 AddSlotEffects( SLOT_MELEE
+Equipped
);
1165 UpdateWeaponAnimation();
1169 int Inventory::GetEquipped() const
1174 int Inventory::GetEquippedHeader() const
1176 return EquippedHeader
;
1179 //returns the fist weapon if there is nothing else
1180 //This will return the actual weapon, I mean the bow in the case of bow+arrow combination
1181 CREItem
*Inventory::GetUsedWeapon(bool leftorright
, int &slot
) const
1185 if (SLOT_MAGIC
!=-1) {
1187 ret
= GetSlotItem(slot
);
1188 if (ret
&& ret
->ItemResRef
[0]) {
1194 slot
= GetShieldSlot();
1196 ret
= GetSlotItem(slot
);
1200 //we don't want to return fist for shield slot
1205 slot
= GetEquippedSlot();
1206 if((core
->QuerySlotEffects(slot
) & SLOT_EFFECT_MISSILE
) == SLOT_EFFECT_MISSILE
) {
1207 slot
= FindRangedWeapon();
1209 ret
= GetSlotItem(slot
);
1211 //return fist weapon
1213 ret
= GetSlotItem(slot
);
1218 // Returns index of first empty slot or slot with the same
1219 // item and not full stack. On fail returns -1
1220 // Can be used to check for full inventory
1221 int Inventory::FindCandidateSlot(int slottype
, size_t first_slot
, const char *resref
)
1223 if (first_slot
>= Slots
.size())
1226 for (size_t i
= first_slot
; i
< Slots
.size(); i
++) {
1227 if (!(core
->QuerySlotType( (unsigned int) i
) & slottype
) ) {
1231 CREItem
*item
= Slots
[i
];
1234 return (int) i
; //this is a good empty slot
1239 if (!(item
->Flags
&IE_INV_ITEM_STACKED
) ) {
1242 if (strnicmp( item
->ItemResRef
, resref
, 8 )!=0) {
1245 // check if the item fits in this slot, we use the cached
1246 // stackamount value
1247 if (item
->Usages
[0]<item
->StackAmount
) {
1255 void Inventory::AddSlotItemRes(const ieResRef ItemResRef
, int SlotID
, int Charge0
, int Charge1
, int Charge2
)
1257 CREItem
*TmpItem
= new CREItem();
1258 strnlwrcpy(TmpItem
->ItemResRef
, ItemResRef
, 8);
1260 TmpItem
->Usages
[0]=(ieWord
) Charge0
;
1261 TmpItem
->Usages
[1]=(ieWord
) Charge1
;
1262 TmpItem
->Usages
[2]=(ieWord
) Charge2
;
1264 if (core
->ResolveRandomItem(TmpItem
) && gamedata
->Exists(TmpItem
->ItemResRef
, IE_ITM_CLASS_ID
)) {
1265 AddSlotItem( TmpItem
, SlotID
);
1272 void Inventory::SetSlotItemRes(const ieResRef ItemResRef
, int SlotID
, int Charge0
, int Charge1
, int Charge2
)
1275 CREItem
*TmpItem
= new CREItem();
1276 strnlwrcpy(TmpItem
->ItemResRef
, ItemResRef
, 8);
1278 TmpItem
->Usages
[0]=(ieWord
) Charge0
;
1279 TmpItem
->Usages
[1]=(ieWord
) Charge1
;
1280 TmpItem
->Usages
[2]=(ieWord
) Charge2
;
1282 if (core
->ResolveRandomItem(TmpItem
) && gamedata
->Exists(TmpItem
->ItemResRef
, IE_ITM_CLASS_ID
)) {
1283 SetSlotItem( TmpItem
, SlotID
);
1288 //if the item isn't creatable, we still destroy the old item
1294 void Inventory::BreakItemSlot(ieDword slot
)
1299 const Item
*itm
= GetItemPointer(slot
, Slot
);
1301 //if it is the magic weapon slot, don't break it, just remove it, because it couldn't be removed
1302 if(slot
==(unsigned int) SLOT_MAGIC
) {
1305 memcpy(newItem
, itm
->ReplacementItem
,sizeof(newItem
) );
1307 gamedata
->FreeItem( itm
, Slot
->ItemResRef
, true );
1308 //this depends on setslotitemres using setslotitem
1309 SetSlotItemRes(newItem
, slot
, 0,0,0);
1312 void Inventory::dump()
1314 printf( "INVENTORY:\n" );
1315 for (unsigned int i
= 0; i
< Slots
.size(); i
++) {
1316 CREItem
* itm
= Slots
[i
];
1322 printf ( "%2u: %8.8s - (%d %d %d) %x Wt: %d x %dLb\n", i
, itm
->ItemResRef
, itm
->Usages
[0], itm
->Usages
[1], itm
->Usages
[2], itm
->Flags
, itm
->StackAmount
, itm
->Weight
);
1325 printf( "Equipped: %d\n", Equipped
);
1328 printf( "Total weight: %d\n", Weight
);
1331 void Inventory::EquipBestWeapon(int flags
)
1335 ieDword best_slot
= SLOT_FIST
;
1336 ITMExtHeader
*header
;
1338 char AnimationType
[2]={0,0};
1339 ieWord MeleeAnimation
[3]={100,0,0};
1341 //cannot change equipment when holding magic weapons
1342 if (Equipped
== SLOT_MAGIC
-SLOT_MELEE
) {
1346 if (flags
&EQUIP_RANGED
) {
1347 for(i
=SLOT_RANGED
;i
<LAST_RANGED
;i
++) {
1348 const Item
*itm
= GetItemPointer(i
, Slot
);
1351 int tmp
= itm
->GetDamagePotential(true, header
);
1355 memcpy(AnimationType
,itm
->AnimationType
,sizeof(AnimationType
) );
1356 memcpy(MeleeAnimation
,header
->MeleeAnimation
,sizeof(MeleeAnimation
) );
1358 gamedata
->FreeItem( itm
, Slot
->ItemResRef
, false );
1361 //ranged melee weapons like throwing daggers (not bows!)
1362 for(i
=SLOT_MELEE
;i
<LAST_MELEE
;i
++) {
1363 const Item
*itm
= GetItemPointer(i
, Slot
);
1366 int tmp
= itm
->GetDamagePotential(true, header
);
1370 memcpy(AnimationType
,itm
->AnimationType
,sizeof(AnimationType
) );
1371 memcpy(MeleeAnimation
,header
->MeleeAnimation
,sizeof(MeleeAnimation
) );
1373 gamedata
->FreeItem( itm
, Slot
->ItemResRef
, false );
1377 if (flags
&EQUIP_MELEE
) {
1378 for(i
=SLOT_MELEE
;i
<LAST_MELEE
;i
++) {
1379 const Item
*itm
= GetItemPointer(i
, Slot
);
1381 //the Slot flag is enough for this
1382 //though we need animation type/damagepotential anyway
1383 if (Slot
->Flags
&IE_INV_ITEM_BOW
) continue;
1385 int tmp
= itm
->GetDamagePotential(false, header
);
1389 memcpy(AnimationType
,itm
->AnimationType
,sizeof(AnimationType
) );
1390 memcpy(MeleeAnimation
,header
->MeleeAnimation
,sizeof(MeleeAnimation
) );
1392 gamedata
->FreeItem( itm
, Slot
->ItemResRef
, false );
1396 EquipItem(best_slot
);
1397 UpdateWeaponAnimation();
1400 #define ID_NONEED 0 //id is not important
1401 #define ID_NEED 1 //id is important
1402 #define ID_NO 2 //shouldn't id
1404 /* returns true if there are more item usages not fitting in given array */
1405 bool Inventory::GetEquipmentInfo(ItemExtHeader
*array
, int startindex
, int count
)
1409 memset(array
, 0, count
* sizeof(ItemExtHeader
) );
1410 for(unsigned int idx
=0;idx
<Slots
.size();idx
++) {
1411 if (!core
->QuerySlotEffects(idx
)) {
1416 const Item
*itm
= GetItemPointer(idx
, slot
);
1420 for(int ehc
=0;ehc
<itm
->ExtHeaderCount
;ehc
++) {
1421 ITMExtHeader
*ext_header
= itm
->ext_headers
+ehc
;
1422 if (ext_header
->Location
!=ITEM_LOC_EQUIPMENT
) {
1425 //skipping if we cannot use the item
1426 int idreq1
= (slot
->Flags
&IE_INV_ITEM_IDENTIFIED
);
1427 int idreq2
= ext_header
->IDReq
;
1430 if (idreq1
) continue;
1433 if (!idreq1
) continue;
1438 if (actual
>startindex
) {
1440 //store the item, return if we can't store more
1442 gamedata
->FreeItem(itm
, slot
->ItemResRef
, false);
1446 memcpy(array
[pos
].itemname
, slot
->ItemResRef
, sizeof(ieResRef
) );
1447 array
[pos
].slot
= idx
;
1448 array
[pos
].headerindex
= ehc
;
1449 int slen
= ((char *) &(array
[pos
].itemname
)) -((char *) &(array
[pos
].AttackType
));
1450 memcpy(&(array
[pos
].AttackType
), &(ext_header
->AttackType
), slen
);
1451 if (ext_header
->Charges
) {
1452 //don't modify ehc, it is a counter
1453 if (ehc
>=CHARGE_COUNTERS
) {
1454 array
[pos
].Charges
=slot
->Usages
[0];
1456 array
[pos
].Charges
=slot
->Usages
[ehc
];
1459 array
[pos
].Charges
=0xffff;
1464 gamedata
->FreeItem(itm
, slot
->ItemResRef
, false);
1470 //The slot index value is optional, if you supply it,
1471 // then ItemExcl will be returned as if the item was already unequipped
1472 ieDword
Inventory::GetEquipExclusion(int index
) const
1478 const Item
*itm
= GetItemPointer(index
, slot
);
1482 ieDword ret
= ItemExcl
&~itm
->ItemExcl
;
1483 gamedata
->FreeItem(itm
, slot
->ItemResRef
, false);
1487 void Inventory::UpdateShieldAnimation(Item
*it
)
1489 char AnimationType
[2]={0,0};
1490 int WeaponType
= -1;
1493 memcpy(AnimationType
, it
->AnimationType
, 2);
1494 if (core
->CanUseItemType(SLOT_WEAPON
, it
))
1495 WeaponType
= IE_ANI_WEAPON_2W
;
1497 WeaponType
= IE_ANI_WEAPON_1H
;
1499 WeaponType
= IE_ANI_WEAPON_1H
;
1501 Owner
->SetUsedShield(AnimationType
, WeaponType
);
1504 void Inventory::UpdateWeaponAnimation()
1506 int slot
= GetEquippedSlot();
1507 int effect
= core
->QuerySlotEffects( slot
);
1508 if (effect
== SLOT_EFFECT_MISSILE
) {
1510 slot
= FindRangedWeapon();
1512 int WeaponType
= -1;
1514 char AnimationType
[2]={0,0};
1515 ieWord MeleeAnimation
[3]={100,0,0};
1520 ITMExtHeader
*header
= 0;
1521 const Item
*itm
= GetItemPointer(slot
, Slot
);
1523 itm
->GetDamagePotential(false, header
);
1524 memcpy(AnimationType
,itm
->AnimationType
,sizeof(AnimationType
) );
1525 //for twohanded flag, you don't need itm
1526 if (Slot
->Flags
& IE_INV_ITEM_TWOHANDED
)
1527 WeaponType
= IE_ANI_WEAPON_2H
;
1530 // Examine shield slot to check if we're using two weapons
1531 // TODO: for consistency, use same Item* access method as above
1532 bool twoweapon
= false;
1533 int slot
= GetShieldSlot();
1536 si
= GetSlotItem( (ieDword
) slot
);
1539 Item
* it
= gamedata
->GetItem(si
->ItemResRef
);
1540 if (core
->CanUseItemType(SLOT_WEAPON
, it
))
1542 gamedata
->FreeItem(it
, si
->ItemResRef
, false);
1546 WeaponType
= IE_ANI_WEAPON_2W
;
1548 WeaponType
= IE_ANI_WEAPON_1H
;
1553 memcpy(MeleeAnimation
,header
->MeleeAnimation
,
1554 sizeof(MeleeAnimation
) );
1556 gamedata
->FreeItem( itm
, Slot
->ItemResRef
, false );
1557 Owner
->SetUsedWeapon(AnimationType
, MeleeAnimation
, WeaponType
);
1560 //this function will also check disabled slots (if that feature will be imped)
1561 bool Inventory::IsSlotBlocked(int slot
) const
1563 if (slot
<SLOT_MELEE
) return false;
1564 if (slot
>LAST_MELEE
) return false;
1569 otherslot
= SLOT_LEFT
;
1571 return HasItemInSlot("",otherslot
);
1574 inline bool Inventory::TwoHandedInSlot(int slot
) const
1578 item
= GetSlotItem(slot
);
1579 if (!item
) return false;
1580 if (item
->Flags
&IE_INV_ITEM_TWOHANDED
) {
1586 int Inventory::WhyCantEquip(int slot
, int twohanded
) const
1588 // check only for hand slots
1589 if ((slot
<SLOT_MELEE
|| slot
>LAST_MELEE
) && (slot
!= SLOT_LEFT
) ) {
1593 //magic items have the highest priority
1594 if ( HasItemInSlot("", SLOT_MAGIC
)) {
1595 //magic weapon is in use
1596 return STR_MAGICWEAPON
;
1599 //can't equip in shield slot if a weapon slot is twohanded
1600 for (int i
=SLOT_MELEE
; i
<=LAST_MELEE
;i
++) {
1606 otherslot
= SLOT_LEFT
;
1608 if (slot
==otherslot
) {
1609 if (TwoHandedInSlot(i
)) {
1610 return STR_TWOHANDED_USED
;
1617 if (slot
>=SLOT_MELEE
&&slot
<=LAST_MELEE
&& (slot
&1) ) {
1618 return STR_NOT_IN_OFFHAND
;
1621 if (slot
==SLOT_LEFT
) {
1622 return STR_NOT_IN_OFFHAND
;
1625 if (IsSlotBlocked(slot
)) {
1626 //cannot equip two handed while shield slot is in use?
1627 return STR_OFFHAND_USED
;
1633 //recharge items on rest, if rest was partial, recharge only 'hours'
1634 //if this latter functionality is unwanted, then simply don't recharge if
1636 void Inventory::ChargeAllItems(int hours
)
1638 //this loop is going from start
1639 for (size_t i
= 0; i
< Slots
.size(); i
++) {
1640 CREItem
*item
= Slots
[i
];
1645 Item
*itm
= gamedata
->GetItem( item
->ItemResRef
);
1648 for(int h
=0;h
<CHARGE_COUNTERS
;h
++) {
1649 ITMExtHeader
*header
= itm
->GetExtHeader(h
);
1650 if (header
&& (header
->RechargeFlags
&IE_ITEM_RECHARGE
)) {
1651 unsigned short add
= header
->Charges
;
1652 if (hours
&& add
>hours
) add
=hours
;
1653 add
+=item
->Usages
[h
];
1654 if(add
>header
->Charges
) add
=header
->Charges
;
1655 item
->Usages
[h
]=add
;
1658 gamedata
->FreeItem( itm
, item
->ItemResRef
, false );