TickHook: Fix crash when TickHook isn't set.
[gemrb.git] / gemrb / core / Inventory.cpp
blobb1bf807ec07b503dde9133b8e62f33e8c7044d44
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)
22 //or actors (.cre).
24 #include "Inventory.h"
26 #include "win32def.h"
27 #include "strrefs.h"
29 #include "DisplayMessage.h"
30 #include "Game.h"
31 #include "GameData.h"
32 #include "Interface.h"
33 #include "Item.h"
34 #include "ScriptEngine.h"
35 #include "Scriptable/Actor.h"
37 #include <cstdio>
39 static int SLOT_HEAD = -1;
40 static int SLOT_MAGIC = -1;
41 static int SLOT_FIST = -1;
42 static int SLOT_MELEE = -1;
43 static int LAST_MELEE = -1;
44 static int SLOT_RANGED = -1;
45 static int LAST_RANGED = -1;
46 static int SLOT_QUICK = -1;
47 static int LAST_QUICK = -1;
48 static int SLOT_INV = -1;
49 static int LAST_INV = -1;
50 static int SLOT_LEFT = -1;
52 //IWD2 style slots
53 static bool IWD2 = false;
54 static int MagicBit = 0;
56 static void InvalidSlot(int slot)
58 printMessage("Inventory"," ",LIGHT_RED);
59 printf("Invalid slot: %d!\n",slot);
60 abort();
63 //This inline function returns both an item pointer and the slot data.
64 //slot is a dynamic slot number (SLOT_*)
65 inline Item *Inventory::GetItemPointer(ieDword slot, CREItem *&item) const
67 item = GetSlotItem(slot);
68 if (!item) return NULL;
69 if (!item->ItemResRef[0]) return NULL;
70 return gamedata->GetItem(item->ItemResRef);
73 void Inventory::Init(int mb)
75 SLOT_MAGIC=-1;
76 SLOT_FIST=-1;
77 SLOT_MELEE=-1;
78 LAST_MELEE=-1;
79 SLOT_RANGED=-1;
80 LAST_RANGED=-1;
81 SLOT_QUICK=-1;
82 LAST_QUICK=-1;
83 SLOT_LEFT=-1;
84 //TODO: set this correctly
85 IWD2 = false;
86 MagicBit = mb;
89 Inventory::Inventory()
91 Owner = NULL;
92 InventoryType = INVENTORY_HEAP;
93 Changed = false;
94 Weight = 0;
95 Equipped = IW_NO_EQUIPPED;
96 EquippedHeader = 0;
97 ItemExcl = 0;
98 memset(ItemTypes, 0, sizeof(ItemTypes));
101 Inventory::~Inventory()
103 for (size_t i = 0; i < Slots.size(); i++) {
104 if (Slots[i]) {
105 delete( Slots[i] );
106 Slots[i] = NULL;
111 // duplicates the source inventory into the current one
112 // also changes the items to not drop, so simulacrum and similar don't become factories
113 void Inventory::CopyFrom(const Actor *source)
115 if (!source) {
116 return;
119 SetSlotCount(source->inventory.GetSlotCount());
121 // allocate the items and mark them undroppable
122 CREItem *tmp, *item;
123 for (size_t i = 0; i < source->inventory.Slots.size(); i++) {
124 item = source->inventory.Slots[i];
125 if (item) {
126 tmp = new CREItem();
127 memcpy(tmp, item, sizeof(CREItem));
128 tmp->Flags |= IE_INV_ITEM_UNDROPPABLE;
129 int ret = AddSlotItem(tmp, i);
130 if (ret != ASI_SUCCESS) {
131 delete tmp;
136 // preserve the equipped status
137 Equipped = source->inventory.GetEquipped();
138 EquippedHeader = source->inventory.GetEquippedHeader();
140 Changed = true;
141 CalculateWeight();
144 CREItem *Inventory::GetItem(unsigned int slot)
146 if (slot >= Slots.size() ) {
147 InvalidSlot(slot);
148 return NULL;
150 CREItem *item = Slots[slot];
151 Slots.erase(Slots.begin()+slot);
152 return item;
155 //This hack sets the charge counters for non-rechargeable items,
156 //if their charge is zero
157 inline void HackCharges(CREItem *item)
159 Item *itm = gamedata->GetItem( item->ItemResRef );
160 if (itm) {
161 for (int i=0;i<3;i++) {
162 if (item->Usages[i]) {
163 continue;
165 ITMExtHeader *h = itm->GetExtHeader(i);
166 if (h && !(h->RechargeFlags&IE_ITEM_RECHARGE)) {
167 //HACK: the original (bg2) allows for 0 charged gems
168 if (h->Charges) {
169 item->Usages[i] = h->Charges;
170 } else {
171 item->Usages[i] = 1;
175 gamedata->FreeItem( itm, item->ItemResRef, false );
179 void Inventory::AddItem(CREItem *item)
181 if (!item) return; //invalid items get no slot
182 Slots.push_back(item);
183 HackCharges(item);
184 //this will update the flags (needed for unmovable items in containers)
185 //but those *can* be picked up (like the bg2 portal key), so we skip it
186 //Changed=true;
189 void Inventory::CalculateWeight()
191 if (!Changed) {
192 return;
194 Weight = 0;
195 for (size_t i = 0; i < Slots.size(); i++) {
196 CREItem *slot = Slots[i];
197 if (!slot) {
198 continue;
200 if (slot->Weight == -1) {
201 Item *itm = gamedata->GetItem( slot->ItemResRef );
202 if (itm) {
203 //simply adding the item flags to the slot
204 slot->Flags |= (itm->Flags<<8);
205 //some slot flags might be affected by the item flags
206 if (!(slot->Flags & IE_INV_ITEM_CRITICAL)) {
207 slot->Flags |= IE_INV_ITEM_DESTRUCTIBLE;
209 //this is for converting IWD items magic flag
210 if (MagicBit) {
211 if (slot->Flags&IE_INV_ITEM_UNDROPPABLE) {
212 slot->Flags|=IE_INV_ITEM_MAGICAL;
213 slot->Flags&=~IE_INV_ITEM_UNDROPPABLE;
217 if (!(slot->Flags & IE_INV_ITEM_MOVABLE)) {
218 slot->Flags |= IE_INV_ITEM_UNDROPPABLE;
221 if (slot->Flags & IE_INV_ITEM_STOLEN2) {
222 slot->Flags |= IE_INV_ITEM_STOLEN;
225 //auto identify basic items
226 if (!itm->LoreToID) {
227 slot->Flags |= IE_INV_ITEM_IDENTIFIED;
230 //if item is stacked mark it as so
231 if (itm->StackAmount) {
232 slot->Flags |= IE_INV_ITEM_STACKED;
235 slot->Weight = itm->Weight;
236 slot->StackAmount = itm->StackAmount;
237 gamedata->FreeItem( itm, slot->ItemResRef, false );
239 else {
240 printMessage( "Inventory", " ", LIGHT_RED);
241 printf("Invalid item: %s!\n", slot->ItemResRef);
242 slot->Weight = 0;
244 } else {
245 slot->Flags &= ~IE_INV_ITEM_ACQUIRED;
247 if (slot->Weight > 0) {
248 Weight += slot->Weight * ((slot->Usages[0] && slot->StackAmount > 1) ? slot->Usages[0] : 1);
251 Changed = false;
254 void Inventory::AddSlotEffects(ieDword index)
256 CREItem* slot;
258 const Item *itm = GetItemPointer(index, slot);
259 if (!itm) {
260 printMessage("Inventory","Invalid item equipped...\n",LIGHT_RED);
261 return;
263 ItemExcl|=itm->ItemExcl;
264 ieDword pos = itm->ItemType/32;
265 ieDword bit = itm->ItemType%32;
266 if (pos<4) {
267 ItemTypes[pos]|=1<<bit;
270 ieWord gradient = itm->GetWieldedGradient();
271 if (gradient!=0xffff) {
272 Owner->SetBase(IE_COLORS, gradient);
275 //get the equipping effects
276 EffectQueue *eqfx = itm->GetEffectBlock(Owner, Owner->Pos, -1, index, 0);
277 gamedata->FreeItem( itm, slot->ItemResRef, false );
279 Owner->RefreshEffects(eqfx);
280 //call gui for possible paperdoll animation changes
281 if (Owner->InParty) {
282 core->SetEventFlag(EF_UPDATEANIM);
286 //no need to know the item effects 'personally', the equipping slot
287 //is stored in them
288 void Inventory::RemoveSlotEffects(ieDword index)
290 Owner->fxqueue.RemoveEquippingEffects(index);
291 Owner->RefreshEffects(NULL);
292 //call gui for possible paperdoll animation changes
293 if (Owner->InParty) {
294 core->SetEventFlag(EF_UPDATEANIM);
298 void Inventory::SetInventoryType(int arg)
300 InventoryType = arg;
303 void Inventory::SetSlotCount(unsigned int size)
305 if (Slots.size()) {
306 printf("Inventory size changed???\n");
307 //we don't allow reassignment,
308 //if you want this, delete the previous Slots here
309 abort();
311 Slots.assign((size_t) size, NULL);
314 /** if you supply a "" string, then it checks if the slot is empty */
315 bool Inventory::HasItemInSlot(const char *resref, unsigned int slot) const
317 if (slot >= Slots.size()) {
318 return false;
320 const CREItem *item = Slots[slot];
321 if (!item) {
322 return false;
324 if (!resref[0]) {
325 return true;
327 if (strnicmp( item->ItemResRef, resref, 8 )==0) {
328 return true;
330 return false;
333 bool Inventory::HasItemType(ieDword type) const
335 if (type>255) return false;
336 int idx = type/32;
337 int bit = type%32;
338 return (ItemTypes[idx] & (1<<bit) )!=0;
341 /** counts the items in the inventory, if stacks == 1 then stacks are
342 accounted for their heap size */
343 int Inventory::CountItems(const char *resref, bool stacks) const
345 int count = 0;
346 size_t slot = Slots.size();
347 while(slot--) {
348 const CREItem *item = Slots[slot];
349 if (!item) {
350 continue;
352 if (resref && resref[0]) {
353 if (!strnicmp(resref, item->ItemResRef, 8) )
354 continue;
356 if (stacks && (item->Flags&IE_INV_ITEM_STACKED) ) {
357 count+=item->Usages[0];
359 else {
360 count++;
363 return count;
366 /** this function can look for stolen, equipped, identified, destructible
367 etc, items. You just have to specify the flags in the bitmask
368 specifying 1 in a bit signifies a requirement */
369 bool Inventory::HasItem(const char *resref, ieDword flags) const
371 size_t slot = Slots.size();
372 while(slot--) {
373 const CREItem *item = Slots[slot];
374 if (!item) {
375 continue;
377 if ( (flags&item->Flags)!=flags) {
378 continue;
380 if (resref[0] && strnicmp(item->ItemResRef, resref,8) ) {
381 continue;
383 return true;
385 return false;
388 void Inventory::KillSlot(unsigned int index)
390 if (InventoryType==INVENTORY_HEAP) {
391 Slots.erase(Slots.begin()+index);
392 return;
394 CREItem *item = Slots[index];
395 if (!item) {
396 return;
399 //the used up item vanishes from the quickslot bar
400 if (Owner->IsSelected()) {
401 core->SetEventFlag( EF_ACTION );
404 Slots[index] = NULL;
405 int effect = core->QuerySlotEffects( index );
406 if (!effect) {
407 return;
409 RemoveSlotEffects( index );
410 Item *itm = gamedata->GetItem(item->ItemResRef);
411 //this cannot happen, but stuff happens!
412 if (!itm) {
413 return;
415 ItemExcl &= ~itm->ItemExcl;
417 switch (effect) {
418 case SLOT_EFFECT_LEFT:
419 UpdateShieldAnimation(0);
420 break;
421 case SLOT_EFFECT_MISSILE:
422 //getting a new projectile of the same type
423 if (Equipped + SLOT_MELEE == (int) index) {
424 if (Equipped < 0) {
425 //always get the projectile weapon header (this quiver was equipped)
426 ITMExtHeader *header = itm->GetWeaponHeader(true);
427 Equipped = FindRangedProjectile(header->ProjectileQualifier);
428 if (Equipped!=IW_NO_EQUIPPED) {
429 EquipItem(Equipped+SLOT_MELEE);
430 } else {
431 EquipItem(SLOT_FIST);
435 UpdateWeaponAnimation();
436 break;
437 case SLOT_EFFECT_MELEE:
438 // reset Equipped if it was the removed item
439 if (Equipped+SLOT_MELEE == (int)index)
440 Equipped = IW_NO_EQUIPPED;
441 else if (Equipped < 0) {
442 //always get the projectile weapon header (this is a bow, because Equipped is negative)
443 ITMExtHeader *header = itm->GetWeaponHeader(true);
444 if (header) {
445 //find the equipped type
446 int type = header->ProjectileQualifier;
447 int weaponslot = FindTypedRangedWeapon(type);
448 CREItem *item2 = Slots[weaponslot];
449 if (item2) {
450 Item *itm2 = gamedata->GetItem(item2->ItemResRef);
451 if (itm2) {
452 if (type == header->ProjectileQualifier) {
453 Equipped = FindRangedProjectile(header->ProjectileQualifier);
454 if (Equipped!=IW_NO_EQUIPPED) {
455 EquipItem(Equipped+SLOT_MELEE);
456 } else {
457 EquipItem(SLOT_FIST);
460 gamedata->FreeItem(itm2, item2->ItemResRef, false);
465 // reset Equipped if it is a ranged weapon slot
466 // but not magic weapon slot!
468 UpdateWeaponAnimation();
469 break;
470 case SLOT_EFFECT_HEAD:
471 Owner->SetUsedHelmet("");
472 break;
473 case SLOT_EFFECT_ITEM:
474 //remove the armor type only if this item is responsible for it
475 if ((ieDword) (itm->AnimationType[0]-'1') == Owner->GetBase(IE_ARMOR_TYPE)) {
476 Owner->SetBase(IE_ARMOR_TYPE, 0);
478 break;
480 gamedata->FreeItem(itm, item->ItemResRef, false);
482 /** if resref is "", then destroy ALL items
483 this function can look for stolen, equipped, identified, destructible
484 etc, items. You just have to specify the flags in the bitmask
485 specifying 1 in a bit signifies a requirement */
486 unsigned int Inventory::DestroyItem(const char *resref, ieDword flags, ieDword count)
488 unsigned int destructed = 0;
489 size_t slot = Slots.size();
491 while(slot--) {
492 //ignore the fist slot
493 if (slot == (unsigned int)SLOT_FIST) {
494 continue;
497 CREItem *item = Slots[slot];
498 if (!item) {
499 continue;
501 // here you can simply destroy all items of a specific type
502 if ( (flags&item->Flags)!=flags) {
503 continue;
505 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
506 continue;
508 //we need to acknowledge that the item was destroyed
509 //use unequip stuff, decrease encumbrance etc,
510 //until that, we simply erase it
511 ieDword removed;
513 if (item->Flags&IE_INV_ITEM_STACKED) {
514 removed=item->Usages[0];
515 if (count && (removed + destructed > count) ) {
516 removed = count - destructed;
517 item = RemoveItem( (unsigned int) slot, removed );
519 else {
520 KillSlot( (unsigned int) slot);
522 } else {
523 removed=1;
524 KillSlot( (unsigned int) slot);
526 delete item;
527 Changed = true;
528 destructed+=removed;
529 if (count && (destructed>=count) )
530 break;
532 if (Changed && Owner && Owner->InParty) displaymsg->DisplayConstantString(STR_LOSTITEM, 0xbcefbc);
534 return destructed;
537 CREItem *Inventory::RemoveItem(unsigned int slot, unsigned int count)
539 CREItem *item;
541 if (slot >= Slots.size() ) {
542 InvalidSlot(slot);
543 return NULL;
545 Changed = true;
546 item = Slots[slot];
548 if (!item) {
549 return NULL;
552 if (!count || !(item->Flags&IE_INV_ITEM_STACKED) ) {
553 KillSlot(slot);
554 return item;
556 if (count >= item->Usages[0]) {
557 KillSlot(slot);
558 return item;
561 CREItem *returned = new CREItem(*item);
562 item->Usages[0]-=count;
563 returned->Usages[0]=(ieWord) count;
564 return returned;
567 //flags set disable item transfer
568 //except for undroppable and equipped, which are opposite (and shouldn't be set)
569 int Inventory::RemoveItem(const char *resref, unsigned int flags, CREItem **res_item)
571 size_t slot = Slots.size();
572 unsigned int mask = (flags^(IE_INV_ITEM_UNDROPPABLE|IE_INV_ITEM_EQUIPPED));
573 if (core->HasFeature(GF_NO_DROP_CAN_MOVE) ) {
574 mask &= ~IE_INV_ITEM_UNDROPPABLE;
576 while(slot--) {
577 CREItem *item = Slots[slot];
578 if (!item) {
579 continue;
582 if (flags && (mask&item->Flags)==flags) {
583 continue;
585 if (!flags && (mask&item->Flags)!=0) {
586 continue;
588 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
589 continue;
591 *res_item=RemoveItem( (unsigned int) slot, 0);
592 return (int) slot;
594 *res_item = NULL;
595 return -1;
598 void Inventory::SetSlotItem(CREItem* item, unsigned int slot)
600 if (slot >= Slots.size() ) {
601 InvalidSlot(slot);
602 return;
604 Changed = true;
605 if (Slots[slot]) {
606 delete Slots[slot];
609 HackCharges(item);
611 Slots[slot] = item;
613 //update the action bar next time
614 if (Owner->IsSelected()) {
615 core->SetEventFlag( EF_ACTION );
619 int Inventory::AddSlotItem(CREItem* item, int slot, int slottype)
621 int twohanded = item->Flags&IE_INV_ITEM_TWOHANDED;
622 if (slot >= 0) {
623 if ((unsigned)slot >= Slots.size()) {
624 InvalidSlot(slot);
625 return ASI_FAILED;
628 //check for equipping weapons
629 if (WhyCantEquip(slot,twohanded)) {
630 return ASI_FAILED;
633 if (!Slots[slot]) {
634 item->Flags |= IE_INV_ITEM_ACQUIRED;
635 SetSlotItem(item, slot);
636 EquipItem(slot);
637 return ASI_SUCCESS;
640 CREItem *myslot = Slots[slot];
641 if (ItemsAreCompatible( myslot, item )) {
642 //calculate with the max movable stock
643 int chunk = item->Usages[0];
644 int newamount = myslot->Usages[0]+chunk;
645 if (newamount>myslot->StackAmount) {
646 newamount=myslot->StackAmount;
647 chunk = item->Usages[0]-newamount;
649 if (!chunk) {
650 return -1;
652 myslot->Flags |= IE_INV_ITEM_ACQUIRED;
653 myslot->Usages[0] = (ieWord) (myslot->Usages[0] + chunk);
654 item->Usages[0] = (ieWord) (item->Usages[0] - chunk);
655 Changed = true;
656 EquipItem(slot);
657 if (item->Usages[0] == 0) {
658 delete item;
659 return ASI_SUCCESS;
661 return ASI_PARTIAL;
663 return ASI_FAILED;
666 bool which;
667 if (slot==SLOT_AUTOEQUIP) {
668 which=true;
669 } else {
670 which=false;
672 int res = ASI_FAILED;
673 int max = (int) Slots.size();
674 for (int i = 0;i<max;i++) {
675 //never autoequip in the magic slot!
676 if (i==SLOT_MAGIC)
677 continue;
678 if ((i<SLOT_INV || i>LAST_INV)!=which)
679 continue;
680 if (!(core->QuerySlotType(i)&slottype))
681 continue;
682 //the slot has been disabled for this actor
683 if (i>=SLOT_MELEE && i<=LAST_MELEE) {
684 if (Owner->GetQuickSlot(i-SLOT_MELEE)==0xffff) {
685 continue;
688 int part_res = AddSlotItem (item, i);
689 if (part_res == ASI_SUCCESS) return ASI_SUCCESS;
690 else if (part_res == ASI_PARTIAL) res = ASI_PARTIAL;
693 return res;
696 //Used by FillSlot
697 void Inventory::TryEquipAll(int slot)
699 for(int i=SLOT_INV;i<=LAST_INV;i++) {
700 CREItem *item = Slots[i];
701 if (!item) {
702 continue;
705 Slots[i]=NULL;
706 if (AddSlotItem(item, slot) == ASI_SUCCESS) {
707 return;
709 //try to stuff it back, it should work
710 if (AddSlotItem(item, i) != ASI_SUCCESS) {
711 delete item;
716 int Inventory::AddStoreItem(STOItem* item, int action)
718 CREItem *temp;
719 int ret = -1;
721 // item->PurchasedAmount is the number of items bought
722 // (you can still add grouped objects in a single step,
723 // just set up STOItem)
724 while (item->PurchasedAmount) {
725 //the first part of a STOItem is essentially a CREItem
726 temp = new CREItem();
727 memcpy( temp, item, sizeof( CREItem ) );
728 //except the Expired flag
729 temp->Expired=0;
730 if (action==STA_STEAL) {
731 temp->Flags |= IE_INV_ITEM_STOLEN;
733 temp->Flags &= ~IE_INV_ITEM_SELECTED;
735 ret = AddSlotItem( temp, SLOT_ONLYINVENTORY );
736 if (ret != ASI_SUCCESS) {
737 delete temp;
738 break;
740 if (item->InfiniteSupply!=-1) {
741 if (!item->AmountInStock) {
742 break;
744 item->AmountInStock--;
746 item->PurchasedAmount--;
748 return ret;
751 /* could the source item be dropped on the target item to merge them */
752 bool Inventory::ItemsAreCompatible(CREItem* target, CREItem* source) const
754 if (!target) {
755 //this isn't always ok, please check!
756 printMessage("Inventory","Null item encountered by ItemsAreCompatible()",YELLOW);
757 return true;
760 if (!(source->Flags&IE_INV_ITEM_STACKED) ) {
761 return false;
764 if (!strnicmp( target->ItemResRef, source->ItemResRef,8 )) {
765 return true;
767 return false;
770 //depletes a magical item
771 //if flags==0 then magical weapons are not harmed
772 int Inventory::DepleteItem(ieDword flags)
774 for (size_t i = 0; i < Slots.size(); i++) {
775 CREItem *item = Slots[i];
776 if (!item) {
777 continue;
780 //don't harm critical items
781 //don't harm nonmagical items
782 //don't harm indestructible items
783 if ( (item->Flags&(IE_INV_ITEM_CRITICAL|IE_INV_DEPLETABLE)) != IE_INV_DEPLETABLE) {
784 continue;
787 //if flags = 0 then weapons are not depleted
788 if (!flags) {
789 Item *itm = gamedata->GetItem( item->ItemResRef );
790 if (!itm)
791 continue;
792 //if the item is usable in weapon slot, then it is weapon
793 int weapon = core->CanUseItemType( SLOT_WEAPON, itm );
794 gamedata->FreeItem( itm, item->ItemResRef, false );
795 if (weapon)
796 continue;
798 //deplete item
799 item->Usages[0]=0;
800 item->Usages[1]=0;
801 item->Usages[2]=0;
803 return -1;
806 // if flags is 0, skips undroppable items
807 // if flags is IE_INV_ITEM_UNDROPPABLE, doesn't skip undroppable items
808 // TODO: once all callers have been checked, this can be reversed to make more sense
809 int Inventory::FindItem(const char *resref, unsigned int flags) const
811 unsigned int mask = (flags^IE_INV_ITEM_UNDROPPABLE);
812 if (core->HasFeature(GF_NO_DROP_CAN_MOVE) ) {
813 mask &= ~IE_INV_ITEM_UNDROPPABLE;
815 for (size_t i = 0; i < Slots.size(); i++) {
816 const CREItem *item = Slots[i];
817 if (!item) {
818 continue;
820 if ( mask & item->Flags ) {
821 continue;
823 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
824 continue;
826 return (int) i;
828 return -1;
831 bool Inventory::DropItemAtLocation(unsigned int slot, unsigned int flags, Map *map, const Point &loc)
833 if (slot >= Slots.size()) {
834 return false;
836 //these slots will never 'drop' the item
837 if ((slot==(unsigned int) SLOT_FIST) || (slot==(unsigned int) SLOT_MAGIC)) {
838 return false;
841 CREItem *item = Slots[slot];
842 if (!item) {
843 return false;
845 //if you want to drop undoppable items, simply set IE_INV_UNDROPPABLE
846 //by default, it won't drop them
847 if ( ((flags^IE_INV_ITEM_UNDROPPABLE)&item->Flags)!=flags) {
848 return false;
850 if (!map) {
851 return false;
853 map->AddItemToLocation(loc, item);
854 Changed = true;
855 KillSlot(slot);
856 return true;
859 bool Inventory::DropItemAtLocation(const char *resref, unsigned int flags, Map *map, const Point &loc)
861 bool dropped = false;
863 if (!map) {
864 return false;
867 //this loop is going from start
868 for (size_t i = 0; i < Slots.size(); i++) {
869 //these slots will never 'drop' the item
870 if ((i==(unsigned int) SLOT_FIST) || (i==(unsigned int) SLOT_MAGIC)) {
871 continue;
873 CREItem *item = Slots[i];
874 if (!item) {
875 continue;
877 //if you want to drop undoppable items, simply set IE_INV_UNDROPPABLE
878 //by default, it won't drop them
879 if ( ((flags^IE_INV_ITEM_UNDROPPABLE)&item->Flags)!=flags) {
880 continue;
882 if (resref[0] && strnicmp(item->ItemResRef, resref, 8) ) {
883 continue;
885 // mark it as unequipped, so it doesn't cause problems in stores
886 item->Flags &= ~ IE_INV_ITEM_EQUIPPED;
887 map->AddItemToLocation(loc, item);
888 Changed = true;
889 dropped = true;
890 KillSlot((unsigned int) i);
891 //if it isn't all items then we stop here
892 if (resref[0])
893 break;
895 return dropped;
898 CREItem *Inventory::GetSlotItem(unsigned int slot) const
900 if (slot >= Slots.size() ) {
901 InvalidSlot(slot);
902 return NULL;
904 return Slots[slot];
907 ieDword Inventory::GetItemFlag(unsigned int slot) const
909 const CREItem *item = GetSlotItem(slot);
910 if (!item) {
911 return 0;
913 return item->Flags;
916 bool Inventory::ChangeItemFlag(unsigned int slot, ieDword arg, int op)
918 CREItem *item = GetSlotItem(slot);
919 if (!item) {
920 return false;
922 switch (op) {
923 case BM_SET: item->Flags = arg; break;
924 case BM_OR: item->Flags |= arg; break;
925 case BM_NAND: item->Flags &= ~arg; break;
926 case BM_XOR: item->Flags ^= arg; break;
927 case BM_AND: item->Flags &= arg; break;
929 return true;
932 //this is the low level equipping
933 //all checks have been made previously
934 bool Inventory::EquipItem(unsigned int slot)
936 ITMExtHeader *header;
938 if (!Owner) {
939 //maybe assertion too?
940 return false;
942 CREItem *item = GetSlotItem(slot);
943 if (!item) {
944 return false;
947 int weaponslot;
949 // add effects of an item just being equipped to actor's effect queue
950 int effect = core->QuerySlotEffects( slot );
951 Item *itm = gamedata->GetItem(item->ItemResRef);
952 if (!itm) {
953 printf("Invalid item Equipped: %s Slot: %d\n", item->ItemResRef, slot);
954 return false;
956 switch (effect) {
957 case SLOT_EFFECT_LEFT:
958 //no idea if the offhand weapon has style, or simply the right
959 //hand style is dominant
960 UpdateShieldAnimation(itm);
961 break;
962 case SLOT_EFFECT_MELEE:
963 //if weapon is ranged, then find quarrel for it and equip that
964 slot -= SLOT_MELEE;
965 weaponslot = slot;
966 EquippedHeader = 0;
967 header = itm->GetExtHeader(EquippedHeader);
968 if (header && header->AttackType == ITEM_AT_BOW) {
969 //find the ranged projectile associated with it.
970 slot = FindRangedProjectile(header->ProjectileQualifier);
971 EquippedHeader = itm->GetWeaponHeaderNumber(true);
972 } else if (header && header->AttackType == ITEM_AT_PROJECTILE) {
973 EquippedHeader = itm->GetWeaponHeaderNumber(true);
974 } else {
975 EquippedHeader = itm->GetWeaponHeaderNumber(false);
977 header = itm->GetExtHeader(EquippedHeader);
978 if (header) {
979 SetEquippedSlot(slot, EquippedHeader);
980 if (slot != IW_NO_EQUIPPED) {
981 Owner->SetupQuickSlot(ACT_WEAPON1+weaponslot, slot+SLOT_MELEE, EquippedHeader);
983 effect = 0; // SetEquippedSlot will already call AddSlotEffects
984 UpdateWeaponAnimation();
986 break;
987 case SLOT_EFFECT_MISSILE:
988 //Get the ranged header of the projectile (so we theoretically allow shooting of daggers)
989 EquippedHeader = itm->GetWeaponHeaderNumber(true);
990 header = itm->GetExtHeader(EquippedHeader);
991 if (header) {
992 weaponslot = FindTypedRangedWeapon(header->ProjectileQualifier);
993 if (weaponslot != SLOT_FIST) {
994 weaponslot -= SLOT_MELEE;
995 SetEquippedSlot((ieWordSigned) (slot-SLOT_MELEE), EquippedHeader);
996 //It is unsure if we can have multiple equipping headers for bows/arrow
997 //It is unclear which item's header index should go there
998 Owner->SetupQuickSlot(ACT_WEAPON1+weaponslot, slot, 0);
1000 UpdateWeaponAnimation();
1002 break;
1003 case SLOT_EFFECT_HEAD:
1004 Owner->SetUsedHelmet(itm->AnimationType);
1005 break;
1006 case SLOT_EFFECT_ITEM:
1007 //adjusting armour level if needed
1009 int l = itm->AnimationType[0]-'1';
1010 if (l>=0 && l<=3) {
1011 Owner->SetBase(IE_ARMOR_TYPE, l);
1012 } else {
1013 UpdateShieldAnimation(itm);
1016 break;
1018 gamedata->FreeItem(itm, item->ItemResRef, false);
1019 if (effect) {
1020 if (item->Flags & IE_INV_ITEM_CURSED) {
1021 item->Flags|=IE_INV_ITEM_UNDROPPABLE;
1023 AddSlotEffects( slot );
1025 return true;
1028 //the removecurse flag will check if it is possible to move the item to the inventory
1029 //after a remove curse spell
1030 bool Inventory::UnEquipItem(unsigned int slot, bool removecurse)
1032 CREItem *item = GetSlotItem(slot);
1033 if (!item) {
1034 return false;
1036 if (removecurse) {
1037 if (item->Flags & IE_INV_ITEM_MOVABLE) {
1038 item->Flags&=~IE_INV_ITEM_UNDROPPABLE;
1040 if (FindCandidateSlot(SLOT_INVENTORY,0,item->ItemResRef)<0) {
1041 return false;
1044 if (!core->HasFeature(GF_NO_DROP_CAN_MOVE) || (item->Flags&IE_INV_ITEM_CURSED) ) {
1045 if (item->Flags & IE_INV_ITEM_UNDROPPABLE ) {
1046 return false;
1049 item->Flags &= ~IE_INV_ITEM_EQUIPPED; //no idea if this is needed, won't hurt
1050 return true;
1053 // find the projectile
1054 // type = 1 - bow
1055 // 2 - xbow
1056 // 4 - sling
1057 //returns equipped code
1058 int Inventory::FindRangedProjectile(unsigned int type) const
1060 for(int i=SLOT_RANGED;i<=LAST_RANGED;i++) {
1061 CREItem *Slot;
1063 const Item *itm = GetItemPointer(i, Slot);
1064 if (!itm) continue;
1065 ITMExtHeader *ext_header = itm->GetExtHeader(0);
1066 unsigned int weapontype = 0;
1067 if (ext_header) {
1068 weapontype = ext_header->ProjectileQualifier;
1070 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1071 if (weapontype & type) {
1072 return i-SLOT_MELEE;
1075 return IW_NO_EQUIPPED;
1078 // find which bow is attached to the projectile marked by 'Equipped'
1079 // returns slotcode
1080 int Inventory::FindRangedWeapon() const
1082 if (Equipped>=0) return SLOT_FIST;
1083 return FindSlotRangedWeapon(Equipped+SLOT_MELEE);
1086 int Inventory::FindSlotRangedWeapon(unsigned int slot) const
1088 if ((int)slot >= SLOT_MELEE) return SLOT_FIST;
1089 CREItem *Slot;
1090 Item *itm = GetItemPointer(slot, Slot);
1091 if (!itm) return SLOT_FIST;
1093 //always look for a ranged header when looking for a projectile/projector
1094 ITMExtHeader *ext_header = itm->GetWeaponHeader(true);
1095 unsigned int type = 0;
1096 if (ext_header) {
1097 type = ext_header->ProjectileQualifier;
1099 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1100 return FindTypedRangedWeapon(type);
1104 // find bow for a specific projectile type
1105 int Inventory::FindTypedRangedWeapon(unsigned int type) const
1107 if (!type) {
1108 return SLOT_FIST;
1110 for(int i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1111 CREItem *Slot;
1113 const Item *itm = GetItemPointer(i, Slot);
1114 if (!itm) continue;
1115 //always look for a ranged header when looking for a projectile/projector
1116 ITMExtHeader *ext_header = itm->GetWeaponHeader(true);
1117 int weapontype = 0;
1118 if (ext_header) {
1119 weapontype = ext_header->ProjectileQualifier;
1121 gamedata->FreeItem(itm, Slot->ItemResRef, false);
1122 if (weapontype & type) {
1123 return i;
1126 return SLOT_FIST;
1129 void Inventory::SetHeadSlot(int arg) { SLOT_HEAD=arg; }
1130 void Inventory::SetFistSlot(int arg) { SLOT_FIST=arg; }
1131 void Inventory::SetMagicSlot(int arg) { SLOT_MAGIC=arg; }
1132 void Inventory::SetWeaponSlot(int arg)
1134 if (SLOT_MELEE==-1) {
1135 SLOT_MELEE=arg;
1137 LAST_MELEE=arg;
1140 //ranged slots should be before MELEE slots
1141 void Inventory::SetRangedSlot(int arg)
1143 assert(SLOT_MELEE!=-1);
1144 if (SLOT_RANGED==-1) {
1145 SLOT_RANGED=arg;
1147 LAST_RANGED=arg;
1150 void Inventory::SetQuickSlot(int arg)
1152 if (SLOT_QUICK==-1) {
1153 SLOT_QUICK=arg;
1155 LAST_QUICK=arg;
1158 void Inventory::SetInventorySlot(int arg)
1160 if (SLOT_INV==-1) {
1161 SLOT_INV=arg;
1163 LAST_INV=arg;
1166 //multiple shield slots are allowed
1167 //but in this case they should be interspersed with melee slots
1168 void Inventory::SetShieldSlot(int arg)
1170 if (SLOT_LEFT!=-1) {
1171 assert(SLOT_MELEE+1==SLOT_LEFT);
1172 IWD2=true;
1173 return;
1175 SLOT_LEFT=arg;
1178 int Inventory::GetHeadSlot()
1180 return SLOT_HEAD;
1183 int Inventory::GetFistSlot()
1185 return SLOT_FIST;
1188 int Inventory::GetMagicSlot()
1190 return SLOT_MAGIC;
1193 int Inventory::GetWeaponSlot()
1195 return SLOT_MELEE;
1198 int Inventory::GetQuickSlot()
1200 return SLOT_QUICK;
1203 int Inventory::GetInventorySlot()
1205 return SLOT_INV;
1208 //if shield slot is empty, call again for fist slot!
1209 int Inventory::GetShieldSlot() const
1211 if (IWD2) {
1212 if (Equipped>=0 && Equipped<=3) {
1213 return Equipped*2+SLOT_MELEE+1;
1215 return -1;
1217 return SLOT_LEFT;
1220 int Inventory::GetEquippedSlot() const
1222 if (Equipped == IW_NO_EQUIPPED) {
1223 return SLOT_FIST;
1225 if (IWD2 && Equipped>=0) {
1226 //i've absolutely NO idea what is this 4 (Avenger)
1227 //Equipped should be 0-3 in iWD2, no???
1228 if (Equipped >= 4) {
1229 return SLOT_MELEE;
1231 return Equipped*2+SLOT_MELEE;
1233 return Equipped+SLOT_MELEE;
1236 bool Inventory::SetEquippedSlot(ieWordSigned slotcode, ieWord header)
1238 EquippedHeader = header;
1240 //doesn't work if magic slot is used, refresh the magic slot just in case
1241 if (HasItemInSlot("",SLOT_MAGIC) && (slotcode!=SLOT_MAGIC-SLOT_MELEE)) {
1242 Equipped = SLOT_MAGIC-SLOT_MELEE;
1243 UpdateWeaponAnimation();
1244 return false;
1247 //if it is an illegal code, make it fist
1248 if ((size_t) (slotcode+SLOT_MELEE)>Slots.size()) {
1249 slotcode=IW_NO_EQUIPPED;
1252 //unequipping (fist slot will be used now)
1253 if (slotcode == IW_NO_EQUIPPED || !HasItemInSlot("",slotcode+SLOT_MELEE)) {
1254 if (Equipped != IW_NO_EQUIPPED) {
1255 RemoveSlotEffects( SLOT_MELEE+Equipped);
1257 Equipped = IW_NO_EQUIPPED;
1258 //fist slot equipping effects
1259 AddSlotEffects(SLOT_FIST);
1260 UpdateWeaponAnimation();
1261 return true;
1264 //equipping a weapon, but remove its effects first
1265 if (Equipped != IW_NO_EQUIPPED) {
1266 RemoveSlotEffects( SLOT_MELEE+Equipped);
1269 Equipped = slotcode;
1270 int effects = core->QuerySlotEffects( SLOT_MELEE+Equipped );
1271 if (effects) {
1272 CREItem* item = GetSlotItem(SLOT_MELEE+Equipped);
1273 item->Flags|=IE_INV_ITEM_EQUIPPED;
1274 if (item->Flags & IE_INV_ITEM_CURSED) {
1275 item->Flags|=IE_INV_ITEM_UNDROPPABLE;
1277 AddSlotEffects( SLOT_MELEE+Equipped);
1279 UpdateWeaponAnimation();
1280 return true;
1283 int Inventory::GetEquipped() const
1285 return Equipped;
1288 int Inventory::GetEquippedHeader() const
1290 return EquippedHeader;
1293 //returns the fist weapon if there is nothing else
1294 //This will return the actual weapon, I mean the bow in the case of bow+arrow combination
1295 CREItem *Inventory::GetUsedWeapon(bool leftorright, int &slot) const
1297 CREItem *ret;
1299 if (SLOT_MAGIC!=-1) {
1300 slot = SLOT_MAGIC;
1301 ret = GetSlotItem(slot);
1302 if (ret && ret->ItemResRef[0]) {
1303 return ret;
1306 if (leftorright) {
1307 //no shield slot
1308 slot = GetShieldSlot();
1309 if (slot>=0) {
1310 ret = GetSlotItem(slot);
1311 if (ret) {
1312 return ret;
1313 } else {
1314 //we don't want to return fist for shield slot
1315 return NULL;
1319 slot = GetEquippedSlot();
1320 if((core->QuerySlotEffects(slot) & SLOT_EFFECT_MISSILE) == SLOT_EFFECT_MISSILE) {
1321 slot = FindRangedWeapon();
1323 ret = GetSlotItem(slot);
1324 if (!ret) {
1325 //return fist weapon
1326 slot = SLOT_FIST;
1327 ret = GetSlotItem(slot);
1329 return ret;
1332 // Returns index of first empty slot or slot with the same
1333 // item and not full stack. On fail returns -1
1334 // Can be used to check for full inventory
1335 int Inventory::FindCandidateSlot(int slottype, size_t first_slot, const char *resref)
1337 if (first_slot >= Slots.size())
1338 return -1;
1340 for (size_t i = first_slot; i < Slots.size(); i++) {
1341 if (!(core->QuerySlotType( (unsigned int) i) & slottype) ) {
1342 continue;
1345 CREItem *item = Slots[i];
1347 if (!item) {
1348 return (int) i; //this is a good empty slot
1350 if (!resref) {
1351 continue;
1353 if (!(item->Flags&IE_INV_ITEM_STACKED) ) {
1354 continue;
1356 if (strnicmp( item->ItemResRef, resref, 8 )!=0) {
1357 continue;
1359 // check if the item fits in this slot, we use the cached
1360 // stackamount value
1361 if (item->Usages[0]<item->StackAmount) {
1362 return (int) i;
1366 return -1;
1369 void Inventory::AddSlotItemRes(const ieResRef ItemResRef, int SlotID, int Charge0, int Charge1, int Charge2)
1371 CREItem *TmpItem = new CREItem();
1372 strnlwrcpy(TmpItem->ItemResRef, ItemResRef, 8);
1373 TmpItem->Expired=0;
1374 TmpItem->Usages[0]=(ieWord) Charge0;
1375 TmpItem->Usages[1]=(ieWord) Charge1;
1376 TmpItem->Usages[2]=(ieWord) Charge2;
1377 TmpItem->Flags=0;
1378 if (core->ResolveRandomItem(TmpItem) && gamedata->Exists(TmpItem->ItemResRef, IE_ITM_CLASS_ID)) {
1379 AddSlotItem( TmpItem, SlotID );
1380 } else {
1381 delete TmpItem;
1383 CalculateWeight();
1386 void Inventory::SetSlotItemRes(const ieResRef ItemResRef, int SlotID, int Charge0, int Charge1, int Charge2)
1388 if(ItemResRef[0]) {
1389 CREItem *TmpItem = new CREItem();
1390 strnlwrcpy(TmpItem->ItemResRef, ItemResRef, 8);
1391 TmpItem->Expired=0;
1392 TmpItem->Usages[0]=(ieWord) Charge0;
1393 TmpItem->Usages[1]=(ieWord) Charge1;
1394 TmpItem->Usages[2]=(ieWord) Charge2;
1395 TmpItem->Flags=0;
1396 if (core->ResolveRandomItem(TmpItem) && gamedata->Exists(TmpItem->ItemResRef, IE_ITM_CLASS_ID)) {
1397 SetSlotItem( TmpItem, SlotID );
1398 } else {
1399 delete TmpItem;
1401 } else {
1402 //if the item isn't creatable, we still destroy the old item
1403 KillSlot( SlotID );
1405 CalculateWeight();
1408 void Inventory::BreakItemSlot(ieDword slot)
1410 ieResRef newItem;
1411 CREItem *Slot;
1413 const Item *itm = GetItemPointer(slot, Slot);
1414 if (!itm) return;
1415 //if it is the magic weapon slot, don't break it, just remove it, because it couldn't be removed
1416 if(slot ==(unsigned int) SLOT_MAGIC) {
1417 newItem[0]=0;
1418 } else {
1419 memcpy(newItem, itm->ReplacementItem,sizeof(newItem) );
1421 gamedata->FreeItem( itm, Slot->ItemResRef, true );
1422 //this depends on setslotitemres using setslotitem
1423 SetSlotItemRes(newItem, slot, 0,0,0);
1426 void Inventory::dump()
1428 printf( "INVENTORY:\n" );
1429 for (unsigned int i = 0; i < Slots.size(); i++) {
1430 CREItem* itm = Slots[i];
1432 if (!itm) {
1433 continue;
1436 printf ( "%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->StackAmount, itm->Weight );
1439 printf( "Equipped: %d\n", Equipped );
1440 Changed = true;
1441 CalculateWeight();
1442 printf( "Total weight: %d\n", Weight );
1445 void Inventory::EquipBestWeapon(int flags)
1447 int i;
1448 int damage = -1;
1449 ieDword best_slot = SLOT_FIST;
1450 ITMExtHeader *header;
1451 CREItem *Slot;
1452 char AnimationType[2]={0,0};
1453 ieWord MeleeAnimation[3]={100,0,0};
1455 //cannot change equipment when holding magic weapons
1456 if (Equipped == SLOT_MAGIC-SLOT_MELEE) {
1457 return;
1460 if (flags&EQUIP_RANGED) {
1461 for(i=SLOT_RANGED;i<LAST_RANGED;i++) {
1462 const Item *itm = GetItemPointer(i, Slot);
1463 if (!itm) continue;
1464 //best ranged
1465 int tmp = itm->GetDamagePotential(true, header);
1466 if (tmp>damage) {
1467 best_slot = i;
1468 damage = tmp;
1469 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1470 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1472 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1475 //ranged melee weapons like throwing daggers (not bows!)
1476 for(i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1477 const Item *itm = GetItemPointer(i, Slot);
1478 if (!itm) continue;
1479 //best ranged
1480 int tmp = itm->GetDamagePotential(true, header);
1481 if (tmp>damage) {
1482 best_slot = i;
1483 damage = tmp;
1484 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1485 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1487 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1491 if (flags&EQUIP_MELEE) {
1492 for(i=SLOT_MELEE;i<=LAST_MELEE;i++) {
1493 const Item *itm = GetItemPointer(i, Slot);
1494 if (!itm) continue;
1495 //the Slot flag is enough for this
1496 //though we need animation type/damagepotential anyway
1497 if (Slot->Flags&IE_INV_ITEM_BOW) continue;
1498 //best melee
1499 int tmp = itm->GetDamagePotential(false, header);
1500 if (tmp>damage) {
1501 best_slot = i;
1502 damage = tmp;
1503 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1504 memcpy(MeleeAnimation,header->MeleeAnimation,sizeof(MeleeAnimation) );
1506 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1510 EquipItem(best_slot);
1511 UpdateWeaponAnimation();
1514 #define ID_NONEED 0 //id is not important
1515 #define ID_NEED 1 //id is important
1516 #define ID_NO 2 //shouldn't id
1518 /* returns true if there are more item usages not fitting in given array */
1519 bool Inventory::GetEquipmentInfo(ItemExtHeader *array, int startindex, int count)
1521 int pos = 0;
1522 int actual = 0;
1523 memset(array, 0, count * sizeof(ItemExtHeader) );
1524 for(unsigned int idx=0;idx<Slots.size();idx++) {
1525 if (!core->QuerySlotEffects(idx)) {
1526 continue;
1528 CREItem *slot;
1530 const Item *itm = GetItemPointer(idx, slot);
1531 if (!itm) {
1532 continue;
1534 for(int ehc=0;ehc<itm->ExtHeaderCount;ehc++) {
1535 ITMExtHeader *ext_header = itm->ext_headers+ehc;
1536 if (ext_header->Location!=ITEM_LOC_EQUIPMENT) {
1537 continue;
1539 //skipping if we cannot use the item
1540 int idreq1 = (slot->Flags&IE_INV_ITEM_IDENTIFIED);
1541 int idreq2 = ext_header->IDReq;
1542 switch (idreq2) {
1543 case ID_NO:
1544 if (idreq1) continue;
1545 break;
1546 case ID_NEED:
1547 if (!idreq1) continue;
1548 default:;
1551 actual++;
1552 if (actual>startindex) {
1554 //store the item, return if we can't store more
1555 if (!count) {
1556 gamedata->FreeItem(itm, slot->ItemResRef, false);
1557 return true;
1559 count--;
1560 memcpy(array[pos].itemname, slot->ItemResRef, sizeof(ieResRef) );
1561 array[pos].slot = idx;
1562 array[pos].headerindex = ehc;
1563 int slen = ((char *) &(array[pos].itemname)) -((char *) &(array[pos].AttackType));
1564 memcpy(&(array[pos].AttackType), &(ext_header->AttackType), slen);
1565 if (ext_header->Charges) {
1566 //don't modify ehc, it is a counter
1567 if (ehc>=CHARGE_COUNTERS) {
1568 array[pos].Charges=slot->Usages[0];
1569 } else {
1570 array[pos].Charges=slot->Usages[ehc];
1572 } else {
1573 array[pos].Charges=0xffff;
1575 pos++;
1578 gamedata->FreeItem(itm, slot->ItemResRef, false);
1581 return false;
1584 //The slot index value is optional, if you supply it,
1585 // then ItemExcl will be returned as if the item was already unequipped
1586 ieDword Inventory::GetEquipExclusion(int index) const
1588 if (index<0) {
1589 return ItemExcl;
1591 CREItem *slot;
1592 const Item *itm = GetItemPointer(index, slot);
1593 if (!itm) {
1594 return ItemExcl;
1596 ieDword ret = ItemExcl&~itm->ItemExcl;
1597 gamedata->FreeItem(itm, slot->ItemResRef, false);
1598 return ret;
1601 void Inventory::UpdateShieldAnimation(Item *it)
1603 char AnimationType[2]={0,0};
1604 int WeaponType = -1;
1606 if (it) {
1607 memcpy(AnimationType, it->AnimationType, 2);
1608 if (core->CanUseItemType(SLOT_WEAPON, it))
1609 WeaponType = IE_ANI_WEAPON_2W;
1610 else
1611 WeaponType = IE_ANI_WEAPON_1H;
1612 } else {
1613 WeaponType = IE_ANI_WEAPON_1H;
1615 Owner->SetUsedShield(AnimationType, WeaponType);
1618 void Inventory::UpdateWeaponAnimation()
1620 int slot = GetEquippedSlot();
1621 int effect = core->QuerySlotEffects( slot );
1622 if (effect == SLOT_EFFECT_MISSILE) {
1623 // ranged weapon
1624 slot = FindRangedWeapon();
1626 int WeaponType = -1;
1628 char AnimationType[2]={0,0};
1629 ieWord MeleeAnimation[3]={100,0,0};
1630 CREItem *Slot;
1632 // TODO: fix bows?
1634 ITMExtHeader *header = 0;
1635 const Item *itm = GetItemPointer(slot, Slot);
1636 if (itm) {
1637 itm->GetDamagePotential(false, header);
1638 memcpy(AnimationType,itm->AnimationType,sizeof(AnimationType) );
1639 //for twohanded flag, you don't need itm
1640 if (Slot->Flags & IE_INV_ITEM_TWOHANDED)
1641 WeaponType = IE_ANI_WEAPON_2H;
1642 else {
1644 // Examine shield slot to check if we're using two weapons
1645 // TODO: for consistency, use same Item* access method as above
1646 bool twoweapon = false;
1647 int slot = GetShieldSlot();
1648 CREItem* si = NULL;
1649 if (slot>0) {
1650 si = GetSlotItem( (ieDword) slot );
1652 if (si) {
1653 Item* it = gamedata->GetItem(si->ItemResRef);
1654 if (core->CanUseItemType(SLOT_WEAPON, it))
1655 twoweapon = true;
1656 gamedata->FreeItem(it, si->ItemResRef, false);
1659 if (twoweapon)
1660 WeaponType = IE_ANI_WEAPON_2W;
1661 else
1662 WeaponType = IE_ANI_WEAPON_1H;
1666 if (header)
1667 memcpy(MeleeAnimation,header->MeleeAnimation, sizeof(MeleeAnimation) );
1668 if (itm)
1669 gamedata->FreeItem( itm, Slot->ItemResRef, false );
1670 Owner->SetUsedWeapon(AnimationType, MeleeAnimation, WeaponType);
1673 //this function will also check disabled slots (if that feature will be imped)
1674 bool Inventory::IsSlotBlocked(int slot) const
1676 if (slot<SLOT_MELEE) return false;
1677 if (slot>LAST_MELEE) return false;
1678 int otherslot;
1679 if (IWD2) {
1680 otherslot = slot+1;
1681 } else {
1682 otherslot = SLOT_LEFT;
1684 return HasItemInSlot("",otherslot);
1687 inline bool Inventory::TwoHandedInSlot(int slot) const
1689 CREItem *item;
1691 item = GetSlotItem(slot);
1692 if (!item) return false;
1693 if (item->Flags&IE_INV_ITEM_TWOHANDED) {
1694 return true;
1696 return false;
1699 int Inventory::WhyCantEquip(int slot, int twohanded) const
1701 // check only for hand slots
1702 if ((slot<SLOT_MELEE || slot>LAST_MELEE) && (slot != SLOT_LEFT) ) {
1703 return 0;
1706 //magic items have the highest priority
1707 if ( HasItemInSlot("", SLOT_MAGIC)) {
1708 //magic weapon is in use
1709 return STR_MAGICWEAPON;
1712 //can't equip in shield slot if a weapon slot is twohanded
1713 for (int i=SLOT_MELEE; i<=LAST_MELEE;i++) {
1714 //see GetShieldSlot
1715 int otherslot;
1716 if (IWD2) {
1717 otherslot = ++i;
1718 } else {
1719 otherslot = SLOT_LEFT;
1721 if (slot==otherslot) {
1722 if (TwoHandedInSlot(i)) {
1723 return STR_TWOHANDED_USED;
1728 if (twohanded) {
1729 if (IWD2) {
1730 if (slot>=SLOT_MELEE&&slot<=LAST_MELEE && (slot&1) ) {
1731 return STR_NOT_IN_OFFHAND;
1733 } else {
1734 if (slot==SLOT_LEFT) {
1735 return STR_NOT_IN_OFFHAND;
1738 if (IsSlotBlocked(slot)) {
1739 //cannot equip two handed while shield slot is in use?
1740 return STR_OFFHAND_USED;
1743 return 0;
1746 //recharge items on rest, if rest was partial, recharge only 'hours'
1747 //if this latter functionality is unwanted, then simply don't recharge if
1748 //hours != 0
1749 void Inventory::ChargeAllItems(int hours)
1751 //this loop is going from start
1752 for (size_t i = 0; i < Slots.size(); i++) {
1753 CREItem *item = Slots[i];
1754 if (!item) {
1755 continue;
1758 Item *itm = gamedata->GetItem( item->ItemResRef );
1759 if (!itm)
1760 continue;
1761 for(int h=0;h<CHARGE_COUNTERS;h++) {
1762 ITMExtHeader *header = itm->GetExtHeader(h);
1763 if (header && (header->RechargeFlags&IE_ITEM_RECHARGE)) {
1764 unsigned short add = header->Charges;
1765 if (hours && add>hours) add=hours;
1766 add+=item->Usages[h];
1767 if(add>header->Charges) add=header->Charges;
1768 item->Usages[h]=add;
1771 gamedata->FreeItem( itm, item->ItemResRef, false );
1775 #define ITM_STEALING (IE_INV_ITEM_UNSTEALABLE | IE_INV_ITEM_MOVABLE | IE_INV_ITEM_EQUIPPED)
1776 unsigned int Inventory::FindStealableItem()
1778 unsigned int slot;
1779 int inc;
1781 slot = core->Roll(1, Slots.size(),-1);
1782 inc = slot&1?1:-1;
1784 printf("Start Slot: %d, increment: %d\n", slot, inc);
1785 //as the unsigned value underflows, it will be greater than Slots.size()
1786 for(;slot<Slots.size(); slot+=inc) {
1787 CREItem *item = Slots[slot];
1788 //can't steal empty slot
1789 if (!item) continue;
1790 //bit 1 is stealable slot
1791 if (!(core->QuerySlotFlags(slot)&1) ) continue;
1792 //can't steal equipped weapon
1793 if ((unsigned int) (Equipped+SLOT_MELEE) == core->QuerySlot(slot)) continue;
1794 //can't steal flagged items
1795 if ((item->Flags & ITM_STEALING) != IE_INV_ITEM_MOVABLE) continue;
1796 return slot;
1798 return 0;
1801 // extension to allow more or less than head gear to avert critical hits:
1802 // If an item with bit 25 set is equipped in a non-helmet slot, aversion is enabled
1803 // If an item with bit 25 set is equipped in a helmet slot, aversion is disabled
1804 bool Inventory::ProvidesCriticalAversion()
1806 for (size_t i = 0; i < Slots.size(); i++) {
1807 CREItem *item = Slots[i];
1808 if (!item || ! (item->Flags & IE_INV_ITEM_EQUIPPED)) {
1809 continue;
1812 Item *itm = gamedata->GetItem(item->ItemResRef);
1813 if (!itm) {
1814 continue;
1817 for (int h = 0; h < itm->ExtHeaderCount; h++) {
1818 ITMExtHeader *header = itm->GetExtHeader(h);
1819 if ((int)i == SLOT_HEAD) {
1820 if (header && (header->RechargeFlags & IE_ITEM_TOGGLE_CRITS)) {
1821 return false;
1822 } else {
1823 return true;
1825 } else {
1826 if (header && (header->RechargeFlags & IE_ITEM_TOGGLE_CRITS)) {
1827 return true;
1832 return false;