OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
inventory.cpp
Go to the documentation of this file.
1#include "inventory.h"
2
3#include <Tempest/Log>
4
6#include "world/objects/npc.h"
8#include "world/world.h"
9#include "game/gamescript.h"
10#include "serialize.h"
11#include "gothic.h"
12
13using namespace Tempest;
14
16 return *owner->items[at];
17 }
18
20 return owner->items[at].get();
21 }
22
24 auto& cur = *owner->items[at];
25 return subId==0 && cur.isEquipped();
26 }
27
29 auto& cur = *owner->items[at];
30 return subId==0 ? cur.slot() : Item::NSLOT;
31 }
32
34 auto& cur = *owner->items[at];
35 if(!cur.isMulti()) {
36 if(cur.isEquipped() && subId==0)
37 return cur.equipCount();
38 if(cur.isEquipped())
39 return cur.count()-cur.equipCount();
40 return cur.count();
41 }
42 return cur.count();
43 }
44
46 auto& it = owner->items;
47 auto& cur = *it[at];
48 if(!cur.isMulti()) {
49 if(cur.isEquipped() && cur.count()>1 && subId==0) {
50 ++subId;
51 return *this;
52 }
53 subId = 0;
54 }
55 at++;
56 skipHidden();
57 return *this;
58 }
59
61 return at<owner->items.size();
62 }
63
64Inventory::Iterator::Iterator(Inventory::IteratorType t, const Inventory* owner)
65 :type(t), owner(owner) {
66 owner->sortItems();
67 skipHidden();
68 }
69
70void Inventory::Iterator::skipHidden() {
71 auto& it = owner->items;
72 if(type==T_Trade) {
73 while(at<it.size() && (it[at]->isEquipped() || it[at]->isGold()))
74 ++at;
75 }
76 if(type==T_Ransack) {
77 while(at<it.size() && it[at]->isEquipped()) {
78 ++at;
79 }
80 }
81 }
82
83
86
89
90bool Inventory::isEmpty() const {
91 return items.size()==0 && active==nullptr;
92 }
93
94void Inventory::implLoad(Npc* owner, World& world, Serialize &s) {
95 uint32_t sz=0;
96 items.clear();
97 s.read(sz);
98 for(size_t i=0;i<sz;++i)
99 items.emplace_back(std::make_unique<Item>(world,s,Item::T_Inventory));
100
101 s.read(sz);
102 mdlSlots.resize(sz);
103 for(auto& i:mdlSlots) {
104 s.read(i.slot);
105 i.item = readPtr(s);
106 }
107 for(size_t i=0;i<mdlSlots.size();)
108 if(mdlSlots[i].item==nullptr) {
109 mdlSlots[i] = std::move(mdlSlots.back());
110 mdlSlots.pop_back();
111 } else {
112 ++i;
113 }
114
115 s.read(ammotSlot.slot);
116 ammotSlot.item = readPtr(s);
117 s.read(stateSlot.slot);
118 stateSlot.item = readPtr(s);
119
120 armor = readPtr(s);
121 belt = readPtr(s);
122 amulet = readPtr(s);
123 ringL = readPtr(s);
124 ringR = readPtr(s);
125 melee = readPtr(s);
126 range = readPtr(s);
127 if(s.version()>45)
128 shield = readPtr(s);
129 for(auto& i:numslot)
130 i = readPtr(s);
131
132 uint8_t id=255;
133 s.read(id);
134 if(id==1)
135 active=&melee;
136 else if(id==2)
137 active=&range;
138 else if(3<=id && id<10)
139 active=&numslot[id-3];
140 s.read(curItem,stateItem);
141
142 if(owner!=nullptr)
143 updateView(*owner);
144 }
145
146void Inventory::load(Serialize &s, Npc& owner) {
147 implLoad(&owner,owner.world(),s);
148 }
149
151 implLoad(nullptr,w,s);
152 }
153
154void Inventory::save(Serialize &fout) const {
155 uint32_t sz=uint32_t(items.size());
156 fout.write(sz);
157 for(auto& i:items)
158 i->save(fout);
159
160 sz=uint32_t(mdlSlots.size());
161 fout.write(sz);
162 for(auto& i:mdlSlots){
163 fout.write(i.slot,indexOf(i.item));
164 }
165 fout.write(ammotSlot.slot,indexOf(ammotSlot.item));
166 fout.write(stateSlot.slot,indexOf(stateSlot.item));
167
168 fout.write(indexOf(armor) );
169 fout.write(indexOf(belt) );
170 fout.write(indexOf(amulet));
171 fout.write(indexOf(ringL) );
172 fout.write(indexOf(ringR) );
173 fout.write(indexOf(melee) );
174 fout.write(indexOf(range) );
175 fout.write(indexOf(shield));
176 for(auto& i:numslot)
177 fout.write(indexOf(i));
178
179 uint8_t id=255;
180 if(active==&melee)
181 id=1;
182 else if(active==&range)
183 id=2;
184 for(int i=0;i<8;++i)
185 if(active==&numslot[i])
186 id = uint8_t(3+i);
187 fout.write(id);
188 fout.write(curItem,stateItem);
189 }
190
194
195int32_t Inventory::priceOf(size_t cls) const {
196 for(auto& i:items)
197 if(i->clsId()==cls)
198 return i->cost();
199 return 0;
200 }
201
202int32_t Inventory::sellPriceOf(size_t cls) const {
203 for(auto& i:items)
204 if(i->clsId()==cls)
205 return i->sellCost();
206 return 0;
207 }
208
209size_t Inventory::goldCount() const {
210 for(auto& i:items)
211 if(i->isGold())
212 return i->count();
213 return 0;
214 }
215
216size_t Inventory::itemCount(const size_t cls) const {
217 for(auto& i:items)
218 if(i->clsId()==cls)
219 return i->count();
220 return 0;
221 }
222
223Item* Inventory::addItem(std::unique_ptr<Item> &&p) {
224 if(p==nullptr)
225 return nullptr;
226 sorted=false;
227
228 const auto cls = p->clsId();
229 p->clearView();
230 Item* it=findByClass(cls);
231 if(it==nullptr) {
232 p->clearView();
233 items.emplace_back(std::move(p));
234 return items.back().get();
235 } else {
236 it->setCount(it->count()+p->count());
237 it->handle().owner = p->handle().owner;
238 it->handle().owner_guild = p->handle().owner_guild;
239 return p.get();
240 }
241 }
242
243Item* Inventory::addItem(std::string_view name, size_t count, World &owner) {
244 auto& vm = owner.script();
245 size_t id = vm.findSymbolIndex(name);
246 if(id!=size_t(-1))
247 return addItem(id,count,owner);
248 return nullptr;
249 }
250
251Item* Inventory::addItem(size_t itemSymbol, size_t count, World &owner) {
252 if(count<=0)
253 return nullptr;
254 sorted=false;
255
256 Item* it=findByClass(itemSymbol);
257 if(it==nullptr) {
258 try {
259 std::unique_ptr<Item> ptr{new Item(owner,itemSymbol,Item::T_Inventory)};
260 ptr->setCount(count);
261 items.emplace_back(std::move(ptr));
262 return items.back().get();
263 }
264 catch(const std::runtime_error& call) {
265 Log::e("[invalid call in VM, while initializing item: ",itemSymbol,"]");
266 return nullptr;
267 }
268 } else {
269 it->setCount(it->count()+count);
270 return it;
271 }
272 }
273
274void Inventory::delItem(size_t itemSymbol, size_t count, Npc& owner) {
275 if(count<=0)
276 return;
277 Item* it=findByClass(itemSymbol);
278 return delItem(it,count,owner);
279 }
280
281void Inventory::delItem(Item *it, size_t count, Npc& owner) {
282 if(it==nullptr)
283 return;
284
285 if(it->count()>count)
286 it->setCount(it->count()-count); else
287 it->setCount(0);
288
289 if(it->count()>0)
290 return;
291
292 // unequip, if have to
293 unequip(it,owner);
294
295 // clear slots
296 for(size_t i=0;i<mdlSlots.size();)
297 if(mdlSlots[i].item==it) {
298 mdlSlots[i] = std::move(mdlSlots.back());
299 mdlSlots.pop_back();
300 } else {
301 ++i;
302 }
303 sorted=false;
304
305 for(size_t i=0;i<items.size();++i)
306 if(items[i]->clsId()==it->clsId()){
307 items.erase(items.begin()+int(i));
308 break;
309 }
310 }
311
312void Inventory::transfer(Inventory &to, Inventory &from, Npc* fromNpc, size_t itemSymbol, size_t count, World &wrld) {
313 for(size_t i=0;i<from.items.size();++i){
314 auto& it = *from.items[i];
315 if(it.clsId()!=itemSymbol)
316 continue;
317
318 from.sorted = false;
319 to.sorted = false;
320
321 if(count>it.count())
322 count=it.count();
323
324 if(it.count()==count) {
325 if(it.isEquipped()) {
326 if(fromNpc==nullptr){
327 Log::e("Inventory: invalid transfer call");
328 return; // error
329 }
330 from.unequip(&it,*fromNpc);
331 }
332 to.addItem(std::move(from.items[i]));
333 from.items.erase(from.items.begin()+int(i));
334 } else {
335 it.setCount(it.count()-count);
336 to.addItem(itemSymbol,count,wrld);
337 }
338 }
339 }
340
342 return findByClass(instance);
343 }
344
345bool Inventory::unequip(size_t cls, Npc &owner) {
346 Item* it=findByClass(cls);
347 if(it==nullptr || !it->isEquipped())
348 return false;
349 unequip(it,owner);
350 return true;
351 }
352
353void Inventory::unequip(Item *it, Npc &owner) {
354 if(armor==it) {
355 setSlot(armor,nullptr,owner,false);
356 return;
357 }
358 if(belt==it) {
359 setSlot(belt,nullptr,owner,false);
360 return;
361 }
362 if(amulet==it) {
363 setSlot(amulet,nullptr,owner,false);
364 return;
365 }
366 if(ringL==it) {
367 setSlot(ringL,nullptr,owner,false);
368 return;
369 }
370 if(ringR==it) {
371 setSlot(ringR,nullptr,owner,false);
372 return;
373 }
374 if(melee==it) {
375 setSlot(melee,nullptr,owner,false);
376 return;
377 }
378 if(range==it) {
379 setSlot(range,nullptr,owner,false);
380 return;
381 }
382 if(shield==it) {
383 setSlot(shield,nullptr,owner,false);
384 return;
385 }
386
387 for(auto& i:numslot)
388 if(i==it)
389 setSlot(i,nullptr,owner,false);
390 if(it->isEquipped()) {
391 // error
392 Log::e("[",owner.displayName().data(),"] inconsistent inventory state");
393 setSlot(it,nullptr,owner,false);
394 }
395 }
396
397bool Inventory::setSlot(Item *&slot, Item* next, Npc& owner, bool force) {
398 GameScript& vm = owner.world().script();
399
400 if(next!=nullptr) {
401 int32_t atr=0,nValue=0,plMag=0,itMag=0;
402 if(!force && !next->checkCondUse(owner,atr,nValue)) {
403 vm.printCannotUseError(owner,atr,nValue);
404 return false;
405 }
406
407 if(!force && !next->checkCondRune(owner,plMag,itMag)) {
408 vm.printCannotCastError(owner,plMag,itMag);
409 return false;
410 }
411 }
412
413 if(slot!=nullptr) {
414 auto& itData = slot->handle();
415 auto mainFlag = ItmFlags(itData.main_flag);
416 auto flag = ItmFlags(itData.flags);
417
418 applyArmor(*slot,owner,-1);
419 if(slot->isEquipped())
420 slot->setAsEquipped(false);
421 if(&slot==active)
422 applyWeaponStats(owner,*slot,-1);
423 slot=nullptr;
424
425 if(flag & ITM_SHIELD){
427 }
428 else if(mainFlag & ITM_CAT_ARMOR){
429 owner.updateArmor();
430 }
431 else if(mainFlag & ITM_CAT_NF){
433 }
434 else if(mainFlag & ITM_CAT_FF){
436 }
437 vm.invokeItem(&owner,uint32_t(itData.on_unequip));
438 }
439
440 if(next==nullptr)
441 return false;
442
443 auto& itData = next->handle();
444 slot=next;
445 slot->setAsEquipped(true);
446 slot->setSlot(slotId(slot));
447 applyArmor(*slot,owner,1);
448
449 updateArmorView (owner);
450 updateSwordView (owner);
451 updateBowView (owner);
452 updateShieldView(owner);
453 if(&slot==active) {
454 updateRuneView (owner);
455 applyWeaponStats(owner,*slot,1);
456 }
457 vm.invokeItem(&owner,uint32_t(itData.on_equip));
458 return true;
459 }
460
462 auto& world = owner.world();
463
464 updateArmorView (owner);
465 updateSwordView (owner);
466 updateBowView (owner);
467 updateShieldView(owner);
468 updateRuneView (owner);
469
470 for(auto& i:mdlSlots) {
471 auto vbody = world.addView(i.item->handle());
472 owner.setSlotItem(std::move(vbody),i.slot);
473 }
474 if(ammotSlot.item!=nullptr) {
475 auto vbody = world.addView(ammotSlot.item->handle());
476 owner.setAmmoItem(std::move(vbody),ammotSlot.slot);
477 }
478 if(stateSlot.item!=nullptr) {
479 auto vitm = world.addView(stateSlot.item->handle());
480 owner.setStateItem(std::move(vitm),stateSlot.slot);
481 }
482 }
483
485 if(armor==nullptr)
486 return;
487
488 auto& itData = armor->handle();
489 auto flag = ItmFlags(itData.main_flag);
490 if(flag & ITM_CAT_ARMOR)
491 owner.updateArmor();
492 }
493
495 if(melee==nullptr) {
497 return;
498 }
499
500 auto vbody = owner.world().addView(melee->handle());
501 owner.setSword(std::move(vbody));
502 }
503
505 if(range==nullptr) {
507 return;
508 }
509
510 auto flag = range->mainFlag();
511 if(flag & ITM_CAT_FF){
512 auto vbody = owner.world().addView(range->handle());
513 owner.setRangedWeapon(std::move(vbody));
514 }
515 }
516
518 if(shield==nullptr) {
520 return;
521 }
522
523 auto flag = ItmFlags(shield->itemFlag());
524 if(flag & ITM_SHIELD){
525 auto vbody = owner.world().addView(shield->handle());
526 owner.setShield(std::move(vbody));
527 }
528 }
529
531 if(active==nullptr || *active==nullptr)
532 return;
533
534 auto* sp = *active;
535 if(!sp->isSpellOrRune())
536 return;
537
538 const VisualFx* vfx = owner.world().script().spellVfx(sp->spellId());
539 owner.setMagicWeapon(Effect(*vfx,owner.world(),owner,SpellFxKey::Init));
540 }
541
543 auto a = bestMeleeWeapon(owner);
544 if(a!=nullptr)
545 setSlot(melee,a,owner,false);
546 }
547
549 auto a = bestRangedWeapon(owner);
550 if(a!=nullptr)
551 setSlot(range,a,owner,false);
552 }
553
555 setSlot(melee, nullptr,owner,false);
556 setSlot(range, nullptr,owner,false);
557 setSlot(shield,nullptr,owner,false);
558 }
559
561 setSlot(armor,nullptr,owner,false);
562 }
563
564void Inventory::clear(GameScript&, Npc&, bool includeMissionItm) {
565 std::vector<std::unique_ptr<Item>> used;
566 for(auto& i:items)
567 if(i->isEquipped() || (i->isMission() && !includeMissionItm)){
568 used.emplace_back(std::move(i));
569 }
570 items = std::move(used); // Gothic don't clear items, which are in use
571 }
572
573void Inventory::clear(GameScript& vm, Interactive& owner, bool includeMissionItm) {
574 std::vector<std::unique_ptr<Item>> used;
575 for(auto& i:items)
576 if(i->isMission() && !includeMissionItm){
577 used.emplace_back(std::move(i));
578 }
579 items = std::move(used); // Gothic don't clear items, which are in use
580 }
581
582bool Inventory::hasSpell(int32_t splId) const {
583 for(auto& i:items)
584 if(i->spellId()==splId)
585 return true;
586 return false;
587 }
588
590 for(auto& i:items)
591 if(i->isMission())
592 return true;
593 return false;
594 }
595
597 if(active!=nullptr)
598 return *active;
599 return nullptr;
600 }
601
603 uint32_t munition = 0;
604 for(auto& i:items) {
605 uint32_t cls = uint32_t(i->handle().munition);
606 if(cls>0 && cls!=munition) {
607 for(auto& it:items)
608 if(it->clsId()==cls)
609 return true;
610 munition = cls;
611 }
612 }
613 return false;
614 }
615
617 if(active!=nullptr)
618 return *active;
619 return nullptr;
620 }
621
623 if(active==&melee)
624 active=nullptr; else
625 active=&melee;
626 }
627
628void Inventory::switchActiveWeapon(Npc& owner, uint8_t slot) {
629 if(active!=nullptr && *active!=nullptr)
630 applyWeaponStats(owner,**active,-1);
631 active=nullptr;
632
633 if(slot==Item::NSLOT)
634 return;
635
636 Item** next=nullptr;
637 if(slot==1)
638 next=&melee;
639 if(slot==2)
640 next=&range;
641 if(3<=slot && slot<=10)
642 next=&numslot[slot-3];
643 if(next==active)
644 return;
645 if(next!=nullptr && *next!=nullptr)
646 active=next;
647
648 if(active!=nullptr && *active!=nullptr)
649 applyWeaponStats(owner,**active,1);
650 }
651
652void Inventory::switchActiveSpell(int32_t spell, Npc& owner) {
653 for(uint8_t i=0;i<8;++i) {
654 auto s = numslot[i];
655 if(s!=nullptr && s->isSpellOrRune() && s->spellId()==spell){
656 switchActiveWeapon(owner,uint8_t(i+3));
657 updateRuneView(owner);
658 return;
659 }
660 }
661
662 for(auto& i:items)
663 if(i->spellId()==spell){
664 setSlot(numslot[0],i.get(),owner,true);
665 switchActiveWeapon(owner,3);
666 updateRuneView(owner);
667 return;
668 }
669 }
670
672 for(uint8_t i=0;i<8;++i){
673 if(active==&numslot[i])
674 return uint8_t(i+3);
675 }
676 return Item::NSLOT;
677 }
678
680 return stateSlot.item!=nullptr || stateItem!=0;
681 }
682
683void Inventory::putCurrentToSlot(Npc& owner, std::string_view slot) {
684 if(curItem>0) {
685 putToSlot(owner,size_t(curItem),slot);
686 curItem = 0;
687 return;
688 }
689 if(stateItem>0)
690 implPutState(owner,size_t(stateItem),slot);
691 }
692
693void Inventory::putToSlot(Npc& owner, size_t cls, std::string_view slot) {
694 clearSlot(owner,slot,false);
695
696 Item* it=findByClass(cls);
697 if(it==nullptr)
698 it = addItem(cls,1,owner.world());
699
700 for(auto& i:mdlSlots)
701 if(i.slot==slot) {
702 i.item = it;
703
704 auto vitm = owner.world().addView(it->handle());
705 owner.setSlotItem(std::move(vitm),slot);
706 return;
707 }
708 mdlSlots.emplace_back();
709 MdlSlot& sl = mdlSlots.back();
710 sl.slot = slot;
711 sl.item = it;
712 auto vitm = owner.world().addView(it->handle());
713 owner.setSlotItem(std::move(vitm),slot);
714 }
715
716bool Inventory::clearSlot(Npc& owner, std::string_view slot, bool remove) {
717 uint32_t count = 0;
718 const bool all = slot.empty();
719 for(size_t i=0;i<mdlSlots.size();)
720 if(all || mdlSlots[i].slot==slot) {
721 owner.clearSlotItem(slot);
722 auto last = mdlSlots[i].item;
723 mdlSlots[i] = mdlSlots.back();
724 mdlSlots.pop_back();
725 if(remove)
726 delItem(last,1,owner);
727 ++count;
728 } else {
729 ++i;
730 }
731 if(all || stateSlot.slot==slot) {
732 if(stateSlot.item!=nullptr)
733 ++count;
734 implPutState(owner,0,stateSlot.slot);
735 }
736 return count>0;
737 }
738
739void Inventory::putAmmunition(Npc& owner, size_t cls, std::string_view slot) {
740 Item* it = (cls==0 ? nullptr : findByClass(cls));
741 if(it==nullptr) {
742 ammotSlot.slot.clear();
743 ammotSlot.item = nullptr;
744 owner.setAmmoItem(MeshObjects::Mesh(),"");
745 return;
746 }
747
748 ammotSlot.slot = slot;
749 ammotSlot.item = it;
750 auto& itData = it->handle();
751 auto vitm = owner.world().addView(itData);
752 owner.setAmmoItem(std::move(vitm),slot);
753 }
754
755void Inventory::implPutState(Npc& owner, size_t cls, std::string_view slot) {
756 Item* it = (cls==0 ? nullptr : findByClass(cls));
757 if(it==nullptr) {
758 stateSlot.slot.clear();
759 stateSlot.item = nullptr;
761 return;
762 }
763
764 stateSlot.slot = slot;
765 stateSlot.item = it;
766 auto vitm = owner.world().addView(it->handle());
767 owner.setStateItem(std::move(vitm),slot);
768 }
769
770bool Inventory::putState(Npc& owner, size_t cls, int state) {
771 Item* it = (cls==0 ? nullptr : findByClass(cls));
772 if(it==nullptr) {
773 setStateItem(0);
774 return owner.stopItemStateAnim();
775 }
776
777 if(!owner.setAnimItem(it->handle().scheme_name,state))
778 return false;
779
781 setStateItem(cls);
782 return true;
783 }
784
785void Inventory::moveItem(Npc& owner, Inventory& invNpc, Interactive& mobsi) {
786 if(owner.inventory().mdlSlots.empty())
787 return;
788
789 // DEF_PLACE_ITEM has no slot parameter, so assume ZS_SLOT, based on testing
790 std::string_view zsSlot = "ZS_SLOT";
791
792 auto& world = owner.world();
793 auto& slot = invNpc.mdlSlots.back();
794
795 auto vbody = world.addView(slot.item->handle());
796 mobsi.setSlotItem(std::move(vbody), zsSlot);
797
798 mobsi.inventory().mdlSlots.resize(1);
799 MdlSlot& sl = mobsi.inventory().mdlSlots.back();
800 sl.slot = zsSlot;
801 sl.item = slot.item;
802
803 auto itm = slot.item;
804 owner.clearSlotItem(slot.slot);
805 invNpc.mdlSlots.pop_back();
806 invNpc.delItem(itm,1,owner);
807 }
808
810 curItem = int32_t(cls);
811 }
812
813void Inventory::setStateItem(size_t cls) {
814 stateItem = int32_t(cls);
815 }
816
817bool Inventory::equipNumSlot(Item *next, uint8_t slotHint, Npc &owner, bool force) {
818 if(slotHint!=Item::NSLOT) {
819 return setSlot(numslot[slotHint-3],next,owner,force);
820 }
821
822 for(auto& i:numslot) {
823 if(i==nullptr) {
824 setSlot(i,next,owner,force);
825 return true;
826 }
827 }
828 return false;
829 }
830
831void Inventory::applyArmor(Item &it, Npc &owner, int32_t sgn) {
832 for(size_t i=0;i<PROT_MAX;++i){
833 auto v = owner.protection(Protection(i));
834 owner.changeProtection(Protection(i),v+it.handle().protection[i]*sgn);
835 }
836 }
837
838bool Inventory::use(size_t cls, Npc &owner, uint8_t slotHint, bool force) {
839 Item* it=findByClass(cls);
840 if(it==nullptr)
841 return false;
842
843 auto& itData = it->handle();
844 auto mainflag = ItmFlags(itData.main_flag);
845 auto flag = ItmFlags(itData.flags);
846
847 if(flag & ITM_SHIELD)
848 return setSlot(shield,it,owner,force);
849
850 if(mainflag & ITM_CAT_NF)
851 return setSlot(melee,it,owner,force);
852
853 if(mainflag & ITM_CAT_FF)
854 return setSlot(range,it,owner,force);
855
856 if(mainflag & ITM_CAT_RUNE) {
857 if(it->isEquipped() && slotHint==it->slot())
858 return false;
859 if(it->isEquipped())
860 unequip(it,owner);
861 return equipNumSlot(it,slotHint,owner,force);
862 }
863
864 if(mainflag & ITM_CAT_ARMOR)
865 return setSlot(armor,it,owner,force);
866
867 if(flag & ITM_BELT)
868 return setSlot(belt,it,owner,force);
869
870 if(flag & ITM_AMULET)
871 return setSlot(amulet,it,owner,force);
872
873 if(flag & ITM_RING) {
874 if(ringL==nullptr)
875 return setSlot(ringL,it,owner,force);
876 if(ringR==nullptr)
877 return setSlot(ringR,it,owner,force);
878 return false;
879 }
880
881 bool deleteLater = false;
882 if(flag & ITM_TORCH) {
884 return false;
885 if(owner.toggleTorch()) {
886 deleteLater = true;
887 } else {
888 return true;
889 }
890 }
891
892 if(!owner.setAnimItem(itData.scheme_name,-1))
893 return false;
894
895 // owner.stopDlgAnim();
896 setCurrentItem(it->clsId());
897 if(itData.on_state[0]!=0){
898 auto& vm = owner.world().script();
899 vm.invokeItem(&owner,uint32_t(itData.on_state[0]));
900 }
901
902 if(deleteLater)
903 owner.delItem(cls,1);
904
905 return true;
906 }
907
908bool Inventory::equip(size_t cls, Npc &owner, bool force) {
909 Item* it=findByClass(cls);
910 if(it==nullptr || it->isEquipped())
911 return false;
912 return use(cls,owner,Item::NSLOT,force);
913 }
914
916 if(!owner.isPlayer())
917 return; // gothic doesn't care
918 invalidateCond(armor,owner);
919 invalidateCond(belt ,owner);
920 invalidateCond(amulet,owner);
921 invalidateCond(ringL ,owner);
922 invalidateCond(ringR ,owner);
923 invalidateCond(melee ,owner);
924 invalidateCond(range ,owner);
925 invalidateCond(shield,owner);
926 for(auto& i:numslot)
927 invalidateCond(i,owner);
928 }
929
930void Inventory::invalidateCond(Item *&slot, Npc &owner) {
931 if(slot && !slot->checkCond(owner)) {
932 unequip(slot,owner);
933 }
934 }
935
937 if(owner.isMonster())
938 return;
941 }
942
943void Inventory::equipArmor(int32_t cls, Npc &owner) {
944 if(cls<=0)
945 return;
946 auto it = findByClass(size_t(cls));
947 if(it==nullptr)
948 return;
949 if(uint32_t(it->mainFlag()) & ITM_CAT_ARMOR){
950 if(!it->isEquipped())
951 use(size_t(cls),owner,Item::NSLOT,true);
952 }
953 }
954
956 auto a = bestArmor(owner);
957 if(a!=nullptr)
958 setSlot(armor,a,owner,false);
959 }
960
961Item *Inventory::findByClass(size_t cls) {
962 for(auto& i:items)
963 if(i->clsId()==cls)
964 return i.get();
965 return nullptr;
966 }
967
968// Find the Nth item with a specific flag
969// num starts at "1" for first item
970Item* Inventory::findByFlags(ItmFlags f, uint32_t num) const {
971 uint32_t found = 0;
972
973 for(auto& i:items) {
974 auto& itData = i->handle();
975 auto flag = ItmFlags(itData.main_flag);
976 if((flag & f)==0)
977 continue;
978
979 found++;
980 if (found==num)
981 return i.get();
982 }
983 return nullptr;
984 }
985
986Item* Inventory::bestItem(Npc &owner, ItmFlags f) {
987 Item* ret = nullptr;
988 int32_t value = std::numeric_limits<int32_t>::min();
989 int32_t damage = std::numeric_limits<int32_t>::min();
990 for(auto& i:items) {
991 auto& itData = i->handle();
992 auto flag = ItmFlags(itData.main_flag);
993 if((flag & f)==0)
994 continue;
995 if(!i->checkCond(owner))
996 continue;
997 if(itData.munition>0 && findByClass(size_t(itData.munition))==nullptr)
998 continue;
999
1000 if(std::make_tuple(itData.damage_total, itData.value)>std::make_tuple(damage, value)){
1001 ret = i.get();
1002 damage = itData.damage_total;
1003 value = itData.value;
1004 }
1005 }
1006 return ret;
1007 }
1008
1009Item *Inventory::bestArmor(Npc &owner) {
1010 return bestItem(owner,ITM_CAT_ARMOR);
1011 }
1012
1013Item *Inventory::bestMeleeWeapon(Npc &owner) {
1014 return bestItem(owner,ITM_CAT_NF);
1015 }
1016
1017Item *Inventory::bestRangedWeapon(Npc &owner) {
1018 return bestItem(owner,ITM_CAT_FF);
1019 }
1020
1021void Inventory::applyWeaponStats(Npc& owner, const Item &weapon, int sgn) {
1022 auto& hnpc = owner.handle();
1023 //hnpc.damagetype = sgn>0 ? weapon.handle()->damageType : (1 << GEngineClasses::DAM_INDEX_BLUNT);
1024 for(size_t i=0; i<zenkit::DamageType::NUM; ++i){
1025 hnpc.damage[i] += sgn*weapon.handle().damage[i];
1026 if(weapon.handle().damage_type & (1<<i)) {
1027 hnpc.damage[i] += sgn*weapon.handle().damage_total;
1028 }
1029 }
1030
1031 // assert inconsistent plus/minus
1032 for(size_t i=0; i<zenkit::DamageType::NUM; ++i) {
1033 assert(hnpc.damage[i]>=0);
1034 }
1035 }
1036
1037void Inventory::sortItems() const {
1038 if(sorted)
1039 return;
1040 sorted = true;
1041 std::sort(items.begin(),items.end(),[](std::unique_ptr<Item>& l, std::unique_ptr<Item>& r){
1042 return less(*l,*r);
1043 });
1044 }
1045
1046bool Inventory::less(const Item &il, const Item &ir) {
1047 auto ordL = orderId(il);
1048 auto ordR = orderId(ir);
1049
1050 if(ordL<ordR)
1051 return true;
1052 if(ordL>ordR)
1053 return false;
1054
1055 int32_t lV = 0, rV = 0;
1057 lV = 0;
1058 rV = 0;
1059 } else {
1060 lV = il.cost();
1061 rV = ir.cost();
1062 }
1063
1064 return std::make_tuple(il.mainFlag(), -il.handle().damage_total, -lV, -il.clsId())
1065 < std::make_tuple(ir.mainFlag(), -ir.handle().damage_total, -rV, -ir.clsId());
1066 }
1067
1068int Inventory::orderId(const Item& i) {
1069 auto& invCatOrder = Gothic::invCatOrder();
1070 auto mflg = ItmFlags(i.mainFlag());
1071
1072 for(size_t i=0; i<invCatOrder.size(); ++i)
1073 if(mflg!=0 && (mflg&invCatOrder[i]))
1074 return int(i);
1075 return int(invCatOrder.size());
1076 }
1077
1078uint8_t Inventory::slotId(Item *&slt) const {
1079 if(&slt==&melee)
1080 return 1;
1081 if(&slt==&range)
1082 return 2;
1083
1084 uint8_t id=3;
1085 for(auto& i:numslot){
1086 if(&i==&slt)
1087 return id;
1088 ++id;
1089 }
1090
1091 return 255;
1092 }
1093
1094uint32_t Inventory::indexOf(const Item *it) const {
1095 if(it==nullptr)
1096 return uint32_t(-1);
1097 for(size_t i=0;i<items.size();++i)
1098 if(items[i].get()==it)
1099 return uint32_t(i);
1100 return uint32_t(-1);
1101 }
1102
1103Item *Inventory::readPtr(Serialize &fin) {
1104 uint32_t v=uint32_t(-1);
1105 fin.read(v);
1106 if(v<items.size())
1107 return items[v].get();
1108 return nullptr;
1109 }
1110
1111
size_t findSymbolIndex(std::string_view s)
void printCannotCastError(Npc &npc, int32_t plM, int32_t itM)
void printCannotUseError(Npc &npc, int32_t atr, int32_t nValue)
void invokeItem(Npc *npc, ScriptFn fn)
static auto invCatOrder() -> const std::vector< ItmFlags > &
Definition gothic.cpp:447
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view slot)
Inventory & inventory()
const Item & operator*() const
Definition inventory.cpp:15
const Item * operator->() const
Definition inventory.cpp:19
bool isEquipped() const
Definition inventory.cpp:23
size_t count() const
Definition inventory.cpp:33
uint8_t slot() const
Definition inventory.cpp:28
bool isValid() const
Definition inventory.cpp:60
Iterator & operator++()
Definition inventory.cpp:45
void updateRuneView(Npc &owner)
void unequipArmor(GameScript &vm, Npc &owner)
Item * findByFlags(ItmFlags f, uint32_t num) const
void updateBowView(Npc &owner)
int32_t sellPriceOf(size_t item) const
void clear(GameScript &vm, Npc &owner, bool includeMissionItm=false)
void equipBestArmor(Npc &owner)
void setCurrentItem(size_t cls)
bool hasStateItem() const
bool hasRangedWeaponWithAmmo() const
void equipArmor(int32_t cls, Npc &owner)
bool putState(Npc &owner, size_t cls, int state)
void equipBestMeleeWeapon(Npc &owner)
bool equip(size_t cls, Npc &owner, bool force)
void putToSlot(Npc &owner, size_t cls, std::string_view slot)
void switchActiveWeaponFist()
bool isEmpty() const
Definition inventory.cpp:90
Iterator iterator(IteratorType t) const
static void transfer(Inventory &to, Inventory &from, Npc *fromNpc, size_t cls, size_t count, World &wrld)
void invalidateCond(Npc &owner)
bool unequip(size_t cls, Npc &owner)
bool use(size_t cls, Npc &owner, uint8_t slotHint, bool force)
const Item * activeWeapon() const
void updateArmorView(Npc &owner)
void switchActiveWeapon(Npc &owner, uint8_t slot)
uint8_t currentSpellSlot() const
void switchActiveSpell(int32_t spell, Npc &owner)
void updateSwordView(Npc &owner)
void putAmmunition(Npc &owner, size_t cls, std::string_view slot)
bool hasSpell(int32_t spl) const
Item * addItem(std::unique_ptr< Item > &&p)
size_t goldCount() const
void delItem(size_t cls, size_t count, Npc &owner)
int32_t priceOf(size_t item) const
static void moveItem(Npc &owner, Inventory &invNpc, Interactive &mobsi)
void putCurrentToSlot(Npc &owner, std::string_view slot)
void equipBestRangedWeapon(Npc &owner)
void load(Serialize &s, Npc &owner)
void unequipWeapons(GameScript &vm, Npc &owner)
void autoEquipWeapons(Npc &owner)
bool clearSlot(Npc &owner, std::string_view slot, bool remove)
bool hasMissionItems() const
void setStateItem(size_t cls)
void save(Serialize &s) const
size_t itemCount(const size_t id) const
Item * getItem(size_t instance)
void updateView(Npc &owner)
void updateShieldView(Npc &owner)
Definition item.h:14
@ T_Inventory
Definition item.h:21
bool isEquipped() const
Definition item.h:40
int32_t cost() const
Definition item.cpp:282
bool checkCondRune(const Npc &other, int32_t &cPl, int32_t &cIt) const
Definition item.cpp:307
size_t count() const
Definition item.cpp:270
ItmFlags mainFlag() const
Definition item.cpp:195
uint8_t slot() const
Definition item.h:48
void setCount(size_t cnt)
Definition item.cpp:266
bool checkCond(const Npc &other) const
Definition item.cpp:290
size_t clsId() const
Definition item.cpp:313
int32_t itemFlag() const
Definition item.cpp:199
void setSlot(uint8_t s)
Definition item.h:49
const zenkit::IItem & handle() const
Definition item.h:83
void setAsEquipped(bool e)
Definition item.cpp:129
@ NSLOT
Definition item.h:16
bool checkCondUse(const Npc &other, int32_t &atr, int32_t &nv) const
Definition item.cpp:295
Definition npc.h:25
int32_t protection(Protection p) const
Definition npc.cpp:1208
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
void setRangedWeapon(MeshObjects::Mesh &&bow)
Definition npc.cpp:883
void setShield(MeshObjects::Mesh &&shield)
Definition npc.cpp:888
void changeProtection(Protection p, int32_t val)
Definition npc.cpp:1214
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:899
auto inventory() const -> const Inventory &
Definition npc.h:330
bool setAnimItem(std::string_view scheme, int state)
Definition npc.cpp:965
bool isPlayer() const
Definition npc.cpp:539
std::string_view displayName() const
Definition npc.cpp:734
void setSword(MeshObjects::Mesh &&sword)
Definition npc.cpp:878
auto world() -> World &
Definition npc.cpp:624
void setStateItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:903
zenkit::INpc & handle()
Definition npc.h:327
void delItem(size_t id, uint32_t amount)
Definition npc.cpp:3466
void updateArmor()
Definition npc.cpp:860
bool toggleTorch()
Definition npc.cpp:777
bool stopItemStateAnim()
Definition npc.cpp:987
bool isMonster() const
Definition npc.cpp:1227
void clearSlotItem(std::string_view slot)
Definition npc.cpp:911
void setMagicWeapon(Effect &&spell)
Definition npc.cpp:893
void setAmmoItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:907
void write(const Arg &... a)
Definition serialize.h:76
uint16_t version() const
Definition serialize.h:51
void read(Arg &... a)
Definition serialize.h:81
Definition world.h:31
MeshObjects::Mesh addView(std::string_view visual) const
Definition world.cpp:251
GameScript & script() const
Definition world.cpp:1019
static CommandLine * instance
ItmFlags
Definition constants.h:312
@ ITM_AMULET
Definition constants.h:336
@ ITM_BELT
Definition constants.h:337
@ ITM_CAT_NF
Definition constants.h:314
@ ITM_CAT_FOOD
Definition constants.h:318
@ ITM_SHIELD
Definition constants.h:332
@ ITM_CAT_FF
Definition constants.h:315
@ ITM_CAT_RUNE
Definition constants.h:322
@ ITM_CAT_DOCS
Definition constants.h:319
@ ITM_RING
Definition constants.h:325
@ ITM_CAT_POTION
Definition constants.h:320
@ ITM_TORCH
Definition constants.h:338
@ ITM_CAT_ARMOR
Definition constants.h:317
Protection
Definition constants.h:484
@ PROT_MAX
Definition constants.h:493