OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
mdlvisual.cpp
Go to the documentation of this file.
1#include "mdlvisual.h"
2
4#include "game/serialize.h"
5#include "utils/string_frm.h"
6#include "world/objects/npc.h"
9#include "world/world.h"
10#include "objvisual.h"
11#include "gothic.h"
12
13using namespace Tempest;
14
16 :skInst(std::make_unique<Pose>()) {
17 pos.identity();
18 }
19
22
23void MdlVisual::save(Serialize &fout, const Npc&) const {
24 fout.write(fgtMode);
25 if(skeleton!=nullptr)
26 fout.write(skeleton->name()); else
27 fout.write(std::string(""));
28 solver.save(fout);
29 skInst->save(fout);
30 }
31
32void MdlVisual::save(Serialize& fout, const Interactive&) const {
33 solver.save(fout);
34 skInst->save(fout);
35 }
36
37void MdlVisual::load(Serialize& fin, Npc& npc) {
38 std::string s;
39
40 fin.read(fgtMode);
41 fin.read(s);
42 npc.setVisual(s);
43
44 solver.load(fin);
45 skInst->load(fin,solver);
46 if(skeleton!=nullptr)
47 rebindAttaches(*skeleton);
48 }
49
51 solver.load(fin);
52 skInst->load(fin,solver);
53 if(skeleton!=nullptr)
54 rebindAttaches(*skeleton);
55 }
56
57// Mdl_SetVisual
59 if(v!=nullptr)
60 rebindAttaches(*v);
61 solver.setSkeleton(v);
62 skInst->setSkeleton(v);
63 view.setSkeleton(v);
64
65 skeleton = v;
66 setObjMatrix(pos); // invalidate obj matrix
67 }
68
70 if(e)
71 skInst->setFlags(Pose::NoFlags); else
72 skInst->setFlags(Pose::NoTranslation);
73 }
74
76 implSetBody(nullptr,owner,std::move(body),0);
77 }
78
79// Mdl_SetVisualBody
80void MdlVisual::setVisualBody(Npc& npc, MeshObjects::Mesh &&h, MeshObjects::Mesh &&body, int32_t version) {
81 bind(head,std::move(h),"BIP01 HEAD");
82 implSetBody(&npc,npc.world(),std::move(body),version);
83 head.view.setAsGhost(hnpcFlagGhost);
84 head.view.setFatness(npc.fatness());
85 }
86
87bool MdlVisual::hasOverlay(const Skeleton* sk) const {
88 return solver.hasOverlay(sk);
89 }
90
91// Mdl_ApplyOverlayMdsTimed, Mdl_ApplyOverlayMds
92void MdlVisual::addOverlay(const Skeleton *sk, uint64_t time) {
93 solver.addOverlay(sk,time);
94 }
95
96// Mdl_RemoveOverlayMDS
97void MdlVisual::delOverlay(std::string_view sk) {
98 solver.delOverlay(sk);
99 }
100
101// Mdl_RemoveOverlayMDS
103 solver.delOverlay(sk);
104 }
105
107 solver.clearOverlays();
108 }
109
110void MdlVisual::setBody(Npc& npc, MeshObjects::Mesh&& a, const int32_t version) {
111 implSetBody(&npc,npc.world(),std::move(a),version);
112 setObjMatrix(pos);
113 }
114
116 // NOTE: Giant_Bug have no version tag in attachments;
117 // Light dragon hunter armour has broken attachment with no tags
118 // Big dragon hunter armour has many atachments with version tags
119 implSetBody(&npc,npc.world(),std::move(armor),-1);
120 setObjMatrix(pos);
121 }
122
123void MdlVisual::implSetBody(Npc* npc, World& world, MeshObjects::Mesh&& body, const int32_t version) {
124 attach.clear();
125 if(auto p = body.protoMesh()) {
126 for(auto& att:p->attach) {
127 if(!att.hasNode) {
128 auto view = world.addAtachView(att,version);
129 setSlotAttachment(std::move(view),att.name);
130 }
131 }
132 syncAttaches();
133 }
134 view = std::move(body);
135 view.setAsGhost(hnpcFlagGhost);
136 view.setFatness(npc==nullptr ? 0 : npc->fatness());
137 view.setSkeleton(skeleton);
138 view.setPose(pos,*skInst);
139 hnpcVisual.view.setMesh(&view);
140 }
141
142void MdlVisual::setSlotAttachment(MeshObjects::Mesh&& itm, std::string_view bone) {
143 if(bone.empty() || skeleton==nullptr)
144 return;
145
146 size_t id = skeleton->findNode(bone);
147 if(id==size_t(-1))
148 return;
149
150 for(auto& i:attach) {
151 if(i.boneId==id) {
152 i.view=std::move(itm);
153 syncAttaches();
154 return;
155 }
156 }
157
158 MeshAttach slt;
159 slt.bone = skeleton->nodes[id].name;
160 bind(slt,std::move(itm),slt.bone);
161 attach.push_back(std::move(slt));
162 }
163
165 bind(sword,std::move(s),"ZS_RIGHTHAND");
166 }
167
169 bind(bow,std::move(b),"ZS_BOW");
170 }
171
173 bind(shield,std::move(s),"ZS_SHIELD");
174 }
175
176void MdlVisual::setAmmoItem(MeshObjects::Mesh&& a, std::string_view bone) {
177 bind(ammunition,std::move(a),bone);
178 }
179
181 auto n = std::move(pfx.view);
182 n.setLooped(false);
183 n.setActive(false);
184 startEffect(owner,std::move(n),0,true);
185
186 pfx.view = std::move(spell);
187 pfx.view.setLooped(true);
188 pfx.view.setPhysicsDisable();
189 if(skeleton!=nullptr)
190 pfx.view.bindAttaches(*skInst,*skeleton);
191 }
192
193void MdlVisual::setMagicWeaponKey(World& owner, SpellFxKey key, int32_t keyLvl) {
194 pfx.view.setKey(owner,key,keyLvl);
195 }
196
197void MdlVisual::setSlotItem(MeshObjects::Mesh &&itm, std::string_view bone) {
198 if(bone.empty() || skeleton==nullptr)
199 return;
200
201 size_t id = skeleton->findNode(bone);
202 if(id==size_t(-1))
203 return;
204
205 // HACK: light dragon hunter armour
206 if(id==head.boneId && !head.view.isEmpty())
207 return;
208
209 for(auto& i:item) {
210 if(i.boneId==id) {
211 i.view=std::move(itm);
212 syncAttaches();
213 return;
214 }
215 }
216
217 MeshAttach slt;
218 slt.bone = skeleton->nodes[id].name;
219 bind(slt,std::move(itm),slt.bone);
220 item.push_back(std::move(slt));
221 syncAttaches();
222 }
223
224void MdlVisual::setStateItem(MeshObjects::Mesh&& a, std::string_view bone) {
225 bind(stateItm,std::move(a),bone);
226 syncAttaches();
227 }
228
229void MdlVisual::clearSlotItem(std::string_view bone) {
230 size_t id = skeleton==nullptr ? size_t(-1) : skeleton->findNode(bone);
231
232 for(size_t i=0;i<item.size();++i) {
233 if(id==size_t(-1) || item[i].boneId==id) {
234 item[i] = std::move(item.back());
235 item.pop_back();
236 syncAttaches();
237 return;
238 }
239 }
240 }
241
242bool MdlVisual::setFightMode(zenkit::MdsFightMode mode) {
244
245 switch(mode) {
246 case zenkit::MdsFightMode::INVALID:
247 return false;
248 case zenkit::MdsFightMode::NONE:
250 break;
251 case zenkit::MdsFightMode::FIST:
253 break;
254 case zenkit::MdsFightMode::SINGLE_HANDED:
256 break;
257 case zenkit::MdsFightMode::DUAL_HANDED:
259 break;
260 case zenkit::MdsFightMode::BOW:
262 break;
263 case zenkit::MdsFightMode::CROSSBOW:
265 break;
266 case zenkit::MdsFightMode::MAGIC:
268 break;
269 }
270
271 return setToFightMode(f);
272 }
273
275 MeshAttach* att = &sword;
276 auto& pose = *skInst;
277
278 if(fgtMode!=WeaponState::W1H && fgtMode!=WeaponState::W2H &&
279 fgtMode!=WeaponState::Bow && fgtMode!=WeaponState::CBow)
280 return;
281
282 auto p = pos;
283 if(att->boneId<pose.boneCount())
284 p = pose.bone(att->boneId);
285
286 Item* itm = nullptr;
287 if(fgtMode==WeaponState::W1H || fgtMode==WeaponState::W2H)
288 itm = npc.currentMeleeWeapon(); else
289 itm = npc.currentRangedWeapon();
290
291 if(itm==nullptr)
292 return;
293
294 auto it = npc.world().addItemDyn(itm->clsId(),p,npc.handle().symbol_index());
295 it->setCount(1);
296
297 npc.delItem(itm->clsId(),1);
298 }
299
301 MeshAttach* att = &sword;
302 auto& pose = *skInst;
303
304 if(fgtMode!=WeaponState::W1H && fgtMode!=WeaponState::W2H)
305 return;
306
307 auto p = pos;
308 if(att->boneId<pose.boneCount())
309 p = pose.bone(att->boneId);
310
311 Item* itm = npc.currentShield();
312 if(itm==nullptr)
313 return;
314
315 auto it = npc.world().addItemDyn(itm->clsId(),p,npc.handle().symbol_index());
316 it->setCount(1);
317
318 npc.delItem(itm->clsId(),1);
319 }
320
321void MdlVisual::startEffect(World& owner, Effect&& vfx, int32_t slot, bool noSlot) {
322 uint64_t timeUntil = vfx.effectPrefferedTime();
323 if(timeUntil!=0)
324 timeUntil+=owner.tickCount();
325
326 if(skeleton==nullptr)
327 return;
328
329 vfx.setMesh(&view);
330 vfx.setBullet(nullptr,owner);
331
332 for(auto& i:effects) {
333 if(i.id==slot && !i.noSlot) {
334 i.timeUntil = timeUntil;
335 i.view = std::move(vfx);
336 i.view.bindAttaches(*skInst,*skeleton);
337 syncAttaches();
338 return;
339 }
340 }
341
342 PfxSlot slt;
343 slt.id = slot;
344 slt.timeUntil = timeUntil;
345 slt.noSlot = noSlot;
346 slt.view = std::move(vfx);
347 slt.view.bindAttaches(*skInst,*skeleton);
348 effects.push_back(std::move(slt));
349 syncAttaches();
350 }
351
353 for(size_t i=0;i<effects.size();++i) {
354 if(effects[i].view.is(vfx)) {
355 effects[i] = std::move(effects.back());
356 effects.pop_back();
357 syncAttaches();
358 return;
359 }
360 }
361 }
362
363void MdlVisual::stopEffect(int32_t slot) {
364 for(size_t i=0;i<effects.size();++i) {
365 if(effects[i].id==slot) {
366 effects[i] = std::move(effects.back());
367 effects.pop_back();
368 syncAttaches();
369 return;
370 }
371 }
372 }
373
374void MdlVisual::setNpcEffect(World& owner, Npc& npc, std::string_view s, zenkit::NpcFlag flags) {
375 if(hnpcVisualName!=s) {
376 hnpcVisualName = s;
377 auto vfx = Gothic::inst().loadVisualFx(s);
378 if(vfx==nullptr) {
379 hnpcVisual.view = Effect();
380 return;
381 }
382 hnpcVisual.view = Effect(*vfx,owner,npc,SpellFxKey::Count);
383 if(skeleton!=nullptr)
384 hnpcVisual.view.bindAttaches(*skInst,*skeleton);
385 hnpcVisual.view.setActive(true);
386 hnpcVisual.view.setLooped(true);
387 hnpcVisual.view.setMesh(&view);
388 }
389
390 const bool nextGhost = (flags & zenkit::NpcFlag::GHOST);
391 if(hnpcFlagGhost!=nextGhost) {
392 hnpcFlagGhost=nextGhost;
393 view.setAsGhost(hnpcFlagGhost);
394 head.view.setAsGhost(hnpcFlagGhost);
395 for(auto& i:attach)
396 i.view.setAsGhost(hnpcFlagGhost);
397 }
398 }
399
400void MdlVisual::setFatness(float fatness) {
401 view.setFatness(fatness);
402 head.view.setFatness(fatness);
403 for(auto& i:attach)
404 i.view.setFatness(fatness);
405 }
406
407void MdlVisual::emitBlockEffect(Npc& dest, Npc& source) {
408 auto& world = source.world();
409
410 auto& pose = *skInst;
411 auto p = pos;
412 if(sword.boneId<pose.boneCount())
413 p = pose.bone(sword.boneId);
414
415 auto src = source.inventory().activeWeapon();
416 auto dst = dest .inventory().activeWeapon();
417
418 if(src==nullptr || dst==nullptr)
419 return;
420
421 auto s = world.addWeaponBlkEffect(ItemMaterial(src->handle().material),ItemMaterial(dst->handle().material),p);
422 s.play();
423 }
424
426 if(f==fgtMode)
427 return false;
428 fgtMode = f;
429 return true;
430 }
431
432void MdlVisual::setObjMatrix(const Tempest::Matrix4x4 &m, bool syncAttach) {
433 pos = m;
434 skInst->setObjectMatrix(m,syncAttach);
435 view.setPose(m,*skInst);
436 if(syncAttach)
437 syncAttaches();
438 }
439
440void MdlVisual::setHeadRotation(float dx, float dz) {
441 skInst->setHeadRotation(dx,dz);
442 syncAttaches(head);
443 }
444
446 return skInst->headRotation();
447 }
448
449void MdlVisual::updateWeaponSkeleton(const Item* weapon, const Item* range) {
450 auto st = fgtMode;
451 if(st==WeaponState::W1H || st==WeaponState::W2H){
452 bind(sword, "ZS_RIGHTHAND");
453 } else {
454 bool twoHands = weapon!=nullptr && weapon->is2H();
455 bind(sword,twoHands ? "ZS_LONGSWORD" : "ZS_SWORD");
456 }
457
458 if(st==WeaponState::Bow || st==WeaponState::CBow){
459 if(st==WeaponState::Bow)
460 bind(bow,"ZS_LEFTHAND"); else
461 bind(bow,"ZS_RIGHTHAND");
462 } else {
463 bool cbow = range!=nullptr && range->isCrossbow();
464 bind(bow,cbow ? "ZS_CROSSBOW" : "ZS_BOW");
465 }
466
467 bind(shield, st==WeaponState::W1H ? "ZS_LEFTARM" : "ZS_SHIELD");
468
469 pfx.view.setActive(st==WeaponState::Mage);
470 syncAttaches();
471 }
472
473void MdlVisual::setTorch(bool t, World& owner) {
474 if(!t) {
475 torch.view.reset();
476 return;
477 }
478 size_t torchId = owner.script().findSymbolIndex("ItLsTorchburning");
479 if(torchId==size_t(-1))
480 return;
481
482 auto hitem = std::make_shared<zenkit::IItem>();
483 owner.script().initializeInstanceItem(hitem, torchId);
484 torch.view.reset(new ObjVisual());
485 torch.view->setVisual(*hitem,owner,false);
486 torch.boneId = (skeleton==nullptr ? size_t(-1) : skeleton->findNode("ZS_LEFTHAND"));
487
488 auto& pose = *skInst;
489 auto p = pos;
490 if(torch.boneId<pose.boneCount())
491 p = pose.bone(torch.boneId);
492 torch.view->setObjMatrix(p);
493 }
494
496 return torch.view!=nullptr;
497 }
498
499bool MdlVisual::updateAnimation(Npc* npc, Interactive* mobsi, World& world, uint64_t dt, bool force) {
500 Pose& pose = *skInst;
501 uint64_t tickCount = world.tickCount();
502 auto pos3 = Vec3{pos.at(3,0), pos.at(3,1), pos.at(3,2)};
503
504 if(npc!=nullptr && world.isInSfxRange(pos3))
505 pose.processSfx(*npc,tickCount);
506 if(mobsi!=nullptr && world.isInSfxRange(pos3))
507 pose.processSfx(*mobsi,tickCount);
508 if(world.isInPfxRange(pos3))
509 pose.processPfx(*this,world,tickCount);
510
511 for(size_t i=0;i<effects.size();) {
512 if(effects[i].timeUntil<tickCount) {
513 effects[i] = std::move(effects.back());
514 effects.pop_back();
515 } else {
516 effects[i].view.tick(dt);
517 ++i;
518 }
519 }
520
521 solver.update(tickCount);
522 pose.setObjectMatrix(pos,false);
523 const bool changed = pose.update(tickCount, force);
524
525 if(changed)
526 view.setPose(pos,pose);
527 return changed;
528 }
529
531 Pose& pose = *skInst;
532 uint64_t tickCount = world.tickCount();
533 pose.processLayers(solver,tickCount);
534 }
535
536bool MdlVisual::processEvents(World& world, uint64_t &barrier, Animation::EvCount &ev) {
537 Pose& pose = *skInst;
538 uint64_t tickCount = world.tickCount();
539 return pose.processEvents(barrier,tickCount,ev);
540 }
541
542Vec3 MdlVisual::mapBone(const size_t boneId) const {
543 Pose& pose = *skInst;
544 if(boneId==size_t(-1))
545 return {pos.at(3,0), pos.at(3,1), pos.at(3,2)};
546
547 auto mat = pose.bone(boneId);
548 return {mat.at(3,0), mat.at(3,1), mat.at(3,2)};
549 }
550
552 if(fgtMode==WeaponState::Bow || fgtMode==WeaponState::CBow)
553 return mapBone(ammunition.boneId);
554 if(fgtMode==WeaponState::Mage && skeleton!=nullptr)
555 return mapBone(skeleton->findNode("ZS_RIGHTHAND"));
556 return {pos.at(3,0), pos.at(3,1), pos.at(3,2)};
557 }
558
560 if(skeleton->BIP01_HEAD==size_t(-1))
561 return {pos.at(3,0), pos.at(3,1)+180, pos.at(3,2)};
562 return mapBone(skeleton->BIP01_HEAD);
563 }
564
565void MdlVisual::stopAnim(Npc& npc, std::string_view anim) {
566 skInst->stopAnim(anim);
567 // Avoid issues with pfx on/off events within looped animations. Such as for Rupert and Bulko.
568 if(anim.empty())
569 effects.clear();
570 if(!skInst->hasAnim())
572 }
573
575 if(!skInst->stopItemStateAnim(solver,npc.world().tickCount()))
576 return false;
577 if(!skInst->hasAnim())
579 return true;
580 }
581
582bool MdlVisual::hasAnim(std::string_view scheme) const {
583 return solver.solveFrm(scheme)!=nullptr;
584 }
585
587 skInst->stopWalkAnim();
588 if(!skInst->hasAnim())
590 }
591
593 return skInst->isStanding();
594 }
595
596bool MdlVisual::isAnimExist(std::string_view name) const {
597 const Animation::Sequence *sq = solver.solveFrm(name);
598 return sq!=nullptr;
599 }
600
601const Animation::Sequence* MdlVisual::startAnimAndGet(std::string_view name, uint64_t tickCount, bool forceAnim) {
602 auto sq = solver.solveFrm(name);
603 if(sq!=nullptr) {
604 const Pose::StartHint hint = Pose::StartHint(forceAnim ? Pose::Force : Pose::NoHint);
605 if(skInst->startAnim(solver,sq,0,BS_NONE,hint,tickCount)) {
606 return sq;
607 }
608 }
609 return nullptr;
610 }
611
612const Animation::Sequence* MdlVisual::startAnimAndGet(Npc &npc, std::string_view name, uint8_t comb, BodyState bs) {
613 const Animation::Sequence* sq = solver.solveFrm(name);
614 if(skInst->startAnim(solver,sq,comb,bs,Pose::NoHint,npc.world().tickCount()))
615 return sq;
616 return nullptr;
617 }
618
620 uint8_t comb,
621 WeaponState st, WalkBit wlk) {
622 // for those use MdlVisual::setRotation
625
630 auto inter = npc.interactive();
631 const Animation::Sequence *sq = solver.solveAnim(inter,a,*skInst);
632 if(sq!=nullptr){
633 auto bs=inter->isLadder() ? BS_CLIMB : BS_MOBINTERACT;
634 if(skInst->startAnim(solver,sq,comb,bs,Pose::NoHint,npc.world().tickCount()))
635 return sq;
636 }
637 return nullptr;
638 }
639
641 skInst->stopAllAnim();
642 return nullptr;
643 }
644
645 const Animation::Sequence *sq = solver.solveAnim(a,st,wlk,*skInst);
646 if(sq==nullptr)
647 return nullptr;
648
649 bool forceAnim=false;
653 skInst->stopAllAnim();
654 forceAnim = true;
655 }
657 forceAnim = true;
658 }
659
660 BodyState bs = BS_NONE;
661 switch(a) {
663 bs = BS_NONE;
664 break;
668 bs = BS_LIE;
669 break;
672 bs = BS_STAND;
673 break;
677 if(bool(wlk & WalkBit::WM_Walk))
678 bs = BS_WALK; else
679 bs = BS_RUN;
680 break;
683 bs = BS_PARADE; else
684 bs = BS_RUN;
689 break;
694 bs = BS_FALL;
695 break;
698 bs = BS_JUMP;
699 break;
703 bs = BS_CLIMB;
704 break;
707 bs = BS_NONE;
708 break;
711 bs = BS_DEAD;
712 break;
715 bs = BS_UNCONSCIOUS;
716 break;
721 bs = BS_MOBINTERACT;
722 break;
727 bs = BS_HIT;
728 break;
730 bs = BS_PARADE;
731 break;
734 bs = BS_STUMBLE;
735 break;
737 bs = BS_AIMNEAR; //TODO: BS_AIMFAR
738 break;
740 bs = BS_TAKEITEM;
741 break;
743 bs = BS_DROPITEM;
744 break;
746 bs = BS_STAND;
747 break;
748 }
749
750 if(bool(wlk & WalkBit::WM_Dive))
751 bs = BS_DIVE;
752 else if(bool(wlk & WalkBit::WM_Swim))
753 bs = BS_SWIM;
754 else if(bool(wlk & WalkBit::WM_Sneak) && (st!=WeaponState::Bow && st!=WeaponState::CBow))
755 bs = BS_SNEAK;
756
757 Pose::StartHint hint = (forceAnim ? Pose::Force : Pose::NoHint);
758
759 if(skInst->startAnim(solver,sq,comb,bs,hint,npc.world().tickCount()))
760 return sq;
761 return nullptr;
762 }
763
765 const bool run = (skInst->bodyState()&BS_MAX)==BS_RUN;
766
767 if(st==fgtMode)
768 return true;
769 const Animation::Sequence *sq = solver.solveAnim(st,fgtMode,run);
770 if(sq==nullptr)
771 return false;
772 if(skInst->startAnim(solver,sq,0,run ? BS_RUN : BS_NONE,Pose::NoHint,npc.world().tickCount()))
773 return true;
774 return false;
775 }
776
777void MdlVisual::setAnimRotate(Npc &npc, int dir) {
778 skInst->setAnimRotate(solver,npc,fgtMode,AnimationSolver::TurnType::Std,dir);
779 }
780
781void MdlVisual::setAnimWhirl(Npc &npc, int dir) {
782 skInst->setAnimRotate(solver,npc,fgtMode,AnimationSolver::TurnType::Whirl,dir);
783 }
784
786 skInst->interrupt();
787 item.clear();
789 }
790
791Tempest::Vec3 MdlVisual::displayPosition() const {
792 if(skeleton!=nullptr)
793 return {0,skeleton->colisionHeight()*1.5f,0};
794 return {0.f,0.f,0.f};
795 }
796
798 auto p = pos;
799 if(nullptr!=skeleton) {
800 size_t nodeId = skeleton->findNode("BIP01");
801 if(nodeId!=size_t(-1))
802 p = pose().bone(nodeId);
803 }
804 float rx = p.at(2,0);
805 float rz = p.at(2,2);
806 return float(std::atan2(rz,rx)) * 180.f / float(M_PI);
807 }
808
810 WeaponState st, WalkBit wlk) {
812 const Animation::Sequence *sq = solver.solveAnim(a,st,wlk,*skInst);
813 if(auto ret = skInst->continueCombo(solver,sq,bs,npc.world().tickCount()))
814 return ret;
815 }
816 return startAnimAndGet(npc,a,0,st,wlk);
817 }
818
819uint16_t MdlVisual::comboLength() const {
820 return skInst->comboLength();
821 }
822
824 auto b = view.bounds();
825 if(!head.view.isEmpty())
826 b.assign(b,head.view.bounds());
827 return b;
828 }
829
830void MdlVisual::bind(MeshAttach& slot, MeshObjects::Mesh&& itm, std::string_view bone) {
831 slot.boneId = skeleton==nullptr ? size_t(-1) : skeleton->findNode(bone);
832 slot.view = std::move(itm);
833 slot.bone = bone;
834 // sync?
835 }
836
837void MdlVisual::bind(PfxAttach& slot, Effect&& itm, std::string_view bone) {
838 slot.boneId = skeleton==nullptr ? size_t(-1) : skeleton->findNode(bone);
839 slot.view = std::move(itm);
840 slot.bone = bone;
841 // sync?
842 }
843
844template<class View>
845void MdlVisual::bind(Attach<View>& slot, std::string_view bone) {
846 slot.boneId = skeleton==nullptr ? size_t(-1) : skeleton->findNode(bone);
847 slot.bone = bone;
848 // sync?
849 }
850
851void MdlVisual::rebindAttaches(const Skeleton& to) {
852 auto mesh = {&head,&sword,&bow,&ammunition,&stateItm};
853 for(auto i:mesh)
854 rebindAttaches(*i,to);
855 for(auto& i:item)
856 rebindAttaches(i,to);
857 for(auto& i:attach)
858 rebindAttaches(i,to);
859 for(auto& i:effects)
860 i.view.bindAttaches(*skInst,to);
861 pfx.view.bindAttaches(*skInst,to);
862 hnpcVisual.view.bindAttaches(*skInst,to);
863 }
864
865template<class View>
866void MdlVisual::rebindAttaches(Attach<View>& mesh, const Skeleton& to) {
867 if(mesh.bone.empty())
868 mesh.boneId = size_t(-1); else
869 mesh.boneId = to.findNode(mesh.bone);
870 }
871
873 MdlVisual::MeshAttach* mesh[] = {&head, &sword,&shield,&bow,&ammunition,&stateItm};
874 for(auto i:mesh)
875 syncAttaches(*i);
876 for(auto& i:item)
877 syncAttaches(i);
878 for(auto& i:attach)
879 syncAttaches(i);
880 for(auto& i:effects) {
881 i.view.setObjMatrix(pos);
882 // i.view.setTarget(targetPos);
883 }
884 pfx.view.setObjMatrix(pos);
885 hnpcVisual.view.setObjMatrix(pos);
886 if(torch.view!=nullptr) {
887 auto& pose = *skInst;
888 auto p = pos;
889 if(torch.boneId<pose.boneCount())
890 p = pose.bone(torch.boneId);
891 torch.view->setObjMatrix(p);
892 }
893 }
894
896 return skeleton;
897 }
898
899std::string_view MdlVisual::visualSkeletonScheme() const {
900 if(skeleton==nullptr)
901 return "";
902 auto ret = skeleton->name();
903 auto end = ret.find_first_of("._");
904 return ret.substr(0, end);
905 }
906
907template<class View>
908void MdlVisual::syncAttaches(Attach<View>& att) {
909 if(att.view.isEmpty())
910 return;
911 auto& pose = *skInst;
912 auto p = pos;
913 if(att.boneId<pose.boneCount())
914 p = pose.bone(att.boneId);
915 att.view.setObjMatrix(p);
916 }
917
918const Animation::Sequence* MdlVisual::startAnimItem(Npc &npc, std::string_view scheme, int state) {
919 return skInst->setAnimItem(solver,npc,scheme,state);
920 }
921
922const Animation::Sequence* MdlVisual::startAnimSpell(Npc &npc, std::string_view scheme, bool invest) {
923 const bool run = (skInst->bodyState()&BS_MAX)==BS_RUN; // not really the case, as in Gothic player can't cast spell, while running
924
925 const Animation::Sequence *sq = solver.solveAnim(scheme,run,invest);
926 if(skInst->startAnim(solver,sq,0,BS_CASTING,Pose::NoHint,npc.world().tickCount())) {
927 return sq;
928 }
929 return nullptr;
930 }
931
933 if((npc.bodyState()&BS_FLAG_FREEHANDS)==0 || fgtMode!=WeaponState::NoWeapon)
934 return true;
935 if(npc.bodyStateMasked()!=BS_STAND)
936 return true;
937
938 const uint16_t count = Gothic::inst().version().dialogGestureCount();
939 const int id = std::rand()%count + 1;
940
941 char name[32]={};
942 std::snprintf(name,sizeof(name),"T_DIALOGGESTURE_%02d",id);
943
944 const Animation::Sequence *sq = solver.solveFrm(name);
945 if(skInst->startAnim(solver,sq,0,BS_STAND,Pose::NoHint,npc.world().tickCount()))
946 return true;
947 return false;
948 }
949
950void MdlVisual::startMMAnim(Npc&, std::string_view anim, std::string_view bone) {
951 MdlVisual::MeshAttach* mesh[] = {&head,&sword,&shield,&bow,&ammunition,&stateItm};
952 for(auto i:mesh) {
953 if(i->bone!=bone)
954 continue;
955 i->view.startMMAnim(anim,1,uint64_t(-1));
956 }
957 }
958
959void MdlVisual::startFaceAnim(Npc& npc, std::string_view anim, float intensity, uint64_t duration) {
960 if(duration!=uint64_t(-1) && duration!=0)
961 duration += npc.world().tickCount();
962 head.view.startMMAnim(anim,intensity,duration);
963 }
964
966 const uint16_t count = Gothic::inst().version().dialogGestureCount();
967 for(uint16_t i=0; i<count; i++){
968 char buf[32]={};
969 std::snprintf(buf,sizeof(buf),"T_DIALOGGESTURE_%02d",i+1);
970 skInst->stopAnim(buf);
971 }
972
974 // avoid PCI traffic on distant npc's
975 startFaceAnim(npc,"VISEME",1,0);
976 }
977 }
void addOverlay(const Skeleton *sk, uint64_t time)
const Animation::Sequence * solveFrm(std::string_view format) const
void load(Serialize &fin)
const Animation::Sequence * solveAnim(Anim a, WeaponState st, WalkBit wlk, const Pose &pose) const
void setSkeleton(const Skeleton *sk)
void delOverlay(std::string_view sk)
void update(uint64_t tickCount)
void save(Serialize &fout) const
bool hasOverlay(const Skeleton *sk) const
void assign(const Tempest::Vec3 &cen, float sizeSz)
size_t findSymbolIndex(std::string_view s)
void initializeInstanceItem(const std::shared_ptr< zenkit::IItem > &item, size_t instance)
static Gothic & inst()
Definition gothic.cpp:249
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
auto loadVisualFx(std::string_view name) -> const VisualFx *
Definition gothic.cpp:383
Definition item.h:14
size_t clsId() const
Definition item.cpp:313
bool isCrossbow() const
Definition item.cpp:243
bool is2H() const
Definition item.cpp:238
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)
uint16_t comboLength() const
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)
void setYTranslationEnable(bool e)
Definition mdlvisual.cpp:69
bool setToFightMode(const WeaponState ws)
Tempest::Vec3 displayPosition() const
const Pose & pose() const
Definition mdlvisual.h:77
void setRangedWeapon(MeshObjects::Mesh &&bow)
void setShield(MeshObjects::Mesh &&shield)
void clearOverlays()
bool setFightMode(zenkit::MdsFightMode mode)
bool processEvents(World &world, uint64_t &barrier, Animation::EvCount &ev)
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 setPose(const Tempest::Matrix4x4 &obj, const Pose &p)
void setAsGhost(bool g)
void setFatness(float f)
void setSkeleton(const Skeleton *sk)
Bounds bounds() const
Definition npc.h:25
auto walkMode() const
Definition npc.h:113
float fatness() const
Definition npc.h:101
BodyState bodyState() const
Definition npc.cpp:3147
Item * currentShield()
Definition npc.cpp:3370
void setVisual(std::string_view visual)
Definition npc.cpp:743
auto inventory() const -> const Inventory &
Definition npc.h:330
auto interactive() const -> Interactive *
Definition npc.h:294
Item * currentMeleeWeapon()
Definition npc.cpp:3362
auto world() -> World &
Definition npc.cpp:624
zenkit::INpc & handle()
Definition npc.h:327
void delItem(size_t id, uint32_t amount)
Definition npc.cpp:3466
auto processPolicy() const -> NpcProcessPolicy
Definition npc.h:109
Item * currentRangedWeapon()
Definition npc.cpp:3366
BodyState bodyStateMasked() const
Definition npc.cpp:3161
Definition pose.h:16
bool update(uint64_t tickCount, bool force)
Definition pose.cpp:315
StartHint
Definition pose.h:25
@ Force
Definition pose.h:27
@ NoHint
Definition pose.h:26
void setObjectMatrix(const Tempest::Matrix4x4 &obj, bool sync)
Definition pose.cpp:589
@ NoTranslation
Definition pose.h:22
@ NoFlags
Definition pose.h:21
size_t boneCount() const
Definition pose.cpp:808
void processLayers(AnimationSolver &solver, uint64_t tickCount)
Definition pose.cpp:270
auto bone(size_t id) const -> const Tempest::Matrix4x4 &
Definition pose.cpp:804
void processPfx(MdlVisual &visual, World &world, uint64_t tickCount)
Definition pose.cpp:572
bool processEvents(uint64_t &barrier, uint64_t now, Animation::EvCount &ev) const
Definition pose.cpp:577
void processSfx(Npc &npc, uint64_t tickCount)
Definition pose.cpp:562
void write(const Arg &... a)
Definition serialize.h:76
void read(Arg &... a)
Definition serialize.h:81
size_t findNode(std::string_view name, size_t def=size_t(-1)) const
Definition skeleton.cpp:52
std::vector< Node > nodes
Definition skeleton.h:22
size_t BIP01_HEAD
Definition skeleton.h:27
std::string_view name() const
Definition skeleton.h:34
float colisionHeight() const
Definition skeleton.cpp:85
void play()
Definition sound.cpp:126
Definition world.h:31
MeshObjects::Mesh addAtachView(const ProtoMesh::Attach &visual, const int32_t version)
Definition world.cpp:267
uint64_t tickCount() const
Definition world.cpp:387
Sound addWeaponBlkEffect(ItemMaterial src, ItemMaterial reciver, const Tempest::Matrix4x4 &pos)
Definition world.cpp:758
bool isInSfxRange(const Tempest::Vec3 &pos) const
Definition world.cpp:791
bool isInPfxRange(const Tempest::Vec3 &pos) const
Definition world.cpp:795
GameScript & script() const
Definition world.cpp:1019
SpellFxKey
Definition constants.h:268
WalkBit
Definition constants.h:209
ItemMaterial
Definition constants.h:242
BodyState
Definition constants.h:140
@ BS_AIMNEAR
Definition constants.h:178
@ BS_SNEAK
Definition constants.h:156
@ BS_DIVE
Definition constants.h:161
@ 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_LIE
Definition constants.h:166
@ BS_JUMP
Definition constants.h:162
@ BS_FLAG_FREEHANDS
Definition constants.h:150
@ BS_FALL
Definition constants.h:164
@ BS_MOBINTERACT
Definition constants.h:169
@ BS_SWIM
Definition constants.h:159
@ 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_TAKEITEM
Definition constants.h:171
@ BS_WALK
Definition constants.h:155
@ BS_STAND
Definition constants.h:186
@ BS_DROPITEM
Definition constants.h:172
WeaponState
Definition constants.h:191