OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
worldobjects.cpp
Go to the documentation of this file.
1#include "worldobjects.h"
2
3#include "game/serialize.h"
6#include "world/objects/npc.h"
8#include "world/objects/vob.h"
14#include "world.h"
15#include "utils/workers.h"
16#include "utils/dbgpainter.h"
17#include "gothic.h"
18
19#include <Tempest/Painter>
20#include <Tempest/Application>
21#include <Tempest/Log>
22
23using namespace Tempest;
24
25int32_t WorldObjects::MobStates::stateByTime(gtime t) const {
26 t = t.timeInDay();
27 for(size_t i=routines.size(); i>0; ) {
28 --i;
29 if(routines[i].time<=t) {
30 return routines[i].state;
31 }
32 }
33 if(routines.size()>0)
34 return routines.back().state;
35 return 0;
36 }
37
38void WorldObjects::MobStates::save(Serialize& fout) {
39 fout.write(curState,scheme);
40 fout.write(uint32_t(routines.size()));
41 for(auto& i:routines) {
42 fout.write(i.time,i.state);
43 }
44 }
45
46void WorldObjects::MobStates::load(Serialize& fin) {
47 fin.read(curState,scheme);
48 uint32_t sz=0;
49 fin.read(sz);
50 routines.resize(sz);
51 for(auto& i:routines) {
52 fin.read(i.time,i.state);
53 }
54 }
55
56WorldObjects::SearchOpt::SearchOpt(float rangeMin, float rangeMax, float azi, TargetCollect collectAlgo, TargetType collectType, WorldObjects::SearchFlg flags)
57 :rangeMin(rangeMin),rangeMax(rangeMax),azi(azi),collectAlgo(collectAlgo),collectType(collectType),flags(flags) {
58 }
59
60WorldObjects::WorldObjects(World& owner):owner(owner){
61 npcNear.reserve(512);
62 }
63
66
68 {
69 uint16_t v = 0;
70 fin.setEntry("worlds/",fin.worldName(),"/version");
71 fin.read(v);
72 fin.setVersion(v);
73 }
74 itemArr.clear();
75 items.clear();
76
77 uint32_t sz = fin.directorySize("worlds/",fin.worldName(),"/npc/");
78 npcArr.resize(sz);
79 for(size_t i=0; i<sz; ++i)
80 npcArr[i] = std::make_unique<Npc>(owner,size_t(-1),"");
81 for(size_t i=0; i<npcArr.size(); ++i) {
82 npcArr[i]->load(fin,i,"/npc/");
83 }
84
85 if(fin.version()>50) {
86 sz = fin.directorySize("worlds/",fin.worldName(),"/npc_invalid/");
87 npcInvalid.resize(sz);
88 for(size_t i=0; i<npcInvalid.size(); ++i)
89 npcInvalid[i] = std::make_unique<Npc>(owner,size_t(-1),"");
90 for(size_t i=0; i<npcInvalid.size(); ++i)
91 npcInvalid[i]->load(fin,i,"/npc_invalid/");
92 }
93
94 fin.setEntry("worlds/",fin.worldName(),"/items");
95 fin.read(sz);
96 for(size_t i=0; i<sz; ++i) {
97 auto it = std::make_unique<Item>(owner,fin,Item::T_World);
98 itemArr.emplace_back(std::move(it));
99 items.add(itemArr.back().get());
100 }
101
102 for(auto& i:rootVobs)
103 i->loadVobTree(fin);
104
105 fin.setEntry("worlds/",fin.worldName(),"/triggerEvents");
106 fin.read(sz);
107 triggerEvents.resize(sz);
108 for(auto& i:triggerEvents)
109 i.load(fin);
110
111 fin.setEntry("worlds/",fin.worldName(),"/routines");
112 fin.read(sz);
113 routines.resize(sz);
114 for(auto& i:routines)
115 i.load(fin);
116
117 for(auto& i:interactiveObj)
118 i->postValidate();
119 for(auto& i:npcArr)
120 i->postValidate();
121 }
122
124 fout.setEntry("worlds/",fout.worldName(),"/version");
126
127 for(size_t i=0; i<npcArr.size(); ++i)
128 npcArr[i]->save(fout,i,"/npc/");
129 for(size_t i=0; i<npcInvalid.size(); ++i)
130 npcInvalid[i]->save(fout,i,"/npc_invalid/");
131
132 fout.setEntry("worlds/",fout.worldName(),"/items");
133 fout.write(uint32_t(itemArr.size()));
134 for(auto& i:itemArr)
135 i->save(fout);
136
137 fout.setEntry("worlds/",fout.worldName(),"/mobsi");
138 for(auto& i:rootVobs)
139 i->saveVobTree(fout);
140
141 fout.setEntry("worlds/",fout.worldName(),"/triggerEvents");
142 fout.write(uint32_t(triggerEvents.size()));
143 for(auto& i:triggerEvents)
144 i.save(fout);
145
146 fout.setEntry("worlds/",fout.worldName(),"/routines");
147 fout.write(uint32_t(routines.size()));
148 for(auto& i:routines)
149 i.save(fout);
150 }
151
152void WorldObjects::tick(uint64_t dt, uint64_t dtPlayer) {
153 auto passive=std::move(sndPerc);
154 sndPerc.clear();
155
156 bool needSort = false;
157 for(size_t i=1; i<npcArr.size(); ++i) {
158 auto& a = npcArr[i-1];
159 auto& b = npcArr[i-0];
160 if(a->handle().id>b->handle().id) {
161 needSort = true;
162 break;
163 }
164 }
165
166 if(needSort) {
167 std::sort(npcArr.begin(),npcArr.end(),[](std::unique_ptr<Npc>& a, std::unique_ptr<Npc>& b){
168 return a->handle().id<b->handle().id;
169 });
170 }
171
172 auto camera = Gothic::inst().camera();
173 const bool freeCam = (camera!=nullptr && camera->isFree());
174 const auto pl = owner.player();
175 for(size_t i=0; i<npcArr.size(); ++i) {
176 auto& npc = *npcArr[i];
177 uint64_t d = (pl==&npc ? dtPlayer : dt);
178 if(freeCam && pl==&npc)
179 continue;
180 npc.tick(d);
181 }
182
183 for(auto& i:routines) {
184 auto s = i.stateByTime(owner.time());
185 if(s!=i.curState) {
186 setMobState(i.scheme,s);
187 i.curState = s;
188 }
189 }
190
191 for(CollisionZone* z:collisionZn)
192 z->tick(dt);
193
194 for(auto& i:interactiveObj)
195 i->tick(dt);
196
197 for(auto i:triggersTk)
198 i->tick(dt);
199
200 tickTriggers(dt);
201
202 bullets.remove_if([](Bullet& b){
203 return b.isFinished();
204 });
205
206 for(size_t i=0; i<effects.size();) {
207 if(effects[i].timeUntil<owner.tickCount()) {
208 effects[i].eff.setActive(false);
209 effects[i] = std::move(effects.back());
210 effects.pop_back();
211 } else {
212 effects[i].eff.tick(dt);
213 ++i;
214 }
215 }
216
217 npcNear.clear();
218 //const int PERC_DIST_INTERMEDIAT = 1000;
219 const float nearDist = 3000*3000;
220 const float farDist = 6000*6000;
221
222 auto cpos = camera!=nullptr ? camera->destPosition() : Vec3();
223 auto plPos = pl!=nullptr ? pl->position() : cpos;
224 for(auto& i:npcArr) {
225 float dist = (i->position()-plPos).quadLength();
226 if(dist<nearDist){
227 npcNear.push_back(i.get());
228 if(i.get()!=pl)
229 i->setProcessPolicy(NpcProcessPolicy::AiNormal);
230 } else
231 if(dist<farDist) {
232 i->setProcessPolicy(NpcProcessPolicy::AiFar);
233 } else {
234 i->setProcessPolicy(NpcProcessPolicy::AiFar2);
235 }
236 // debug
237 // if(i.get()!=pl)
238 // i->setProcessPolicy(NpcProcessPolicy::AiFar2);
239 }
240 tickNear(dt);
241
242 if(pl==nullptr)
243 return;
244
245 for(auto& ptr:npcNear) {
246 Npc& i = *ptr;
247 if(i.isPlayer() || i.isDead())
248 continue;
249
250 const uint64_t percNextTime = i.percNextTime();
251 if(percNextTime<=owner.tickCount()) {
252 i.perceptionProcess(*pl);
253 }
254
256 for(auto& r:passive)
257 passivePerceptionProcess(r, *ptr, *pl);
258 }
259 }
260 }
261
262uint32_t WorldObjects::npcId(const Npc *ptr) const {
263 if(ptr==nullptr)
264 return uint32_t(-1);
265 for(size_t i=0;i<npcArr.size();++i)
266 if(npcArr[i].get()==ptr)
267 return uint32_t(i);
268 return uint32_t(-1);
269 }
270
271uint32_t WorldObjects::itmId(const void *ptr) const {
272 for(size_t i=0;i<itemArr.size();++i)
273 if(&itemArr[i]->handle()==ptr)
274 return uint32_t(i);
275 return uint32_t(-1);
276 }
277
278uint32_t WorldObjects::mobsiId(const void* ptr) const {
279 uint32_t ret=0;
280 for(auto& i:interactiveObj) {
281 if(i==ptr)
282 return ret;
283 ++ret;
284 }
285 return uint32_t(-1);
286 }
287
288Npc* WorldObjects::addNpc(size_t npcInstance, std::string_view at) {
289 Npc* npc = new Npc(owner,npcInstance,at);
290 if(auto pos = npc->currentTaPoint()) {
291 if(pos->isLocked()) {
292 auto p = owner.findNextPoint(*pos);
293 if(p!=nullptr)
294 pos = p;
295 }
296 npc->setPosition (pos->position() );
297 npc->setDirection (pos->direction());
298 npc->attachToPoint(pos);
301 npcArr.emplace_back(npc);
302 } else {
303 Log::e("addNpc: ", npcInstance, " has invalid spawnpoint");
304 auto& point = owner.deadPoint();
305 npc->attachToPoint(nullptr);
306 npc->setPosition(point.position());
308 npcInvalid.emplace_back(npc);
309 }
310
311 return npc;
312 }
313
314Npc* WorldObjects::addNpc(size_t npcInstance, const Vec3& pos) {
315 auto point = owner.findWayPoint(pos);
316 if(point==nullptr) {
317 point = owner.findFreePoint(pos, "");
318 }
319
320 auto pstr = point==nullptr ? "" : point->name; // vanilla assign some point to all npc's
321 Npc* npc = new Npc(owner,npcInstance,pstr);
322 npc->setPosition (pos.x,pos.y,pos.z);
325
326 npcArr.emplace_back(npc);
327 return npc;
328 }
329
330Npc* WorldObjects::insertPlayer(std::unique_ptr<Npc> &&npc, std::string_view at) {
331 auto pos = owner.findPoint(at);
332 if(pos==nullptr) {
333 Log::e("insertPlayer: invalid waypoint, using fallback");
334 // freemine.zen
335 pos = &owner.startPoint();
336 }
337
338 if(pos->isLocked()) {
339 auto p = owner.findNextPoint(*pos);
340 if(p)
341 pos=p;
342 }
343 npc->setPosition (pos->position() );
344 npc->setDirection (pos->direction());
345 npc->attachToPoint(pos);
347 npcArr.emplace_back(std::move(npc));
348 return npcArr.back().get();
349 }
350
351std::unique_ptr<Npc> WorldObjects::takeNpc(const Npc* ptr) {
352 for(size_t i=0; i<npcArr.size(); ++i){
353 auto& npc=*npcArr[i];
354 if(&npc==ptr){
355 auto ret=std::move(npcArr[i]);
356 npcArr.erase(npcArr.begin() + int32_t(i));
357 return ret;
358 }
359 }
360 return nullptr;
361 }
362
364 auto ptr = takeNpc(&npc);
365 if(ptr==nullptr)
366 return;
367 auto& point = owner.deadPoint();
368 npc.attachToPoint(nullptr);
369 npc.setPosition(point.position());
371 npcRemoved.emplace_back(std::move(ptr));
372 }
373
374void WorldObjects::tickNear(uint64_t /*dt*/) {
375 for(Npc* i:npcNear) {
376 auto pos = i->position() + Vec3(0,i->translateY(),0);
377 for(CollisionZone* z:collisionZn)
378 if(z->checkPos(pos))
379 z->onIntersect(*i);
380 }
381 }
382
384 triggerEvents.push_back(e);
385 }
386
387void WorldObjects::tickTriggers(uint64_t /*dt*/) {
389
390 auto evt = std::move(triggerEvents);
391 triggerEvents.clear();
392
393 for(auto& e:evt)
394 owner.execTriggerEvent(e);
395 }
396
398 auto def = std::move(triggersDef);
399 triggersDef.clear();
400 for(auto i:def) {
401 i->processDelayedEvents();
402 if(i->hasDelayedEvents())
403 triggersDef.push_back(i);
404 }
405 }
406
408 bool emitted=false;
409
410 for(auto& i:triggers) {
411 auto& t = *i;
412 if(t.name()!=e.target)
413 continue; // NOTE: trigger name is not unique - more then one trigger can be activated
414 t.processEvent(e);
415 emitted = true;
416 }
417
418 return emitted;
419 }
420
422 static bool doAnim=true;
423 if(!doAnim)
424 return;
425 if(dt==0)
426 return;
427 Workers::parallelTasks(npcArr,[dt](std::unique_ptr<Npc>& i){
428 i->updateAnimation(dt);
429 });
430 interactiveObj.parallelFor([dt](Interactive& i){
431 i.updateAnimation(dt);
432 });
433 }
434
436 std::atomic_flag flg = ATOMIC_FLAG_INIT;
437 Workers::parallelFor(npcArr,[&dst,&flg](std::unique_ptr<Npc>& i) {
438 if(isTargetedBy(*i,dst))
439 flg.test_and_set();
440 });
441 return flg.test_and_set();
442 }
443
444bool WorldObjects::isTargetedBy(Npc& npc, Npc& dst) {
445 if(npc.target()!=&dst)
446 return false;
448 return false;
449 if(!npc.isAttack())
450 return false;
451 return true;
452 }
453
455 for(auto& i:npcArr){
456 if(i->processPolicy()==NpcProcessPolicy::Player)
457 return i.get();
458 }
459 return nullptr;
460 }
461
463 for(auto& i:npcArr) {
464 if(i->handle().symbol_index()==instance) {
465 if(n==0)
466 return i.get();
467 --n;
468 }
469 }
470 return nullptr;
471 }
472
474 for(auto& i:itemArr) {
475 if(i->handle().symbol_index()==instance) {
476 if(n==0)
477 return i.get();
478 --n;
479 }
480 }
481 return nullptr;
482 }
483
484void WorldObjects::detectNpcNear(const std::function<void(Npc&)>& f) {
485 for(auto& i:npcNear)
486 f(*i);
487 }
488
489void WorldObjects::detectNpc(const float x, const float y, const float z,
490 const float r, const std::function<void(Npc&)>& f) {
491 float maxDist=r*r;
492 for(auto& i:npcArr) {
493 auto qDist = (i->position()-Vec3(x,y,z)).quadLength();
494 if(qDist<maxDist)
495 f(*i);
496 }
497 }
498
499void WorldObjects::detectItem(const float x, const float y, const float z,
500 const float r, const std::function<void(Item&)>& f) {
501 float maxDist=r*r;
502 for(auto& i:itemArr) {
503 auto qDist = (i->position()-Vec3(x,y,z)).quadLength();
504 if(qDist<maxDist)
505 f(*i);
506 }
507 }
508
510 triggers.emplace_back(tg);
511 }
512
514 for(auto& i:triggersDef)
515 if(i==&t)
516 return;
517 triggersDef.push_back(&t);
518 }
519
520bool WorldObjects::triggerOnStart(bool firstTime) {
521 bool ret = false;
523 for(auto& i:triggers) {
524 if(auto ts = dynamic_cast<TriggerWorldStart*>(i)) {
525 ts->processEvent(evt);
526 ret = true;
527 }
528 }
529 return ret;
530 }
531
533 for(auto& i:triggersTk)
534 if(i==&t)
535 return;
536 triggersTk.push_back(&t);
537 }
538
540 for(auto& i:triggersTk)
541 if(i==&t) {
542 i = triggersTk.back();
543 triggersTk.pop_back();
544 return;
545 }
546 }
547
549 currentCsCamera = cs;
550 }
551
553 return currentCsCamera;
554 }
555
557 collisionZn.push_back(&z);
558 }
559
561 for(auto& i:collisionZn)
562 if(i==&z) {
563 i = collisionZn.back();
564 collisionZn.pop_back();
565 return;
566 }
567 }
568
570 ex.setBullet(nullptr,owner);
571
572 EffectState e;
573 e.eff = std::move(ex);
574 e.timeUntil = owner.tickCount()+e.eff.effectPrefferedTime();
575 effects.push_back(std::move(e));
576 }
577
579 for(auto& i:npcArr)
580 i->stopEffect(vfx);
581 }
582
583Item* WorldObjects::addItem(const zenkit::VItem& vob) {
584 size_t inst = owner.script().findSymbolIndex(vob.instance);
585 Item* it = addItem(inst,"");
586 if(it==nullptr)
587 return nullptr;
588 auto m = Tempest::Matrix4x4(vob.rotation.columns[0].x, vob.rotation.columns[1].x, vob.rotation.columns[2].x, vob.position.x,
589 vob.rotation.columns[0].y, vob.rotation.columns[1].y, vob.rotation.columns[2].y, vob.position.y,
590 vob.rotation.columns[0].z, vob.rotation.columns[1].z, vob.rotation.columns[2].z, vob.position.z,
591 0, 0, 0, 1);
592 it->setObjMatrix(m);
593 return it;
594 }
595
596std::unique_ptr<Item> WorldObjects::takeItem(Item &it) {
597 for(auto& i:itemArr)
598 if(i.get()==&it){
599 auto ret=std::move(i);
600 i = std::move(itemArr.back());
601 itemArr.pop_back();
602 items.del(ret.get());
603 ret->setPhysicsDisable();
604 onItemRemoved(*ret);
605 return ret;
606 }
607 return nullptr;
608 }
609
611 if(auto ptr=takeItem(it)){
612 (void)ptr;
613 }
614 }
615
616size_t WorldObjects::hasItems(std::string_view tag, size_t itemCls) {
617 for(auto& i:interactiveObj)
618 if(i->tag()==tag) {
619 return i->inventory().itemCount(itemCls);
620 }
621 return 0;
622 }
623
625 for(auto& i:npcArr)
626 i->onWldItemRemoved(itm);
627 owner.script().onWldItemRemoved(itm);
628 }
629
630Bullet& WorldObjects::shootBullet(const Item& itmId, const Vec3& pos, const Vec3& dir, float tgRange, float speed) {
631 bullets.emplace_back(owner,itmId,pos);
632 auto& b = bullets.back();
633
634 const float rgnBias = 0.f;
635 const float l = dir.length();
636 b.setDirection(dir*speed/l);
637 b.setTargetRange(tgRange + rgnBias);
638 return b;
639 }
640
641Item *WorldObjects::addItem(size_t itemInstance, std::string_view at) {
642 Item* item = nullptr;
643 Tempest::Vec3 pos;
644 Tempest::Vec3 dir;
645 const WayPoint* waypoint = owner.findPoint(at);
646
647 if(waypoint != nullptr) {
648 pos = waypoint->position();
649 dir = waypoint->direction();
650 }
651
652 item = addItem(itemInstance, pos, dir);
653 return item;
654 }
655
656Item* WorldObjects::addItem(size_t itemInstance, const Tempest::Vec3& pos) {
657 return addItem(itemInstance, pos, {0,0,0});
658 }
659
660Item* WorldObjects::addItem(size_t itemInstance, const Tempest::Vec3& pos, const Tempest::Vec3& dir) {
661 if(itemInstance==size_t(-1))
662 return nullptr;
663
664 std::unique_ptr<Item> ptr{new Item(owner,itemInstance,Item::T_World)};
665 auto* it=ptr.get();
666 itemArr.emplace_back(std::move(ptr));
667 items.add(itemArr.back().get());
668
669 it->setPosition (pos.x, pos.y, pos.z);
670 it->setDirection(dir.x, dir.y, dir.z);
671
672 return it;
673 }
674
675Item* WorldObjects::addItemDyn(size_t itemInstance, const Tempest::Matrix4x4& pos, size_t ownerNpc) {
676 //size_t ItLsTorchburned = owner.script().findSymbolIndex("ItLsTorchburned");
677 size_t ItLsTorchburning = owner.script().findSymbolIndex("ItLsTorchburning");
678
679 if(itemInstance==size_t(-1))
680 return nullptr;
681
682 std::unique_ptr<Item> ptr;
683 if(itemInstance==ItLsTorchburning)
684 ptr.reset(new ItemTorchBurning(owner,itemInstance,Item::T_WorldDyn)); else
685 ptr.reset(new Item(owner,itemInstance,Item::T_WorldDyn));
686
687 auto* it=ptr.get();
688 it->handle().owner = ownerNpc==size_t(-1) ? 0 : int32_t(ownerNpc);
689 itemArr.emplace_back(std::move(ptr));
690 items.add(itemArr.back().get());
691
692 it->setObjMatrix(pos);
693
694 return it;
695 }
696
698 interactiveObj.add(obj);
699 }
700
702 objStatic.push_back(obj);
703 }
704
705void WorldObjects::addRoot(const std::shared_ptr<zenkit::VirtualObject>& vob, bool startup) {
706 auto p = Vob::load(nullptr,owner,*vob,(startup ? Vob::Startup : Vob::None) | Vob::Static);
707 if(p==nullptr)
708 return;
709 rootVobs.emplace_back(std::move(p));
710 }
711
713 items.invalidate();
714 interactiveObj.invalidate();
715 }
716
718 return interactiveObj.hasObject(def) ? def : nullptr;
719 }
720
722 if(def==nullptr)
723 return nullptr;
724 for(auto& i:npcArr)
725 if(i.get()==def)
726 return def;
727 return nullptr;
728 }
729
731 return items.hasObject(def) ? def : nullptr;
732 }
733
734bool WorldObjects::testFocusNpc(const Npc &pl, Npc* def, const SearchOpt& opt) {
735 if(def && testObj(*def,pl,opt))
736 return true;
737 return false;
738 }
739
741 def = validateInteractive(def);
742 if(def && testObj(*def,pl,opt))
743 return def;
744 if(owner.view()==nullptr)
745 return nullptr;
746 if(!bool(opt.collectType&TARGET_TYPE_ALL))
747 return nullptr;
748
749 Interactive* ret = nullptr;
750 float rlen = opt.rangeMax*opt.rangeMax;
751 interactiveObj.find(pl.position(),opt.rangeMax,[&](Interactive& n){
752 float nlen = rlen;
753 if(testObj(n,pl,opt,nlen)){
754 rlen = nlen;
755 ret = &n;
756 }
757 return false;
758 });
759 return ret;
760 }
761
762Npc* WorldObjects::findNpcNear(const Npc& pl, Npc* def, const SearchOpt& opt) {
763 def = validateNpc(def);
764 if(def) {
765 auto xopt = opt;
766 xopt.flags = SearchFlg(xopt.flags | SearchFlg::NoAngle | SearchFlg::NoRay);
767 if(def && testObj(*def,pl,xopt))
768 return def;
769 }
770 auto r = findObj(npcNear,pl,opt);
771 if(r!=nullptr && (!Gothic::options().hideFocus || !r->isDead() ||
772 r->inventory().iterator(Inventory::T_Ransack).isValid()))
773 return r;
774 return nullptr;
775 }
776
777Item *WorldObjects::findItem(const Npc &pl, Item *def, const SearchOpt& opt) {
778 def = validateItem(def);
779 if(def && testObj(*def,pl,opt))
780 return def;
781 if(owner.view()==nullptr)
782 return nullptr;
784 return nullptr;
785
786 Item* ret = nullptr;
787 float rlen = opt.rangeMax*opt.rangeMax;
788 items.find(pl.position(),opt.rangeMax,[&](Item& n){
789 float nlen = rlen;
790 if(testObj(n,pl,opt,nlen)){
791 rlen = nlen;
792 ret = &n;
793 }
794 return false;
795 });
796 return ret;
797 }
798
800 static bool ddraw=false;
801 if(!ddraw)
802 return;
803 for(auto& i:interactiveObj) {
804 auto m = p.mvp;
805 m.mul(i->transform());
806
807 float x=0,y=0,z=0;
808 m.project(x,y,z);
809
810 if(z<0.f || z>1.f)
811 continue;
812
813 x = (0.5f*x+0.5f)*float(p.w);
814 y = (0.5f*y+0.5f)*float(p.h);
815
816 p.setBrush(Tempest::Color(1,1,1,1));
817 p.painter.drawRect(int(x),int(y),1,1);
818
819 i->marchInteractives(p);
820 }
821 }
822
824 for(auto& i:triggers)
825 if(auto c = dynamic_cast<CsCamera*>(i)) {
826 c->debugDraw(p);
827 }
828 }
829
830Interactive *WorldObjects::availableMob(const Npc &pl, std::string_view dest) {
831 const float dist=100*10.f;
832 Interactive* ret =nullptr;
833
834 if(auto i = pl.interactive()){
835 if(i->checkMobName(dest))
836 return i;
837 }
838
839 float curDist=dist*dist;
840 interactiveObj.find(pl.position(),dist,[&](Interactive& i){
841 if(i.isAvailable() && i.checkMobName(dest)) {
842 float d = pl.qDistTo(i);
843 if(d<curDist){
844 ret = &i;
845 curDist = d;
846 }
847 }
848 return false;
849 });
850
851 if(ret==nullptr)
852 return nullptr;
853 return ret;
854 }
855
856void WorldObjects::setMobRoutine(gtime time, std::string_view scheme, int32_t state) {
857 MobRoutine r;
858 r.time = time;
859 r.state = state;
860
861 for(auto& i:routines) {
862 if(i.scheme==scheme) {
863 i.routines.push_back(r);
864 std::sort(i.routines.begin(),i.routines.end(),[](const MobRoutine& l, const MobRoutine& r){
865 return l.time<r.time;
866 });
867 return;
868 }
869 }
870 MobStates st;
871 st.scheme = scheme;
872 st.routines.push_back(r);
873 routines.emplace_back(std::move(st));
874 }
875
876void WorldObjects::sendPassivePerc(Npc &self, Npc &other, Npc* victim, Item* itm, int32_t perc) {
878 m.what = perc;
879 m.pos = self.position();
880 m.self = &self;
881 m.other = &other;
882 m.victim = victim;
883 if(itm!=nullptr)
884 m.item = itm->handle().symbol_index();
885 sndPerc.push_back(m);
886 }
887
888void WorldObjects::sendImmediatePerc(Npc& self, Npc& other, Npc& victim, Item* itm, int32_t perc) {
889 const auto pl = owner.player();
890 if(pl==nullptr)
891 return;
892
894 r.what = perc;
895 r.pos = self.position();
896 r.self = &self;
897 r.other = &other;
898 r.victim = &victim;
899 if(itm!=nullptr)
900 r.item = itm->handle().symbol_index();
901
902 for(auto& ptr:npcNear) {
903 passivePerceptionProcess(r, *ptr, *pl);
904 }
905 }
906
907void WorldObjects::passivePerceptionProcess(PerceptionMsg& msg, Npc& npc, Npc& pl) {
908 if(npc.isPlayer() || npc.isDead())
909 return;
910
912 return;
913
914 if(msg.self==&npc)
915 return;
916
917 const float distance = npc.qDistTo(msg.pos);
918 const float range = float(owner.script().percRanges().at(PercType(msg.what), npc.handle().senses_range));
919
920 if(distance > range*range)
921 return;
922
923 if(npc.isDown() || npc.isPlayer())
924 return;
925
926 if(msg.other==nullptr)
927 return;
928
929 /*
930 // active only
931 const bool active = isActivePerception(PercType(msg.what));
932 if(active && npc.canSenseNpc(*msg.other, true)==SensesBit::SENSE_NONE) {
933 return;
934 }
935
936 // approximation of behavior of original G2
937 if(active && msg.victim!=nullptr && npc.canSenseNpc(*msg.victim,true,float(msg.other->handle().senses_range))==SensesBit::SENSE_NONE) {
938 return;
939 }
940 */
941
944 return;
945 }
946
947 if(msg.item!=size_t(-1) && msg.other!=nullptr)
948 owner.script().setInstanceItem(*msg.other,msg.item);
949 npc.perceptionProcess(*msg.other,msg.victim,distance,PercType(msg.what));
950 }
951
953 for(auto& r:routines)
954 r.curState = 0;
955
956 for(auto& i:npcInvalid)
957 if(i->handlePtr().use_count()>1)
958 npcArr.push_back(std::move(i)); else
959 npcRemoved.push_back(std::move(i));
960 npcInvalid.clear();
961
962 for(size_t i=0;i<npcArr.size();) {
963 auto& n = *npcArr[i];
964 if(n.resetPositionToTA()){
965 ++i;
966 } else {
967 npcInvalid.emplace_back(std::move(npcArr[i]));
968 npcArr.erase(npcArr.begin()+int(i));
969
970 auto& point = owner.deadPoint();
971 auto& npc = *npcInvalid.back();
972 npc.attachToPoint(nullptr);
973 npc.setPosition(point.position());
975 }
976 }
977 for(auto& i:routines) {
978 auto s = i.stateByTime(owner.time());
979 i.curState = s;
980 }
981 for(auto& i:interactiveObj) {
982 int32_t state = -1;
983 for(auto& r:routines) {
984 if(i->schemeName()==r.scheme)
985 state = r.curState;
986 }
987 i->resetPositionToTA(state);
988 }
989 }
990
991void WorldObjects::setMobState(std::string_view scheme, int32_t st) {
992 for(auto& i:rootVobs)
993 i->setMobState(scheme,st);
994 }
995
996template<class T>
997static T& deref(std::unique_ptr<T>& x){ return *x; }
998
999template<class T>
1000static T& deref(T* x){ return *x; }
1001
1002template<class T>
1003static T& deref(T& x){ return x; }
1004
1005template<class T>
1006static bool checkFlag(T&,WorldObjects::SearchFlg){ return true; }
1007
1009 if(n.handle().no_focus)
1010 return false;
1011 if(bool(f&WorldObjects::NoDeath) && n.isDead())
1012 return false;
1014 return false;
1015 return true;
1016 }
1017
1019 if(bool(f&WorldObjects::FcOverride) && !i.overrideFocus())
1020 return false;
1021 return true;
1022 }
1023
1024template<class T>
1025static bool checkTargetType(T&, TargetType) { return true; }
1026
1027static bool checkTargetType(Npc& n, TargetType t) {
1028 return n.isTargetableBySpell(t);
1029 }
1030
1031template<class T>
1032static bool canSee(const Npc&,const T&){ return true; }
1033
1034static bool canSee(const Npc& pl, const Npc& n){
1035 return pl.canSeeNpc(n,true);
1036 }
1037
1038static bool canSee(const Npc& pl, const Interactive& n){
1039 return n.canSeeNpc(pl,true);
1040 }
1041
1042static bool canSee(const Npc& pl, const Item& n){
1043 return pl.canSeeItem(n,true);
1044 }
1045
1046template<class T>
1047auto WorldObjects::findObj(T &src,const Npc &pl, const SearchOpt& opt) -> typename std::remove_reference<decltype(src[0])>::type {
1048 typename std::remove_reference<decltype(src[0])>::type ret=nullptr;
1049 float rlen = opt.rangeMax*opt.rangeMax;
1050 if(owner.view()==nullptr)
1051 return nullptr;
1052
1053 if(opt.collectAlgo==TARGET_COLLECT_NONE || opt.collectAlgo==TARGET_COLLECT_CASTER)
1054 return nullptr;
1055
1056 for(auto& n:src){
1057 float nlen = rlen;
1058 if(testObj(n,pl,opt,nlen)){
1059 rlen = nlen;
1060 ret = n;
1061 }
1062 }
1063 return ret;
1064 }
1065
1066template<class T>
1067bool WorldObjects::testObj(T &src, const Npc &pl, const WorldObjects::SearchOpt &opt) {
1068 float rlen = opt.rangeMax*opt.rangeMax;
1069 return testObj(src,pl,opt,rlen);
1070 }
1071
1072template<class T>
1073bool WorldObjects::testObj(T &src, const Npc &pl, const WorldObjects::SearchOpt &opt,float& rlen){
1074 const float qmax = opt.rangeMax*opt.rangeMax;
1075 const float qmin = opt.rangeMin*opt.rangeMin;
1076 const float plAng = pl.rotationRad()+float(M_PI/2);
1077 const float ang = float(std::cos(double(opt.azi)*M_PI/180.0));
1078
1079 auto& npc=deref(src);
1080 if(reinterpret_cast<void*>(&npc)==reinterpret_cast<const void*>(&pl))
1081 return false;
1082
1083 if(!checkFlag(npc,opt.flags))
1084 return false;
1086 return false;
1087
1088 float l = pl.qDistTo(npc);
1089 if(l>qmax || l<qmin)
1090 return false;
1091
1092 auto pos = npc.position();
1093 auto dpos = pl.position()-pos;
1094 auto angle = std::atan2(dpos.z,dpos.x);
1095
1096 if(std::cos(plAng-angle)<ang && !bool(opt.flags&SearchFlg::NoAngle))
1097 return false;
1098
1099 l = std::sqrt(l);
1100 if(l<rlen && (bool(opt.flags&SearchFlg::NoRay) || canSee(pl,npc))){
1101 rlen=l;
1102 return true;
1103 }
1104 return false;
1105 }
bool isFinished() const
Definition bullet.cpp:100
void setBrush(const Tempest::Brush &brush)
const int w
Definition dbgpainter.h:20
Tempest::Painter & painter
Definition dbgpainter.h:18
const int h
Definition dbgpainter.h:21
const Tempest::Matrix4x4 mvp
Definition dbgpainter.h:19
void invokeRefreshAtInsert(Npc &npc)
size_t findSymbolIndex(std::string_view s)
const PerDist & percRanges() const
Definition gamescript.h:150
void onWldItemRemoved(const Item &itm)
void setInstanceItem(Npc &holder, size_t itemId)
Camera * camera()
Definition gothic.cpp:319
static auto options() -> const Options &
Definition gothic.cpp:496
static Gothic & inst()
Definition gothic.cpp:249
bool canSeeNpc(const Npc &npc, bool freeLos) const
bool overrideFocus() const
Definition item.h:14
@ T_World
Definition item.h:19
@ T_WorldDyn
Definition item.h:20
void setObjMatrix(const Tempest::Matrix4x4 &m)
Definition item.cpp:117
const zenkit::IItem & handle() const
Definition item.h:83
Definition npc.h:25
float qDistTo(const Tempest::Vec3 pos) const
void updateTransform()
Definition npc.cpp:4565
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
auto currentTaPoint() const -> const WayPoint *
Definition npc.cpp:3118
bool canSeeNpc(const Npc &oth, bool freeLos) const
Definition npc.cpp:4403
float rotationRad() const
Definition npc.cpp:662
void setDirection(const Tempest::Vec3 &pos)
Definition npc.cpp:436
bool setPosition(float x, float y, float z)
Definition npc.cpp:388
bool isDead() const
Definition npc.cpp:3962
bool isDown() const
Definition npc.cpp:3974
auto interactive() const -> Interactive *
Definition npc.h:294
bool isPlayer() const
Definition npc.cpp:539
auto position() const -> Tempest::Vec3
Definition npc.cpp:628
bool isUnconscious() const
Definition npc.cpp:3970
void attachToPoint(const WayPoint *p)
Definition npc.cpp:4384
bool perceptionProcess(Npc &pl)
Definition npc.cpp:4023
zenkit::INpc & handle()
Definition npc.h:327
auto processPolicy() const -> NpcProcessPolicy
Definition npc.h:109
bool isAttack() const
Definition npc.cpp:3978
bool isTargetableBySpell(TargetType t) const
Definition npc.cpp:2984
uint64_t percNextTime() const
Definition npc.cpp:4097
bool canSeeItem(const Item &it, bool freeLos) const
Definition npc.cpp:4487
Npc * target() const
Definition npc.cpp:2916
void tick(uint64_t dt)
Definition npc.cpp:2186
Tempest::Vec3 pos
void write(const Arg &... a)
Definition serialize.h:76
bool setEntry(const Args &... args)
Definition serialize.h:57
uint32_t directorySize(const Args &... args)
Definition serialize.h:63
uint16_t version() const
Definition serialize.h:51
void read(Arg &... a)
Definition serialize.h:81
void setVersion(uint16_t v)
Definition serialize.h:52
std::string_view worldName() const
Definition serialize.cpp:87
void del(T *v)
Definition spaceindex.h:58
bool hasObject(const T *v) const
Definition spaceindex.h:62
void add(T *v)
Definition spaceindex.h:54
void find(const Tempest::Vec3 &p, float R, const Func &f)
Definition spaceindex.h:73
void parallelFor(F func)
Definition spaceindex.h:81
std::string target
@ Static
Definition vob.h:16
@ None
Definition vob.h:14
@ Startup
Definition vob.h:15
static std::unique_ptr< Vob > load(Vob *parent, World &world, const zenkit::VirtualObject &vob, Flags flags)
Definition vob.cpp:127
Tempest::Vec3 position() const
Definition waypoint.cpp:52
std::string name
Definition waypoint.h:35
Tempest::Vec3 direction() const
Definition waypoint.cpp:56
static void parallelFor(T *b, T *e, const F &func)
Definition workers.h:20
static void parallelTasks(std::vector< T > &data, const F &func)
Definition workers.h:30
void resetPositionToTA()
Item * validateItem(Item *def)
void addRoot(const std::shared_ptr< zenkit::VirtualObject > &vob, bool startup)
auto takeItem(Item &it) -> std::unique_ptr< Item >
void addInteractive(Interactive *obj)
void load(Serialize &fout)
void detectNpc(const float x, const float y, const float z, const float r, const std::function< void(Npc &)> &f)
CsCamera * currentCs() const
void tick(uint64_t dt, uint64_t dtPlayer)
Item * findItem(const Npc &pl, Item *def, const SearchOpt &opt)
void enableDefTrigger(AbstractTrigger &trigger)
void enableTicks(AbstractTrigger &t)
Item & itm(size_t i)
uint32_t itmId(const void *ptr) const
void addTrigger(AbstractTrigger *trigger)
void updateAnimation(uint64_t dt)
void setMobRoutine(gtime time, std::string_view scheme, int32_t state)
void detectItem(const float x, const float y, const float z, const float r, const std::function< void(Item &)> &f)
void runEffect(Effect &&e)
void removeNpc(Npc &npc)
Npc * findNpcNear(const Npc &pl, Npc *def, const SearchOpt &opt)
void stopEffect(const VisualFx &vfx)
void invalidateVobIndex()
bool isTargeted(Npc &npc)
Interactive * findInteractive(const Npc &pl, Interactive *def, const SearchOpt &opt)
uint32_t npcId(const Npc *ptr) const
Bullet & shootBullet(const Item &itmId, const Tempest::Vec3 &pos, const Tempest::Vec3 &dir, float tgRange, float speed)
bool triggerOnStart(bool firstTime)
Interactive * validateInteractive(Interactive *def)
void marchInteractives(DbgPainter &p) const
uint32_t mobsiId(const void *ptr) const
Item * addItemDyn(size_t itemInstance, const Tempest::Matrix4x4 &pos, size_t owner)
void save(Serialize &fout)
void enableCollizionZone(CollisionZone &z)
const Npc & npc(size_t i) const
void execDelayedEvents()
WorldObjects(World &owner)
Npc * insertPlayer(std::unique_ptr< Npc > &&npc, std::string_view at)
Npc * validateNpc(Npc *def)
Npc * addNpc(size_t itemInstance, std::string_view at)
void setCurrentCs(CsCamera *cs)
void sendPassivePerc(Npc &self, Npc &other, Npc *victim, Item *itm, int32_t perc)
void addStatic(StaticObj *obj)
void removeItem(Item &it)
size_t hasItems(std::string_view tag, size_t itemCls)
Npc * findNpcByInstance(size_t instance, size_t n=0)
void sendImmediatePerc(Npc &self, Npc &other, Npc &victim, Item *itm, int32_t perc)
auto takeNpc(const Npc *npc) -> std::unique_ptr< Npc >
void onItemRemoved(const Item &itm)
bool testFocusNpc(const Npc &pl, Npc *def, const SearchOpt &opt)
bool execTriggerEvent(const TriggerEvent &e)
void disableCollizionZone(CollisionZone &z)
void detectNpcNear(const std::function< void(Npc &)> &f)
Item * findItemByInstance(size_t instance, size_t n=0)
void triggerEvent(const TriggerEvent &e)
Interactive * availableMob(const Npc &pl, std::string_view name)
void disableTicks(AbstractTrigger &t)
Item * addItem(size_t itemInstance, std::string_view at)
void marchCsCameras(DbgPainter &p) const
Definition world.h:31
uint64_t tickCount() const
Definition world.cpp:387
const WayPoint * findPoint(std::string_view name, bool inexact=true) const
Definition world.cpp:871
gtime time() const
Definition world.cpp:405
const WayPoint & deadPoint() const
Definition world.cpp:965
const WayPoint * findFreePoint(const Npc &pos, std::string_view name) const
Definition world.cpp:897
const WayPoint & startPoint() const
Definition world.cpp:961
const WayPoint * findWayPoint(std::string_view name) const
Definition world.cpp:875
WorldView * view() const
Definition world.h:79
const WayPoint * findNextPoint(const WayPoint &pos) const
Definition world.cpp:957
GameScript & script() const
Definition world.cpp:1019
Npc * player() const
Definition world.h:111
gtime timeInDay() const
Definition gametime.h:18
static CommandLine * instance
TargetType
Definition constants.h:288
@ TARGET_TYPE_ALL
Definition constants.h:289
@ TARGET_TYPE_ITEMS
Definition constants.h:290
SensesBit
Definition constants.h:297
TargetCollect
Definition constants.h:277
@ TARGET_COLLECT_CASTER
Definition constants.h:279
@ TARGET_COLLECT_NONE
Definition constants.h:278
PercType
Definition constants.h:396
@ PERC_ASSESSQUIETSOUND
Definition constants.h:413
@ PERC_ASSESSENTERROOM
Definition constants.h:430
int at(PercType perc, int r) const
static bool checkTargetType(T &, TargetType)
static bool checkFlag(T &, WorldObjects::SearchFlg)
static bool canSee(const Npc &, const T &)
static T & deref(std::unique_ptr< T > &x)