OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
npc.cpp
Go to the documentation of this file.
1#include "npc.h"
2
3#include <Tempest/Matrix4x4>
4#include <Tempest/Log>
5
7#include "graphics/visualfx.h"
9#include "game/serialize.h"
10#include "game/gamescript.h"
11#include "utils/string_frm.h"
13#include "world/objects/item.h"
14#include "world/world.h"
15#include "utils/versioninfo.h"
16#include "utils/fileext.h"
17#include "camera.h"
18#include "gothic.h"
19#include "resources.h"
20
21using namespace Tempest;
22
23static std::string_view humansTorchOverlay = "_TORCH.MDS";
24
25std::string_view Npc::Routine::wayPointName() const {
26 return point!=nullptr ? point->name : fallbackName;
27 }
28
29
30void Npc::GoTo::save(Serialize& fout) const {
31 fout.write(npc, uint8_t(flag), wp, pos);
32 }
33
34void Npc::GoTo::load(Serialize& fin) {
35 fin.read(npc, reinterpret_cast<uint8_t&>(flag), wp, pos);
36 //NOTE: no real need to version check
37 //if(fin.version()<53) {
38 if(flag==GoToHint::GT_EnemyG || flag==GoToHint::GT_EnemyA)
39 clear(); // not persistent flags, and should be cleared by FAI
40 // }
41 }
42
43Vec3 Npc::GoTo::target() const {
44 if(npc!=nullptr)
45 return npc->position() + Vec3(0, npc->translateY(), 0);
46 if(wp!=nullptr)
47 return wp->position();
48 return pos;
49 }
50
51bool Npc::GoTo::isClose(const Npc& self, float dist) const {
52 if(npc!=nullptr)
53 return MoveAlgo::isClose(self, *npc, dist);
54 if(wp!=nullptr)
55 return MoveAlgo::isClose(self, *wp, dist);
56 return MoveAlgo::isClose(self, pos, dist);
57 }
58
59bool Npc::GoTo::empty() const {
60 return flag==Npc::GT_No;
61 }
62
63void Npc::GoTo::clear() {
64 npc = nullptr;
65 wp = nullptr;
66 flag = Npc::GT_No;
67 }
68
69void Npc::GoTo::set(Npc* to, Npc::GoToHint hnt) {
70 npc = to;
71 wp = nullptr;
72 flag = hnt;
73 }
74
75void Npc::GoTo::set(const WayPoint* to, GoToHint hnt) {
76 npc = nullptr;
77 wp = to;
78 flag = hnt;
79 }
80
81void Npc::GoTo::set(const Item* to) {
82 pos = to->position();
83 flag = Npc::GT_Item;
84 }
85
86void Npc::GoTo::set(const Vec3& to) {
87 pos = to;
88 flag = GT_Point;
89 }
90
91void Npc::GoTo::setFlee() {
92 flag = GT_Flee;
93 }
94
95
98 hnpc = std::make_shared<zenkit::INpc>(*self.hnpc);
99 invent = std::move(self.invent);
100 self.invent = Inventory(); // cleanup
101
102 std::memcpy(talentsSk,self.talentsSk,sizeof(talentsSk));
103 std::memcpy(talentsVl,self.talentsVl,sizeof(talentsVl));
104
105 body = std::move(self.body);
106 head = std::move(self.head);
107 vHead = self.vHead;
108 vTeeth = self.vTeeth;
109 vColor = self.vColor;
110 bdColor = self.bdColor;
111
112 skeleton = self.visual.visualSkeleton();
113 }
114
115 TransformBack(Npc& owner, zenkit::DaedalusVm& vm, Serialize& fin) {
116 hnpc = std::make_shared<zenkit::INpc>();
117 hnpc->user_ptr = this;
118 fin.readNpc(vm, hnpc);
119 invent.load(fin,owner);
122
123 std::string sk;
124 fin.read(sk);
126 }
127
128 void undo(Npc& self) {
129 int32_t aivar[zenkit::INpc::aivar_count]={};
130
131 auto exp = self.hnpc->exp;
132 auto exp_next = self.hnpc->exp_next;
133 auto lp = self.hnpc->lp;
134 auto level = self.hnpc->level;
135 std::memcpy(aivar,self.hnpc->aivar,sizeof(aivar));
136
137 self.hnpc = hnpc;
138 self.hnpc->exp = exp;
139 self.hnpc->exp_next = exp_next;
140 self.hnpc->lp = lp;
141 self.hnpc->level = level;
142 std::memcpy(self.hnpc->aivar,aivar,sizeof(aivar));
143
144 self.invent = std::move(invent);
145 std::memcpy(self.talentsSk,talentsSk,sizeof(talentsSk));
146 std::memcpy(self.talentsVl,talentsVl,sizeof(talentsVl));
147
148 self.body = std::move(body);
149 self.head = std::move(head);
150 self.vHead = vHead;
151 self.vTeeth = vTeeth;
152 self.vColor = vColor;
153 self.bdColor = bdColor;
154 }
155
156 void save(Serialize& fout) {
157 fout.write(*hnpc);
158 invent.save(fout);
161 fout.write(skeleton!=nullptr ? skeleton->name() : "");
162 }
163
164 std::shared_ptr<zenkit::INpc> hnpc={};
168
169 std::string body,head;
170 int32_t vHead=0, vTeeth=0, vColor=0;
171 int32_t bdColor=0;
172
173 const Skeleton* skeleton = nullptr;
174 };
175
176
177Npc::Npc(World &owner, size_t instance, std::string_view waypoint, NpcProcessPolicy aiPolicy)
178 :owner(owner),aiPolicy(aiPolicy),mvAlgo(*this) {
179 outputPipe = owner.script().openAiOuput();
180
181 hnpc = std::make_shared<zenkit::INpc>();
182 hnpc->user_ptr = this;
183 hnpc->id = int32_t(instance & 0x7FFFFFFF);
184 hnpc->wp = std::string(waypoint);
185
186 if(instance==size_t(-1))
187 return;
188
190
191 // vanilla behavior: equip best weapon and set non-zero damage type
192 if(!isPlayer())
193 invent.autoEquipWeapons(*this);
194 if(hnpc->damage_type==0)
195 hnpc->damage_type = 2;
196 setTrueGuild(hnpc->guild); // https://worldofplayers.ru/threads/12446/post-878087
197 setPerceptionTime(5000); // https://github.com/Try/OpenGothic/pull/720#issuecomment-2602908614
198 }
199
201 if(currentInteract)
202 currentInteract->detach(*this,true);
203 }
204
205void Npc::save(Serialize &fout, size_t id, std::string_view directory) {
206 fout.setEntry("worlds/",fout.worldName(),directory,id,"/data");
207 fout.write(*hnpc);
208 fout.write(body,head,vHead,vTeeth,bdColor,vColor,bdFatness);
209 fout.write(x,y,z,angle,sz);
210 fout.write(wlkMode,trGuild,talentsSk,talentsVl,refuseTalkMilis);
211 fout.write(permAttitude,tmpAttitude);
212 fout.write(perceptionTime,perceptionNextTime);
213 for(auto& i:perception)
214 fout.write(i.func);
215
216 // extra state
217 fout.write(lastHitType,lastHitSpell);
218 if(currentSpellCast<uint32_t(-1))
219 fout.write(uint32_t(currentSpellCast)); else
220 fout.write(uint32_t(-1));
221 fout.write(uint8_t(castLevel),castNextTime,manaInvested,aiExpectedInvest);
222 fout.write(spellInfo);
223
224 saveTrState(fout);
225 saveAiState(fout);
226
227 fout.write(currentInteract,currentOther,currentVictim);
228 fout.write(currentLookAt,currentLookAtNpc,currentTarget,nearestEnemy);
229
230 go2.save(fout);
231 fout.write(currentFp,currentFpLock);
232 wayPath.save(fout);
233
234 mvAlgo.save(fout);
235 fghAlgo.save(fout);
236 fout.write(lastEventTime,angleY,runAng);
237 fout.write(invTorch);
238 fout.write(isUsingTorch());
239
240 Vec3 phyPos = physic.position();
241 fout.write(phyPos);
242
243 fout.setEntry("worlds/",fout.worldName(),directory,id,"/visual");
244 visual.save(fout,*this);
245
246 fout.setEntry("worlds/",fout.worldName(),directory,id,"/inventory");
247 if(!invent.isEmpty() || id==size_t(-1))
248 invent.save(fout);
249 }
250
251void Npc::load(Serialize &fin, size_t id, std::string_view directory) {
252 fin.setEntry("worlds/",fin.worldName(),directory,id,"/data");
253
254 hnpc = std::make_shared<zenkit::INpc>();
255 hnpc->user_ptr = this;
256 fin.readNpc(owner.script().getVm(), hnpc);
257 fin.read(body,head,vHead,vTeeth,bdColor,vColor,bdFatness);
258
259 auto* sym = owner.script().findSymbol(hnpc->symbol_index());
260 if (sym != nullptr)
261 sym->set_instance(hnpc);
262
263 fin.read(x,y,z,angle,sz);
264 fin.read(wlkMode,trGuild,talentsSk,talentsVl,refuseTalkMilis);
265 durtyTranform = TR_Pos|TR_Rot|TR_Scale;
266
267 fin.read(permAttitude,tmpAttitude);
268 fin.read(perceptionTime,perceptionNextTime);
269 for(auto& i:perception)
270 fin.read(i.func);
271
272 // extra state
273 fin.read(lastHitType,lastHitSpell);
274 {
275 uint32_t currentSpellCastU32 = uint32_t(-1);
276 fin.read(currentSpellCastU32);
277 currentSpellCast = (currentSpellCastU32==uint32_t(-1) ? size_t(-1) : currentSpellCastU32);
278 }
279 fin.read(reinterpret_cast<uint8_t&>(castLevel),castNextTime);
280 if(fin.version()>44)
281 fin.read(manaInvested,aiExpectedInvest);
282 fin.read(spellInfo);
283 loadTrState(fin);
284 loadAiState(fin);
285
286 fin.read(currentInteract,currentOther,currentVictim);
287 if(fin.version()>=42)
288 fin.read(currentLookAt);
289 fin.read(currentLookAtNpc,currentTarget,nearestEnemy);
290
291 go2.load(fin);
292 fin.read(currentFp,currentFpLock);
293 wayPath.load(fin);
294
295 mvAlgo.load(fin);
296 fghAlgo.load(fin);
297 fin.read(lastEventTime,angleY,runAng);
298
299 bool isUsingTorch = false;
300 if(fin.version()>36) {
301 fin.read(invTorch);
302 fin.read(isUsingTorch);
303 }
304
305 Vec3 phyPos = {};
306 fin.read(phyPos);
307
308 fin.setEntry("worlds/",fin.worldName(),directory,id,"/visual");
309 visual.load(fin,*this);
310 physic.setPosition(phyPos);
311
312 setVisualBody(vHead,vTeeth,vColor,bdColor,body,head);
313
314 if(fin.setEntry("worlds/",fin.worldName(),directory,id,"/inventory"))
315 invent.load(fin,*this);
316
317 // post-alignment
319 if(isUsingTorch)
320 visual.setTorch(true,owner);
321 if(isDead())
322 physic.setEnable(false);
323 }
324
326 if(currentInteract!=nullptr && !currentInteract->isAttached(*this))
327 currentInteract = nullptr;
328 }
329
330void Npc::saveAiState(Serialize& fout) const {
331 fout.write(aniWaitTime,waitTime,faiWaitTime,outWaitTime);
332 fout.write(uint8_t(aiPolicy));
333 fout.write(aiState.funcIni,aiState.funcLoop,aiState.funcEnd,aiState.sTime,aiState.eTime,aiState.started,aiState.loopNextTime);
334 fout.write(aiPrevState);
335
336 aiQueue.save(fout);
337 aiQueueOverlay.save(fout);
338
339 fout.write(uint32_t(routines.size()));
340 for(auto& i:routines) {
341 fout.write(i.start,i.end,i.callback,i.point,i.fallbackName);
342 }
343 }
344
345void Npc::loadAiState(Serialize& fin) {
346 fin.read(aniWaitTime);
347 fin.read(waitTime,faiWaitTime);
348 fin.read(outWaitTime);
349 fin.read(reinterpret_cast<uint8_t&>(aiPolicy));
350 fin.read(aiState.funcIni,aiState.funcLoop,aiState.funcEnd,aiState.sTime,aiState.eTime,aiState.started,aiState.loopNextTime);
351 fin.read(aiPrevState);
352
353#ifndef NDEBUG
354 if(auto s = owner.script().findSymbol(aiState.funcIni.ptr)) {
355 aiState.hint = s->name().c_str();
356 }
357#endif
358
359 aiQueue.load(fin);
360 aiQueueOverlay.load(fin);
361
362 uint32_t size=0;
363 fin.read(size);
364 routines.resize(size);
365 for(auto& i:routines) {
366 fin.read(i.start,i.end,i.callback,i.point);
367 if(fin.version()>51)
368 fin.read(i.fallbackName);
369 }
370 }
371
372void Npc::saveTrState(Serialize& fout) const {
373 if(transformSpl!=nullptr) {
374 fout.write(true);
375 transformSpl->save(fout);
376 } else {
377 fout.write(false);
378 }
379 }
380
381void Npc::loadTrState(Serialize& fin) {
382 bool hasTr = false;
383 fin.read(hasTr);
384 if(hasTr)
385 transformSpl.reset(new TransformBack(*this, owner.script().getVm(), fin));
386 }
387
388bool Npc::setPosition(float ix, float iy, float iz) {
389 if(x==ix && y==iy && z==iz)
390 return false;
391 x = ix;
392 y = iy;
393 z = iz;
394 durtyTranform |= TR_Pos;
395 physic.setPosition(Vec3{x,y,z});
396 return true;
397 }
398
399bool Npc::setPosition(const Tempest::Vec3& pos) {
400 return setPosition(pos.x,pos.y,pos.z);
401 }
402
403void Npc::setViewPosition(const Tempest::Vec3& pos) {
404 x = pos.x;
405 y = pos.y;
406 z = pos.z;
407 durtyTranform |= TR_Pos;
408 }
409
410int Npc::aiOutputOrderId() const {
411 return aiQueue.aiOutputOrderId();
412 }
413
414bool Npc::performOutput(const AiQueue::AiAction &act) {
415 if(act.target==nullptr) //FIXME: target is null after loading
416 return true;
417 const int order = act.target->aiOutputOrderId();
418 if(order<act.i0)
419 return false;
420 if(aiOutputBarrier>owner.tickCount() && act.target==this && !isPlayer())
421 return false;
422 if(aiPolicy>=NpcProcessPolicy::AiFar)
423 return true; // don't waste CPU on far-away svm-talks
424 //if(act.act!=AI_OutputSvmOverlay && bodyStateMasked()!=BS_STAND)
425 // return false;
426 if(act.act==AI_Output && outputPipe->output (*this,act.s0))
427 return true;
428 auto svm = owner.script().messageFromSvm(act.s0,hnpc->voice);
429 if(act.act==AI_OutputSvm && outputPipe->outputSvm(*this,svm))
430 return true;
431 if(act.act==AI_OutputSvmOverlay && outputPipe->outputOv(*this,svm))
432 return true;
433 return false;
434 }
435
436void Npc::setDirection(const Tempest::Vec3& pos) {
437 float a = angleDir(pos.x, pos.z);
438 setDirection(a);
439 }
440
441void Npc::setDirection(float rotation) {
442 angle = rotation;
443 durtyTranform |= TR_Rot;
444 }
445
446void Npc::setDirectionY(float rotation) {
447 if(rotation>90)
448 rotation = 90;
449 if(rotation<-90)
450 rotation = -90;
451 rotation = std::fmod(rotation,360.f);
452 if(!mvAlgo.isSwim() && !(interactive()!=nullptr && interactive()->isLadder()))
453 return;
454 angleY = rotation;
455 durtyTranform |= TR_Rot;
456 }
457
458void Npc::setRunAngle(float angle) {
459 durtyTranform |= TR_Rot;
460 runAng = angle;
461 }
462
463float Npc::angleDir(float x, float z) {
464 float a=-90;
465 if(x!=0.f || z!=0.f)
466 a = 90+180.f*std::atan2(z,x)/float(M_PI);
467 return a;
468 }
469
471 const bool g2 = owner.version().game==2;
472 const bool isDragon = (g2 && guild()==GIL_DRAGON);
473 const bool isDead = this->isDead();
474
475 if(isDead && !invent.hasMissionItems() && !isDragon)
476 return false;
477
478 invent.clearSlot(*this,"",currentInteract!=nullptr);
479 if(!isPlayer())
480 setInteraction(nullptr,true);
481
482 // return monsters to their way-points
483 // if(routines.empty() && !isPlayer())
484 // return currentTaPoint()!=nullptr;
485
486 attachToPoint(nullptr);
487 clearAiQueue();
488
489 if(!isDead) {
490 visual.stopAnim(*this,"");
491 clearState(true);
492 }
493
494 if(isPlayer())
495 return true;
496
497 auto at = currentTaPoint();
498 if(at==nullptr)
499 return false;
500
501 if(at->isLocked() && !isDead) {
502 auto p = owner.findNextPoint(*at);
503 if(p!=nullptr)
504 at = p;
505 }
506 setPosition (at->position() );
507 setDirection(at->direction());
508 owner.script().fixNpcPosition(*this,0,0);
509
510 if(!isDead) {
511 attachToPoint(at);
512 invent.autoEquipWeapons(*this);
513 }
514
515 owner.script().invokeRefreshAtInsert(*this);
516 return true;
517 }
518
520 visual.stopDlgAnim(*this);
521 }
522
524 mvAlgo.clearSpeed();
525 }
526
528 if(aiPolicy==t)
529 return;
530 if(aiPolicy==NpcProcessPolicy::Player)
531 runAng = 0;
532 aiPolicy=t;
533 }
534
536 wlkMode = m;
537 }
538
539bool Npc::isPlayer() const {
540 return aiPolicy==NpcProcessPolicy::Player;
541 }
542
544 setPosition(physic.position());
545 visual.setAnimRotate(*this,0);
546 return mvAlgo.startClimb(jump);
547 }
548
549bool Npc::checkHealth(bool onChange, bool allowUnconscious) {
550 if(isDead()) {
551 return false;
552 }
553 if(isUnconscious() && allowUnconscious) {
554 return false;
555 }
556
557 const int minHp = isMonster() ? 0 : 1;
558 if(hnpc->attribute[ATR_HITPOINTS]<=minHp) {
559 if(currentOther==nullptr || !allowUnconscious || !isHuman() ||
560 owner.script().personAttitude(*this,*currentOther)==ATT_HOSTILE){
561 if(hnpc->attribute[ATR_HITPOINTS]<=0)
562 onNoHealth(true,HS_Dead);
563 return false;
564 }
565
566 if(onChange) {
567 onNoHealth(false,HS_Dead);
568 return false;
569 }
570 }
571 physic.setEnable(true);
572 return true;
573 }
574
575void Npc::onNoHealth(bool death, HitSound sndMask) {
576 invent.switchActiveWeapon(*this,Item::NSLOT);
577 visual.dropWeapon(*this);
578 visual.dropShield(*this);
579 dropTorch();
581 updateWeaponSkeleton();
582
583 setOther(lastHit);
584 clearAiQueue();
585 attachToPoint(nullptr);
586
587 const char* svm = death ? "SVM_%d_DEAD" : "SVM_%d_AARGH";
588 const char* state = death ? "ZS_Dead" : "ZS_Unconscious";
589
590 if(!death)
591 hnpc->attribute[ATR_HITPOINTS]=1;
592
593 size_t fdead=owner.script().findSymbolIndex(state);
594 startState(fdead,"",gtime::endOfTime(),true);
595 // Note: clear perceptions for William in Jarkentar
596 for(size_t i=0;i<PERC_Count;++i)
598 if(hnpc->voice>0 && sndMask!=HS_NoSound) {
599 emitSoundSVM(svm);
600 }
601
602 setInteraction(nullptr,true);
603 invent.clearSlot(*this,"",false);
604
605 if(death)
606 physic.setEnable(false);
607
608 if(death)
609 setAnim(lastHitType=='A' ? Anim::DeadA : Anim::DeadB); else
610 setAnim(lastHitType=='A' ? Anim::UnconsciousA : Anim::UnconsciousB);
611 }
612
613bool Npc::hasAutoroll() const {
614 auto gl = std::min<uint32_t>(guild(),GIL_MAX);
615 return owner.script().guildVal().disable_autoroll[gl]==0;
616 }
617
618void Npc::stopWalkAnimation() {
619 if(interactive()==nullptr)
620 visual.stopWalkAnim(*this);
621 setAnimRotate(0);
622 }
623
625 return owner;
626 }
627
628Vec3 Npc::position() const {
629 return {x,y,z};
630 }
631
632Matrix4x4 Npc::transform() const {
633 return visual.transform();
634 }
635
636Vec3 Npc::cameraBone(bool isFirstPerson) const {
637 const size_t head = visual.pose().findNode("BIP01 HEAD");
638
639 Vec3 r = {};
640 if(isFirstPerson && head!=size_t(-1)) {
641 r = visual.mapBone(head);
642 } else {
643 auto mt = visual.pose().rootBone();
644 mt.project(r);
645 }
646
647 return r;
648 }
649
650Matrix4x4 Npc::cameraMatrix(bool isFirstPerson) const {
651 const size_t head = visual.pose().findNode("BIP01 HEAD");
652 if(isFirstPerson && head!=size_t(-1)) {
653 return visual.pose().bone(head);
654 }
655 return visual.pose().rootBone();
656 }
657
658float Npc::rotation() const {
659 return angle;
660 }
661
662float Npc::rotationRad() const {
663 return angle*float(M_PI)/180.f;
664 }
665
666float Npc::rotationY() const {
667 return angleY;
668 }
669
670float Npc::rotationYRad() const {
671 return angleY*float(M_PI)/180.f;
672 }
673
675 auto b = visual.bounds();
677 return b;
678 }
679
680float Npc::translateY() const {
681 return visual.pose().translateY();
682 }
683
685 auto p = position();
686 p.y = physic.centerY();
687 return p;
688 }
689
691 return currentLookAtNpc;
692 }
693
694std::string_view Npc::portalName() {
695 return mvAlgo.portalName();
696 }
697
698std::string_view Npc::formerPortalName() {
699 return mvAlgo.formerPortalName();
700 }
701
702float Npc::qDistTo(const Vec3 pos) const {
703 auto dp = pos - Vec3(x,y+translateY(),z);
704 return dp.quadLength();
705 }
706
707float Npc::qDistTo(const WayPoint *f) const {
708 if(f==nullptr)
709 return 0.f;
710 return qDistTo(f->position());
711 }
712
713float Npc::qDistTo(const Npc &p) const {
714 return qDistTo(Vec3(p.x,p.y+p.translateY(),p.z));
715 }
716
717float Npc::qDistTo(const Interactive &p) const {
718 auto pos = p.nearestPoint(*this);
719 return qDistTo(pos);
720 }
721
722float Npc::qDistTo(const Item& p) const {
723 auto pos = p.midPosition();
724 return qDistTo(pos);
725 }
726
727uint8_t Npc::calcAniComb() const {
728 if(currentTarget==nullptr)
729 return 0;
730 auto dpos = currentTarget->position()-position();
731 return Pose::calcAniComb(dpos,angle);
732 }
733
734std::string_view Npc::displayName() const {
735 return hnpc->name[0];
736 }
737
738Tempest::Vec3 Npc::displayPosition() const {
739 auto p = visual.displayPosition();
740 return p+position();
741 }
742
743void Npc::setVisual(std::string_view visual) {
744 auto skelet = Resources::loadSkeleton(visual);
745 setVisual(skelet);
746 setPhysic(owner.physic()->ghostObj(visual));
747 }
748
749bool Npc::hasOverlay(std::string_view sk) const {
750 auto skelet = Resources::loadSkeleton(sk);
751 return hasOverlay(skelet);
752 }
753
754bool Npc::hasOverlay(const Skeleton* sk) const {
755 return visual.hasOverlay(sk);
756 }
757
758void Npc::addOverlay(std::string_view sk, uint64_t time) {
759 auto skelet = Resources::loadSkeleton(sk);
760 addOverlay(skelet,time);
761 }
762
763void Npc::addOverlay(const Skeleton* sk, uint64_t time) {
764 if(time!=0)
765 time+=owner.tickCount();
766 visual.addOverlay(sk,time);
767 }
768
769void Npc::delOverlay(std::string_view sk) {
770 visual.delOverlay(sk);
771 }
772
773void Npc::delOverlay(const Skeleton *sk) {
774 visual.delOverlay(sk);
775 }
776
779 if(isUsingTorch()) {
780 visual.setTorch(false,owner);
781 delOverlay(overlay);
782 return false;
783 }
784 visual.setTorch(true,owner);
785 addOverlay(overlay,0);
786 return true;
787 }
788
789void Npc::setTorch(bool use) {
790 if(isUsingTorch()==use)
791 return;
792
794 visual.setTorch(use,owner);
795 if(use) {
796 addOverlay(overlay,0);
797 } else {
798 delOverlay(overlay);
799 }
800 }
801
802bool Npc::isUsingTorch() const {
803 return visual.isUsingTorch();
804 }
805
806void Npc::dropTorch(bool burnout) {
807 auto sk = visual.visualSkeleton();
808 if(sk==nullptr)
809 return;
810
811 if(!isUsingTorch())
812 return;
813
815 visual.setTorch(false,owner);
816 delOverlay(overlay);
817
818 size_t torchId = 0;
819 if(burnout)
820 torchId = owner.script().findSymbolIndex("ItLsTorchburned"); else
821 torchId = owner.script().findSymbolIndex("ItLsTorchburning");
822
823 size_t leftHand = sk->findNode("ZS_LEFTHAND");
824 if(torchId!=size_t(-1) && leftHand!=size_t(-1)) {
825
826 auto mat = visual.transform();
827 if(leftHand<visual.pose().boneCount())
828 mat = visual.pose().bone(leftHand);
829
830 owner.addItemDyn(torchId,mat,hnpc->symbol_index());
831 }
832 }
833
834Tempest::Vec3 Npc::animMoveSpeed(uint64_t dt) const {
835 return visual.pose().animMoveSpeed(owner.tickCount(),dt);
836 }
837
838void Npc::setVisual(const Skeleton* v) {
839 visual.setVisual(v);
841 }
842
843void Npc::setVisualBody(int32_t headTexNr, int32_t teethTexNr, int32_t bodyTexNr, int32_t bodyTexColor,
844 std::string_view ibody, std::string_view ihead) {
845 body = ibody;
846 head = ihead;
847 vHead = headTexNr;
848 vTeeth = teethTexNr;
849 vColor = bodyTexNr;
850 bdColor = bodyTexColor;
851
852 auto vhead = head.empty() ? MeshObjects::Mesh() : owner.addView(FileExt::addExt(head,".MMB"),vHead,vTeeth,bdColor);
853 auto vbody = body.empty() ? MeshObjects::Mesh() : owner.addView(FileExt::addExt(body,".ASC"),vColor,0,bdColor);
854 visual.setVisualBody(*this,std::move(vhead),std::move(vbody),bdColor);
855 updateArmor();
856
857 durtyTranform|=TR_Pos; // update obj matrix
858 }
859
861 auto ar = invent.currentArmor();
862 auto& w = owner;
863
864 if(ar==nullptr) {
865 auto vbody = body.empty() ? MeshObjects::Mesh() : w.addView(FileExt::addExt(body,".ASC"),vColor,0,bdColor);
866 visual.setBody(*this,std::move(vbody),bdColor);
867 } else {
868 auto& itData = ar->handle();
869 auto flag = ItmFlags(itData.main_flag);
870 if(flag & ITM_CAT_ARMOR){
871 auto& asc = itData.visual_change;
872 auto vbody = asc.empty() ? MeshObjects::Mesh() : w.addView(asc,vColor,0,bdColor);
873 visual.setArmor(*this,std::move(vbody));
874 }
875 }
876 }
877
879 visual.setSword(std::move(s));
880 updateWeaponSkeleton();
881 }
882
884 visual.setRangedWeapon(std::move(b));
885 updateWeaponSkeleton();
886 }
887
889 visual.setShield(std::move(s));
890 updateWeaponSkeleton();
891 }
892
894 s.setOrigin(this);
895 visual.setMagicWeapon(std::move(s),owner);
896 updateWeaponSkeleton();
897 }
898
899void Npc::setSlotItem(MeshObjects::Mesh&& itm, std::string_view slot) {
900 visual.setSlotItem(std::move(itm),slot);
901 }
902
903void Npc::setStateItem(MeshObjects::Mesh&& itm, std::string_view slot) {
904 visual.setStateItem(std::move(itm),slot);
905 }
906
907void Npc::setAmmoItem(MeshObjects::Mesh&& itm, std::string_view slot) {
908 visual.setAmmoItem(std::move(itm),slot);
909 }
910
911void Npc::clearSlotItem(std::string_view slot) {
912 visual.clearSlotItem(slot);
913 }
914
915void Npc::updateWeaponSkeleton() {
917 }
918
920 physic = std::move(item);
921 physic.setUserPointer(this);
922 physic.setPosition(Vec3{x,y,z});
923 }
924
925void Npc::setFatness(float f) {
926 bdFatness = f;
927 visual.setFatness(f);
928 }
929
930void Npc::setScale(float x, float y, float z) {
931 sz[0]=x;
932 sz[1]=y;
933 sz[2]=z;
934 durtyTranform |= TR_Scale;
935 }
936
937const Animation::Sequence* Npc::playAnimByName(std::string_view name, BodyState bs) {
938 return visual.startAnimAndGet(*this,name,calcAniComb(),bs);
939 }
940
942 return setAnimAngGet(a)!=nullptr;
943 }
944
946 return setAnimAngGet(a,calcAniComb());
947 }
948
950 auto st = weaponState();
951 auto wlk = walkMode();
952 if(mvAlgo.isDive())
953 wlk = WalkBit::WM_Dive;
954 else if(mvAlgo.isSwim())
955 wlk = WalkBit::WM_Swim;
956 else if(mvAlgo.isInWater())
957 wlk = WalkBit::WM_Water;
958 return visual.startAnimAndGet(*this,a,comb,st,wlk);
959 }
960
961void Npc::setAnimRotate(int rot) {
962 visual.setAnimRotate(*this,rot);
963 }
964
965bool Npc::setAnimItem(std::string_view scheme, int state) {
966 if(scheme.empty())
967 return true;
969 setAnim(Anim::Idle);
970 return false;
971 }
972 if(auto sq = visual.startAnimItem(*this,scheme,state)) {
973 implAniWait(uint64_t(sq->totalTime()));
974 return true;
975 }
976 return false;
977 }
978
979void Npc::stopAnim(std::string_view ani) {
980 visual.stopAnim(*this,ani);
981 }
982
983void Npc::startFaceAnim(std::string_view anim, float intensity, uint64_t duration) {
984 visual.startFaceAnim(*this,anim,intensity,duration);
985 }
986
988 return visual.stopItemStateAnim(*this);
989 }
990
991bool Npc::hasAnim(std::string_view scheme) const {
992 return visual.hasAnim(scheme);
993 }
994
996 return hasAnim("S_SWIM") && hasAnim("S_SWIMF");
997 }
998
1001 return false;
1002 return visual.pose().isInAnim("T_1HSFINISH") || visual.pose().isInAnim("T_2HSFINISH");
1003 }
1004
1005bool Npc::isStanding() const {
1006 return visual.isStanding();
1007 }
1008
1009bool Npc::isSwim() const {
1010 return mvAlgo.isSwim();
1011 }
1012
1013bool Npc::isInWater() const {
1014 return mvAlgo.isInWater();
1015 }
1016
1017bool Npc::isDive() const {
1018 return mvAlgo.isDive();
1019 }
1020
1021bool Npc::isCasting() const {
1022 return castLevel!=CS_NoCast;
1023 }
1024
1025bool Npc::isJumpAnim() const {
1026 return visual.pose().isJumpAnim();
1027 }
1028
1029bool Npc::isFlyAnim() const {
1030 return visual.pose().isFlyAnim();
1031 }
1032
1033bool Npc::isFalling() const {
1034 return mvAlgo.isFalling();
1035 }
1036
1038 return mvAlgo.isInAir() && (visual.pose().isInAnim("S_FALL") || visual.pose().isInAnim("S_FALLB"));
1039 }
1040
1041bool Npc::isSlide() const {
1042 return mvAlgo.isSlide();
1043 }
1044
1045bool Npc::isInAir() const {
1046 return mvAlgo.isInAir();
1047 }
1048
1055
1057 const auto scheme = visual.visualSkeletonScheme();
1058 if(scheme.empty())
1059 return;
1060
1061 const auto lvl = talentsSk[t];
1062 if(t==TALENT_1H){
1063 if(lvl==0){
1064 delOverlay(string_frm(scheme,"_1HST1.MDS"));
1065 delOverlay(string_frm(scheme,"_1HST2.MDS"));
1066 }
1067 else if(lvl==1){
1068 addOverlay(string_frm(scheme,"_1HST1.MDS"),0);
1069 delOverlay(string_frm(scheme,"_1HST2.MDS"));
1070 }
1071 else if(lvl==2){
1072 delOverlay(string_frm(scheme,"_1HST1.MDS"));
1073 addOverlay(string_frm(scheme,"_1HST2.MDS"),0);
1074 }
1075 }
1076 else if(t==TALENT_2H){
1077 if(lvl==0){
1078 delOverlay(string_frm(scheme,"_2HST1.MDS"));
1079 delOverlay(string_frm(scheme,"_2HST2.MDS"));
1080 }
1081 else if(lvl==1){
1082 addOverlay(string_frm(scheme,"_2HST1.MDS"),0);
1083 delOverlay(string_frm(scheme,"_2HST2.MDS"));
1084 }
1085 else if(lvl==2){
1086 delOverlay(string_frm(scheme,"_2HST1.MDS"));
1087 addOverlay(string_frm(scheme,"_2HST2.MDS"),0);
1088 }
1089 }
1090 else if(t==TALENT_BOW){
1091 if(lvl==0){
1092 delOverlay(string_frm(scheme,"_BOWT1.MDS"));
1093 delOverlay(string_frm(scheme,"_BOWT2.MDS"));
1094 }
1095 else if(lvl==1){
1096 addOverlay(string_frm(scheme,"_BOWT1.MDS"),0);
1097 delOverlay(string_frm(scheme,"_BOWT2.MDS"));
1098 }
1099 else if(lvl==2){
1100 delOverlay(string_frm(scheme,"_BOWT1.MDS"));
1101 addOverlay(string_frm(scheme,"_BOWT2.MDS"),0);
1102 }
1103 }
1104 else if(t==TALENT_CROSSBOW){
1105 if(lvl==0){
1106 delOverlay(string_frm(scheme,"_CBOWT1.MDS"));
1107 delOverlay(string_frm(scheme,"_CBOWT2.MDS"));
1108 }
1109 else if(lvl==1){
1110 addOverlay(string_frm(scheme,"_CBOWT1.MDS"),0);
1111 delOverlay(string_frm(scheme,"_CBOWT2.MDS"));
1112 }
1113 else if(lvl==2){
1114 delOverlay(string_frm(scheme,"_CBOWT1.MDS"));
1115 addOverlay(string_frm(scheme,"_CBOWT2.MDS"),0);
1116 }
1117 }
1118 else if(t==TALENT_ACROBAT){
1119 if(lvl==0)
1120 delOverlay(string_frm(scheme,"_ACROBATIC.MDS")); else
1121 addOverlay(string_frm(scheme,"_ACROBATIC.MDS"),0);
1122 }
1123 }
1124
1125void Npc::setTalentSkill(Talent t, int32_t lvl) {
1126 if(t>=TALENT_MAX_G2)
1127 return;
1128 talentsSk[t] = lvl;
1130 }
1131
1132int32_t Npc::talentSkill(Talent t) const {
1133 if(t<TALENT_MAX_G2)
1134 return talentsSk[t];
1135 return 0;
1136 }
1137
1138void Npc::setTalentValue(Talent t, int32_t lvl) {
1139 if(t<TALENT_MAX_G2)
1140 talentsVl[t] = lvl;
1141 }
1142
1143int32_t Npc::talentValue(Talent t) const {
1144 if(t<TALENT_MAX_G2)
1145 return talentsVl[t];
1146 return 0;
1147 }
1148
1149int32_t Npc::hitChance(Talent t) const {
1150 if(t<=zenkit::INpc::hitchance_count)
1151 return hnpc->hitchance[t];
1152 return 0;
1153 }
1154
1155bool Npc::isRefuseTalk() const {
1156 return refuseTalkMilis>=owner.tickCount();
1157 }
1158
1159int32_t Npc::mageCycle() const {
1160 return talentSkill(TALENT_MAGE);
1161 }
1162
1163bool Npc::canSneak() const {
1164 return talentSkill(TALENT_SNEAK)!=0;
1165 }
1166
1167void Npc::setRefuseTalk(uint64_t milis) {
1168 refuseTalkMilis = owner.tickCount()+milis;
1169 }
1170
1171int32_t Npc::attribute(Attribute a) const {
1172 if(a<ATR_MAX)
1173 return hnpc->attribute[a];
1174 return 0;
1175 }
1176
1177void Npc::changeAttribute(Attribute a, int32_t val, bool allowUnconscious) {
1178 if(a>=ATR_MAX || val==0)
1179 return;
1180
1181 if(val<0 && a==ATR_HITPOINTS) {
1182 if(isPlayer() && Gothic::inst().isGodMode())
1183 return;
1184 if(isPlayer() && owner.currentCs()!=nullptr)
1185 return;
1186 if(isImmortal())
1187 return;
1188 }
1189
1190 hnpc->attribute[a]+=val;
1191 if(hnpc->attribute[a]<0)
1192 hnpc->attribute[a]=0;
1193 if(a==ATR_HITPOINTS && hnpc->attribute[a]>hnpc->attribute[ATR_HITPOINTSMAX])
1194 hnpc->attribute[a] = hnpc->attribute[ATR_HITPOINTSMAX];
1195 if(a==ATR_MANA && hnpc->attribute[a]>hnpc->attribute[ATR_MANAMAX])
1196 hnpc->attribute[a] = hnpc->attribute[ATR_MANAMAX];
1197
1198 if(val<0)
1199 invent.invalidateCond(*this);
1200
1201 if(a==ATR_HITPOINTS) {
1202 checkHealth(true,allowUnconscious);
1203 if(aiPolicy==NpcProcessPolicy::AiFar || aiPolicy==NpcProcessPolicy::AiFar2)
1204 aiState.started = true;
1205 }
1206 }
1207
1208int32_t Npc::protection(Protection p) const {
1209 if(p<PROT_MAX)
1210 return hnpc->protection[p];
1211 return 0;
1212 }
1213
1214void Npc::changeProtection(Protection p, int32_t val) {
1215 if(p<PROT_MAX)
1216 hnpc->protection[p]=val;
1217 }
1218
1219uint32_t Npc::instanceSymbol() const {
1220 return uint32_t(hnpc->symbol_index());
1221 }
1222
1223uint32_t Npc::guild() const {
1224 return std::min(uint32_t(hnpc->guild), uint32_t(GIL_MAX-1));
1225 }
1226
1227bool Npc::isMonster() const {
1228 const bool g2 = owner.version().game==2;
1229 const auto SEPERATOR_ORC = g2 ? GIL_SEPERATOR_ORC : GIL_G1_SEPERATOR_ORC;
1230 const auto SEPERATOR_HUM = g2 ? GIL_SEPERATOR_HUM : GIL_G1_SEPERATOR_HUM;
1231 return SEPERATOR_HUM<guild() && guild()<SEPERATOR_ORC;
1232 }
1233
1234bool Npc::isHuman() const {
1235 const bool g2 = owner.version().game==2;
1236 const auto SEPERATOR_HUM = g2 ? GIL_SEPERATOR_HUM : GIL_G1_SEPERATOR_HUM;
1237 return guild() < SEPERATOR_HUM;
1238 }
1239
1240void Npc::setTrueGuild(int32_t g) {
1241 trGuild = g;
1242 }
1243
1244int32_t Npc::trueGuild() const {
1245 if(trGuild==GIL_NONE)
1246 return hnpc->guild;
1247 return trGuild;
1248 }
1249
1250int32_t Npc::magicCyrcle() const {
1251 return talentSkill(TALENT_RUNES);
1252 }
1253
1254int32_t Npc::level() const {
1255 return hnpc->level;
1256 }
1257
1258int32_t Npc::experience() const {
1259 return hnpc->exp;
1260 }
1261
1262int32_t Npc::experienceNext() const {
1263 return hnpc->exp_next;
1264 }
1265
1266int32_t Npc::learningPoints() const {
1267 return hnpc->lp;
1268 }
1269
1270int32_t Npc::diveTime() const {
1271 return mvAlgo.diveTime();
1272 }
1273
1275 permAttitude = att;
1276 }
1277
1278bool Npc::isFriend() const {
1279 bool g2 = owner.version().game==2;
1280 return ( g2 && hnpc->type==zenkit::NpcType::G2_FRIEND) ||
1281 (!g2 && hnpc->type==zenkit::NpcType::G1_FRIEND);
1282 }
1283
1285 tmpAttitude = att;
1286 }
1287
1288bool Npc::implPointAt(const Tempest::Vec3& to) {
1289 auto dpos = to-position();
1290 uint8_t comb = Pose::calcAniComb(dpos,angle);
1291
1292 return (setAnimAngGet(Npc::Anim::PointAt,comb)!=nullptr);
1293 }
1294
1295bool Npc::implLookAtWp(uint64_t dt) {
1296 if(currentLookAt==nullptr)
1297 return false;
1298 auto dvec = currentLookAt->position();
1299 return implLookAt(dvec.x,dvec.y,dvec.z,dt);
1300 }
1301
1302bool Npc::implLookAtNpc(uint64_t dt) {
1303 if(currentLookAtNpc==nullptr)
1304 return false;
1305 auto selfHead = visual.mapHeadBone();
1306 auto otherHead = currentLookAtNpc->visual.mapHeadBone();
1307 auto dvec = otherHead - selfHead;
1308 return implLookAt(dvec.x,dvec.y,dvec.z,dt);
1309 }
1310
1311bool Npc::implLookAt(float dx, float dy, float dz, uint64_t dt) {
1312 static const float rotSpeed = 200; // deg per second
1313 static const float maxRot = 80; // maximum rotation
1314 Vec2 dst;
1315
1316 dst.x = visual.viewDirection()-angleDir(dx,dz);
1317 while(dst.x>180)
1318 dst.x -= 360;
1319 while(dst.x<-180)
1320 dst.x += 360;
1321
1322 dst.y = std::atan2(dy,std::sqrt(dx*dx+dz*dz));
1323 dst.y = dst.y*180.f/float(M_PI);
1324
1325 if(dst.x<-maxRot || dst.x>maxRot) {
1326 dst.x = 0;
1327 dst.y = 0;
1328 }
1329
1330 if(dst.y<-20)
1331 dst.y = -20;
1332 if(dst.y>20)
1333 dst.y = 20;
1334
1335 auto rot = visual.headRotation();
1336 auto drot = dst-rot;
1337
1338 drot.x = std::min(std::abs(drot.x),rotSpeed*float(dt)/1000.f);
1339 drot.y = std::min(std::abs(drot.y),rotSpeed*float(dt)/1000.f);
1340 if(dst.x<rot.x)
1341 drot.x = -drot.x;
1342 if(dst.y<rot.y)
1343 drot.y = -drot.y;
1344
1345 rot+=drot;
1346 visual.setHeadRotation(rot.x,rot.y);
1347
1348 return false;
1349 }
1350
1351bool Npc::implTurnAway(const Npc &oth, uint64_t dt) {
1352 if(&oth==this)
1353 return true;
1354
1355 // turn npc's back to oth, so calculate direction from oth to npc
1356 auto dx = x-oth.x;
1357 auto dz = z-oth.z;
1358 auto gl = guild();
1359 float step = float(owner.script().guildVal().turn_speed[gl]);
1360 return rotateTo(dx,dz,step,AnimationSolver::TurnType::Std,dt);
1361 }
1362
1363bool Npc::implTurnTo(const Npc &oth, uint64_t dt) {
1364 if(&oth==this)
1365 return false;
1366 auto dx = oth.x-x;
1367 auto dz = oth.z-z;
1368 return implTurnTo(dx,dz,AnimationSolver::TurnType::Std,dt);
1369 }
1370
1371bool Npc::implTurnTo(const Npc& oth, AnimationSolver::TurnType anim, uint64_t dt) {
1372 if(&oth==this)
1373 return false;
1374 auto dx = oth.x-x;
1375 auto dz = oth.z-z;
1376 return implTurnTo(dx,dz,anim,dt);
1377 }
1378
1379bool Npc::implTurnTo(const WayPoint* wp, AnimationSolver::TurnType anim, uint64_t dt) {
1380 if(wp==nullptr)
1381 return false;
1382 return implTurnTo(wp->dir.x,wp->dir.z,anim,dt);
1383 }
1384
1385bool Npc::implTurnTo(float dx, float dz, AnimationSolver::TurnType anim, uint64_t dt) {
1386 auto gl = guild();
1387 float step = float(owner.script().guildVal().turn_speed[gl]);
1388 return rotateTo(dx,dz,step,anim,dt);
1389 }
1390
1391bool Npc::implWhirlTo(const Npc &oth, uint64_t dt) {
1392 return implTurnTo(oth,AnimationSolver::TurnType::Whirl,dt);
1393 }
1394
1395bool Npc::implGoTo(uint64_t dt) {
1396 float dist = 0;
1397 if(go2.npc) {
1398 dist = fghAlgo.prefferedAttackDistance(*this,*go2.npc,owner.script());
1399 // dist = fghAlgo.baseDistance(*this,*go2.npc,owner.script());
1400 } else {
1401 // use smaller threshold, to avoid edge-looping in script
1403 if(!mvAlgo.checkLastBounce())
1405 if(go2.wp!=nullptr && go2.wp->useCounter()>1)
1406 dist = float(MAX_AI_USE_DISTANCE);
1407 }
1408 return implGoTo(dt,dist);
1409 }
1410
1411bool Npc::implGoTo(uint64_t dt, float destDist) {
1412 if(go2.flag==GT_No)
1413 return false;
1414
1415 if(isInAir() || interactive()!=nullptr) {
1416 mvAlgo.tick(dt);
1417 return true;
1418 }
1419
1420 auto target = go2.target();
1421 auto dpos = target - position();
1422
1423 if(go2.flag==GT_Flee) {
1424 // nop
1425 }
1426 else if(go2.isClose(*this, destDist)) {
1427 bool finished = true;
1428 if(go2.flag==GT_Way) {
1429 go2.wp = go2.wp->hasLadderConn(wayPath.first()) ? wayPath.first() : wayPath.pop();
1430 if(go2.wp!=nullptr) {
1431 attachToPoint(go2.wp);
1432 if(setGoToLadder()) {
1433 mvAlgo.tick(dt);
1434 return true;
1435 }
1436 finished = false;
1437 }
1438 }
1439 if(finished) {
1440 if(go2.flag==Npc::GT_NextFp && implTurnTo(go2.wp,AnimationSolver::TurnType::Std,dt))
1441 return true;
1442 clearGoTo();
1443 }
1444 }
1445 else {
1446 if(setGoToLadder()) {
1447 mvAlgo.tick(dt);
1448 return true;
1449 }
1450 if(mvAlgo.checkLastBounce() && implTurnTo(dpos.x,dpos.z,AnimationSolver::TurnType::Std,dt)) {
1451 mvAlgo.tick(dt);
1452 return true;
1453 }
1454 }
1455
1456 if(!go2.empty()) {
1458 mvAlgo.tick(dt);
1459 return true;
1460 }
1461 return false;
1462 }
1463
1464bool Npc::implAttack(uint64_t dt) {
1465 if(currentTarget==nullptr || isPlayer() || isTalk())
1466 return false;
1467
1468 if(currentTarget->isDown()){
1469 // NOTE: don't clear internal target, to make scripts happy
1470 // currentTarget=nullptr;
1471 fghAlgo.onClearTarget();
1472 return false;
1473 }
1474
1475 if(aiQueue.size()>0) {
1476 // do not messup weapon change animations by MOVE intruction
1477 return false;
1478 }
1479
1480 if(!fghAlgo.hasInstructions())
1481 return false;
1482
1483 const auto bs = bodyStateMasked();
1484 if(bs==BS_LIE) {
1485 setAnim(Npc::Anim::Idle);
1486 mvAlgo.tick(dt,MoveAlgo::FaiMove);
1487 return true;
1488 }
1489 if(bs==BS_STUMBLE || isInAir()) {
1490 mvAlgo.tick(dt,MoveAlgo::FaiMove);
1491 return true;
1492 }
1493
1494 if(faiWaitTime>=owner.tickCount() || waitTime>=owner.tickCount()) {
1495 adjustAttackRotation(dt);
1496 mvAlgo.tick(dt,MoveAlgo::FaiMove);
1497 return true;
1498 }
1499
1500 const auto ws = weaponState();
1501 const auto act = fghAlgo.nextFromQueue(*this,*currentTarget,owner.script());
1502
1503 // vanilla behavior, required for orcs in G1 orcgraveyard
1506 return true;
1507 }
1508
1509 if(act==FightAlgo::MV_BLOCK) {
1510 if(!fghAlgo.isInFocusAngle(*this, *currentTarget)) {
1511 fghAlgo.consumeAction();
1512 return true;
1513 }
1514
1515 switch(ws) {
1516 case WeaponState::Fist: {
1517 if(blockFist())
1518 fghAlgo.consumeAction();
1519 break;
1520 }
1521 case WeaponState::W1H:
1522 case WeaponState::W2H: {
1523 if(blockSword())
1524 fghAlgo.consumeAction();
1525 break;
1526 }
1527 default:
1528 fghAlgo.consumeAction();
1529 break;
1530 }
1531 return true;
1532 }
1533
1535 //NOTE: FIGHT_DIST_CANCEL in scipts is often longer, than senses_range of npc
1536 const auto tgPos = currentTarget->centerPosition();
1537 const auto sense = canRayHitPoint(tgPos,true,MaxFightRange);
1538 if(!sense) {
1539 if(bs==BS_RUN)
1540 setAnim(Npc::Anim::Idle); else
1541 adjustAttackRotation(dt);
1542 mvAlgo.tick(dt,MoveAlgo::FaiMove);
1543 return true;
1544 }
1545
1546 static const Anim ani[4] = {Anim::Attack, Anim::AttackL, Anim::AttackR};
1547 if((act!=FightAlgo::MV_ATTACK && bodyState()!=BS_RUN) &&
1548 !fghAlgo.isInWRange(*this,*currentTarget,owner.script())) {
1549 fghAlgo.consumeAction();
1550 return true;
1551 }
1552
1554 bool obsticle = false;
1555 if(currentTarget!=nullptr) {
1556 auto hit = owner.physic()->rayNpc(this->mapWeaponBone(),currentTarget->centerPosition(),this);
1557 if(hit.hasCol && hit.npcHit!=currentTarget) {
1558 obsticle = true;
1559 // if(hit.npcHit!=nullptr && owner.script().personAttitude(*this,*hit.npcHit)==ATT_HOSTILE)
1560 // obsticle = false;
1561 if(hit.npcHit!=nullptr && hit.npcHit!=currentTarget && owner.script().isFriendlyFire(*this,*hit.npcHit))
1562 obsticle = false;
1563 }
1564 }
1565 if(auto spl = activeWeapon()) {
1566 if(spl->isSpell() && !spl->isSpellShoot())
1567 obsticle = false;
1568 }
1569 if(obsticle) {
1570 auto anim = (owner.script().rand(2)==0 ? Npc::Anim::MoveL : Npc::Anim::MoveR);
1571 if(setAnim(anim)){
1572 visual.setAnimRotate(*this,0);
1573 implFaiWait(visual.pose().animationTotalTime());
1574 fghAlgo.consumeAction();
1575 return true;
1576 }
1577 }
1578 }
1579
1580 if(ws==WeaponState::Mage) {
1581 if(bs==BS_RUN) {
1582 setAnim(Npc::Anim::Idle);
1583 return false;
1584 }
1585 setAnimRotate(0);
1586 const auto cast = beginCastSpell();
1587 if(cast==BeginCastResult::BC_No)
1588 return false;
1589 fghAlgo.consumeAction();
1590 }
1591 else if(ws==WeaponState::Bow || ws==WeaponState::CBow) {
1592 if(shootBow()) {
1593 fghAlgo.consumeAction();
1594 } else {
1595 if(!implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt)) {
1596 if(!aimBow())
1597 setAnim(Anim::Idle);
1598 }
1599 }
1600 }
1601 else if(ws==WeaponState::Fist || ws==WeaponState::W1H || ws==WeaponState::W2H) {
1602 const auto atkType = (ws==WeaponState::Fist) ? Anim::Attack : ani[act-FightAlgo::MV_ATTACK];
1603 const bool atk = doAttack(atkType, BS_HIT);
1604
1605 if(atk || mvAlgo.isSwim() || mvAlgo.isDive()) {
1606 uint64_t aniTime = visual.pose().atkTotalTime()+1;
1607 implFaiWait(aniTime);
1608 if(bs==BS_RUN)
1609 implAniWait(aniTime);
1610 fghAlgo.consumeAction();
1611 } else {
1612 adjustAttackRotation(dt);
1613 }
1614 }
1615 else {
1616 // Attack action without any weapon. Can happend at weapon transition(orc shaman) - skip it.
1617 fghAlgo.consumeAction();
1618 }
1619 return true;
1620 }
1621
1622 if(act==FightAlgo::MV_TURN2HIT) {
1623 if(!implTurnTo(*currentTarget,dt))
1624 fghAlgo.consumeAction();
1625 return true;
1626 }
1627
1628 if(act==FightAlgo::MV_STRAFEL) {
1629 if(setAnim(Npc::Anim::MoveL)) {
1630 visual.setAnimRotate(*this,0);
1631 implFaiWait(visual.pose().animationTotalTime());
1632 fghAlgo.consumeAction();
1633 }
1634 return true;
1635 }
1636
1637 if(act==FightAlgo::MV_STRAFER) {
1638 if(setAnim(Npc::Anim::MoveR)) {
1639 visual.setAnimRotate(*this,0);
1640 implFaiWait(visual.pose().animationTotalTime());
1641 fghAlgo.consumeAction();
1642 }
1643 return true;
1644 }
1645
1646 if(act==FightAlgo::MV_STRAFE_E) {
1647 // finalize strafe
1648 if(!setAnim(Npc::Anim::Idle))
1649 return false;
1650 fghAlgo.consumeAction();
1651 return true;
1652 }
1653
1654 if(act==FightAlgo::MV_JUMPBACK) {
1655 if(isSwim()) {
1656 fghAlgo.consumeAction();
1657 return true;
1658 }
1659 if(bodyStateMasked()==BS_PARADE) {
1660 fghAlgo.consumeAction();
1661 return true;
1662 }
1663 if(!fghAlgo.isInFocusAngle(*this, *currentTarget)) {
1664 //NOTE: jump-back is ultimate defence, so better to use it only if npc face player directly
1665 fghAlgo.consumeAction();
1666 aiState.loopNextTime = owner.tickCount(); // force ZS_MM_Attack_Loop call
1667 return true;
1668 }
1669 if(setAnim(Npc::Anim::MoveBack)) {
1670 implFaiWait(visual.pose().animationTotalTime());
1671 fghAlgo.consumeAction();
1672 }
1673 return true;
1674 }
1675
1676 if(act==FightAlgo::MV_MOVEA || act==FightAlgo::MV_MOVEG ||
1678 const bool prGRange = fghAlgo.isInGRange(*this, *currentTarget, owner.script());
1679
1680 if(!(mvAlgo.checkLastBounce() && implTurnTo(*currentTarget,dt))) {
1681 go2.set(currentTarget,(act==FightAlgo::MV_MOVEG || act==FightAlgo::MV_TURNG) ?
1683 implGoTo(dt);
1684 go2.clear();
1685 }
1686
1687 const bool isGRange = fghAlgo.isInGRange(*this, *currentTarget, owner.script());
1688 const bool isWRange = fghAlgo.isInWRange(*this, *currentTarget, owner.script());
1689 const bool isFocus = fghAlgo.isInFocusAngle(*this, *currentTarget);
1690
1691 if((isWRange || (isGRange!=prGRange)) && isFocus) {
1692 visual.setAnimRotate(*this, 0);
1693 fghAlgo.consumeAction();
1694 aiState.loopNextTime = owner.tickCount(); // force ZS_MM_Attack_Loop call
1695 implAiTick(dt);
1696 return true;
1697 }
1698
1699 implAiTick(dt);
1700 return true;
1701 }
1702
1703 if(act==FightAlgo::MV_WAIT) {
1704 implFaiWait(200);
1705 fghAlgo.consumeAction();
1706 stopWalkAnimation();
1707 return true;
1708 }
1709
1710 if(act==FightAlgo::MV_WAITLONG) {
1711 implFaiWait(300);
1712 fghAlgo.consumeAction();
1713 stopWalkAnimation();
1714 return true;
1715 }
1716
1717 if(act==FightAlgo::MV_NULL) {
1718 fghAlgo.consumeAction();
1719 stopWalkAnimation();
1720 return true;
1721 }
1722
1723 return true;
1724 }
1725
1726void Npc::adjustAttackRotation(uint64_t dt) {
1727 if(currentTarget!=nullptr && !currentTarget->isDown()) {
1728 auto ws = weaponState();
1729 if(ws!=WeaponState::NoWeapon) {
1733 implTurnTo(*currentTarget,anim,dt);
1734 }
1735 }
1736 }
1737
1738bool Npc::implAiTick(uint64_t dt) {
1739 // Note AI-action queue takes priority, test case: Vatras pray at night
1740 if(aiQueue.size()==0) {
1741 tickRoutine();
1742 if(aiQueue.size()>0)
1743 nextAiAction(aiQueue,dt);
1744 return false;
1745 }
1746 nextAiAction(aiQueue,dt);
1747 return true;
1748 }
1749
1750void Npc::implAiWait(uint64_t dt) {
1751 auto w = owner.tickCount()+dt;
1752 if(w>waitTime)
1753 waitTime = w;
1754 }
1755
1756void Npc::implAniWait(uint64_t dt) {
1757 auto w = owner.tickCount()+dt;
1758 if(w>aniWaitTime)
1759 aniWaitTime = w;
1760 }
1761
1762void Npc::implFaiWait(uint64_t dt) {
1763 faiWaitTime = owner.tickCount()+dt;
1764 aiState.loopNextTime = faiWaitTime;
1765 }
1766
1767void Npc::implSetFightMode(const Animation::EvCount& ev) {
1768 const auto ws = visual.fightMode();
1769 if(!visual.setFightMode(ev.weaponCh))
1770 return;
1771
1772 if(ev.weaponCh==zenkit::MdsFightMode::NONE && (ws==WeaponState::W1H || ws==WeaponState::W2H)) {
1773 if(auto melee = invent.currentMeleeWeapon()) {
1774 if(melee->handle().material==ItemMaterial::MAT_METAL)
1775 sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_ME.WAV",{x,y+translateY(),z},2500,false); else
1776 sfxWeapon = ::Sound(owner,::Sound::T_Regular,"UNDRAWSOUND_WO.WAV",{x,y+translateY(),z},2500,false);
1777 sfxWeapon.play();
1778 }
1779 }
1780 else if(ev.weaponCh==zenkit::MdsFightMode::SINGLE_HANDED || ev.weaponCh==zenkit::MdsFightMode::DUAL_HANDED) {
1781 if(auto melee = invent.currentMeleeWeapon()) {
1782 if(melee->handle().material==ItemMaterial::MAT_METAL)
1783 sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_ME.WAV",{x,y+translateY(),z},2500,false); else
1784 sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_WO.WAV",{x,y+translateY(),z},2500,false);
1785 sfxWeapon.play();
1786 }
1787 }
1788 else if(ev.weaponCh==zenkit::MdsFightMode::BOW || ev.weaponCh==zenkit::MdsFightMode::CROSSBOW) {
1789 sfxWeapon = ::Sound(owner,::Sound::T_Regular,"DRAWSOUND_BOW",{x,y+translateY(),z},2500,false);
1790 sfxWeapon.play();
1791 }
1792 dropTorch();
1793 visual.stopDlgAnim(*this);
1794 updateWeaponSkeleton();
1795 }
1796
1797bool Npc::implAiFlee(uint64_t dt) {
1798 if(currentTarget==nullptr)
1799 return true;
1800
1801 if(isFalling())
1802 return true;
1803
1804 auto& oth = *currentTarget;
1805
1806 const WayPoint* wp = nullptr;
1807 const float maxDist = 5*100; // 5 meters
1808
1809 owner.findWayPoint(position(),[&](const WayPoint& p) {
1810 if(p.useCounter()>0 || qDistTo(&p)>maxDist*maxDist)
1811 return false;
1812 if(p.underWater)
1813 return false;
1814 if(!canRayHitPoint(p.position() + Vec3(0,10,0),true))
1815 return false;
1816 if(wp==nullptr || oth.qDistTo(&p)>oth.qDistTo(wp))
1817 wp = &p;
1818 return false;
1819 });
1820
1821 if(go2.flag!=GT_Flee && go2.flag!=GT_No) {
1822 clearGoTo();
1823 }
1824
1825 auto anim = (go2.flag!=GT_No)?AnimationSolver::TurnType::None:AnimationSolver::TurnType::Std;
1826 if(wp==nullptr || oth.qDistTo(wp)<oth.qDistTo(*this)) {
1827 auto dx = oth.x-x;
1828 auto dz = oth.z-z;
1829 if(implTurnTo(-dx,-dz,anim,dt))
1830 return (go2.flag==GT_Flee);
1831 } else {
1832 auto dx = wp->pos.x-x;
1833 auto dz = wp->pos.z-z;
1834 if(implTurnTo(dx,dz,anim,dt))
1835 return (go2.flag==GT_Flee);
1836 }
1837
1838 go2.setFlee();
1839 setAnim(Anim::Move);
1840 return true;
1841 }
1842
1843bool Npc::setGoToLadder() {
1844 if(go2.wp==nullptr || go2.wp!=wayPath.first())
1845 return false;
1846 auto inter = go2.wp->ladder;
1847 auto pos = inter->nearestPoint(*this);
1848 if(MoveAlgo::isClose(*this,pos,MAX_AI_USE_DISTANCE)) {
1849 if(!inter->isAvailable())
1851 else if(setInteraction(inter))
1852 wayPath.pop();
1853 return true;
1854 }
1855 return false;
1856 }
1857
1858void Npc::commitDamage() {
1859 if(currentTarget==nullptr)
1860 return;
1861 if(!fghAlgo.isInAttackRange(*this,*currentTarget,owner.script()))
1862 return;
1863 if(!fghAlgo.isInFocusAngle(*this,*currentTarget))
1864 return;
1865 currentTarget->takeDamage(*this,nullptr);
1866 }
1867
1868void Npc::takeDamage(Npc &other, const Bullet* b) {
1869 if(isDown())
1870 return;
1871
1872 assert(b==nullptr || !b->isSpell());
1873 const auto& pose = visual.pose();
1874 const bool isJumpb = pose.isJumpBack(owner.tickCount());
1875 const bool isBlock = (!other.isMonster() || other.inventory().activeWeapon()!=nullptr) &&
1876 fghAlgo.isInFocusAngle(*this,other) &&
1877 pose.isDefence(owner.tickCount());
1878
1879 lastHit = &other;
1880 if(!isPlayer())
1881 setOther(&other);
1882 owner.sendPassivePerc(*this,other,*this,PERC_ASSESSFIGHTSOUND);
1883
1884 if(!(isBlock || isJumpb) || b!=nullptr) {
1885 takeDamage(other,b,COLL_DOEVERYTHING,0,false);
1886 } else {
1887 if(invent.activeWeapon()!=nullptr)
1888 visual.emitBlockEffect(*this,other);
1889 }
1890 }
1891
1892void Npc::takeDamage(Npc& other, const Bullet* b, const VisualFx* vfx, int32_t splId) {
1893 if(isDown())
1894 return;
1895
1896 lastHitSpell = splId;
1897 lastHit = &other;
1898 if(!isPlayer())
1899 setOther(&other);
1900
1901 CollideMask bMask = owner.script().canNpcCollideWithSpell(*this,&other,splId);
1902 if(bMask!=COLL_DONOTHING)
1903 Effect::onCollide(owner,vfx,position(),this,&other,splId);
1904 takeDamage(other,b,bMask,splId,true);
1905 }
1906
1907void Npc::takeDamage(Npc& other, const Bullet* b, const CollideMask bMask, int32_t splId, bool isSpell) {
1908 float a = angleDir(other.x-x,other.z-z);
1909 float da = a-angle;
1910 if(std::cos(da*M_PI/180.0)<0)
1911 lastHitType='A'; else
1912 lastHitType='B';
1913
1915 DamageCalculator::Val hitResult;
1917 const bool dontKill = ((b==nullptr && splId==0) || (bMask & COLL_DONTKILL)) && (!isSwim());
1918 int32_t damageType = DamageCalculator::damageTypeMask(other);
1919
1920 if(isSpell) {
1921 auto& spl = owner.script().spellDesc(splId);
1922 splCat = SpellCategory(spl.spell_type);
1923 damageType = spl.damage_type;
1924 for(size_t i=0; i<zenkit::DamageType::NUM; ++i)
1925 if((damageType&(1<<i))!=0)
1926 dmg[i] = spl.damage_per_level;
1927 }
1928
1929 if(!isSpell || splCat==SpellCategory::SPELL_BAD) {
1931 fghAlgo.onTakeHit();
1932 implFaiWait(0);
1933 }
1934
1935 hitResult = DamageCalculator::damageValue(other,*this,b,isSpell,dmg,bMask);
1936 if(!isSpell && !isDown() && hitResult.hasHit)
1937 owner.addWeaponHitEffect(other,b,*this).play();
1938
1939 if(isDown()) {
1940 onNoHealth(dontKill,HS_NoSound);
1941 return;
1942 }
1943
1944 if(hitResult.hasHit) {
1945 if(bodyStateMasked()!=BS_UNCONSCIOUS && interactive()==nullptr && !isSwim() && !mvAlgo.isClimb()) {
1946 const bool noInter = (hnpc->bodystate_interruptable_override!=0);
1947 if(!noInter) {
1948 //NOTE: kepp rotation animation: this results in more accurate fight with trolls
1949 // visual.setAnimRotate(*this,0);
1950 visual.interrupt(); // TODO: put down in pipeline, at Pose and merge with setAnimAngGet
1951 }
1952
1953 if(damageType & (1<<zenkit::DamageType::FLY))
1954 setAnimAngGet(lastHitType=='A' ? Anim::FallDeepA : Anim::FallDeepB); else
1955 setAnimAngGet(lastHitType=='A' ? Anim::StumbleA : Anim::StumbleB);
1956 }
1957 }
1958
1959 if(hitResult.value>0) {
1960 currentOther = &other;
1961 changeAttribute(ATR_HITPOINTS,-hitResult.value,dontKill);
1962
1964 owner.sendPassivePerc(*this,other,*this,PERC_ASSESSOTHERSDAMAGE);
1965 if(isUnconscious()){
1966 owner.sendPassivePerc(*this,other,*this,PERC_ASSESSDEFEAT);
1967 }
1968 else if(isDead()) {
1969 owner.sendPassivePerc(*this,other,*this,PERC_ASSESSMURDER);
1970 }
1971 else {
1972 if(owner.script().rand(2)==0) {
1973 emitSoundSVM("SVM_%d_AARGH");
1974 }
1975 }
1976 }
1977 }
1978
1979 if((damageType & (1<<zenkit::DamageType::FLY)) && !isLie()) {
1980 mvAlgo.accessDamFly(x-other.x,z-other.z); // throw enemy
1981 }
1982 }
1983
1984void Npc::takeFallDamage(const Vec3& fallSpeed) {
1985 if(bodyStateMasked()==BS_FALL) {
1986 if(!isFallingDeep()) {
1987 // small fall
1988 setAnim(Anim::Idle);
1989 } else {
1990 const float a = angleDir(-fallSpeed.x,-fallSpeed.z);
1991 const float da = a-angle;
1992 if(std::cos(da*M_PI/180.0)<0 || Vec2(fallSpeed.x,fallSpeed.z).length()<0.1f)
1993 lastHitType='A'; else
1994 lastHitType='B';
1995 setAnim(lastHitType=='A' ? Anim::FallenA : Anim::FallenB);
1996 }
1997 }
1998 auto dmg = DamageCalculator::damageFall(*this,fallSpeed.length());
1999 if(!dmg.hasHit)
2000 return;
2001 int32_t hp = attribute(ATR_HITPOINTS);
2002 if(hp>dmg.value) {
2003 emitSoundSVM("SVM_%d_AARGH");
2004 clearState(true);
2005 }
2006 changeAttribute(ATR_HITPOINTS,-dmg.value,false);
2007 }
2008
2009void Npc::takeDrownDamage() {
2011 }
2012
2013Npc *Npc::updateNearestEnemy() {
2014 if(aiPolicy!=NpcProcessPolicy::AiNormal)
2015 return nullptr;
2016
2017 Npc* ret = nullptr;
2018 float dist = std::numeric_limits<float>::max();
2019 if(nearestEnemy!=nullptr &&
2020 (!nearestEnemy->isDown() && canSenseNpc(*nearestEnemy,true)!=SensesBit::SENSE_NONE)) {
2021 ret = nearestEnemy;
2022 dist = qDistTo(*ret);
2023 }
2024
2025 owner.detectNpcNear([this,&ret,&dist](Npc& n){
2026 if(!isEnemy(n) || n.isDown() || &n==this)
2027 return;
2028
2029 float d = qDistTo(n);
2030 if(d<dist && canSenseNpc(n,true)!=SensesBit::SENSE_NONE) {
2031 ret = &n;
2032 dist = d;
2033 }
2034 });
2035 nearestEnemy = ret;
2036 return nearestEnemy;
2037 }
2038
2039Npc* Npc::updateNearestBody() {
2040 if(aiPolicy!=NpcProcessPolicy::AiNormal)
2041 return nullptr;
2042
2043 Npc* ret = nullptr;
2044 float dist = std::numeric_limits<float>::max();
2045
2046 owner.detectNpcNear([this,&ret,&dist](Npc& n){
2047 if(!n.isDead())
2048 return;
2049
2050 float d = qDistTo(n);
2051 if(d<dist && canSenseNpc(n,true)!=SensesBit::SENSE_NONE) {
2052 ret = &n;
2053 dist = d;
2054 }
2055 });
2056 return ret;
2057 }
2058
2059void Npc::tickTimedEvt(Animation::EvCount& ev) {
2060 if(ev.timed.empty())
2061 return;
2062
2063 std::sort(ev.timed.begin(),ev.timed.end(),[](const Animation::EvTimed& a,const Animation::EvTimed& b){
2064 return a.time<b.time;
2065 });
2066
2067 // https://auronen.cokoliv.eu/gmc/zengin/anims/events/
2068 for(auto& i:ev.timed) {
2069 switch(i.def) {
2070 case zenkit::MdsEventType::ITEM_CREATE: {
2071 if(auto it = invent.addItem(i.item,1,world())) {
2072 invent.putToSlot(*this,it->clsId(),i.slot[0]);
2073 }
2074 break;
2075 }
2076 case zenkit::MdsEventType::ITEM_INSERT: {
2077 invent.putCurrentToSlot(*this,i.slot[0]);
2078 break;
2079 }
2080 case zenkit::MdsEventType::ITEM_REMOVE:
2081 case zenkit::MdsEventType::ITEM_DESTROY: {
2082 invent.clearSlot(*this, "", i.def != zenkit::MdsEventType::ITEM_REMOVE);
2083 break;
2084 }
2085 case zenkit::MdsEventType::ITEM_PLACE: {
2086 if(currentInteract!=nullptr)
2087 Inventory::moveItem(*this, invent, *currentInteract);
2088 break;
2089 }
2090 case zenkit::MdsEventType::ITEM_EXCHANGE: {
2091 if(!invent.clearSlot(*this,i.slot[0],true)) {
2092 // fallback for cooking animations
2093 invent.putCurrentToSlot(*this,i.slot[0]);
2094 invent.clearSlot(*this,"",true);
2095 }
2096 if(auto it = invent.addItem(i.item,1,world())) {
2097 invent.putToSlot(*this,it->clsId(),i.slot[0]);
2098 }
2099 break;
2100 }
2101 case zenkit::MdsEventType::SET_FIGHT_MODE:
2102 break;
2103 case zenkit::MdsEventType::MUNITION_PLACE: {
2104 auto active=invent.activeWeapon();
2105 if(active!=nullptr) {
2106 const int32_t munition = active->handle().munition;
2107 invent.putAmmunition(*this,uint32_t(munition),i.slot[0]);
2108 }
2109 break;
2110 }
2111 case zenkit::MdsEventType::MUNITION_REMOVE: {
2112 invent.putAmmunition(*this,0,"");
2113 break;
2114 }
2115 case zenkit::MdsEventType::TORCH_DRAW:
2116 setTorch(true);
2117 break;
2118 case zenkit::MdsEventType::TORCH_INVENTORY:
2120 break;
2121 case zenkit::MdsEventType::TORCH_DROP:
2122 dropTorch();
2123 break;
2124 case zenkit::MdsEventType::SOUND_DRAW:
2125 break;
2126 case zenkit::MdsEventType::SOUND_UNDRAW:
2127 break;
2128 case zenkit::MdsEventType::MESH_SWAP:
2129 break;
2130 case zenkit::MdsEventType::HIT_LIMB:
2131 break;
2132 case zenkit::MdsEventType::HIT_DIRECTION:
2133 break;
2134 case zenkit::MdsEventType::DAMAGE_MULTIPLIER:
2135 break;
2136 case zenkit::MdsEventType::PARRY_FRAME:
2137 break;
2138 case zenkit::MdsEventType::OPTIMAL_FRAME:
2139 break;
2140 case zenkit::MdsEventType::HIT_END:
2141 break;
2142 case zenkit::MdsEventType::COMBO_WINDOW:
2143 break;
2144 case zenkit::MdsEventType::UNKNOWN:
2145 break;
2146 }
2147 }
2148 }
2149
2150void Npc::tickRegen(int32_t& v, const int32_t max, const int32_t chg, const uint64_t dt) {
2151 uint64_t tick = owner.tickCount();
2152 if(tick<dt || chg==0)
2153 return;
2154 int32_t time0 = int32_t(tick%1000);
2155 int32_t time1 = time0+int32_t(dt);
2156
2157 int32_t val0 = (time0*chg)/1000;
2158 int32_t val1 = (time1*chg)/1000;
2159
2160 int32_t nextV = std::max(0,std::min(v+val1-val0,max));
2161 if(v!=nextV) {
2162 v = nextV;
2163 // check health, in case of negative chg
2164 checkHealth(true,false);
2165 }
2166 }
2167
2170 const bool hasEvents = visual.processEvents(owner,lastEventTime,ev);
2171 visual.processLayers(owner);
2172 visual.setNpcEffect(owner,*this,hnpc->effect,hnpc->flags);
2173 if(!hasEvents)
2174 return;
2175
2176 for(auto& i:ev.morph)
2177 visual.startMMAnim(*this,i.anim,i.node);
2179 world().sendImmediatePerc(*this,*this,*this,PERC_ASSESSQUIETSOUND);
2180 if(ev.def_opt_frame>0)
2181 commitDamage();
2182 implSetFightMode(ev);
2183 tickTimedEvt(ev);
2184 }
2185
2186void Npc::tick(uint64_t dt) {
2187 static bool dbg = false;
2188 static int kId = -1;
2189 if(dbg && !isPlayer() && hnpc->id!=kId)
2190 return;
2191
2192 assert(go2.flag!=GoToHint::GT_EnemyG && go2.flag!=GoToHint::GT_EnemyA);
2193
2195
2196 if(!visual.pose().hasAnim())
2198
2199 if(isDive()) {
2200 uint32_t gl = guild();
2201 int32_t v = world().script().guildVal().dive_time[gl]*1000;
2202 int32_t t = diveTime();
2203 if(v>=0 && t>v+int(dt)) {
2204 int tickSz = world().script().npcDamDiveTime();
2205 if(tickSz>0) {
2206 t-=v;
2207 int dmg = t/tickSz - (t-int(dt))/tickSz;
2208 if(dmg>0)
2209 changeAttribute(ATR_HITPOINTS,-dmg,false);
2210 }
2211 }
2212 }
2213
2214 nextAiAction(aiQueueOverlay,dt);
2215
2216 if(tickCast(dt))
2217 return;
2218
2219 if(!isDead()) {
2220 tickRegen(hnpc->attribute[ATR_HITPOINTS],hnpc->attribute[ATR_HITPOINTSMAX],
2221 hnpc->attribute[ATR_REGENERATEHP],dt);
2222 tickRegen(hnpc->attribute[ATR_MANA],hnpc->attribute[ATR_MANAMAX],
2223 hnpc->attribute[ATR_REGENERATEMANA],dt);
2224 }
2225
2226 if(waitTime>=owner.tickCount() || aniWaitTime>=owner.tickCount() || outWaitTime>owner.tickCount()) {
2227 if(!isPlayer() && go2.flag!=GT_Flee && faiWaitTime<owner.tickCount())
2228 adjustAttackRotation(dt);
2229 mvAlgo.tick(dt,MoveAlgo::WaitMove);
2230 return;
2231 }
2232
2233 if(!isDown()) {
2234 implLookAtNpc(dt);
2235 implLookAtWp(dt);
2236
2237 if(implAttack(dt))
2238 return;
2239
2240 if(implGoTo(dt)) {
2241 if(go2.flag==GT_Flee)
2242 implAiTick(dt);
2243 return;
2244 }
2245 }
2246
2247 mvAlgo.tick(dt);
2248 implAiTick(dt);
2249 }
2250
2251bool Npc::prepareTurn() {
2252 const auto st = bodyStateMasked();
2253 if(interactive()==nullptr && (st==BS_WALK || st==BS_SNEAK)) {
2254 visual.stopWalkAnim(*this);
2255 setAnimRotate(0);
2256 return false;
2257 }
2258 if(interactive()==nullptr) {
2259 visual.stopWalkAnim(*this);
2260 visual.stopDlgAnim(*this);
2261 }
2262 return true;
2263 }
2264
2265void Npc::nextAiAction(AiQueue& queue, uint64_t dt) {
2266 if(isInAir())
2267 return;
2268 if(queue.size()==0)
2269 return;
2270 auto act = queue.pop();
2271 switch(act.act) {
2272 case AI_None: break;
2273 case AI_LookAtNpc:{
2274 currentLookAt=nullptr;
2275 currentLookAtNpc=act.target;
2276 break;
2277 }
2278 case AI_LookAt:{
2279 currentLookAtNpc=nullptr;
2280 currentLookAt=act.point;
2281 break;
2282 }
2283 case AI_TurnAway: {
2284 if(!prepareTurn()) {
2285 queue.pushFront(std::move(act));
2286 break;
2287 }
2288 if(act.target!=nullptr && implTurnAway(*act.target,dt)) {
2289 queue.pushFront(std::move(act));
2290 break;
2291 }
2292 break;
2293 }
2294 case AI_TurnToNpc: {
2295 if(!prepareTurn()) {
2296 queue.pushFront(std::move(act));
2297 break;
2298 }
2299 if(act.target!=nullptr && implTurnTo(*act.target,dt)) {
2300 queue.pushFront(std::move(act));
2301 break;
2302 }
2303 // Not looking quite correct in dialogs, when npc turns around
2304 // Example: Esteban dialog
2305 // currentLookAt = nullptr;
2306 // currentLookAtNpc = nullptr;
2307 break;
2308 }
2309 case AI_WhirlToNpc: {
2310 if(!prepareTurn()) {
2311 queue.pushFront(std::move(act));
2312 break;
2313 }
2314 if(act.target!=nullptr && implWhirlTo(*act.target,dt)) {
2315 queue.pushFront(std::move(act));
2316 break;
2317 }
2318 break;
2319 }
2320 case AI_GoToNpc:
2321 if(!setInteraction(nullptr)) {
2322 queue.pushFront(std::move(act));
2323 break;
2324 }
2325 currentFp = nullptr;
2326 currentFpLock = FpLock();
2327 go2.set(act.target);
2328 wayPath.clear();
2329 break;
2330 case AI_GoToNextFp: {
2331 if(!setInteraction(nullptr)) {
2332 queue.pushFront(std::move(act));
2333 break;
2334 }
2335 auto fp = owner.findNextFreePoint(*this,act.s0);
2336 if(fp!=nullptr) {
2337 currentFp = fp;
2338 currentFpLock = FpLock(*fp);
2339 go2.set(fp,GoToHint::GT_NextFp);
2340 wayPath.clear();
2341 }
2342 break;
2343 }
2344 case AI_GoToPoint: {
2345 if(isInAir() || !setInteraction(nullptr)) {
2346 queue.pushFront(std::move(act));
2347 break;
2348 }
2349 if(wayPath.last()!=act.point) {
2350 wayPath = owner.wayTo(*this,*act.point);
2351 auto wpoint = wayPath.pop();
2352
2353 if(wpoint!=nullptr) {
2354 go2.set(wpoint);
2355 attachToPoint(wpoint);
2356 } else {
2357 attachToPoint(act.point);
2358 clearGoTo();
2359 }
2360 }
2361 break;
2362 }
2363 case AI_StopLookAt:
2364 currentLookAtNpc=nullptr;
2365 currentLookAt=nullptr;
2366 visual.setHeadRotation(0,0);
2367 break;
2368 case AI_RemoveWeapon:
2369 if(!isDead()) {
2370 if(closeWeapon(false)) {
2371 stopWalkAnimation();
2372 }
2373 auto ws = weaponState();
2374 if(ws!=WeaponState::NoWeapon){
2375 queue.pushFront(std::move(act));
2376 }
2377 }
2378 break;
2379 case AI_StartState:
2380 // NOTE: a new state can be stater within a daly routiine, such as TA_Sleep, with: ZS_GotoBed -> ZS_Sleep.
2381 // In such cases it's important to preserve aiState.eTime.
2382 if(startState(act.func,act.s0,aiState.eTime,act.i0==0)) {
2383 setOther(act.target);
2384 setVictim(act.victim);
2385 }
2386 break;
2387 case AI_PlayAnim:{
2388 owner.script().eventPlayAni(*this, act.s0);
2389 if(auto sq = playAnimByName(act.s0,BS_NONE)) {
2390 implAniWait(uint64_t(sq->totalTime()));
2391 } else {
2392 if(visual.isAnimExist(act.s0))
2393 queue.pushFront(std::move(act));
2394 }
2395 break;
2396 }
2397 case AI_PlayAnimBs:{
2398 BodyState bs = BodyState(act.i0);
2399 if(auto sq = playAnimByName(act.s0,bs)) {
2400 implAniWait(uint64_t(sq->totalTime()));
2401 } else {
2402 if(visual.isAnimExist(act.s0)) {
2403 queue.pushFront(std::move(act));
2404 } else {
2405 /* ZS_MM_Rtn_Sleep will set NPC_WALK mode and run T_STAND_2_SLEEP animation.
2406 * The problem is: T_STAND_2_SLEEP may not exists, in that case only NPC_WALK should be applied,
2407 * we will do so by playing Idle anim.
2408 */
2409 setAnim(Anim::Idle);
2410 }
2411 }
2412 break;
2413 }
2414 case AI_Wait:
2415 implAiWait(uint64_t(act.i0));
2416 break;
2417 case AI_StandUp:
2418 case AI_StandUpQuick: {
2419 const auto bs = bodyStateMasked();
2420 // NOTE: B_ASSESSTALK calls AI_StandUp, to make npc stand, if it's not on a chair or something
2421 if(interactive()!=nullptr) {
2422 if((interactive()->isLadder() && !isPlayer()) || !setInteraction(nullptr,false)) {
2423 queue.pushFront(std::move(act));
2424 }
2425 break;
2426 }
2427 else if(bs==BS_UNCONSCIOUS || bs==BS_LIE) {
2428 if(!setAnim(Anim::Idle))
2429 queue.pushFront(std::move(act)); else
2430 implAniWait(visual.pose().animationTotalTime());
2431 }
2432 else if(bs!=BS_DEAD) {
2433 visual.stopAnim(*this,"");
2435 setAnim(Anim::Idle);
2436 }
2437 break;
2438 }
2439 case AI_EquipArmor:
2440 invent.equipArmor(act.i0,*this);
2441 break;
2442 case AI_EquipBestArmor:
2443 invent.equipBestArmor(*this);
2444 break;
2445 case AI_EquipMelee:
2446 invent.equipBestMeleeWeapon(*this);
2447 break;
2448 case AI_EquipRange:
2449 invent.equipBestRangedWeapon(*this);
2450 break;
2451 case AI_UseMob: {
2452 if(act.i0<0) {
2453 if(!setInteraction(nullptr))
2454 queue.pushFront(std::move(act));
2455 break;
2456 }
2457 /*
2458 * Rhademes doesn't quit talk properly
2459 if(owner.script().isTalk(*this)) {
2460 queue.pushFront(std::move(act));
2461 break;
2462 }*/
2463
2464 auto inter = owner.availableMob(*this,act.s0);
2465 if(inter==nullptr) {
2466 /* in L`Hiver, version 1.3 there is a typo: "COOL" instead of "BSCOOL"
2467 * maybe 'scheme' need to be checked loosely, or maybe ignored.
2468 *
2469 * For now, if no mob found - discard command, to avoid npc soft-lock.
2470 */
2471 // queue.pushFront(std::move(act));
2472 break;
2473 }
2474
2475 if(currentInteract!=nullptr && inter!=currentInteract) {
2476 setInteraction(nullptr);
2477 queue.pushFront(std::move(act));
2478 break;
2479 }
2480
2481 if(inter!=nullptr) {
2482 auto pos = inter->nearestPoint(*this);
2483 auto dp = pos-position();
2484 dp.y = 0;
2485 if(currentInteract==nullptr && dp.quadLength()>MAX_AI_USE_DISTANCE*MAX_AI_USE_DISTANCE) { // too far
2486 go2.set(pos);
2487 // go to MOBSI and then complete AI_UseMob
2488 queue.pushFront(std::move(act));
2489 return;
2490 }
2491 if(!setInteraction(inter)) {
2492 // queue.pushFront(std::move(act));
2493 }
2494 }
2495
2496 if(currentInteract==nullptr || currentInteract->stateId()!=act.i0) {
2497 queue.pushFront(std::move(act));
2498 return;
2499 }
2500
2501 clearGoTo();
2502 break;
2503 }
2504 case AI_UseItem: {
2505 if(!isStanding()) {
2506 setAnim(Npc::Anim::Idle);
2507 queue.pushFront(std::move(act));
2508 break;
2509 }
2510 if(act.i0!=0)
2511 useItem(uint32_t(act.i0));
2512 break;
2513 }
2514 case AI_UseItemToState:
2515 if(act.i0!=0) {
2516 uint32_t itm = uint32_t(act.i0);
2517 int state = act.i1;
2518 if(state>0)
2519 visual.stopDlgAnim(*this);
2520 if(!invent.putState(*this,state>=0 ? itm : 0,state))
2521 queue.pushFront(std::move(act));
2522 }
2523 break;
2524 case AI_Teleport: {
2525 setPosition (act.point->position() );
2527 if(isPlayer()) {
2529 Gothic::inst().camera()->reset(this);
2530 }
2531 }
2532 break;
2533 case AI_DrawWeapon:
2534 if(!isDead()) {
2535 if(!drawWeaponMelee() &&
2536 !drawWeaponBow())
2537 queue.pushFront(std::move(act));
2538 }
2539 break;
2540 case AI_DrawWeaponMelee:
2541 if(!isDead()) {
2542 if(!drawWeaponMelee())
2543 queue.pushFront(std::move(act));
2544 }
2545 break;
2546 case AI_DrawWeaponRange:
2547 if(!isDead()) {
2548 if(!drawWeaponBow())
2549 queue.pushFront(std::move(act));
2550 }
2551 break;
2552 case AI_DrawSpell: {
2553 if(!isDead()) {
2554 const int32_t spell = act.i0;
2555 if(drawSpell(spell))
2556 aiExpectedInvest = act.i1; else
2557 queue.pushFront(std::move(act));
2558 }
2559 break;
2560 }
2561 case AI_Attack:
2562 if(currentTarget!=nullptr) {
2563 if(!fghAlgo.fetchInstructions(*this,*currentTarget,owner.script()))
2564 queue.pushFront(std::move(act));
2565 }
2566 break;
2567 case AI_Flee:
2568 if(!implAiFlee(dt))
2569 queue.pushFront(std::move(act));
2570 break;
2571 case AI_Dodge:
2572 if(auto sq = setAnimAngGet(Anim::MoveBack)) {
2573 visual.setAnimRotate(*this,0);
2574 implAniWait(uint64_t(sq->totalTime()));
2575 } else {
2576 queue.pushFront(std::move(act));
2577 }
2578 break;
2579 case AI_UnEquipWeapons:
2580 invent.unequipWeapons(owner.script(),*this);
2581 break;
2582 case AI_UnEquipArmor:
2583 invent.unequipArmor(owner.script(),*this);
2584 break;
2585 case AI_Output:
2586 case AI_OutputSvm:
2587 case AI_OutputSvmOverlay:{
2588 if(performOutput(act)) {
2589 if(aiPolicy!=NpcProcessPolicy::AiFar2) {
2590 uint64_t msgTime = 0;
2591 if(act.act==AI_Output) {
2592 msgTime = owner.script().messageTime(act.s0);
2593 } else {
2594 auto svm = owner.script().messageFromSvm(act.s0,hnpc->voice);
2595 msgTime = owner.script().messageTime(svm);
2596 }
2597 visual.startFaceAnim(*this,"VISEME",1,msgTime);
2598 }
2599 if(act.act!=AI_OutputSvmOverlay) {
2600 visual.startAnimDialog(*this);
2601 visual.setAnimRotate(*this,0);
2602 }
2603 } else {
2604 queue.pushFront(std::move(act));
2605 }
2606 break;
2607 }
2608 case AI_ProcessInfo: {
2609 const int PERC_DIST_DIALOG = 2000;
2610
2611 if(act.target==nullptr)
2612 break;
2613
2614 if(owner.isInDialog()) {
2615 queue.pushFront(std::move(act));
2616 break;
2617 }
2618
2619 if(this!=act.target && act.target->isPlayer() && act.target->currentInteract!=nullptr) {
2620 //queue.pushFront(std::move(act));
2621 break;
2622 }
2623
2624 if(act.target->qDistTo(*this)>PERC_DIST_DIALOG*PERC_DIST_DIALOG) {
2625 break;
2626 }
2627
2628 if(act.target->interactive()==nullptr && !act.target->isAiBusy())
2629 act.target->stopWalkAnimation();
2630 if(interactive()==nullptr && !isAiBusy())
2631 stopWalkAnimation();
2632
2633 if(auto p = owner.script().openDlgOuput(*this,*act.target)) {
2634 outputPipe = p;
2635 setOther(act.target);
2636 act.target->setOther(this);
2637 act.target->outputPipe = p;
2638 } else {
2639 queue.pushFront(std::move(act));
2640 }
2641 }
2642 break;
2643 case AI_StopProcessInfo:
2644 if(outputPipe->close()) {
2645 outputPipe = owner.script().openAiOuput();
2646 if(currentOther!=nullptr)
2647 currentOther->outputPipe = owner.script().openAiOuput();
2648 } else {
2649 queue.pushFront(std::move(act));
2650 }
2651 break;
2652 case AI_ContinueRoutine:
2654 break;
2655 case AI_AlignToWp:
2656 case AI_AlignToFp:{
2657 if(auto fp = currentFp){
2658 if(fp->dir.x!=0.f || fp->dir.z!=0.f){
2659 if(implTurnTo(fp->dir.x,fp->dir.z,AnimationSolver::TurnType::Std,dt))
2660 queue.pushFront(std::move(act));
2661 }
2662 }
2663 break;
2664 }
2665 case AI_SetNpcsToState:{
2666 const int32_t r = act.i0*act.i0;
2667 owner.detectNpc(position(),float(hnpc->senses_range),[&act,this,r](Npc& other) {
2668 if(&other==this)
2669 return;
2670 if(other.isDead())
2671 return;
2672 if(qDistTo(other)>float(r))
2673 return;
2674 other.aiPush(AiQueue::aiStartState(act.func,1,other.currentOther,other.currentVictim,other.hnpc->wp));
2675 });
2676 break;
2677 }
2678 case AI_SetWalkMode:{
2679 setWalkMode(WalkBit(act.i0));
2680 break;
2681 }
2682 case AI_FinishingMove:{
2683 if(act.target==nullptr || !act.target->isUnconscious())
2684 break;
2685
2686 if(!fghAlgo.isInFinishRange(*this,*act.target,owner.script())){
2687 queue.pushFront(std::move(act));
2688 go2.set(act.target);
2689 setAnim(Npc::Anim::Move);
2690 implGoTo(dt,fghAlgo.attackFinishDistance(owner.script()));
2691 }
2692 else if(!isStanding()) {
2693 clearGoTo();
2694 queue.pushFront(std::move(act));
2695 }
2696 else if(!implTurnTo(*act.target,dt)) {
2697 queue.pushFront(std::move(act));
2698 }
2699 else if(canFinish(*act.target)){
2700 setTarget(act.target);
2701 if(!finishingMove())
2702 queue.pushFront(std::move(act));
2703 }
2704 break;
2705 }
2706 case AI_TakeItem:{
2707 if(act.item==nullptr)
2708 break;
2709 if(takeItem(*act.item)==nullptr)
2710 queue.pushFront(std::move(act));
2711 break;
2712 }
2713 case AI_GotoItem:{
2714 go2.set(act.item);
2715 break;
2716 }
2717 case AI_PointAt:{
2718 if(act.point==nullptr)
2719 break;
2720 if(!implPointAt(act.point->position()))
2721 queue.pushFront(std::move(act));
2722 break;
2723 }
2724 case AI_PointAtNpc:{
2725 if(act.target==nullptr)
2726 break;
2727 if(!implPointAt(act.target->position()))
2728 queue.pushFront(std::move(act));
2729 break;
2730 }
2731 case AI_StopPointAt:{
2732 visual.stopAnim(*this,"T_POINT");
2733 break;
2734 }
2735 case AI_PrintScreen:{
2736 auto msg = act.s0;
2737 auto posx = act.i0;
2738 auto posy = act.i1;
2739 int timesec = act.i2;
2740 auto font = act.s1;
2741
2742 bool complete = false;
2743 if(aiOutputBarrier<=owner.tickCount()) {
2744 if(outputPipe->printScr(*this,timesec,msg,posx,posy,font))
2745 complete = true;
2746 }
2747
2748 if(!complete)
2749 queue.pushFront(std::move(act));
2750 break;
2751 }
2752 }
2753 }
2754
2755bool Npc::startState(ScriptFn id, std::string_view wp) {
2756 return startState(id,wp,gtime::endOfTime(),false);
2757 }
2758
2759bool Npc::startState(ScriptFn id, std::string_view wp, gtime endTime, bool noFinalize) {
2760 if(!id.isValid())
2761 return false;
2762
2763 if(aiState.funcIni==id) {
2764 if(!noFinalize) {
2765 // NOTE: B_AssessQuietSound can cause soft-lock on npc without this
2766 aiState.started = false;
2767 }
2768 if(!wp.empty())
2769 hnpc->wp = wp;
2770 return true;
2771 }
2772
2773 clearAiQueue();
2774 clearState(noFinalize);
2775 if(!wp.empty())
2776 hnpc->wp = wp;
2777
2778 {
2779 // ZS_GotoBed -> ZS_Sleep relie on clean state
2780 for(size_t i=0; i<PERC_Count; ++i)
2782 }
2783
2784 if(wp=="TOT" && aiPolicy!=NpcProcessPolicy::Player && aiPolicy!=NpcProcessPolicy::AiNormal) {
2785 // workaround for Pedro removal script
2786 auto& point = owner.deadPoint();
2787 attachToPoint(nullptr);
2788 setPosition(point.position());
2789 }
2790
2791 auto& st = owner.script().aiState(id);
2792 // allowed player states are hard-coded
2793 // https://forum.worldofplayers.de/forum/threads/1533803-G1-AI_StartState-hardcoded-ZS-states-for-Player?p=26034737&viewfull=1#post26034737
2794 if(isPlayer() && !isPlayerEnabledState(st))
2795 return false;
2796
2797 aiState.started = false;
2798 aiState.funcIni = st.funcIni;
2799 aiState.funcLoop = st.funcLoop;
2800 aiState.funcEnd = st.funcEnd;
2801 aiState.sTime = owner.tickCount();
2802 aiState.eTime = endTime;
2803 aiState.loopNextTime = owner.tickCount();
2804 aiState.hint = st.name();
2805 return true;
2806 }
2807
2808void Npc::clearState(bool noFinalize) {
2809 if(aiState.funcIni.isValid() && aiState.started) {
2810 if(!noFinalize)
2811 owner.script().invokeState(this,currentOther,currentVictim,aiState.funcEnd); // cleanup
2812 aiPrevState = aiState.funcIni;
2813 invent.putState(*this,0,0);
2814 visual.stopItemStateAnim(*this);
2815 }
2816 aiState = AiState();
2817 }
2818
2819bool Npc::isPlayerEnabledState(const ::AiState& st) const {
2820 static const std::array playerEnabledStatesG1 = {
2821 "ZS_DEAD", "ZS_UNCONSCIOUS", "ZS_MAGICFREEZE",
2822 "ZS_PYRO", "ZS_ASSESSMAGIC", "ZS_ASSESSSTOPMAGIC",
2823 "ZS_ZAPPED", "ZS_SHORTZAPPED", "ZS_MAGICSLEEP",
2824 "ZS_MAGICFEAR"
2825 };
2826 static const std::array playerEnabledStatesG2 = {
2827 "ZS_DEAD", "ZS_UNCONSCIOUS", "ZS_MAGICFREEZE",
2828 "ZS_PYRO", "ZS_ASSESSMAGIC", "ZS_ASSESSSTOPMAGIC",
2829 "ZS_ZAPPED", "ZS_SHORTZAPPED", "ZS_MAGICSLEEP",
2830 "ZS_WHIRLWIND"
2831 };
2832
2833 const auto* sym = owner.script().findSymbol(st.funcIni);
2834 if(sym==nullptr)
2835 return false;
2836
2837 const auto& playerEnabledStates = (owner.version().game==2 ? playerEnabledStatesG2 : playerEnabledStatesG1);
2838 for(auto* pState:playerEnabledStates)
2839 if(sym->name()==pState)
2840 return true;
2841 return false;
2842 }
2843
2844void Npc::tickRoutine() {
2845 if(!aiState.funcIni.isValid() && !isPlayer()) {
2846 auto r = currentRoutine();
2847 if(r.callback.isValid()) {
2848 auto t = endTime(r);
2849 startState(r.callback, r.wayPointName(), t, false);
2850 }
2851 else if(hnpc->start_aistate!=0) {
2852 auto endTime = owner.time();
2853 endTime.addMilis(uint64_t(gtime(4, 0).toInt()));
2854 startState(uint32_t(hnpc->start_aistate), "", endTime, false);
2855 }
2856 }
2857
2858 if(!aiState.funcIni.isValid())
2859 return;
2860
2861 auto& sc = owner.script();
2862 if(!aiState.started) {
2863 aiState.started = true;
2864 aiState.loopNextTime = owner.tickCount();
2865 // WA: for gothic1 dialogs
2866 perceptionNextTime = owner.tickCount();
2867 sc.invokeState(this,currentOther,currentVictim,aiState.funcIni);
2868 return;
2869 }
2870
2871 const bool fastPath = (aiPolicy==NpcProcessPolicy::AiFar2 && routines.empty()); //HACK: don't process far away Npc
2872 if(aiState.loopNextTime<=owner.tickCount()) {
2873 aiState.loopNextTime = owner.tickCount() + 1000; // one tick per second?
2874 int loop = LOOP_CONTINUE;
2875 if(aiState.funcLoop.isValid()) {
2876 static const float MAX_DIST = 300;
2877 if(fastPath && currentFp!=nullptr && qDistTo(currentFp) < MAX_DIST*MAX_DIST) {
2878 loop = LOOP_CONTINUE;
2879 }
2880 else if(fastPath && currentFp!=nullptr) {
2881 // for debugging
2882 loop = sc.invokeState(this,currentOther,currentVictim,aiState.funcLoop);
2883 }
2884 else {
2885 loop = sc.invokeState(this,currentOther,currentVictim,aiState.funcLoop);
2886 }
2887 } else {
2888 // ZS_DEATH have no loop-function, in G1, G2-classic
2889 // ZS_GETMEAT have no loop-function, in G2-notr
2890 loop = owner.version().hasZSStateLoop() ? 1 : 0;
2891 }
2892
2893 if(aiState.eTime<=owner.time()) {
2894 // Avoid interruption of ZS_TALK/ZS_ATTACK
2895 if(currentTarget==nullptr && outputPipe->isFinished())
2896 loop = LOOP_END;
2897 }
2898
2899 if(loop!=LOOP_CONTINUE) {
2900 clearState(false);
2901 currentOther = nullptr;
2902 currentVictim = nullptr;
2903 }
2904 }
2905 }
2906
2908 if(currentTarget==t)
2909 return;
2910
2911 currentTarget = t;
2912 if(!go2.empty() && !isPlayer())
2913 clearGoTo();
2914 }
2915
2917 return currentTarget;
2918 }
2919
2921 nearestEnemy = nullptr;
2922 }
2923
2925 if(isTalk() && ot && !ot->isPlayer())
2926 Log::e("unxepected perc acton");
2927 currentOther = ot;
2928 }
2929
2931 currentVictim = ot;
2932 }
2933
2934bool Npc::haveOutput() const {
2935 if(owner.tickCount()<aiOutputBarrier)
2936 return true;
2937 return aiOutputOrderId()!=std::numeric_limits<int>::max();
2938 }
2939
2940void Npc::setAiOutputBarrier(uint64_t dt, bool overlay) {
2941 aiOutputBarrier = owner.tickCount()+dt;
2942 if(!overlay)
2943 outWaitTime = aiOutputBarrier;
2944 }
2945
2946void Npc::emitSoundEffect(std::string_view sound, float range, bool freeSlot) {
2947 auto sfx = ::Sound(owner,::Sound::T_Regular,sound,{x,y+translateY(),z},range,freeSlot);
2948 sfx.play();
2949 }
2950
2951void Npc::emitSoundGround(std::string_view sound, float range, bool freeSlot) {
2952 auto mat = mvAlgo.groundMaterial();
2953 string_frm buf(sound,"_",MaterialGroupNames[uint8_t(mat)]);
2954 auto sfx = ::Sound(owner,::Sound::T_Regular,buf,{x,y,z},range,freeSlot);
2955 sfx.play();
2956 }
2957
2958void Npc::emitSoundSVM(std::string_view svm) {
2959 if(hnpc->voice==0)
2960 return;
2961 char frm [32]={};
2962 std::snprintf(frm,sizeof(frm),"%.*s",int(svm.size()),svm.data());
2963
2964 char name[32]={};
2965 std::snprintf(name,sizeof(name),frm,int(hnpc->voice));
2966 emitSoundEffect(name,2500,true);
2967 }
2968
2969void Npc::startEffect(Npc& to, const VisualFx& vfx) {
2970 Effect e(vfx,owner,*this,SpellFxKey::Cast);
2971 e.setActive(true);
2972 e.setTarget(&to);
2973 visual.startEffect(owner, std::move(e), 0, true);
2974 }
2975
2976void Npc::stopEffect(const VisualFx& vfx) {
2977 visual.stopEffect(vfx);
2978 }
2979
2981 visual.startEffect(owner, std::move(e), 0, true);
2982 }
2983
2985 if(bool(t&(TARGET_TYPE_ALL|TARGET_TYPE_NPCS)))
2986 return true;
2987
2988 const Guild gil = Guild(trueGuild());
2989
2990 const bool g2 = owner.version().game==2;
2991 const auto SEPERATOR_ORC = g2 ? GIL_SEPERATOR_ORC : GIL_G1_SEPERATOR_ORC;
2992 const auto G1_UNDEAD = (gil == GIL_G1_ZOMBIE || gil == GIL_G1_UNDEADORC || gil == GIL_G1_SKELETON);
2993 const auto G2_UNDEAD = (gil == GIL_GOBBO_SKELETON ||
2994 gil == GIL_SUMMONED_GOBBO_SKELETON || gil == GIL_SKELETON ||
2995 gil == GIL_SUMMONED_SKELETON || gil == GIL_SKELETON_MAGE ||
2996 gil == GIL_SHADOWBEAST_SKELETON || gil == GIL_ZOMBIE);
2997
2998 if(bool(t&TARGET_TYPE_HUMANS) && isHuman())
2999 return true;
3000 if(bool(t&TARGET_TYPE_ORCS) && gil>SEPERATOR_ORC)
3001 return true;
3002 if(bool(t&TARGET_TYPE_UNDEAD) && g2 && G2_UNDEAD)
3003 return true;
3004 if(bool(t&TARGET_TYPE_UNDEAD) && !g2 && G1_UNDEAD)
3005 return true;
3006
3007 return false;
3008 }
3009
3011 auto active = invent.getItem(currentSpellCast);
3012 if(active==nullptr || !active->isSpellOrRune())
3013 return;
3014
3015 const int32_t splId = active->spellId();
3016 const auto& spl = owner.script().spellDesc(splId);
3017
3018 if(owner.version().game==2)
3019 owner.script().invokeSpell(*this,currentTarget,*active);
3020
3021 if(active->isSpellShoot()) {
3022 const int lvl = (castLevel-CS_Emit_0)+1;
3024 for(size_t i=0; i<zenkit::DamageType::NUM; ++i)
3025 if((spl.damage_type&(1<<i))!=0) {
3026 dmg[i] = spl.damage_per_level*lvl;
3027 }
3028
3029 auto& b = owner.shootSpell(*active, *this, currentTarget);
3030 b.setDamage(dmg);
3031 b.setOrigin(this);
3032 b.setTarget(nullptr);
3033 visual.setMagicWeaponKey(owner,SpellFxKey::Init);
3034 } else {
3035 // NOTE: use pfx_ppsIsLoopingChg ?
3036 const VisualFx* vfx = owner.script().spellVfx(splId);
3037 if(vfx!=nullptr) {
3038 auto e = Effect(*vfx,owner,Vec3(x,y,z),SpellFxKey::Cast);
3039 e.setOrigin(this);
3040 e.setTarget((currentTarget==nullptr) ? this : currentTarget);
3041 e.setSpellId(splId,owner);
3042 e.setActive(true);
3043 visual.startEffect(owner,std::move(e),0,true);
3044 }
3045 visual.setMagicWeaponKey(owner,SpellFxKey::Init);
3046 if(currentTarget!=nullptr) {
3047 currentTarget->lastHitSpell = splId;
3048 currentTarget->perceptionProcess(*this,nullptr,0,PERC_ASSESSMAGIC);
3049 }
3050 }
3051
3052 if(active->isSpell()) {
3053 size_t cnt = active->count();
3054 invent.delItem(active->clsId(),1,*this);
3055 if(cnt<=1) {
3056 Item* spl = nullptr;
3057 for(uint8_t i=0;i<8;++i) {
3058 if(auto s = invent.currentSpell(i)) {
3059 spl = s;
3060 break;
3061 }
3062 }
3063 if(spl==nullptr) {
3064 if(spellInfo==0)
3066 } else {
3067 drawSpell(spl->spellId());
3068 }
3069 }
3070 }
3071
3072 if(spellInfo!=0 && transformSpl==nullptr) {
3073 transformSpl.reset(new TransformBack(*this));
3074 invent.updateView(*this);
3075 visual.clearOverlays();
3076
3077 owner.script().initializeInstanceNpc(hnpc, size_t(spellInfo));
3078 spellInfo = 0;
3079 hnpc->level = transformSpl->hnpc->level;
3080 }
3081 }
3082
3083const Npc::Routine& Npc::currentRoutine(bool assertWp) const {
3084 // find routine for current time
3085 // if there is no such routine search counter clock-wise until one is found
3086 auto time = owner.time().timeInDay();
3087 for(auto& i:routines) {
3088 if(assertWp && i.point==nullptr)
3089 continue;
3090 if(i.end<i.start && (time<i.end || i.start<=time))
3091 return i;
3092 if(i.start<=time && time<i.end)
3093 return i;
3094 }
3095
3096 const auto day = gtime(24,0).toInt();
3097 const Routine* rtn = nullptr;
3098 int64_t delta = std::numeric_limits<int64_t>::max();
3099 for(auto& i:routines) {
3100 if(assertWp && i.point==nullptr)
3101 continue;
3102 int64_t d = time.toInt() - i.end.toInt();
3103 if(d<0)
3104 d += day;
3105 // take the last one if multiple with same end time exist
3106 if(d<=delta) {
3107 rtn = &i;
3108 delta = d;
3109 }
3110 }
3111
3112 if(rtn!=nullptr)
3113 return *rtn;
3114 static Routine r;
3115 return r;
3116 }
3117
3119 if(routines.empty())
3120 return owner.findPoint(hnpc->wp,false);
3121 return currentRoutine(true).point;
3122 }
3123
3124gtime Npc::endTime(const Npc::Routine &r) const {
3125 auto wtime = owner.time();
3126 auto time = wtime.timeInDay();
3127
3128 //NOTE: should we consider time extension for invalid routine sequences?
3129 if(r.end<r.start) {
3130 if(time<r.end)
3131 return gtime(wtime.day(),r.end.hour(),r.end.minute());
3132 return gtime(wtime.day()+1,r.end.hour(),r.end.minute());
3133 }
3134 if(r.start<r.end) {
3135 if(r.end.hour()==0 || r.end<time)
3136 return gtime(wtime.day()+1,r.end.hour(),r.end.minute()); else
3137 return gtime(wtime.day(),r.end.hour(),r.end.minute());
3138 }
3139 if(r.start==r.end && r.end.toInt()==0) {
3140 // for example Rtn_Start_1081 in NTR is filled with zeros
3141 return gtime(wtime.day()+1,r.end.hour(),r.end.minute());
3142 }
3143 // error - routine is not active now
3144 return wtime;
3145 }
3146
3148 if(isDead())
3149 return BS_DEAD;
3150 if(isUnconscious())
3151 return BS_UNCONSCIOUS;
3152 if(isFalling())
3153 return BS_FALL;
3154
3155 uint32_t s = visual.pose().bodyState();
3156 if(auto i = interactive())
3157 s = i->stateMask();
3158 return BodyState(s);
3159 }
3160
3162 BodyState bs = bodyState();
3163 return BodyState(bs & (BS_MAX | BS_FLAG_MASK));
3164 }
3165
3167 if(visual.pose().hasState(s))
3168 return true;
3169 if(auto i = interactive())
3170 return s==i->stateMask();
3171 return false;
3172 }
3173
3175 if(visual.pose().hasStateFlag(flg))
3176 return true;
3177 if(auto i = interactive())
3178 return flg==(i->stateMask() & (BS_FLAG_MASK|BS_MOD_MASK));
3179 return false;
3180 }
3181
3182void Npc::setToFightMode(const size_t item) {
3183 if(invent.itemCount(item)==0)
3184 addItem(item,1);
3185
3186 invent.equip(item,*this,true);
3187 invent.switchActiveWeapon(*this,1);
3188
3189 auto w = invent.currentMeleeWeapon();
3190 if(w==nullptr || w->clsId()!=item)
3191 return;
3192
3193 auto weaponSt = WeaponState::W1H;
3194 if(w->is2H()) {
3195 weaponSt = WeaponState::W2H;
3196 } else {
3197 weaponSt = WeaponState::W1H;
3198 }
3199
3200 if(visual.setToFightMode(weaponSt))
3201 updateWeaponSkeleton();
3202
3203 auto& weapon = *currentMeleeWeapon();
3204 auto st = weapon.is2H() ? WeaponState::W2H : WeaponState::W1H;
3205 hnpc->weapon = (st==WeaponState::W1H ? 3:4);
3206 }
3207
3209 auto weaponSt=weaponState();
3210 if(weaponSt==WeaponState::Fist)
3211 return;
3212 invent.switchActiveWeaponFist();
3214 updateWeaponSkeleton();
3215 hnpc->weapon = 1;
3216 }
3217
3219 if(a.act==AI_OutputSvmOverlay)
3220 aiQueueOverlay.pushBack(std::move(a)); else
3221 aiQueue.pushBack(std::move(a));
3222 }
3223
3225 clearState(false);
3226 auto& r = currentRoutine();
3227 if(r.callback.isValid()) {
3228 auto t = endTime(r);
3229 startState(r.callback,r.wayPointName(),t,false);
3230 }
3231 }
3232
3233Item* Npc::addItem(const size_t item, size_t count) {
3234 return invent.addItem(item,count,owner);
3235 }
3236
3237Item* Npc::addItem(std::unique_ptr<Item>&& i) {
3238 return invent.addItem(std::move(i));
3239 }
3240
3242 if(interactive()!=nullptr)
3243 return nullptr;
3245 return nullptr;
3246
3247 auto state = bodyStateMasked();
3248 if(state!=BS_STAND && state!=BS_SNEAK) {
3249 setAnim(Anim::Idle);
3250 return nullptr;
3251 }
3252
3253 auto dpos = item.position()-position();
3254 dpos.y-=translateY();
3255 const Animation::Sequence* sq = setAnimAngGet(Npc::Anim::ItmGet,Pose::calcAniCombVert(dpos));
3256 if(sq==nullptr)
3257 return nullptr;
3258
3259 std::unique_ptr<Item> ptr = owner.takeItem(item);
3260 if(ptr!=nullptr && ptr->isTorchBurn()) {
3261 if(!toggleTorch())
3262 return nullptr;
3263 size_t torchId = owner.script().findSymbolIndex("ItLsTorch");
3264 if(torchId!=size_t(-1))
3265 return nullptr;
3266 ptr.reset(new Item(owner,torchId,Item::T_Inventory));
3267 }
3268
3269 auto it = ptr.get();
3270 if(it==nullptr)
3271 return nullptr;
3272
3273 it = addItem(std::move(ptr));
3274 if(isPlayer() && it!=nullptr)
3275 owner.sendPassivePerc(*this,*this,*it,PERC_ASSESSTHEFT);
3276
3277 implAniWait(uint64_t(sq->totalTime()));
3278 return it;
3279 }
3280
3281void Npc::onWldItemRemoved(const Item& itm) {
3282 aiQueue.onWldItemRemoved(itm);
3283 aiQueueOverlay.onWldItemRemoved(itm);
3284 }
3285
3286void Npc::addItem(size_t id, Interactive &chest, size_t count) {
3287 Inventory::transfer(invent,chest.inventory(),nullptr,id,count,owner);
3288 }
3289
3290void Npc::addItem(size_t id, Npc &from, size_t count) {
3291 Inventory::transfer(invent,from.invent,&from,id,count,owner);
3292 }
3293
3294void Npc::moveItem(size_t id, Interactive &to, size_t count) {
3295 Inventory::transfer(to.inventory(),invent,this,id,count,owner);
3296 }
3297
3298void Npc::sellItem(size_t id, Npc &to, size_t count) {
3299 if(id==owner.script().goldId()->index())
3300 return;
3301 int32_t price = invent.sellPriceOf(id);
3302 Inventory::transfer(to.invent,invent,this,id,count,owner);
3303 invent.addItem(owner.script().goldId()->index(),size_t(price)*count,owner);
3304 }
3305
3306void Npc::buyItem(size_t id, Npc &from, size_t count) {
3307 if(id==owner.script().goldId()->index())
3308 return;
3309
3310 int32_t price = from.invent.priceOf(id);
3311 if(price>0 && size_t(price)*count>invent.goldCount()) {
3312 count = invent.goldCount()/size_t(price);
3313 }
3314 if(count==0) {
3315 owner.script().printCannotBuyError(*this);
3316 return;
3317 }
3318
3319 Inventory::transfer(invent,from.invent,nullptr,id,count,owner);
3320 if(price>=0)
3321 invent.delItem(owner.script().goldId()->index(),size_t( price)*count,*this); else
3322 invent.addItem(owner.script().goldId()->index(),size_t(-price)*count,owner);
3323 }
3324
3325void Npc::dropItem(size_t id, size_t count) {
3326 if(id==size_t(-1))
3327 return;
3328 size_t cnt = invent.itemCount(id);
3329 if(count>cnt)
3330 count = cnt;
3331 if(count<1)
3332 return;
3333
3334 auto sk = visual.visualSkeleton();
3335 if(sk==nullptr)
3336 return;
3337
3338 size_t rightHand = sk->findNode("ZS_RIGHTHAND");
3339 if(rightHand==size_t(-1))
3340 return;
3341
3342 if(!setAnim(Anim::ItmDrop))
3343 return;
3344
3345 auto mat = visual.transform();
3346 if(rightHand<visual.pose().boneCount())
3347 mat = visual.pose().bone(rightHand);
3348
3349 auto it = owner.addItemDyn(id,mat,hnpc->symbol_index());
3350 it->setCount(count);
3351 invent.delItem(id,count,*this);
3352 }
3353
3355 invent.clear(owner.script(),*this);
3356 }
3357
3359 return invent.currentArmor();
3360 }
3361
3363 return invent.currentMeleeWeapon();
3364 }
3365
3367 return invent.currentRangedWeapon();
3368 }
3369
3371 return invent.currentShield();
3372 }
3373
3375 return visual.mapWeaponBone();
3376 }
3377
3378Vec3 Npc::mapHeadBone() const {
3379 return visual.mapHeadBone();
3380 }
3381
3382Vec3 Npc::mapBone(std::string_view bone) const {
3383 if(auto sk = visual.visualSkeleton()) {
3384 size_t id = sk->findNode(bone);
3385 if(id!=size_t(-1))
3386 return visual.mapBone(id);
3387 }
3388
3389 Vec3 ret = {};
3390 ret.y = physic.centerY()-y;
3391 return ret+position();
3392 }
3393
3394bool Npc::turnTo(float dx, float dz, bool noAnim, uint64_t dt) {
3395 return implTurnTo(dx,dz,noAnim?AnimationSolver::TurnType::None:AnimationSolver::TurnType::Std,dt);
3396 }
3397
3398bool Npc::rotateTo(float dx, float dz, float step, AnimationSolver::TurnType anim, uint64_t dt) {
3399 //step *= (float(dt)/1000.f)*60.f/100.f;
3400 step *= (float(dt)/1000.f);
3401
3402 if(dx==0.f && dz==0.f) {
3403 setAnimRotate(0);
3404 return false;
3405 }
3406
3407 if(!isRotationAllowed())
3408 return false;
3409
3410 float a = angleDir(dx,dz);
3411 float da = a-angle;
3412
3413 if(anim == AnimationSolver::TurnType::None || std::cos(double(da)*M_PI/180.0)>0) {
3414 if(float(std::abs(int(da)%180))<=(step*2.f)) {
3415 setAnimRotate(0);
3416 setDirection(a);
3417 return false;
3418 }
3419 } else {
3420 visual.stopWalkAnim(*this);
3421 }
3422
3423 const auto sgn = std::sin(double(da)*M_PI/180.0);
3424 if(sgn==0) {
3425 setAnimRotate(0);
3426 } else {
3427 const int rot = (sgn<0) ? +1 : -1;
3428 switch(anim) {
3430 setAnimRotate(rot);
3431 break;
3433 setAnimRotate(0);
3434 break;
3436 visual.setAnimWhirl(*this, rot);
3437 break;
3438 }
3439 setDirection(angle - float(rot)*step);
3440 }
3441 return true;
3442 }
3443
3445 auto bs = bodyStateMasked();
3446 bool air = (!isPlayer() && isInAir()) || isFallingDeep();
3447 return currentInteract==nullptr && !isFinishingMove() && bs!=BS_CLIMB && bs!=BS_LIE && !air;
3448 }
3449
3450bool Npc::checkGoToNpcdistance(const Npc &other) {
3451 return fghAlgo.isInAttackRange(*this,other,owner.script());
3452 }
3453
3454size_t Npc::itemCount(size_t id) const {
3455 return invent.itemCount(id);
3456 }
3457
3459 return invent.activeWeapon();
3460 }
3461
3462Item *Npc::getItem(size_t id) {
3463 return invent.getItem(id);
3464 }
3465
3466void Npc::delItem(size_t item, uint32_t amount) {
3467 invent.delItem(item,amount,*this);
3468 }
3469
3470void Npc::useItem(size_t item) {
3471 useItem(item,Item::NSLOT,false);
3472 }
3473
3474void Npc::useItem(size_t item, uint8_t slotHint, bool force) {
3475 invent.use(item,*this,slotHint,force);
3476 }
3477
3478void Npc::setCurrentItem(size_t item) {
3479 invent.setCurrentItem(item);
3480 }
3481
3482void Npc::unequipItem(size_t item) {
3483 invent.unequip(item,*this);
3484 }
3485
3487 return !(mvAlgo.isFalling() || mvAlgo.isInAir() || mvAlgo.isSlide() || mvAlgo.isSwim());
3488 }
3489
3490bool Npc::closeWeapon(bool noAnim) {
3491 auto weaponSt=weaponState();
3492 if(weaponSt==WeaponState::NoWeapon)
3493 return true;
3494 if(!noAnim && !visual.startAnim(*this,WeaponState::NoWeapon))
3495 return false;
3496 visual.setAnimRotate(*this,0);
3497 if(isPlayer())
3498 setTarget(nullptr);
3499 invent.switchActiveWeapon(*this,Item::NSLOT);
3500 invent.putAmmunition(*this,0,"");
3501 if(noAnim) {
3503 updateWeaponSkeleton();
3504 }
3505 hnpc->weapon = 0;
3506 // clear spell-cast state
3507 castLevel = CS_NoCast;
3508 currentSpellCast = size_t(-1);
3509 castNextTime = 0;
3510 if(isPlayer())
3511 owner.sendPassivePerc(*this,*this,PERC_ASSESSREMOVEWEAPON);
3512 return true;
3513 }
3514
3516 if(!canSwitchWeapon())
3517 return false;
3518 auto weaponSt=weaponState();
3519 if(weaponSt==WeaponState::Fist)
3520 return true;
3521 if(weaponSt!=WeaponState::NoWeapon) {
3522 closeWeapon(false);
3523 return false;
3524 }
3525
3526 if(isMonster()) {
3527 if(!visual.startAnim(*this,WeaponState::Fist))
3529 } else {
3530 if(!visual.startAnim(*this,WeaponState::Fist))
3531 return false;
3532 }
3533
3534 invent.switchActiveWeaponFist();
3535 hnpc->weapon = 1;
3536 return true;
3537 }
3538
3540 if(!canSwitchWeapon())
3541 return false;
3542 auto weaponSt=weaponState();
3543 if(weaponSt==WeaponState::Fist || weaponSt==WeaponState::W1H || weaponSt==WeaponState::W2H)
3544 return true;
3545 if(invent.currentMeleeWeapon()==nullptr)
3546 return drawWeaponFist();
3547 if(weaponSt!=WeaponState::NoWeapon) {
3548 closeWeapon(false);
3549 return false;
3550 }
3551
3552 if(!setInteraction(nullptr,true))
3553 return false;
3554
3555 auto& weapon = *invent.currentMeleeWeapon();
3556 auto st = weapon.is2H() ? WeaponState::W2H : WeaponState::W1H;
3557 if(!visual.startAnim(*this,st))
3558 return false;
3559
3560 invent.switchActiveWeapon(*this,1);
3561 hnpc->weapon = (st==WeaponState::W1H ? 3:4);
3562 return true;
3563 }
3564
3566 if(!canSwitchWeapon())
3567 return false;
3568 auto weaponSt=weaponState();
3569 if(weaponSt==WeaponState::Bow || weaponSt==WeaponState::CBow || invent.currentRangedWeapon()==nullptr)
3570 return true;
3571 if(weaponSt!=WeaponState::NoWeapon) {
3572 closeWeapon(false);
3573 return false;
3574 }
3575
3576 if(!setInteraction(nullptr,true))
3577 return false;
3578
3579 auto& weapon = *invent.currentRangedWeapon();
3580 auto st = weapon.isCrossbow() ? WeaponState::CBow : WeaponState::Bow;
3581 if(!visual.startAnim(*this,st))
3582 return false;
3583 invent.switchActiveWeapon(*this,2);
3584 hnpc->weapon = (st==WeaponState::Bow ? 5:6);
3585 return true;
3586 }
3587
3588bool Npc::drawMage(uint8_t slot) {
3589 if(!canSwitchWeapon())
3590 return false;
3591 Item* it = invent.currentSpell(uint8_t(slot-3));
3592 if(it==nullptr) {
3593 closeWeapon(false);
3594 return true;
3595 }
3596 return drawSpell(it->spellId());
3597 }
3598
3599bool Npc::drawSpell(int32_t spell) {
3600 if(isFalling() || mvAlgo.isSwim() || bodyStateMasked()==BS_CASTING)
3601 return false;
3602 auto weaponSt=weaponState();
3603 if(weaponSt!=WeaponState::NoWeapon && weaponSt!=WeaponState::Mage) {
3604 closeWeapon(false);
3605 return false;
3606 }
3607
3608 if(!setInteraction(nullptr,true))
3609 return false;
3610
3611 if(!visual.startAnim(*this,WeaponState::Mage))
3612 return false;
3613
3614 invent.switchActiveSpell(spell,*this);
3615 hnpc->weapon = 7;
3616
3617 updateWeaponSkeleton();
3618 return true;
3619 }
3620
3622 return visual.fightMode();
3623 }
3624
3626 auto ws = weaponState();
3627 if(ws!=WeaponState::W1H && ws!=WeaponState::W2H)
3628 return false;
3629
3630 if(!oth.isUnconscious())
3631 return false;
3632
3633 if(!fghAlgo.isInFinishRange(*this,oth,owner.script()))
3634 return false;
3635 return true;
3636 }
3637
3639 auto weaponSt = weaponState();
3640 if(weaponSt==WeaponState::NoWeapon || weaponSt==WeaponState::Mage)
3641 return false;
3642
3643 if(mvAlgo.isSwim())
3644 return false;
3645
3646 if(bs==BS_PARADE && hasState(BS_PARADE))
3647 return false;
3648
3649 auto wlk = walkMode();
3650 if(mvAlgo.isInWater())
3651 wlk = WalkBit::WM_Water;
3652
3653 visual.setAnimRotate(*this,0);
3654 if(auto sq = visual.continueCombo(*this,anim,bs,weaponSt,wlk)) {
3655 (void)sq;
3656 // implAniWait(uint64_t(sq->atkTotalTime(visual.comboLength())+1));
3657 return true;
3658 }
3659 return false;
3660 }
3661
3663 doAttack(Anim::Attack,BS_HIT);
3664 }
3665
3667 auto weaponSt=weaponState();
3668 if(weaponSt!=WeaponState::Fist)
3669 return false;
3670 visual.setAnimRotate(*this,0);
3671 return setAnim(Anim::AttackBlock);
3672 }
3673
3675 if(currentTarget==nullptr || !canFinish(*currentTarget))
3676 return false;
3677
3678 if(doAttack(Anim::AttackFinish,BS_HIT)) {
3679 currentTarget->hnpc->attribute[ATR_HITPOINTS] = 0;
3680 currentTarget->checkHealth(true,false);
3681 owner.sendPassivePerc(*this,*this,*currentTarget,PERC_ASSESSMURDER);
3682 return true;
3683 }
3684 return false;
3685 }
3686
3688 auto active=invent.activeWeapon();
3689 if(active==nullptr)
3690 return;
3691 doAttack(Anim::Attack,BS_HIT);
3692 }
3693
3695 auto active=invent.activeWeapon();
3696 if(active==nullptr)
3697 return false;
3698 return doAttack(Anim::AttackL,BS_HIT);
3699 }
3700
3702 auto active=invent.activeWeapon();
3703 if(active==nullptr)
3704 return false;
3705 return doAttack(Anim::AttackR,BS_HIT);
3706 }
3707
3709 auto active=invent.activeWeapon();
3710 if(active==nullptr)
3711 return false;
3712 return doAttack(Anim::AttackBlock,BS_PARADE);
3713 // return setAnimAngGet(Anim::AttackBlock,calcAniComb())!=nullptr;
3714 }
3715
3717 if(castLevel!=CS_NoCast)
3719
3720 if(!isStanding())
3722
3723 auto active=invent.activeWeapon();
3724 if(active==nullptr)
3726
3727 if(attribute(ATR_MANA)<=0) {
3728 setAnim(Anim::MagNoMana);
3730 }
3731
3732 // castLevel = CS_Invest_0;
3733 currentSpellCast = active->clsId();
3734 castNextTime = owner.tickCount();
3735 hnpc->aivar[88] = 0; // HACK: clear AIV_SpellLevel
3736 manaInvested = 0;
3737
3738 const SpellCode code = SpellCode(owner.script().invokeMana(*this,currentTarget,manaInvested));
3739 switch(code) {
3740 case SPL_SENDSTOP:
3741 case SPL_DONTINVEST:
3742 setAnim(Anim::MagNoMana);
3743 castLevel = CS_NoCast;
3744 currentSpellCast = size_t(-1);
3745 castNextTime = 0;
3748 case SPL_RECEIVEINVEST:
3749 case SPL_NEXTLEVEL: {
3750 ++manaInvested;
3751 auto ani = owner.script().spellCastAnim(*this,*active);
3752 if(!visual.startAnimSpell(*this,ani,true))
3753 Log::d("Couldn't start animation for spell '",currentSpellCast,"'");
3754 castLevel = CS_Invest_0;
3756 }
3757 case SPL_SENDCAST: {
3758 castLevel = CS_Cast_0;
3760 }
3761 default:
3762 Log::d("unexpected Spell_ProcessMana result: '",int(code),"' for spell '",currentSpellCast,"'");
3763 endCastSpell();
3765 }
3766
3768 }
3769
3770bool Npc::tickCast(uint64_t dt) {
3771 if(castLevel==CS_NoCast)
3772 return false;
3773
3774 auto active = currentSpellCast!=size_t(-1) ? invent.getItem(currentSpellCast) : nullptr;
3775
3776 if(currentSpellCast!=size_t(-1)) {
3777 if(active==nullptr || !active->isSpellOrRune() || isDown()) {
3778 // canot cast spell
3779 castLevel = CS_NoCast;
3780 currentSpellCast = size_t(-1);
3781 castNextTime = 0;
3782 return true;
3783 }
3784
3785 if(!isPlayer() && currentTarget!=nullptr) {
3786 implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt);
3787 }
3788 }
3789
3790 if(CS_Cast_0<=castLevel && castLevel<=CS_Cast_Last) {
3791 // cast anim
3792 if(active!=nullptr) {
3793 auto ani = owner.script().spellCastAnim(*this,*active);
3794 bool g2 = owner.version().game==2;
3795 if(g2 || visual.isAnimExist(string_frm("T_MAGRUN_2_",ani,"CAST")))
3796 if(!visual.startAnimSpell(*this,ani,false))
3797 return true;
3798 }
3799 castLevel = CastState(int(castLevel) + int(CS_Emit_0) - int(CS_Cast_0));
3800 castNextTime = 0;
3801 return true;
3802 }
3803
3804 if((CS_Emit_0<=castLevel && castLevel<=CS_Emit_Last) || castLevel==CS_Finalize) {
3805 // final commit
3806 if(!setAnim(Npc::Anim::Idle))
3807 return true;
3808 if(castLevel!=CS_Finalize)
3809 commitSpell();
3810 castLevel = CS_NoCast;
3811 currentSpellCast = size_t(-1);
3812 castNextTime = 0;
3813 spellInfo = 0;
3814 return false;
3815 }
3816
3817 if(active==nullptr)
3818 return false;
3819
3821 return true;
3822
3823 if(owner.tickCount()<castNextTime)
3824 return true;
3825
3826 const SpellCode code = SpellCode(owner.script().invokeMana(*this,currentTarget,manaInvested));
3827
3828 if(owner.version().game==1) {
3829 changeAttribute(ATR_MANA,-1,false);
3830 if(!isPlayer() && code!=SpellCode::SPL_SENDCAST)
3831 assert(attribute(ATR_MANA)>0);
3832 }
3833
3834 if(!isPlayer() && aiExpectedInvest<=manaInvested) {
3835 endCastSpell();
3836 return true;
3837 }
3838
3839 switch(code) {
3843 if(code==SPL_NEXTLEVEL) {
3844 int32_t castLvl = int(castLevel)-int(CS_Invest_0);
3845 if(castLvl<15)
3846 castLevel = CastState(castLevel+1);
3847 visual.setMagicWeaponKey(owner,SpellFxKey::Invest,castLvl+1);
3848 }
3849 auto& spl = owner.script().spellDesc(active->spellId());
3850 castNextTime += uint64_t(spl.time_per_mana);
3851 ++manaInvested;
3852 return true;
3853 }
3857 if(code==SPL_DONTINVEST && isPlayer())
3858 return true;
3859 endCastSpell();
3860 return true;
3861 }
3862 default:
3863 Log::d("unexpected Spell_ProcessMana result: '",int(code),"' for spell '",currentSpellCast,"'");
3864 return false;
3865 }
3866 return true;
3867 }
3868
3869void Npc::endCastSpell(bool playerCtrl) {
3870 if(castLevel<CS_Invest_0 || castLevel>CS_Invest_Last)
3871 return;
3872 int32_t castLvl = int(castLevel)-int(CS_Invest_0);
3873 if(!playerCtrl) {
3874 castLevel = CastState(castLvl+CS_Cast_0);
3875 return;
3876 }
3877 SpellCode code = SpellCode(owner.script().invokeManaRelease(*this,currentTarget,manaInvested));
3878 if(code==SpellCode::SPL_SENDCAST)
3879 castLevel = CastState(castLvl+CS_Cast_0); else
3880 castLevel = CS_Finalize;
3881 }
3882
3883void Npc::setActiveSpellInfo(int32_t info) {
3884 spellInfo = info;
3885 }
3886
3887int32_t Npc::activeSpellLevel() const {
3888 if(CS_Cast_0<=castLevel && castLevel<=CS_Cast_Last)
3889 return int(castLevel)-int(CS_Cast_0)+1;
3890 if(CS_Invest_0<=castLevel && castLevel<=CS_Invest_Last)
3891 return int(castLevel)-int(CS_Invest_0)+1;
3892 return 0;
3893 }
3894
3896 auto active=invent.activeWeapon();
3897 if(active==nullptr)
3898 return false;
3899 if(!setAnim(Anim::AimBow))
3900 return false;
3901 visual.setAnimRotate(*this,0);
3902 return true;
3903 }
3904
3905bool Npc::shootBow(Interactive* focOverride) {
3906 auto active=invent.activeWeapon();
3907 if(active==nullptr)
3908 return false;
3909
3910 auto bs = bodyStateMasked();
3911 if(bs==BS_RUN) {
3912 setAnim(Anim::Idle);
3913 return true;
3914 }
3915
3916 const int32_t munition = active->handle().munition;
3917 if(!hasAmmunition())
3918 return false;
3919
3920 if(!setAnim(Anim::Attack))
3921 return false;
3922
3923 auto itm = invent.getItem(size_t(munition));
3924 if(itm==nullptr)
3925 return false;
3926
3927 auto& b = owner.shootBullet(*itm,*this,currentTarget,focOverride);
3928
3929 invent.delItem(size_t(munition),1,*this);
3930 b.setOrigin(this);
3932
3933 auto rgn = currentRangedWeapon();
3934 if(Gothic::inst().version().game==1) {
3935 b.setHitChance(float(hnpc->attribute[ATR_DEXTERITY])/100.f);
3936 if(rgn!=nullptr && rgn->isCrossbow())
3937 b.setCritChance(float(talentsVl[TALENT_CROSSBOW])/100.f); else
3938 b.setCritChance(float(talentsVl[TALENT_BOW] )/100.f);
3939 }
3940 else {
3941 if(rgn!=nullptr && rgn->isCrossbow())
3942 b.setHitChance(float(hnpc->hitchance[TALENT_CROSSBOW])/100.f); else
3943 b.setHitChance(float(hnpc->hitchance[TALENT_BOW] )/100.f);
3944 }
3945 return true;
3946 }
3947
3949 auto active=invent.activeWeapon();
3950 if(active==nullptr)
3951 return false;
3952 const int32_t munition = active->handle().munition;
3953 if(munition<0 || invent.itemCount(size_t(munition))<=0)
3954 return false;
3955 return true;
3956 }
3957
3958bool Npc::isEnemy(const Npc &other) const {
3959 return owner.script().personAttitude(*this,other)==ATT_HOSTILE;
3960 }
3961
3962bool Npc::isDead() const {
3963 return owner.script().isDead(*this);
3964 }
3965
3966bool Npc::isLie() const {
3967 return bodyStateMasked()==BS_LIE;
3968 }
3969
3971 return owner.script().isUnconscious(*this);
3972 }
3973
3974bool Npc::isDown() const {
3975 return isUnconscious() || isDead();
3976 }
3977
3978bool Npc::isAttack() const {
3979 return owner.script().isAttack(*this);
3980 }
3981
3982bool Npc::isTalk() const {
3983 return owner.script().isTalk(*this);
3984 }
3985
3986bool Npc::isAttackAnim() const {
3987 return visual.pose().isAttackAnim();
3988 }
3989
3990bool Npc::isPrehit() const {
3991 return visual.pose().isPrehit(owner.tickCount());
3992 }
3993
3994bool Npc::isImmortal() const {
3995 return hnpc->flags & zenkit::NpcFlag::IMMORTAL;
3996 }
3997
3998void Npc::setPerceptionTime(uint64_t time) {
3999 perceptionTime = time;
4000 }
4001
4002uint64_t Npc::perceptionTimeClampt() const {
4003 return std::max<uint64_t>(perceptionTime, 1);
4004 }
4005
4007 if(t>0 && t<PERC_Count)
4008 perception[t].func = fn;
4009 }
4010
4012 if(t>0 && t<PERC_Count)
4013 perception[t].func = ScriptFn();
4014 }
4015
4017 if(pl.isDown() || pl.isInAir() || isPlayer())
4018 return;
4019 if(perceptionProcess(pl,nullptr,0,PERC_ASSESSTALK))
4020 setOther(&pl);
4021 }
4022
4024 static bool dbg = false;
4025 static int kId = -1;
4026 if(dbg && hnpc->id!=kId)
4027 return false;
4028
4029 if(isPlayer())
4030 return true;
4031
4032 bool ret=false;
4034 perceptionNextTime = owner.tickCount()+perceptionTimeClampt();
4035 return ret;
4036 }
4037
4038 const float quadDist = pl.qDistTo(*this);
4040 if(perceptionProcess(pl,nullptr,quadDist,PERC_ASSESSPLAYER)) {
4041 ret = true;
4042 }
4043 }
4044
4045 Npc* enem=hasPerc(PERC_ASSESSENEMY) ? updateNearestEnemy() : nullptr;
4046 if(enem!=nullptr){
4047 float dist=qDistTo(*enem);
4048 if(perceptionProcess(*enem,nullptr,dist,PERC_ASSESSENEMY)){
4049 ret = true;
4050 } else {
4051 nearestEnemy = nullptr;
4052 }
4053 }
4054
4055 Npc* body=hasPerc(PERC_ASSESSBODY) ? updateNearestBody() : nullptr;
4056 if(body!=nullptr){
4057 float dist=qDistTo(*body);
4058 if(perceptionProcess(*body,nullptr,dist,PERC_ASSESSBODY)) {
4059 ret = true;
4060 }
4061 }
4062
4063 // if(aiQueue.size()==0) // NOTE: Gothic1 fights
4064 perceptionNextTime = owner.tickCount()+perceptionTimeClampt();
4065 return ret;
4066 }
4067
4068bool Npc::perceptionProcess(Npc &pl, Npc* victim, float quadDist, PercType perc) {
4069 if(!aiState.started) {
4070 // avoid ugly soft-lock (ZS_MM_Attack <-> B_MM_AssessWarn) for the orks near ramp
4071 return false;
4072 }
4073
4074 float r = float(world().script().percRanges().at(perc, hnpc->senses_range));
4075 r = r*r;
4076
4077 if(quadDist>r)
4078 return false;
4079
4080 if(hasPerc(perc)) {
4081 owner.script().invokeState(this,&pl,victim,perception[perc].func);
4082 return true;
4083 }
4084 if(perc==PERC_ASSESSMAGIC && isPlayer()) {
4085 auto defaultFn = owner.script().playerPercAssessMagic();
4086 if(defaultFn.isValid())
4087 owner.script().invokeState(this,&pl,victim,defaultFn);
4088 return true;
4089 }
4090 return false;
4091 }
4092
4093bool Npc::hasPerc(PercType perc) const {
4094 return perception[perc].func.isValid();
4095 }
4096
4097uint64_t Npc::percNextTime() const {
4098 return perceptionNextTime;
4099 }
4100
4101bool Npc::setInteraction(Interactive *id, bool quick) {
4102 if(currentInteract==id)
4103 return true;
4104
4105 if(currentInteract!=nullptr) {
4106 return currentInteract->detach(*this,quick);
4107 }
4108
4109 if(id==nullptr)
4110 return (currentInteract==nullptr);
4111
4112 if(id->attach(*this)) {
4113 currentInteract = id;
4114 if(!quick) {
4115 visual.stopAnim(*this,"");
4116 setAnimRotate(0);
4117 }
4118 return true;
4119 }
4120
4121 return false;
4122 }
4123
4125 if(currentInteract==nullptr)
4126 return;
4127 if(invTorch)
4129 setDirectionY(0);
4130 currentInteract=nullptr;
4131 }
4132
4134 if(invTorch || isUsingTorch()) {
4135 visual.setTorch(invTorch,owner);
4136 invTorch = !invTorch;
4137 }
4138 }
4139
4141 moveMob = id;
4142 moveMobCacheKey = position();
4143 }
4144
4146 if(currentInteract!=nullptr)
4147 return currentInteract;
4148 if((moveMobCacheKey-position()).quadLength()<10.f*10.f)
4149 return moveMob;
4150 return nullptr;
4151 }
4152
4153bool Npc::isInState(ScriptFn stateFn) const {
4154 return aiState.funcIni==stateFn;
4155 }
4156
4157bool Npc::isInRoutine(ScriptFn stateFn) const {
4158 auto& rout = currentRoutine();
4159 return rout.callback==stateFn && aiState.funcIni==stateFn;
4160 }
4161
4162bool Npc::wasInState(ScriptFn stateFn) const {
4163 return aiPrevState==stateFn;
4164 }
4165
4166uint64_t Npc::stateTime() const {
4167 return owner.tickCount()-aiState.sTime;
4168 }
4169
4170void Npc::setStateTime(int64_t time) {
4171 aiState.sTime = owner.tickCount()-uint64_t(time);
4172 }
4173
4174void Npc::addRoutine(gtime s, gtime e, uint32_t callback, std::string_view point) {
4175 auto wp = world().findPoint(point,false);
4176
4177 Routine r;
4178 r.start = s;
4179 r.end = e;
4180 r.callback = callback;
4181 r.point = wp;
4182 if(wp==nullptr)
4183 r.fallbackName = point;
4184 routines.push_back(r);
4185
4186 std::stable_sort(routines.begin(), routines.end(), [](const Routine& l, const Npc::Routine& r) {
4187 return l.start < r.start;
4188 });
4189 }
4190
4191void Npc::excRoutine(size_t callback) {
4192 routines.clear();
4193 owner.script().invokeState(this,currentOther,currentVictim,callback);
4194 // aiState.eTime = gtime();
4195 }
4196
4197void Npc::multSpeed(float s) {
4198 mvAlgo.multSpeed(s);
4199 }
4200
4201bool Npc::testMove(const Vec3& pos) {
4203 return physic.testMove(pos,out);
4204 }
4205
4206bool Npc::tryMove(const Vec3& dp) {
4208 return tryMove(dp, out);
4209 }
4210
4211bool Npc::tryMove(const Vec3& dp, DynamicWorld::CollisionTest& out) {
4212 return tryTranslate(Vec3(x,y,z) + dp, out);
4213 }
4214
4215bool Npc::tryTranslate(const Vec3& to) {
4217 return tryTranslate(to,out);
4218 }
4219
4220bool Npc::tryTranslate(const Vec3& to, DynamicWorld::CollisionTest& out) {
4221 switch(physic.tryMove(to, out)) {
4223 return false;
4225 setViewPosition(out.partial);
4226 return true;
4229 setViewPosition(to);
4230 return true;
4231 }
4232 return false;
4233 }
4234
4236 float len = MoveAlgo::climbMove;
4237 float rot = rotationRad();
4238 float s = std::sin(rot), c = std::cos(rot);
4239 Vec3 dp = Vec3{len*s, 0, -len*c};
4240
4241 auto& g = owner.script().guildVal();
4242 auto gl = guild();
4243
4244 if(isSlide() || isSwim() || isDive()) {
4245 JumpStatus ret;
4246 ret.anim = Anim::Idle;
4247 return ret;
4248 }
4249
4250 const float jumpLow = float(g.jumplow_height[gl]);
4251 const float jumpMid = float(g.jumpmid_height[gl]);
4252 const float jumpUp = float(g.jumpup_height[gl]);
4253
4254 auto pos0 = physic.position();
4255
4256 JumpStatus ret;
4258 if(!isInAir() && physic.testMove(pos0+dp,info)) {
4259 // jump forward
4260 ret.anim = Anim::Jump;
4261 ret.noClimb = true;
4262 return ret;
4263 }
4264
4265 auto lnd = owner.physic()->landRay(pos0 + dp + Vec3(0, jumpUp + jumpLow, 0));
4266 float jumpY = lnd.v.y;
4267 auto pos1 = Vec3(pos0.x,jumpY,pos0.z);
4268 auto pos2 = pos1 + dp;
4269
4270 float dY = jumpY - y;
4271
4272 if(dY<=0.f ||
4273 !physic.testMove(pos2,pos1,info)) {
4274 ret.anim = Anim::JumpUp;
4275 ret.height = y + jumpUp;
4276 ret.noClimb = true;
4277 return ret;
4278 }
4279
4280 if(!physic.testMove(pos1,pos0,info) ||
4281 !physic.testMove(pos2,pos1,info)) {
4282 // check approximate path of climb failed
4283 ret.anim = Anim::Jump;
4284 ret.noClimb = true;
4285 return ret;
4286 }
4287
4288 if(dY>=jumpUp || dY>=jumpMid) {
4289 // Jump to the edge, and then pull up. Height: 200-350cm
4290 ret.anim = Anim::JumpUp;
4291 ret.height = y + jumpUp;
4292 return ret;
4293 }
4294
4296 if(mvAlgo.testSlide(Vec3{pos0.x,jumpY,pos0.z}+dp,out)) {
4297 // cannot climb to non angled surface
4298 ret.anim = Anim::Jump;
4299 ret.noClimb = true;
4300 return ret;
4301 }
4302
4303 if(isInAir() && dY<=jumpLow + translateY()) {
4304 // jumpup -> climb
4305 ret.anim = Anim::JumpHang;
4306 ret.height = jumpY;
4307 return ret;
4308 }
4309
4310 if(isInAir()) {
4311 ret.anim = Anim::Idle;
4312 ret.noClimb = true;
4313 return ret;
4314 }
4315
4316 if(dY<=jumpLow) {
4317 // Without using the hands, just big footstep. Height: 50-100cm
4318 ret.anim = Anim::JumpUpLow;
4319 ret.height = jumpY;
4320 return ret;
4321 }
4322
4323 if(dY<=jumpMid) {
4324 // Supported on the hands in one sentence. Height: 100-200cm
4325 ret.anim = Anim::JumpUpMid;
4326 ret.height = jumpY;
4327 return ret;
4328 }
4329
4330 return JumpStatus(); // error
4331 }
4332
4334 mvAlgo.startDive();
4335 }
4336
4338 if(transformSpl==nullptr)
4339 return;
4340 transformSpl->undo(*this);
4341 setVisual(transformSpl->skeleton);
4342 setVisualBody(vHead,vTeeth,vColor,bdColor,body,head);
4343 closeWeapon(true);
4344
4345 // invalidate tallent overlays
4346 for(size_t i=0; i<TALENT_MAX_G2; ++i)
4347 setTalentSkill(Talent(i),talentsSk[i]);
4348
4349 invent.updateView(*this);
4350 transformSpl.reset();
4351 }
4352
4353std::vector<GameScript::DlgChoice> Npc::dialogChoices(Npc& player,const std::vector<uint32_t> &except,bool includeImp) {
4354 return owner.script().dialogChoices(player.hnpc,this->hnpc,except,includeImp);
4355 }
4356
4358 return aiQueue.size()==0 &&
4359 go2.empty() &&
4360 waitTime<owner.tickCount();
4361 }
4362
4363bool Npc::isAiBusy() const {
4364 return !isAiQueueEmpty() ||
4365 aniWaitTime>=owner.tickCount() ||
4366 outWaitTime>=owner.tickCount();
4367 }
4368
4370 currentLookAt = nullptr;
4371 currentLookAtNpc = nullptr;
4372 visual.setHeadRotation(0,0);
4373
4374 aiQueue.clear();
4375 aiQueueOverlay.clear();
4376 aniWaitTime = 0;
4377 waitTime = 0;
4378 faiWaitTime = 0;
4379 fghAlgo.onClearTarget();
4380 wayPath.clear();
4381 clearGoTo();
4382 }
4383
4385 currentFp = p;
4386 currentFpLock = FpLock(currentFp);
4387 }
4388
4390 if(!go2.empty()) {
4391 stopWalking();
4392 go2.clear();
4393 }
4394 }
4395
4397 if(setAnim(Anim::Idle))
4398 return;
4399 // hard stop
4400 stopWalkAnimation();
4401 }
4402
4403bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const {
4404 const auto mid = oth.bounds().midTr;
4405 if(canSeeNpc(mid,freeLos))
4406 return true;
4407 const auto ppos = oth.physic.position();
4408 if(oth.isDown() && canSeeNpc(ppos,freeLos)) {
4409 // mid of dead npc may endedup inside a wall; extra check for physical center
4410 return true;
4411 }
4412 if(oth.visual.visualSkeleton()==nullptr)
4413 return false;
4414 if(oth.visual.visualSkeleton()->BIP01_HEAD==size_t(-1))
4415 return false;
4416 auto head = oth.visual.mapHeadBone();
4417 if(canSeeNpc(head,freeLos))
4418 return true;
4419 return false;
4420 }
4421
4422bool Npc::canSeeSource() const {
4423 const auto head = visual.mapHeadBone();
4424 const bool ret = owner.sound()->canSeeSource(head);
4425 if(ret)
4426 return ret;
4427 if(currentLookAtNpc!=nullptr)
4428 return canSeeNpc(*currentLookAtNpc, false);
4429 return false;
4430 }
4431
4432bool Npc::canSeeNpc(const Vec3 pos, bool freeLos) const {
4433 return canRayHitPoint(pos, freeLos);
4434 }
4435
4436bool Npc::canRayHitPoint(const Tempest::Vec3 pos, bool freeLos, float extRange) const {
4437 const float range = float(hnpc->senses_range) + extRange;
4438 if(qDistTo(pos)>range*range)
4439 return false;
4440
4441 static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range
4442 const DynamicWorld* w = owner.physic();
4443 // npc eyesight height
4444 auto head = visual.mapHeadBone();
4445 if(freeLos) {
4446 return !w->ray(head, pos).hasCol;
4447 }
4448
4449 float dx = x-pos.x, dz=z-pos.z;
4450 float dir = angleDir(dx,dz);
4451 float da = float(M_PI)*(visual.viewDirection()-dir)/180.f;
4452 if(double(std::cos(da))<=ref) {
4453 if(!w->ray(head, pos).hasCol)
4454 return true;
4455 }
4456 return false;
4457 }
4458
4459SensesBit Npc::canSenseNpc(const Npc &oth, bool freeLos, float extRange) const {
4460 // NOTE1: https://github.com/Try/OpenGothic/pull/589#issuecomment-2045897394
4461 // NOTE2: interacting with chest(lockpicking) or some MOBSI should not produce 'noise'
4462 // NOTE3: seem npc can't hear player in general case, and hearing relevant only for sendImmediatePerc cases
4463 const bool isNoisy = false;
4464 const auto mid = oth.bounds().midTr;
4465 return canSenseNpc(mid,freeLos,isNoisy,extRange);
4466 }
4467
4468SensesBit Npc::canSenseNpc(const Tempest::Vec3 pos, bool freeLos, bool isNoisy, float extRange) const {
4469 const float range = float(hnpc->senses_range)+extRange;
4470 if(qDistTo(pos)>range*range)
4471 return SensesBit::SENSE_NONE;
4472
4474
4475 if(isNoisy) {
4476 // no need to be in same room: https://github.com/Try/OpenGothic/issues/420
4477 ret = ret | SensesBit::SENSE_HEAR;
4478 }
4479
4480 if((hnpc->senses & int32_t(SensesBit::SENSE_SEE))!=0 && canRayHitPoint(pos, freeLos, extRange)) {
4481 ret = ret | SensesBit::SENSE_SEE;
4482 }
4483
4484 return ret & SensesBit(hnpc->senses);
4485 }
4486
4487bool Npc::canSeeItem(const Item& it, bool freeLos) const {
4488 DynamicWorld* w = owner.physic();
4489 static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range
4490
4491 const auto itMid = it.midPosition();
4492 const float range = float(hnpc->senses_range);
4493 if(qDistTo(itMid)>range*range)
4494 return false;
4495
4496 if(!freeLos) {
4497 float dx = x-itMid.x, dz=z-itMid.z;
4498 float dir = angleDir(dx,dz);
4499 float da = float(M_PI)*(visual.viewDirection()-dir)/180.f;
4500 if(double(std::cos(da))>ref)
4501 return false;
4502 }
4503
4504 // npc eyesight height
4505 auto head = visual.mapHeadBone();
4506 auto r = w->ray(head,itMid);
4507 auto err = (head-itMid)*(1.f-r.hitFraction);
4508 if(!r.hasCol || err.length()<25.f) {
4509 return true;
4510 }
4511 if(y<=itMid.y && itMid.y<=head.y) {
4512 auto pl = Vec3(head.x,itMid.y,head.z);
4513 r = w->ray(pl,itMid);
4514 err = (pl-itMid)*(1.f-r.hitFraction);
4515 if(!r.hasCol || err.length()<65.f)
4516 return true;
4517 }
4518 return false;
4519 }
4520
4521bool Npc::isAlignedToGround() const {
4522 auto gl = guild();
4523 return (owner.script().guildVal().surface_align[gl]!=0) || isDead() || isLie();
4524 }
4525
4526Vec3 Npc::groundNormal() const {
4527 auto ground = mvAlgo.groundNormal();
4528 const bool align = isAlignedToGround();
4529
4530 if(!align || mvAlgo.isInAir() || mvAlgo.isSwim())
4531 ground = {0,1,0};
4532 if(ground==Vec3())
4533 ground = {0,1,0};
4534 return ground;
4535 }
4536
4537Matrix4x4 Npc::mkPositionMatrix() const {
4538 const auto ground = groundNormal();
4539 const bool align = isAlignedToGround();
4540
4541 float angY = mvAlgo.isDive() ? angleY : 0;
4542 if(align) {
4543 float rot = rotationRad();
4544 float s = std::sin(rot), c = std::cos(rot);
4545 auto dir = Vec3(s,0,-c);
4546 auto norm = Vec3::normalize(ground);
4547
4548 float cx = Vec3::dotProduct(norm,dir);
4549 angY = -std::asin(cx)*180.f/float(M_PI);
4550 }
4551
4552 Matrix4x4 mt = Matrix4x4();
4553 mt.identity();
4554 mt.translate(x,y,z);
4555 mt.rotateOY(180-angle);
4556 if(angY!=0)
4557 mt.rotateOX(-angY);
4558 if(isPlayer() && !align) {
4559 mt.rotateOZ(runAng);
4560 }
4561 mt.scale(sz[0],sz[1],sz[2]);
4562 return mt;
4563 }
4564
4566 updateAnimation(0, true);
4567 }
4568
4569void Npc::updateAnimation(uint64_t dt, bool force) {
4570 const auto camera = Gothic::inst().camera();
4571 if(isPlayer() && camera!=nullptr && camera->isFree())
4572 dt = 0;
4573
4574 if(durtyTranform) {
4575 const auto ground = groundNormal();
4576 if(lastGroundNormal!=ground) {
4577 durtyTranform |= TR_Rot;
4578 lastGroundNormal = ground;
4579 }
4580
4581 sfxWeapon.setPosition(x,y,z);
4582 Matrix4x4 pos;
4583 if(durtyTranform==TR_Pos) {
4584 pos = visual.transform();
4585 pos.set(3,0,x);
4586 pos.set(3,1,y);
4587 pos.set(3,2,z);
4588 } else {
4589 pos = mkPositionMatrix();
4590 }
4591
4592 if(mvAlgo.isSwim()) {
4593 float chest = mvAlgo.canFlyOverWater() ? 0 : (translateY()-visual.pose().rootNode().at(3,1));
4594 float y = pos.at(3,1);
4595 pos.set(3,1,y+chest);
4596 }
4597
4598 visual.setObjMatrix(pos,false);
4599 durtyTranform = 0;
4600 }
4601
4602 bool syncAtt = visual.updateAnimation(this,nullptr,owner,dt,force);
4603 if(syncAtt)
4604 visual.syncAttaches();
4605 }
virtual bool close()=0
virtual bool printScr(Npc &npc, int time, std::string_view msg, int x, int y, std::string_view font)=0
virtual bool outputSvm(Npc &npc, std::string_view text)=0
virtual bool outputOv(Npc &npc, std::string_view text)=0
virtual bool isFinished()=0
virtual bool output(Npc &npc, std::string_view text)=0
void pushFront(AiAction &&a)
Definition aiqueue.cpp:47
int aiOutputOrderId() const
Definition aiqueue.cpp:61
void onWldItemRemoved(const Item &itm)
Definition aiqueue.cpp:69
static AiAction aiRemoveWeapon()
Definition aiqueue.cpp:95
void clear()
Definition aiqueue.cpp:33
void pushBack(AiAction &&a)
Definition aiqueue.cpp:37
AiAction pop()
Definition aiqueue.cpp:55
void save(Serialize &fout) const
Definition aiqueue.cpp:9
void load(Serialize &fin)
Definition aiqueue.cpp:20
size_t size() const
Definition aiqueue.h:36
Tempest::Vec3 midTr
Definition bounds.h:23
void setObjMatrix(const Tempest::Matrix4x4 &m)
Definition bounds.cpp:101
void setHitChance(float v)
Definition bullet.h:55
void setCritChance(float v)
Definition bullet.h:53
void setTarget(const Npc *n)
Definition bullet.cpp:90
void setDamage(DamageCalculator::Damage d)
Definition bullet.h:50
bool isSpell() const
Definition bullet.cpp:73
void setOrigin(Npc *n)
Definition bullet.cpp:81
void reset()
Definition camera.cpp:42
static auto rangeDamageValue(Npc &src) -> Damage
static Val damageValue(Npc &src, Npc &other, const Bullet *b, bool isSpell, const DamageCalculator::Damage &splDmg, const CollideMask bMsk)
static Val damageFall(Npc &src, float speed)
static int32_t damageTypeMask(Npc &npc)
RayLandResult landRay(const Tempest::Vec3 &from, float maxDy=0) const
RayLandResult ray(const Tempest::Vec3 &from, const Tempest::Vec3 &to) const
NpcItem ghostObj(std::string_view visual)
RayQueryResult rayNpc(const Tempest::Vec3 &from, const Tempest::Vec3 &to, const Npc *except) const
void setActive(bool e)
Definition effect.cpp:115
void setTarget(const Npc *npc)
Definition effect.cpp:132
static void onCollide(World &owner, const VisualFx *root, const Tempest::Vec3 &pos, Npc *npc, Npc *other, int32_t splId)
Definition effect.cpp:301
void load(Serialize &fin)
Definition fightalgo.cpp:19
Action nextFromQueue(Npc &npc, Npc &tg, GameScript &owner)
bool isInWRange(const Npc &npc, const Npc &tg, GameScript &owner) const
float prefferedAttackDistance(const Npc &npc, const Npc &tg, GameScript &owner) const
bool hasInstructions() const
void save(Serialize &fout)
Definition fightalgo.cpp:26
@ MV_WAITLONG
Definition fightalgo.h:30
@ MV_JUMPBACK
Definition fightalgo.h:22
@ MV_TURN2HIT
Definition fightalgo.h:31
@ MV_STRAFE_E
Definition fightalgo.h:34
bool fetchInstructions(Npc &npc, Npc &tg, GameScript &owner)
float attackFinishDistance(GameScript &owner) const
void onClearTarget()
bool isInAttackRange(const Npc &npc, const Npc &tg, GameScript &owner) const
void onTakeHit()
bool isInFinishRange(const Npc &npc, const Npc &tg, GameScript &owner) const
bool isInGRange(const Npc &npc, const Npc &tg, GameScript &owner) const
void consumeAction()
bool isInFocusAngle(const Npc &npc, const Npc &tg) const
Definition fplock.h:6
void invokeRefreshAtInsert(Npc &npc)
size_t findSymbolIndex(std::string_view s)
int invokeMana(Npc &npc, Npc *target, int mana)
bool isTalk(const Npc &pl)
std::string_view spellCastAnim(Npc &npc, Item &fn)
uint32_t messageTime(std::string_view id) const
bool isFriendlyFire(const Npc &src, const Npc &dst) const
const AiState & aiState(ScriptFn id)
void fixNpcPosition(Npc &npc, float angle0, float distBias)
void invokeState(const std::shared_ptr< zenkit::INpc > &hnpc, const std::shared_ptr< zenkit::INpc > &hother, const char *name)
zenkit::DaedalusSymbol * findSymbol(std::string_view s)
const zenkit::ISpell & spellDesc(int32_t splId)
std::string_view messageFromSvm(std::string_view id, int voice) const
AiOuputPipe * openAiOuput()
AiOuputPipe * openDlgOuput(Npc &player, Npc &npc)
bool isUnconscious(const Npc &pl)
ScriptFn playerPercAssessMagic()
void invokeSpell(Npc &npc, Npc *target, Item &fn)
const zenkit::IGuildValues & guildVal() const
Definition gamescript.h:107
uint32_t rand(uint32_t max)
Attitude personAttitude(const Npc &p0, const Npc &p1) const
int invokeManaRelease(Npc &npc, Npc *target, int mana)
void printCannotBuyError(Npc &npc)
auto * goldId() const
Definition gamescript.h:95
auto dialogChoices(std::shared_ptr< zenkit::INpc > self, std::shared_ptr< zenkit::INpc > npc, const std::vector< uint32_t > &except, bool includeImp) -> std::vector< DlgChoice >
const VisualFx * spellVfx(int32_t splId)
bool isAttack(const Npc &pl) const
auto canNpcCollideWithSpell(Npc &npc, Npc *shooter, int32_t spellId) -> CollideMask
void initializeInstanceNpc(const std::shared_ptr< zenkit::INpc > &npc, size_t instance)
auto & getVm()
Definition gamescript.h:78
bool isDead(const Npc &pl)
void eventPlayAni(Npc &npc, std::string_view ani)
Camera * camera()
Definition gothic.cpp:319
static Gothic & inst()
Definition gothic.cpp:249
bool attach(Npc &npc)
Tempest::Vec3 nearestPoint(const Npc &to) const
bool detach(Npc &npc, bool quick)
int32_t stateId() const
Definition interactive.h:52
Inventory & inventory()
bool isAttached(const Npc &to)
void unequipArmor(GameScript &vm, 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)
void equipArmor(int32_t cls, Npc &owner)
bool putState(Npc &owner, size_t cls, int state)
Item * currentSpell(uint8_t s)
Definition inventory.h:109
void equipBestMeleeWeapon(Npc &owner)
bool equip(size_t cls, Npc &owner, bool force)
void putToSlot(Npc &owner, size_t cls, std::string_view slot)
Item * currentRangedWeapon()
Definition inventory.h:107
void switchActiveWeaponFist()
bool isEmpty() const
Definition inventory.cpp:90
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
Item * currentShield()
Definition inventory.h:108
void switchActiveWeapon(Npc &owner, uint8_t slot)
void switchActiveSpell(int32_t spell, Npc &owner)
void putAmmunition(Npc &owner, size_t cls, std::string_view slot)
Item * addItem(std::unique_ptr< Item > &&p)
size_t goldCount() const
Item * currentMeleeWeapon()
Definition inventory.h:106
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)
Item * currentArmor()
Definition inventory.h:105
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 save(Serialize &s) const
size_t itemCount(const size_t id) const
Item * getItem(size_t instance)
void updateView(Npc &owner)
Definition item.h:14
@ T_Inventory
Definition item.h:21
Tempest::Vec3 midPosition() const
Definition item.cpp:184
int32_t spellId() const
Definition item.cpp:258
virtual bool isTorchBurn() const
Definition item.cpp:105
void setCount(size_t cnt)
Definition item.cpp:266
const zenkit::IItem & handle() const
Definition item.h:83
bool isCrossbow() const
Definition item.cpp:243
bool is2H() const
Definition item.cpp:238
@ NSLOT
Definition item.h:16
Tempest::Vec3 position() const
Definition item.cpp:180
std::string_view visualSkeletonScheme() const
void setMagicWeaponKey(World &owner, SpellFxKey key, int32_t keyLvl=0)
bool hasAnim(std::string_view scheme) const
void setArmor(Npc &npc, MeshObjects::Mesh &&armor)
void setBody(Npc &npc, MeshObjects::Mesh &&body, const int32_t version)
void syncAttaches()
void processLayers(World &world)
void stopDlgAnim(Npc &npc)
bool isUsingTorch() const
void setHeadRotation(float dx, float dz)
auto mapBone(const size_t boneId) const -> Tempest::Vec3
void stopAnim(Npc &npc, std::string_view anim)
void setVisualBody(World &owner, MeshObjects::Mesh &&body)
Definition mdlvisual.cpp:75
const Skeleton * visualSkeleton() const
void stopWalkAnim(Npc &npc)
auto mapWeaponBone() const -> Tempest::Vec3
void startEffect(World &owner, Effect &&pfx, int32_t slot, bool noSlot)
void setAmmoItem(MeshObjects::Mesh &&ammo, std::string_view bone)
float viewDirection() const
Bounds bounds() const
void setObjMatrix(const Tempest::Matrix4x4 &m, bool syncAttach=false)
void clearSlotItem(std::string_view bone)
void setNpcEffect(World &owner, Npc &npc, std::string_view s, zenkit::NpcFlag flags)
const Animation::Sequence * startAnimItem(Npc &npc, std::string_view scheme, int state)
bool startAnim(Npc &npc, WeaponState st)
const Animation::Sequence * startAnimAndGet(std::string_view name, uint64_t tickCount, bool forceAnim=false)
void save(Serialize &fout, const Npc &npc) const
Definition mdlvisual.cpp:23
void setSword(MeshObjects::Mesh &&sword)
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view bone)
bool updateAnimation(Npc *npc, Interactive *mobsi, World &world, uint64_t dt, bool force)
void interrupt()
bool startAnimDialog(Npc &npc)
void delOverlay(std::string_view sk)
Definition mdlvisual.cpp:97
auto mapHeadBone() const -> Tempest::Vec3
void setVisual(const Skeleton *visual)
Definition mdlvisual.cpp:58
void setStateItem(MeshObjects::Mesh &&itm, std::string_view bone)
bool setToFightMode(const WeaponState ws)
Tempest::Vec3 displayPosition() const
const Pose & pose() const
Definition mdlvisual.h:77
void setRangedWeapon(MeshObjects::Mesh &&bow)
WeaponState fightMode() const
Definition mdlvisual.h:107
void setShield(MeshObjects::Mesh &&shield)
void clearOverlays()
bool setFightMode(zenkit::MdsFightMode mode)
bool processEvents(World &world, uint64_t &barrier, Animation::EvCount &ev)
const Tempest::Matrix4x4 & transform() const
Definition mdlvisual.h:109
void dropShield(Npc &owner)
Tempest::Vec2 headRotation() const
void addOverlay(const Skeleton *sk, uint64_t time)
Definition mdlvisual.cpp:92
void setAnimWhirl(Npc &npc, int dir)
void stopEffect(const VisualFx &vfx)
bool stopItemStateAnim(Npc &npc)
const Animation::Sequence * startAnimSpell(Npc &npc, std::string_view scheme, bool invest)
const Animation::Sequence * continueCombo(Npc &npc, AnimationSolver::Anim a, BodyState bs, WeaponState st, WalkBit wlk)
void setFatness(float f)
bool isStanding() const
void load(Serialize &fin, Npc &npc)
Definition mdlvisual.cpp:37
void setAnimRotate(Npc &npc, int dir)
void updateWeaponSkeleton(const Item *sword, const Item *bow)
bool hasOverlay(const Skeleton *sk) const
Definition mdlvisual.cpp:87
bool isAnimExist(std::string_view name) const
void setMagicWeapon(Effect &&spell, World &owner)
void startFaceAnim(Npc &npc, std::string_view anim, float intensity, uint64_t duration)
void dropWeapon(Npc &owner)
void startMMAnim(Npc &npc, std::string_view anim, std::string_view node)
void emitBlockEffect(Npc &dst, Npc &src)
void setTorch(bool t, World &owner)
void multSpeed(float s)
Definition movealgo.h:47
static const float closeToPointThreshold
Definition movealgo.h:20
void clearSpeed()
Definition movealgo.cpp:529
bool isSlide() const
Definition movealgo.cpp:804
zenkit::MaterialGroup groundMaterial() const
bool canFlyOverWater() const
Definition movealgo.cpp:673
auto groundNormal() const -> Tempest::Vec3
bool isSwim() const
Definition movealgo.cpp:824
auto formerPortalName() -> std::string_view
@ WaitMove
Definition movealgo.h:34
@ FaiMove
Definition movealgo.h:33
bool isFalling() const
Definition movealgo.cpp:800
bool isClimb() const
Definition movealgo.cpp:816
void load(Serialize &fin)
Definition movealgo.cpp:19
bool isInAir() const
Definition movealgo.cpp:808
bool testSlide(const Tempest::Vec3 &p, DynamicWorld::CollisionTest &out, bool cont=false) const
Definition movealgo.cpp:620
auto portalName() -> std::string_view
bool isDive() const
Definition movealgo.cpp:828
void save(Serialize &fout) const
Definition movealgo.cpp:33
void startDive()
Definition movealgo.cpp:786
static const float climbMove
Definition movealgo.h:21
bool startClimb(JumpStatus ani)
Definition movealgo.cpp:746
void accessDamFly(float dx, float dz)
Definition movealgo.cpp:536
bool checkLastBounce() const
Definition movealgo.cpp:680
static bool isClose(const Npc &npc, const Npc &p, float dist)
Definition movealgo.cpp:709
void tick(uint64_t dt, MvFlags fai=NoFlag)
Definition movealgo.cpp:441
int32_t diveTime() const
Definition movealgo.cpp:703
bool isInWater() const
Definition movealgo.cpp:820
Definition npc.h:25
void setFatness(float f)
Definition npc.cpp:925
auto mapHeadBone() const -> Tempest::Vec3
Definition npc.cpp:3378
void resumeAiRoutine()
Definition npc.cpp:3224
auto mapWeaponBone() const -> Tempest::Vec3
Definition npc.cpp:3374
bool startState(ScriptFn id, std::string_view wp)
Definition npc.cpp:2755
bool drawWeaponFist()
Definition npc.cpp:3515
bool drawWeaponBow()
Definition npc.cpp:3565
auto canSenseNpc(const Npc &oth, bool freeLos, float extRange=0.f) const -> SensesBit
Definition npc.cpp:4459
auto cameraBone(bool isFirstPerson=false) const -> Tempest::Vec3
Definition npc.cpp:636
void startDive()
Definition npc.cpp:4333
void setToFightMode(const size_t item)
Definition npc.cpp:3182
Npc(World &owner, size_t instance, std::string_view waypoint, NpcProcessPolicy aiPolicy=NpcProcessPolicy::AiNormal)
Definition npc.cpp:177
void startDialog(Npc &other)
Definition npc.cpp:4016
void setTalentSkill(Talent t, int32_t lvl)
Definition npc.cpp:1125
auto mapBone(std::string_view bone) const -> Tempest::Vec3
Definition npc.cpp:3382
float qDistTo(const Tempest::Vec3 pos) const
bool hasOverlay(std::string_view sk) const
Definition npc.cpp:749
void commitSpell()
Definition npc.cpp:3010
void updateTransform()
Definition npc.cpp:4565
int32_t protection(Protection p) const
Definition npc.cpp:1208
auto displayPosition() const -> Tempest::Vec3
Definition npc.cpp:738
bool resetPositionToTA()
Definition npc.cpp:470
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
void multSpeed(float s)
Definition npc.cpp:4197
bool canSeeSource() const
Definition npc.cpp:4422
bool doAttack(Anim anim, BodyState bs)
Definition npc.cpp:3638
bool startClimb(JumpStatus jump)
Definition npc.cpp:543
void stopAnim(std::string_view ani)
Definition npc.cpp:979
bool isPrehit() const
Definition npc.cpp:3990
bool isLie() const
Definition npc.cpp:3966
~Npc()
Definition npc.cpp:200
auto currentTaPoint() const -> const WayPoint *
Definition npc.cpp:3118
float rotation() const
Definition npc.cpp:658
float translateY() const
Definition npc.cpp:680
void addRoutine(gtime s, gtime e, uint32_t callback, std::string_view point)
Definition npc.cpp:4174
void stopWalking()
Definition npc.cpp:4396
auto walkMode() const
Definition npc.h:113
void setRangedWeapon(MeshObjects::Mesh &&bow)
Definition npc.cpp:883
void takeDamage(Npc &other, const Bullet *b)
Definition npc.cpp:1868
bool setAnim(Anim a)
Definition npc.cpp:941
uint32_t guild() const
Definition npc.cpp:1223
void clearAiQueue()
Definition npc.cpp:4369
auto playAnimByName(std::string_view name, BodyState bs) -> const Animation::Sequence *
Definition npc.cpp:937
bool tryTranslate(const Tempest::Vec3 &to)
bool isInState(ScriptFn stateFn) const
Definition npc.cpp:4153
void invalidateTalentOverlays()
Definition npc.cpp:1049
bool canSeeNpc(const Npc &oth, bool freeLos) const
Definition npc.cpp:4403
bool isInWater() const
Definition npc.cpp:1013
void setActiveSpellInfo(int32_t info)
Definition npc.cpp:3883
bool drawMage(uint8_t slot)
Definition npc.cpp:3588
bool swingSwordR()
Definition npc.cpp:3701
int32_t experience() const
Definition npc.cpp:1258
bool isSwim() const
Definition npc.cpp:1009
@ HS_Dead
Definition npc.h:42
@ HS_NoSound
Definition npc.h:41
void clearSpeed()
Definition npc.cpp:523
bool checkGoToNpcdistance(const Npc &other)
Definition npc.cpp:3450
void setWalkMode(WalkBit m)
Definition npc.cpp:535
void setShield(MeshObjects::Mesh &&shield)
Definition npc.cpp:888
void fistShoot()
Definition npc.cpp:3662
bool isSlide() const
Definition npc.cpp:1041
void emitSoundSVM(std::string_view sound)
Definition npc.cpp:2958
Npc * lookAtTarget() const
Definition npc.cpp:690
bool wasInState(ScriptFn stateFn) const
Definition npc.cpp:4162
void setVisualBody(int32_t headTexNr, int32_t teethTexNr, int32_t bodyVer, int32_t bodyColor, std::string_view body, std::string_view head)
Definition npc.cpp:843
void changeProtection(Protection p, int32_t val)
Definition npc.cpp:1214
auto animMoveSpeed(uint64_t dt) const -> Tempest::Vec3
Definition npc.cpp:834
bool isFriend() const
Definition npc.cpp:1278
void tickAnimationTags()
Definition npc.cpp:2168
int32_t hitChance(Talent t) const
Definition npc.cpp:1149
void aiPush(AiQueue::AiAction &&a)
Definition npc.cpp:3218
bool isJumpAnim() const
Definition npc.cpp:1025
size_t itemCount(size_t id) const
Definition npc.cpp:3454
float rotationRad() const
Definition npc.cpp:662
void moveItem(size_t id, Interactive &to, size_t count=1)
Definition npc.cpp:3294
bool blockSword()
Definition npc.cpp:3708
void setRefuseTalk(uint64_t milis)
Definition npc.cpp:1167
bool rotateTo(float dx, float dz, float speed, AnimationSolver::TurnType anim, uint64_t dt)
Definition npc.cpp:3398
void setPerceptionDisable(PercType t)
Definition npc.cpp:4011
Bounds bounds() const
Definition npc.cpp:674
MoveAlgo::JumpStatus JumpStatus
Definition npc.h:27
void setDirection(const Tempest::Vec3 &pos)
Definition npc.cpp:436
void setSlotItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:899
auto transform() const -> Tempest::Matrix4x4
Definition npc.cpp:632
void updateAnimation(uint64_t dt, bool force=false)
Definition npc.cpp:4569
float rotationY() const
Definition npc.cpp:666
void setAnimRotate(int rot)
Definition npc.cpp:961
void clearState(bool noFinalize)
Definition npc.cpp:2808
bool isFallingDeep() const
Definition npc.cpp:1037
int32_t talentSkill(Talent t) const
Definition npc.cpp:1132
BodyState bodyState() const
Definition npc.cpp:3147
bool isUsingTorch() const
Definition npc.cpp:802
bool shootBow(Interactive *focOverride=nullptr)
Definition npc.cpp:3905
bool setPosition(float x, float y, float z)
Definition npc.cpp:388
bool hasAnim(std::string_view scheme) const
Definition npc.cpp:991
void setPerceptionEnable(PercType t, size_t fn)
Definition npc.cpp:4006
void setProcessPolicy(NpcProcessPolicy t)
Definition npc.cpp:527
int32_t talentValue(Talent t) const
Definition npc.cpp:1143
int32_t activeSpellLevel() const
Definition npc.cpp:3887
bool hasState(BodyState s) const
Definition npc.cpp:3166
bool canFinish(Npc &oth)
Definition npc.cpp:3625
Item * currentShield()
Definition npc.cpp:3370
bool isInAir() const
Definition npc.cpp:1045
int32_t attribute(Attribute a) const
Definition npc.cpp:1171
GoToHint
Definition npc.h:29
@ GT_EnemyG
Definition npc.h:36
@ GT_NextFp
Definition npc.h:32
@ GT_Way
Definition npc.h:31
@ GT_EnemyA
Definition npc.h:33
@ GT_Item
Definition npc.h:34
@ GT_No
Definition npc.h:30
@ GT_Flee
Definition npc.h:37
auto detectedMob() const -> Interactive *
Definition npc.cpp:4145
bool isDead() const
Definition npc.cpp:3962
bool blockFist()
Definition npc.cpp:3666
void onWldItemRemoved(const Item &itm)
Definition npc.cpp:3281
void unequipItem(size_t item)
Definition npc.cpp:3482
int32_t diveTime() const
Definition npc.cpp:1270
void processDefInvTorch()
Definition npc.cpp:4133
void stopEffect(const VisualFx &vfx)
Definition npc.cpp:2976
void setTorch(bool use)
Definition npc.cpp:789
auto dialogChoices(Npc &player, const std::vector< uint32_t > &except, bool includeImp) -> std::vector< GameScript::DlgChoice >
Definition npc.cpp:4353
bool isAiBusy() const
Definition npc.cpp:4363
int32_t experienceNext() const
Definition npc.cpp:1262
bool testMove(const Tempest::Vec3 &pos)
Definition npc.cpp:4201
auto setAnimAngGet(Anim a) -> const Animation::Sequence *
Definition npc.cpp:945
bool isHuman() const
Definition npc.cpp:1234
void setDirectionY(float rotation)
Definition npc.cpp:446
void stopDlgAnim()
Definition npc.cpp:519
void setVisual(std::string_view visual)
Definition npc.cpp:743
JumpStatus tryJump()
Definition npc.cpp:4235
auto inventory() const -> const Inventory &
Definition npc.h:330
bool isDown() const
Definition npc.cpp:3974
auto interactive() const -> Interactive *
Definition npc.h:294
Item * currentMeleeWeapon()
Definition npc.cpp:3362
void runEffect(Effect &&e)
Definition npc.cpp:2980
void dropItem(size_t id, size_t count=1)
Definition npc.cpp:3325
void clearNearestEnemy()
Definition npc.cpp:2920
auto formerPortalName() -> std::string_view
Definition npc.cpp:698
bool closeWeapon(bool noAnim)
Definition npc.cpp:3490
bool setAnimItem(std::string_view scheme, int state)
Definition npc.cpp:965
void endCastSpell(bool playerCtrl=false)
Definition npc.cpp:3869
AnimationSolver::Anim Anim
Definition npc.h:75
bool isPlayer() const
Definition npc.cpp:539
bool haveOutput() const
Definition npc.cpp:2934
void setVictim(Npc *ot)
Definition npc.cpp:2930
auto centerPosition() const -> Tempest::Vec3
Definition npc.cpp:684
void setTalentValue(Talent t, int32_t lvl)
Definition npc.cpp:1138
Item * activeWeapon()
Definition npc.cpp:3458
void setToFistMode()
Definition npc.cpp:3208
std::string_view displayName() const
Definition npc.cpp:734
bool isFalling() const
Definition npc.cpp:1033
void startFaceAnim(std::string_view anim, float intensity, uint64_t duration)
Definition npc.cpp:983
void setSword(MeshObjects::Mesh &&sword)
Definition npc.cpp:878
auto world() -> World &
Definition npc.cpp:624
auto position() const -> Tempest::Vec3
Definition npc.cpp:628
bool isRotationAllowed() const
Definition npc.cpp:3444
bool hasPerc(PercType perc) const
Definition npc.cpp:4093
void buyItem(size_t id, Npc &from, size_t count=1)
Definition npc.cpp:3306
void setRunAngle(float angle)
Definition npc.cpp:458
bool isUnconscious() const
Definition npc.cpp:3970
bool finishingMove()
Definition npc.cpp:3674
bool isImmortal() const
Definition npc.cpp:3994
void attachToPoint(const WayPoint *p)
Definition npc.cpp:4384
void transformBack()
Definition npc.cpp:4337
bool perceptionProcess(Npc &pl)
Definition npc.cpp:4023
int32_t learningPoints() const
Definition npc.cpp:1266
bool isTalk() const
Definition npc.cpp:3982
bool isAttackAnim() const
Definition npc.cpp:3986
void excRoutine(size_t callback)
Definition npc.cpp:4191
void changeAttribute(Attribute a, int32_t val, bool allowUnconscious)
Definition npc.cpp:1177
void clearInventory()
Definition npc.cpp:3354
void useItem(size_t item)
Definition npc.cpp:3470
bool aimBow()
Definition npc.cpp:3895
void setStateItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:903
int32_t magicCyrcle() const
Definition npc.cpp:1250
int32_t mageCycle() const
Definition npc.cpp:1159
void setStateTime(int64_t time)
Definition npc.cpp:4170
bool turnTo(float dx, float dz, bool noAnim, uint64_t dt)
Definition npc.cpp:3394
void setPhysic(DynamicWorld::NpcItem &&item)
Definition npc.cpp:919
bool drawSpell(int32_t spell)
Definition npc.cpp:3599
void load(Serialize &fout, size_t id, std::string_view directory)
Definition npc.cpp:251
bool isCasting() const
Definition npc.cpp:1021
bool canSneak() const
Definition npc.cpp:1163
float rotationYRad() const
Definition npc.cpp:670
void sellItem(size_t id, Npc &to, size_t count=1)
Definition npc.cpp:3298
void postValidate()
Definition npc.cpp:325
CastState
Definition npc.h:45
@ CS_Emit_Last
Definition npc.h:65
@ CS_Emit_0
Definition npc.h:64
@ CS_Invest_0
Definition npc.h:49
@ CS_NoCast
Definition npc.h:46
@ CS_Finalize
Definition npc.h:47
@ CS_Cast_Last
Definition npc.h:62
@ CS_Invest_Last
Definition npc.h:56
@ CS_Cast_0
Definition npc.h:58
Item * takeItem(Item &i)
Definition npc.cpp:3241
bool drawWeaponMelee()
Definition npc.cpp:3539
uint32_t instanceSymbol() const
Definition npc.cpp:1219
bool setInteraction(Interactive *id, bool quick=false)
Definition npc.cpp:4101
bool isAiQueueEmpty() const
Definition npc.cpp:4357
void setAiOutputBarrier(uint64_t dt, bool overlay)
Definition npc.cpp:2940
bool isRefuseTalk() const
Definition npc.cpp:1155
Item * currentArmor()
Definition npc.cpp:3358
bool hasSwimAnimations() const
Definition npc.cpp:995
bool isDive() const
Definition npc.cpp:1017
bool hasAmmunition() const
Definition npc.cpp:3948
bool isStanding() const
Definition npc.cpp:1005
void setAttitude(Attitude att)
Definition npc.cpp:1274
void setCurrentItem(size_t item)
Definition npc.cpp:3478
void delItem(size_t id, uint32_t amount)
Definition npc.cpp:3466
bool tryMove(const Tempest::Vec3 &dp)
void updateArmor()
Definition npc.cpp:860
auto processPolicy() const -> NpcProcessPolicy
Definition npc.h:109
bool isAttack() const
Definition npc.cpp:3978
void setTarget(Npc *t)
Definition npc.cpp:2907
bool isTargetableBySpell(TargetType t) const
Definition npc.cpp:2984
void save(Serialize &fout, size_t id, std::string_view directory)
Definition npc.cpp:205
auto cameraMatrix(bool isFirstPerson=false) const -> Tempest::Matrix4x4
Definition npc.cpp:650
void emitSoundEffect(std::string_view sound, float range, bool freeSlot)
Definition npc.cpp:2946
void setTempAttitude(Attitude att)
Definition npc.cpp:1284
void clearGoTo()
Definition npc.cpp:4389
Item * currentRangedWeapon()
Definition npc.cpp:3366
bool toggleTorch()
Definition npc.cpp:777
bool stopItemStateAnim()
Definition npc.cpp:987
bool isInRoutine(ScriptFn stateFn) const
Definition npc.cpp:4157
void setScale(float x, float y, float z)
Definition npc.cpp:930
void addOverlay(std::string_view sk, uint64_t time)
Definition npc.cpp:758
void setDetectedMob(Interactive *id)
Definition npc.cpp:4140
uint64_t percNextTime() const
Definition npc.cpp:4097
bool isMonster() const
Definition npc.cpp:1227
Item * addItem(size_t id, size_t amount)
Definition npc.cpp:3233
uint64_t stateTime() const
Definition npc.cpp:4166
Item * getItem(size_t id)
Definition npc.cpp:3462
bool isEnemy(const Npc &other) const
Definition npc.cpp:3958
void swingSword()
Definition npc.cpp:3687
void setTrueGuild(int32_t g)
Definition npc.cpp:1240
bool canSeeItem(const Item &it, bool freeLos) const
Definition npc.cpp:4487
bool swingSwordL()
Definition npc.cpp:3694
Npc * target() const
Definition npc.cpp:2916
bool isFlyAnim() const
Definition npc.cpp:1029
BodyState bodyStateMasked() const
Definition npc.cpp:3161
void delOverlay(std::string_view sk)
Definition npc.cpp:769
void emitSoundGround(std::string_view sound, float range, bool freeSlot)
Definition npc.cpp:2951
void clearSlotItem(std::string_view slot)
Definition npc.cpp:911
void setOther(Npc *ot)
Definition npc.cpp:2924
bool canSwitchWeapon() const
Definition npc.cpp:3486
void setMagicWeapon(Effect &&spell)
Definition npc.cpp:893
BeginCastResult
Definition npc.h:68
@ BC_Cast
Definition npc.h:71
@ BC_NoMana
Definition npc.h:70
@ BC_Invest
Definition npc.h:72
@ BC_No
Definition npc.h:69
void tick(uint64_t dt)
Definition npc.cpp:2186
void quitInteraction()
Definition npc.cpp:4124
void setAmmoItem(MeshObjects::Mesh &&itm, std::string_view slot)
Definition npc.cpp:907
void startEffect(Npc &to, const VisualFx &vfx)
Definition npc.cpp:2969
bool canRayHitPoint(const Tempest::Vec3 pos, bool freeLos=true, float extRange=0.f) const
Definition npc.cpp:4436
bool hasStateFlag(BodyState flg) const
Definition npc.cpp:3174
void setPerceptionTime(uint64_t time)
Definition npc.cpp:3998
auto portalName() -> std::string_view
Definition npc.cpp:694
int32_t trueGuild() const
Definition npc.cpp:1244
bool isFinishingMove() const
Definition npc.cpp:999
int32_t level() const
Definition npc.cpp:1254
auto beginCastSpell() -> BeginCastResult
Definition npc.cpp:3716
BodyState bodyState() const
Definition pose.cpp:117
auto rootNode() const -> const Tempest::Matrix4x4
Definition pose.cpp:789
bool isFlyAnim() const
Definition pose.cpp:650
bool isJumpBack(uint64_t tickCount) const
Definition pose.cpp:631
static uint8_t calcAniComb(const Tempest::Vec3 &dpos, float rotation)
Definition pose.cpp:20
bool hasAnim() const
Definition pose.cpp:705
bool isAttackAnim() const
Definition pose.cpp:677
bool isInAnim(std::string_view sq) const
Definition pose.cpp:691
size_t boneCount() const
Definition pose.cpp:808
bool isJumpAnim() const
Definition pose.cpp:641
bool hasState(BodyState s) const
Definition pose.cpp:124
uint64_t animationTotalTime() const
Definition pose.cpp:709
auto rootBone() const -> const Tempest::Matrix4x4
Definition pose.cpp:797
auto bone(size_t id) const -> const Tempest::Matrix4x4 &
Definition pose.cpp:804
Tempest::Vec3 animMoveSpeed(uint64_t tickCount, uint64_t dt) const
Definition pose.cpp:598
bool isPrehit(uint64_t now) const
Definition pose.cpp:670
static uint8_t calcAniCombVert(const Tempest::Vec3 &dpos)
Definition pose.cpp:34
float translateY() const
Definition pose.h:78
uint64_t atkTotalTime() const
Definition pose.cpp:716
bool hasStateFlag(BodyState f) const
Definition pose.cpp:131
size_t findNode(std::string_view b) const
Definition pose.cpp:812
static const Skeleton * loadSkeleton(std::string_view name)
void readNpc(zenkit::DaedalusVm &vm, std::shared_ptr< zenkit::INpc > &npc)
void write(const Arg &... a)
Definition serialize.h:76
bool setEntry(const Args &... args)
Definition serialize.h:57
uint16_t version() const
Definition serialize.h:51
void read(Arg &... a)
Definition serialize.h:81
std::string_view worldName() const
Definition serialize.cpp:87
size_t findNode(std::string_view name, size_t def=size_t(-1)) const
Definition skeleton.cpp:52
size_t BIP01_HEAD
Definition skeleton.h:27
std::string_view name() const
Definition skeleton.h:34
Definition sound.h:5
@ T_Regular
Definition sound.h:8
void play()
Definition sound.cpp:126
void setPosition(const Tempest::Vec3 &pos)
Definition sound.cpp:99
void save(Serialize &fout)
Definition waypath.cpp:18
const WayPoint * first() const
Definition waypath.cpp:43
const WayPoint * last() const
Definition waypath.cpp:49
void load(Serialize &fin)
Definition waypath.cpp:9
const WayPoint * pop()
Definition waypath.cpp:35
void clear()
Definition waypath.cpp:25
uint32_t useCounter() const
Definition waypoint.h:25
Tempest::Vec3 position() const
Definition waypoint.cpp:52
Tempest::Vec3 pos
Definition waypoint.h:31
Tempest::Vec3 dir
Definition waypoint.h:32
bool underWater
Definition waypoint.h:33
Tempest::Vec3 direction() const
Definition waypoint.cpp:56
bool canSeeSource(const Tempest::Vec3 &npc) const
Definition world.h:31
auto version() const -> const VersionInfo &
Definition world.cpp:1023
void sendPassivePerc(Npc &self, Npc &other, int32_t perc)
Definition world.cpp:697
CsCamera * currentCs() const
Definition world.cpp:536
DynamicWorld * physic() const
Definition world.h:81
Interactive * availableMob(const Npc &pl, std::string_view name)
Definition world.cpp:483
WayPath wayTo(const Npc &pos, const WayPoint &end) const
Definition world.cpp:981
uint64_t tickCount() const
Definition world.cpp:387
const WayPoint * findNextFreePoint(const Npc &pos, std::string_view name) const
Definition world.cpp:923
bool isInDialog() const
Definition world.cpp:582
const WayPoint * findPoint(std::string_view name, bool inexact=true) const
Definition world.cpp:871
gtime time() const
Definition world.cpp:405
void detectNpcNear(std::function< void(Npc &)> f)
Definition world.cpp:969
WorldSound * sound()
Definition world.h:80
Item * addItemDyn(size_t itemInstance, const Tempest::Matrix4x4 &pos, size_t owner)
Definition world.cpp:621
const WayPoint & deadPoint() const
Definition world.cpp:965
auto takeItem(Item &it) -> std::unique_ptr< Item >
Definition world.cpp:625
void detectNpc(const Tempest::Vec3 &p, const float r, const std::function< void(Npc &)> &f)
Definition world.cpp:973
MeshObjects::Mesh addView(std::string_view visual) const
Definition world.cpp:251
const WayPoint * findWayPoint(std::string_view name) const
Definition world.cpp:875
Sound addWeaponHitEffect(Npc &src, const Bullet *srcArrow, Npc &reciver)
Definition world.cpp:721
const WayPoint * findNextPoint(const WayPoint &pos) const
Definition world.cpp:957
GameScript & script() const
Definition world.cpp:1019
Bullet & shootSpell(const Item &itm, const Npc &npc, const Npc *target)
Definition world.cpp:637
Bullet & shootBullet(const Item &itmId, const Npc &npc, const Npc *target, const Interactive *inter)
Definition world.cpp:662
void addMilis(uint64_t t)
Definition gametime.h:15
gtime timeInDay() const
Definition gametime.h:18
static const gtime endOfTime()
Definition gametime.h:23
int64_t toInt() const
Definition gametime.h:14
static CommandLine * instance
Guild
Definition constants.h:8
@ GIL_DRAGON
Definition constants.h:60
@ GIL_SHADOWBEAST_SKELETON
Definition constants.h:50
@ GIL_G1_UNDEADORC
Definition constants.h:114
@ GIL_ZOMBIE
Definition constants.h:47
@ GIL_G1_SEPERATOR_HUM
Definition constants.h:99
@ GIL_SKELETON
Definition constants.h:44
@ GIL_SEPERATOR_ORC
Definition constants.h:71
@ GIL_NONE
Definition constants.h:9
@ GIL_SUMMONED_SKELETON
Definition constants.h:45
@ GIL_SKELETON_MAGE
Definition constants.h:46
@ GIL_SEPERATOR_HUM
Definition constants.h:29
@ GIL_G1_SKELETON
Definition constants.h:115
@ GIL_MAX
Definition constants.h:79
@ GIL_G1_SEPERATOR_ORC
Definition constants.h:120
@ GIL_GOBBO_SKELETON
Definition constants.h:33
@ GIL_G1_ZOMBIE
Definition constants.h:113
@ GIL_SUMMONED_GOBBO_SKELETON
Definition constants.h:34
CollideMask
Definition constants.h:252
@ COLL_DOEVERYTHING
Definition constants.h:254
@ COLL_APPLYVICTIMSTATE
Definition constants.h:258
@ COLL_DONOTHING
Definition constants.h:253
@ COLL_DONTKILL
Definition constants.h:259
Talent
Definition constants.h:435
@ TALENT_1H
Definition constants.h:437
@ TALENT_RUNES
Definition constants.h:449
@ TALENT_BOW
Definition constants.h:439
@ TALENT_CROSSBOW
Definition constants.h:440
@ TALENT_MAGE
Definition constants.h:442
@ TALENT_MAX_G2
Definition constants.h:459
@ TALENT_2H
Definition constants.h:438
@ TALENT_SNEAK
Definition constants.h:443
@ TALENT_ACROBAT
Definition constants.h:446
WalkBit
Definition constants.h:209
@ LOOP_CONTINUE
Definition constants.h:514
@ LOOP_END
Definition constants.h:515
NpcProcessPolicy
Definition constants.h:506
TargetType
Definition constants.h:288
@ TARGET_TYPE_ORCS
Definition constants.h:292
@ TARGET_TYPE_NPCS
Definition constants.h:291
@ TARGET_TYPE_HUMANS
Definition constants.h:293
@ TARGET_TYPE_ALL
Definition constants.h:289
@ TARGET_TYPE_UNDEAD
Definition constants.h:294
SensesBit
Definition constants.h:297
Attitude
Definition constants.h:234
@ ATT_HOSTILE
Definition constants.h:235
@ AI_EquipBestArmor
Definition constants.h:357
@ AI_UseItem
Definition constants.h:361
@ AI_UnEquipWeapons
Definition constants.h:370
@ AI_DrawWeapon
Definition constants.h:383
@ AI_AlignToFp
Definition constants.h:378
@ AI_Output
Definition constants.h:372
@ AI_StopProcessInfo
Definition constants.h:376
@ AI_GoToNpc
Definition constants.h:347
@ AI_GoToNextFp
Definition constants.h:348
@ AI_OutputSvm
Definition constants.h:373
@ AI_UseMob
Definition constants.h:360
@ AI_RemoveWeapon
Definition constants.h:345
@ AI_None
Definition constants.h:342
@ AI_OutputSvmOverlay
Definition constants.h:374
@ AI_UnEquipArmor
Definition constants.h:371
@ AI_EquipRange
Definition constants.h:359
@ AI_GotoItem
Definition constants.h:385
@ AI_PointAt
Definition constants.h:387
@ AI_TakeItem
Definition constants.h:384
@ AI_StandUpQuick
Definition constants.h:355
@ AI_WhirlToNpc
Definition constants.h:391
@ AI_Attack
Definition constants.h:367
@ AI_LookAtNpc
Definition constants.h:343
@ AI_DrawSpell
Definition constants.h:366
@ AI_Dodge
Definition constants.h:369
@ AI_FinishingMove
Definition constants.h:382
@ AI_ContinueRoutine
Definition constants.h:377
@ AI_PointAtNpc
Definition constants.h:386
@ AI_TurnToNpc
Definition constants.h:346
@ AI_GoToPoint
Definition constants.h:349
@ AI_DrawWeaponRange
Definition constants.h:365
@ AI_Teleport
Definition constants.h:363
@ AI_DrawWeaponMelee
Definition constants.h:364
@ AI_ProcessInfo
Definition constants.h:375
@ AI_StartState
Definition constants.h:350
@ AI_AlignToWp
Definition constants.h:379
@ AI_SetWalkMode
Definition constants.h:381
@ AI_SetNpcsToState
Definition constants.h:380
@ AI_StopLookAt
Definition constants.h:344
@ AI_PlayAnimBs
Definition constants.h:352
@ AI_UseItemToState
Definition constants.h:362
@ AI_TurnAway
Definition constants.h:392
@ AI_LookAt
Definition constants.h:390
@ AI_Wait
Definition constants.h:353
@ AI_StopPointAt
Definition constants.h:388
@ AI_EquipMelee
Definition constants.h:358
@ AI_StandUp
Definition constants.h:354
@ AI_PlayAnim
Definition constants.h:351
@ AI_EquipArmor
Definition constants.h:356
@ AI_PrintScreen
Definition constants.h:389
@ AI_Flee
Definition constants.h:368
@ MAX_AI_USE_DISTANCE
Definition constants.h:129
ItmFlags
Definition constants.h:312
@ ITM_CAT_ARMOR
Definition constants.h:317
SpellCode
Definition constants.h:474
@ SPL_RECEIVEINVEST
Definition constants.h:476
@ SPL_SENDSTOP
Definition constants.h:478
@ SPL_DONTINVEST
Definition constants.h:475
@ SPL_SENDCAST
Definition constants.h:477
@ SPL_STATUS_CANINVEST_NO_MANADEC
Definition constants.h:480
@ SPL_NEXTLEVEL
Definition constants.h:479
@ MaxFightRange
Definition constants.h:137
const char * MaterialGroupNames[]
Definition constants.h:496
@ MAT_METAL
Definition constants.h:245
Attribute
Definition constants.h:462
@ ATR_MANAMAX
Definition constants.h:466
@ ATR_HITPOINTSMAX
Definition constants.h:464
@ ATR_MAX
Definition constants.h:471
@ ATR_DEXTERITY
Definition constants.h:468
@ ATR_HITPOINTS
Definition constants.h:463
@ ATR_REGENERATEMANA
Definition constants.h:470
@ ATR_REGENERATEHP
Definition constants.h:469
@ ATR_MANA
Definition constants.h:465
Protection
Definition constants.h:484
@ PROT_MAX
Definition constants.h:493
BodyState
Definition constants.h:140
@ BS_SNEAK
Definition constants.h:156
@ BS_MOD_MASK
Definition constants.h:152
@ BS_CASTING
Definition constants.h:182
@ BS_NONE
Definition constants.h:141
@ BS_PARADE
Definition constants.h:181
@ BS_CLIMB
Definition constants.h:163
@ BS_RUN
Definition constants.h:157
@ BS_FLAG_MASK
Definition constants.h:153
@ BS_LIE
Definition constants.h:166
@ BS_FALL
Definition constants.h:164
@ BS_MAX
Definition constants.h:185
@ BS_HIT
Definition constants.h:180
@ BS_DEAD
Definition constants.h:177
@ BS_STUMBLE
Definition constants.h:175
@ BS_UNCONSCIOUS
Definition constants.h:176
@ BS_WALK
Definition constants.h:155
@ BS_STAND
Definition constants.h:186
SpellCategory
Definition constants.h:262
@ SPELL_BAD
Definition constants.h:265
WeaponState
Definition constants.h:191
PercType
Definition constants.h:396
@ PERC_ASSESSTHEFT
Definition constants.h:416
@ PERC_ASSESSQUIETSOUND
Definition constants.h:413
@ PERC_ASSESSMAGIC
Definition constants.h:426
@ PERC_ASSESSBODY
Definition constants.h:402
@ PERC_ASSESSTALK
Definition constants.h:418
@ PERC_ASSESSMURDER
Definition constants.h:405
@ PERC_ASSESSPLAYER
Definition constants.h:399
@ PERC_ASSESSDEFEAT
Definition constants.h:406
@ PERC_Count
Definition constants.h:432
@ PERC_ASSESSDAMAGE
Definition constants.h:407
@ PERC_ASSESSREMOVEWEAPON
Definition constants.h:410
@ PERC_ASSESSOTHERSDAMAGE
Definition constants.h:408
@ PERC_ASSESSENEMY
Definition constants.h:400
@ PERC_ASSESSFIGHTSOUND
Definition constants.h:412
std::string addExt(const std::string &s, const char *ext)
Definition fileext.h:82
static std::string_view humansTorchOverlay
Definition npc.cpp:23
const WayPoint * point
Definition aiqueue.h:22
std::string s1
Definition aiqueue.h:30
ScriptFn func
Definition aiqueue.h:24
std::string s0
Definition aiqueue.h:27
zenkit::MdsFightMode weaponCh
Definition animation.h:37
uint8_t groundSounds
Definition animation.h:36
std::vector< EvMorph > morph
Definition animation.h:39
std::vector< EvTimed > timed
Definition animation.h:38
uint8_t def_opt_frame
Definition animation.h:35
float totalTime() const
bool testMove(const Tempest::Vec3 &to, CollisionTest &out)
void setPosition(const Tempest::Vec3 &pos)
void setUserPointer(void *p)
const Tempest::Vec3 & position() const
auto tryMove(const Tempest::Vec3 &to, CollisionTest &out) -> DynamicWorld::MoveCode
AnimationSolver::Anim anim
Definition movealgo.h:26
int32_t talentsSk[TALENT_MAX_G2]
Definition npc.cpp:166
int32_t bdColor
Definition npc.cpp:171
int32_t vColor
Definition npc.cpp:170
void save(Serialize &fout)
Definition npc.cpp:156
std::shared_ptr< zenkit::INpc > hnpc
Definition npc.cpp:164
int32_t talentsVl[TALENT_MAX_G2]
Definition npc.cpp:167
int32_t vHead
Definition npc.cpp:170
TransformBack(Npc &owner, zenkit::DaedalusVm &vm, Serialize &fin)
Definition npc.cpp:115
const Skeleton * skeleton
Definition npc.cpp:173
std::string body
Definition npc.cpp:169
TransformBack(Npc &self)
Definition npc.cpp:97
int32_t vTeeth
Definition npc.cpp:170
std::string head
Definition npc.cpp:169
Inventory invent
Definition npc.cpp:165
void undo(Npc &self)
Definition npc.cpp:128