OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
movealgo.cpp
Go to the documentation of this file.
1#include "movealgo.h"
2
3#include "world/objects/npc.h"
5#include "world/world.h"
6#include "serialize.h"
7
9const float MoveAlgo::climbMove = 55;
10const float MoveAlgo::gravity = DynamicWorld::gravity;
11const float MoveAlgo::eps = 2.f; // 2-santimeters
12const int32_t MoveAlgo::flyOverWaterHint = 999999;
13const float MoveAlgo::waterPadd = 15;
14
16 :npc(unit) {
17 }
18
20 fin.read(reinterpret_cast<uint32_t&>(flags));
21 fin.read(mulSpeed,fallSpeed,fallCount,climbStart,climbPos0,climbHeight);
22 fin.read(reinterpret_cast<uint8_t&>(jmp));
23
24 std::string str;
25 fin.read(str);
26 portal = npc.world().physic()->validateSectorName(str);
27 fin.read(str);
28 formerPortal = npc.world().physic()->validateSectorName(str);
29
30 fin.read(diveStart);
31 }
32
33void MoveAlgo::save(Serialize &fout) const {
34 fout.write(uint32_t(flags));
35 fout.write(mulSpeed,fallSpeed,fallCount,climbStart,climbPos0,climbHeight);
36 fout.write(uint8_t(jmp));
37
38 fout.write(portal,formerPortal);
39 fout.write(diveStart);
40 }
41
42void MoveAlgo::tickMobsi(uint64_t dt) {
43 if(npc.interactive()->isStaticState())
44 return;
45
46 auto dp = npc.animMoveSpeed(dt);
47 if(npc.interactive()->isLadder()) {
48 auto mat = npc.interactive()->transform();
49 mat.project(dp);
50 dp -= npc.interactive()->position();
51 } else {
52 Tempest::Vec3 ret;
53 applyRotation(ret,dp);
54 dp = Tempest::Vec3(ret.x, 0, ret.z);
55 }
56
57 if(npc.interactive()->isTrueDoor(npc)) {
58 // some tight-door require collision-detection
59 npc.tryMove(dp);
60 } else {
61 // but chair/bed do not :)
62 auto pos = npc.position();
63 npc.setPosition(pos+dp);
64 }
65 setAsSlide(false);
66 setInAir (false);
67 }
68
69bool MoveAlgo::tryMove(float x, float y, float z) {
71 return tryMove(x,y,z,out);
72 }
73
74bool MoveAlgo::tryMove(float x,float y,float z, DynamicWorld::CollisionTest& out) {
75 return npc.tryMove({x,y,z},out);
76 }
77
78bool MoveAlgo::tickSlide(uint64_t dt) {
79 float fallThreshold = stepHeight();
80 auto pos = npc.position();
81
82 auto norm = normalRay(pos+Tempest::Vec3(0,fallThreshold,0));
83 // check ground
84 float pY = pos.y;
85 bool valid = false;
86 auto ground = dropRay (pos+Tempest::Vec3(0,fallThreshold,0), valid);
87 auto water = waterRay(pos);
88 float dY = pY-ground;
89
90 if(ground+waterDepthChest()<water) {
91 setInAir(true);
92 setAsSlide(false);
93 return true;
94 }
95 if(dY>fallThreshold*1.1) {
96 setInAir (true);
97 setAsSlide(false);
98 return false;
99 }
100
102 if(norm.y<=0 || norm.y>=0.99f || !testSlide(pos+Tempest::Vec3(0,fallThreshold,0),info,true)) {
103 setAsSlide(false);
104 return false;
105 }
106
107 const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0));
108 const auto slide = Tempest::Vec3::crossProduct(norm, tangent);
109
110 auto dp = fallSpeed*float(dt);
111 if(tryMove(dp.x,dp.y,dp.z,info)) {
112 fallSpeed += slide*float(dt)*gravity;
113 fallCount = 1;
114 }
115 else if(tryMove(dp.x,0.f,dp.z,info)) {
116 fallSpeed += Tempest::Vec3(slide.x, 0.f, slide.z)*float(dt)*gravity;
117 fallCount = 1;
118 }
119 else {
120 onGravityFailed(info,dt);
121 }
122
123 /*
124 if(fallCount>0 && !tryMove(dp.x,dp.y,dp.z,info)) {
125 onGravityFailed(info,dt);
126 } else {
127 fallSpeed += slide*float(dt)*gravity;
128 fallCount = 1;
129 }*/
130
131 npc.setAnimRotate(0);
132 if(!npc.isDown()) {
133 if(slideDir())
136 }
137
138 setInAir (false);
139 setAsSlide(true);
140 return true;
141 }
142
143void MoveAlgo::tickGravity(uint64_t dt) {
144 float fallThreshold = stepHeight();
145 // falling
146 if(0.f<fallCount) {
147 fallSpeed/=fallCount;
148 fallCount = 0.f;
149 }
150
151 // check ground
152 auto pos = npc.position();
153 float pY = pos.y;
154 float chest = canFlyOverWater() ? 0 : waterDepthChest();
155 bool valid = false;
156 auto ground = dropRay (pos+Tempest::Vec3(0,fallThreshold,0), valid);
157 auto water = waterRay(pos);
158 float fallStop = std::max(water-chest,ground);
159
160 const auto dp = fallSpeed*float(dt);
161
162 if(pY+dp.y>fallStop || dp.y>0) {
163 // continue falling
165 if(!tryMove(dp.x,dp.y,dp.z,info)) {
166 if(!npc.isDead())
168 onGravityFailed(info,dt);
169 fallSpeed.y = std::max(fallSpeed.y, 0.f);
170 } else {
171 fallSpeed.y -= gravity*float(dt);
172 }
173
174 auto gl = npc.guild();
175 auto h0 = float(npc.world().script().guildVal().falldown_height[gl]);
176 float gravity = DynamicWorld::gravity;
177 float fallTime = fallSpeed.y/gravity;
178 float height = 0.5f*std::abs(gravity)*fallTime*fallTime;
179 auto bs = npc.bodyStateMasked();
180
181 if(height>h0 && !npc.isDead()) {
183 npc.setAnimRotate(0);
184 setAsFalling(true);
185 } else
186 if(fallSpeed.y<-0.3f && !npc.isDead() && bs!=BS_JUMP && bs!=BS_FALL) {
188 npc.setAnimRotate(0);
189 }
190 } else {
191 if(ground+chest<water && !npc.isDead()) {
192 const bool splash = isInAir();
193 clearSpeed();
194 setInAir(false);
195 if(!canFlyOverWater()) {
196 // attach to water
197 tryMove(0.f,water-chest-pY,0.f);
198 setInWater(true);
199 setAsSwim(true);
200 if(splash)
201 emitWaterSplash(water);
202 if(!npc.hasSwimAnimations())
203 npc.takeDrownDamage();
204 }
205 if(!npc.isDead())
207 } else {
208 // attach to ground
209 tryMove(0.f,ground-pY,0.f);
210 npc.takeFallDamage(fallSpeed);
211 clearSpeed();
212 setInAir(false);
213 }
214 }
215 }
216
217void MoveAlgo::tickJumpup(uint64_t dt) {
218 auto pos = npc.position();
219 if(pos.y<climbHeight) {
220 pos.y += fallSpeed.y*float(dt);
221 fallSpeed.y -= gravity*float(dt);
222
223 pos.y = std::min(pos.y,climbHeight);
224 if(!npc.tryTranslate(pos))
225 climbHeight = pos.y;
226 return;
227 }
228
229 Tempest::Vec3 p={}, v={0,0,climbMove};
230 applyRotation(p,v);
231 p = climbPos0;
232 p.y = climbHeight;
233 npc.tryTranslate(p);
234
235 auto climb = npc.tryJump();
236 if(climb.anim==Npc::Anim::JumpHang) {
237 startClimb(climb);
238 } else {
239 setAsClimb(false);
240 setAsJumpup(false);
241 clearSpeed();
242 }
243 }
244
245void MoveAlgo::tickClimb(uint64_t dt) {
246 if(npc.bodyStateMasked()!=BS_CLIMB) {
247 setAsClimb(false);
248 setInAir (false);
249
250 Tempest::Vec3 p={}, v={0,0,climbMove};
251 applyRotation(p,v);
252 p += climbPos0;
253 p.y = climbHeight;
254 if(!npc.tryTranslate(p)) {
255 npc.setPosition(Tempest::Vec3(climbPos0.x,climbHeight,climbPos0.z));
256 npc.tryTranslate(p);
257 }
258 clearSpeed();
259 return;
260 }
261
262 auto dp = animMoveSpeed(dt);
263 auto pos = npc.position();
264
265 if(pos.y<climbHeight) {
266 pos.y += dp.y;
267 pos.y = std::min(pos.y,climbHeight);
268 // npc.tryTranslate(pos);
269 npc.setPosition(pos);
270 }
271
272 pos.x += dp.x;
273 pos.z += dp.z;
274 // npc.tryTranslate(pos);
275 npc.setPosition(pos);
276
277 setAsSlide(false);
278 setInAir (false);
279 }
280
281void MoveAlgo::tickSwim(uint64_t dt) {
282 auto dp = npcMoveSpeed(dt,MvFlags::NoFlag);
283 auto pos = npc.position();
284 float pY = pos.y;
285 float fallThreshold = stepHeight();
286 auto chest = waterDepthChest();
287
288 bool valid = false;
289 bool validW = false;
290 auto ground = dropRay (pos+dp+Tempest::Vec3(0,fallThreshold,0), valid);
291 auto water = waterRay(pos+dp+Tempest::Vec3(0,fallThreshold,0), &validW);
292
293 if(npc.isDead()) {
294 setAsSwim(false);
295 setAsDive(false);
296 return;
297 }
298
299 if(chest==flyOverWaterHint) {
300 setAsSwim(false);
301 setAsDive(false);
302 tryMove(dp.x,ground-pY,dp.z);
303 return;
304 }
305
306 if(ground+chest>=water && !(!validW && isSwim())) {
308 if(testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info))
309 return;
310 setAsSwim(false);
311 setAsDive(false);
312 tryMove(dp.x,ground-pY,dp.z);
313 return;
314 }
315
316 if(isDive() && pos.y+chest>water && validW) {
317 if(npc.world().tickCount()-diveStart>2000) {
318 setAsDive(false);
319 return;
320 }
321 }
322
323 // swim on top of water
324 if(!isDive() && validW) {
325 // Khorinis port hack
326 for(int i=0; i<=50; i+=10) {
327 if(tryMove(dp.x,water-chest-pY+float(i),dp.z))
328 break;
329 }
330 return;
331 }
332
333 if(!isDive() && !validW) {
334 setAsDive(false);
335 setAsSwim(false);
336 setInAir (ground<pos.y);
337 return;
338 }
339
340 tryMove(dp.x,dp.y,dp.z);
341 }
342
343bool MoveAlgo::tickRun(uint64_t dt, MvFlags moveFlg) {
344 const auto dp = npcMoveSpeed(dt,moveFlg);
345 const auto pos = npc.position();
346 const float fallThreshold = stepHeight();
347
348 // moving NPC, by animation
349 bool valid = false;
350 auto ground = dropRay (pos+dp+Tempest::Vec3(0,fallThreshold,0), valid);
351 auto water = waterRay(pos+dp);
352 float dY = pos.y-ground;
353 bool onGound = true;
354
355 if(canFlyOverWater() && ground<water) {
356 dY = pos.y-water;
357 onGound = false;
358 }
359
360 if(pos+dp==pos && dY==0)
361 return false;
362
363 if(-fallThreshold<dY && npc.isFlyAnim()) {
364 // jump animation
365 tryMove(dp.x,dp.y,dp.z);
366 fallSpeed += dp;
367 fallCount += float(dt);
368 setInAir (true);
369 setAsSlide(false);
370 }
371 else if(0.f<=dY && dY<fallThreshold) {
372 const bool walk = bool(npc.walkMode()&WalkBit::WM_Walk);
374 if(onGound && testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info)) {
375 if(walk) {
377 info.normal = dp;
378 info.preFall = true;
379 onMoveFailed(dp,info,dt);
380 return true;
381 }
382 if(!tryMove(dp.x,-dY,dp.z,info))
383 onMoveFailed(dp,info,dt);
384 fallSpeed = Tempest::Vec3();
385 fallCount = 0;
386 setAsSlide(true);
387 return true;
388 }
389 // move down the ramp
390 if(!tryMove(dp.x,-dY,dp.z)) {
391 if(!tryMove(dp.x,dp.y,dp.z,info))
392 onMoveFailed(dp,info,dt);
393 return true;
394 }
395 setInAir (false);
396 setAsSlide(false);
397 }
398 else if(-fallThreshold<dY && dY<0.f) {
400 if(onGound && testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info)) {
401 onMoveFailed(dp,info,dt);
402 return true;
403 }
404 // move up the ramp
405 if(!tryMove(dp.x,-dY,dp.z,info)) {
406 onMoveFailed(dp,info,dt);
407 return true;
408 }
409 setInAir (false);
410 setAsSlide(false);
411 }
412 else if(0.f<dY) {
413 const bool walk = bool(npc.walkMode()&WalkBit::WM_Walk);
414 if(((!walk || !npc.isPlayer()) && (dY<300.f)) || isSlide()) {
415 // start to fall of cliff
416 auto dpCliff = (dp==Tempest::Vec3()) ? Tempest::Vec3(cache.n.x,0,cache.n.z)*float(dt) : dp;
417 if(tryMove(dp.x,dp.y,dp.z)){
418 fallSpeed.x = 0.3f*dpCliff.x;
419 fallSpeed.y = 0.f;
420 fallSpeed.z = 0.3f*dpCliff.z;
421 fallCount = float(dt);
422 //setInAir (true);
423 setAsSlide(false);
424 if(npc.testMove(pos + Tempest::Vec3{0,-fallThreshold*0.1f,0})) {
425 setInAir(true);
426 }
427 } else {
428 tryMove(dpCliff.x,0,dpCliff.z);
429 }
430 } else {
432 info.normal = dp;
433 info.preFall = true;
434 onMoveFailed(dp,info,dt);
435 }
436 }
437
438 return true;
439 }
440
441void MoveAlgo::tick(uint64_t dt, MvFlags moveFlg) {
442 implTick(dt,moveFlg);
443
444 if(cache.sector!=nullptr && portal!=cache.sector) {
445 formerPortal = portal;
446 portal = cache.sector;
447 if(npc.isPlayer() && npc.bodyStateMasked()!=BS_SNEAK) {
448 auto& w = npc.world();
449 w.sendImmediatePerc(npc,npc,npc,PERC_ASSESSENTERROOM);
450 }
451 }
452 }
453
454void MoveAlgo::implTick(uint64_t dt, MvFlags moveFlg) {
455 if(npc.interactive()!=nullptr)
456 return tickMobsi(dt);
457
458 if(isClimb())
459 return tickClimb(dt);
460
461 if(isJumpup())
462 return tickJumpup(dt);
463
464 if(isSwim())
465 return tickSwim(dt);
466
467 if(isInAir()) {
468 if(npc.isJumpAnim()) {
469 auto dp = npcMoveSpeed(dt,moveFlg);
470 tryMove(dp.x,dp.y,dp.z);
471 fallSpeed += dp;
472 fallCount += float(dt);
473 return;
474 }
475 return tickGravity(dt);
476 }
477
478 if(isSlide()) {
479 if(tickSlide(dt))
480 return;
481 if(isInAir())
482 return;
483 }
484
485 const auto pos0 = npc.position();
486 if(!tickRun(dt,moveFlg))
487 return;
488
489 if(npc.isDead() || npc.bodyStateMasked()==BS_JUMP)
490 return;
491
492 auto pos1 = npc.position();
493 float fallThreshold = stepHeight();
494
495 bool valid = false;
496 auto ground = dropRay (pos1+Tempest::Vec3(0,fallThreshold,0), valid);
497 auto water = waterRay(pos1);
498
499 if(canFlyOverWater() && ground<water) {
500 pos1.y = water;
501 npc.tryTranslate(pos1);
502 return;
503 }
504
505 if(npc.isInAir())
506 return;
507
508 const float chest = waterDepthChest();
509 if(ground+chest<water) {
510 if(!npc.hasSwimAnimations()) {
511 // no swim animations
512 npc.setPosition(pos0);
514 info.preFall = true;
515 onMoveFailed(pos1-pos0,info,dt);
516 return;
517 }
518 setInAir(false);
519 setInWater(true);
520 setAsSwim(true);
521 return;
522 }
523
524 if(pos1.y+waterDepthKnee()<water)
525 setInWater(true); else
526 setInWater(false);
527 }
528
530 fallSpeed.x = 0;
531 fallSpeed.y = 0;
532 fallSpeed.z = 0;
533 fallCount = 0;
534 }
535
536void MoveAlgo::accessDamFly(float dx, float dz) {
537 if(flags==0) {
538 float len = std::sqrt(dx*dx+dz*dz);
539 auto vec = Tempest::Vec3(dx,len*0.5f,dz);
540 vec = vec/vec.length();
541
542 fallSpeed = vec*1.f;
543 fallCount = 0;
544 setInAir(true);
545 }
546 }
547
548void MoveAlgo::applyRotation(Tempest::Vec3& out, const Tempest::Vec3& dpos) const {
549 float mul = mulSpeed;
550 if(isDive()) {
551 float rot = -npc.rotationYRad();
552 float s = std::sin(rot), c = std::cos(rot);
553
554 out.y = -dpos.length()*s;
555 mul = c*mulSpeed;
556 } else {
557 out.y = dpos.y;
558 }
559 float rot = npc.rotationRad();
560 applyRotation(out,dpos,rot);
561 out.x *= -mul;
562 out.z *= -mul;
563 }
564
565void MoveAlgo::applyRotation(Tempest::Vec3& out, const Tempest::Vec3& dpos, float rot) const {
566 float s = std::sin(rot), c = std::cos(rot);
567 out.x = (dpos.x*c-dpos.z*s);
568 out.z = (dpos.x*s+dpos.z*c);
569 }
570
571Tempest::Vec3 MoveAlgo::animMoveSpeed(uint64_t dt) const {
572 auto dp = npc.animMoveSpeed(dt);
573 Tempest::Vec3 ret;
574 applyRotation(ret,dp);
575 return ret;
576 }
577
578Tempest::Vec3 MoveAlgo::npcMoveSpeed(uint64_t dt, MvFlags moveFlg) {
579 Tempest::Vec3 dp = animMoveSpeed(dt);
580 if(!npc.isFlyAnim())
581 dp.y = 0.f;
582
583 if(moveFlg&FaiMove) {
584 if(npc.currentTarget!=nullptr && !npc.isPlayer() && !npc.currentTarget->isDown()) {
585 return go2NpcMoveSpeed(dp,*npc.currentTarget);
586 }
587 }
588
589 if(NoFlag==(moveFlg&WaitMove)) {
590 if(npc.go2.npc)
591 return go2NpcMoveSpeed(dp,*npc.go2.npc);
592
593 if(npc.go2.wp)
594 return go2WpMoveSpeed(dp,npc.go2.wp->position());
595
596 if(npc.go2.flag!=Npc::GT_No)
597 return go2WpMoveSpeed(dp,npc.go2.pos);
598 }
599
600 return dp;
601 }
602
603Tempest::Vec3 MoveAlgo::go2NpcMoveSpeed(const Tempest::Vec3& dp,const Npc& tg) {
604 return go2WpMoveSpeed(dp,tg.position());
605 }
606
607Tempest::Vec3 MoveAlgo::go2WpMoveSpeed(Tempest::Vec3 dp, const Tempest::Vec3& to) {
608 auto d = to-npc.position();
609 float qLen = (d.x*d.x+d.z*d.z);
610
611 const float qSpeed = dp.x*dp.x+dp.z*dp.z;
612 if(qSpeed>0.01f){
613 float k = std::sqrt(std::min(qLen,qSpeed)/qSpeed);
614 dp.x*=k;
615 dp.z*=k;
616 }
617 return dp;
618 }
619
620bool MoveAlgo::testSlide(const Tempest::Vec3& pos, DynamicWorld::CollisionTest& out, bool cont) const {
621 if(isInAir() || npc.bodyStateMasked()==BS_JUMP)
622 return false;
623
624 // check ground
625 const auto norm = normalRay(pos);
626 const float slideBegin = std::min(slideAngle() + (cont ? 0.1f : 0.f), 1.f);
627 const float slideEnd = slideAngle2();
628
629 out.normal = norm;
630
631 if(!(slideEnd<norm.y && norm.y<slideBegin)) {
632 return false;
633 }
634
635 const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0));
636 const auto slide = Tempest::Vec3::crossProduct(norm, tangent);
637
638 if(!npc.testMove(pos+slide*gravity*0.15f))
639 return false;
640 return true;
641 }
642
643float MoveAlgo::stepHeight() const {
644 auto gl = npc.guild();
645 auto v = float(npc.world().script().guildVal().step_height[gl]);
646 if(v>0.f)
647 return v;
648 return 50;
649 }
650
651float MoveAlgo::slideAngle() const {
652 auto gl = npc.guild();
653 float k = float(M_PI)/180.f;
654 return std::sin((90.f-float(npc.world().script().guildVal().slide_angle[gl]))*k);
655 }
656
657float MoveAlgo::slideAngle2() const {
658 auto gl = npc.guild();
659 float k = float(M_PI)/180.f;
660 return std::sin(float(npc.world().script().guildVal().slide_angle2[gl])*k);
661 }
662
664 auto gl = npc.guild();
665 return float(npc.world().script().guildVal().water_depth_knee[gl]);
666 }
667
669 auto gl = npc.guild();
670 return float(npc.world().script().guildVal().water_depth_chest[gl]);
671 }
672
674 auto gl = npc.guild();
675 auto& g = npc.world().script().guildVal();
676 return g.water_depth_chest[gl]==flyOverWaterHint &&
677 g.water_depth_knee [gl]==flyOverWaterHint;
678 }
679
681 uint64_t ticks = npc.world().tickCount();
682 return lastBounce+1000<ticks;
683 }
684
685void MoveAlgo::emitWaterSplash(float y) {
686 auto& world = npc.world();
687
688 auto pos = npc.position();
689 pos.y = y;
690
691 Tempest::Matrix4x4 at;
692 at.identity();
693 at.translate(pos);
694
695 Effect e(PfxEmitter(world,"PFX_WATERSPLASH"),"");
696 e.setObjMatrix(at);
697 e.setActive(true);
698 world.runEffect(std::move(e));
699
700 npc.emitSoundEffect("CS_INTRO_WATERSPLASH.WAV",2500,false);
701 }
702
703int32_t MoveAlgo::diveTime() const {
704 if(!isDive())
705 return 0;
706 return int32_t(npc.world().tickCount() - diveStart);
707 }
708
709bool MoveAlgo::isClose(const Npc& npc, const Npc& p, float dist) {
710 float len = npc.qDistTo(p);
711 return (len<dist*dist);
712 /*
713 auto dp = npc.position()-p;
714 dp.y = 0;
715 float len = dp.quadLength();
716 if(len<dist*dist)
717 return true;
718 return false;
719 */
720 }
721
722bool MoveAlgo::isClose(const Npc& npc, const WayPoint& p) {
723 float dist = closeToPointThreshold;
724 if(p.useCounter()>1)
725 dist += 100; // needed for some peasants on Onars farm
726 return isClose(npc,p,dist);
727 }
728
729bool MoveAlgo::isClose(const Npc& npc, const WayPoint& p, float dist) {
730 auto px = p.position();
731 px.y += npc.translateY();
732
733 float len = npc.qDistTo(px);
734 return (len<dist*dist);
735 }
736
737bool MoveAlgo::isClose(const Npc& npc, const Tempest::Vec3& p, float dist) {
738 // NOTE: this need to be consistent with current go2 point implementation
739 auto dp = (p - npc.position());
740 if(std::abs(dp.y)<250)
741 dp.y = 0;
742 float len = dp.quadLength();
743 return (len<dist*dist);
744 }
745
747 auto sq = npc.setAnimAngGet(jump.anim);
748 if(sq==nullptr)
749 return false;
750
751 climbStart = npc.world().tickCount();
752 climbPos0 = npc.position();
753 climbHeight = jump.height;
754
755 const float dHeight = (jump.height-climbPos0.y);
756
757 fallSpeed.x = 0.f;
758 fallSpeed.y = dHeight/sq->totalTime();
759 fallSpeed.z = 0.f;
760 fallCount = 0.f;
761
762 if(jump.anim==Npc::Anim::JumpUp && dHeight>0.f){
763 setAsJumpup(true);
764 setInAir(true);
765
766 float t = std::sqrt(2.f*dHeight/gravity);
767 fallSpeed.y = gravity*t;
768 }
769 else if(jump.anim==Npc::Anim::JumpUpMid ||
770 jump.anim==Npc::Anim::JumpUpLow) {
771 setAsJumpup(false);
772 setAsClimb(true);
773 setInAir(true);
774 }
775 else if(isJumpup() && jump.anim==Npc::Anim::JumpHang) {
776 setAsJumpup(false);
777 setAsClimb(true);
778 setInAir(true);
779 }
780 else {
781 return false;
782 }
783 return true;
784 }
785
787 if(isSwim() && !isDive()) {
788 if(npc.world().tickCount()-diveStart>1000) {
789 setAsDive(true);
790
791 auto pos = npc.position();
792 float pY = pos.y;
793 float chest = canFlyOverWater() ? 0 : waterDepthChest();
794 auto water = waterRay(pos);
795 tryMove(0,water-chest-pY,0);
796 }
797 }
798 }
799
801 return flags&Falling;
802 }
803
804bool MoveAlgo::isSlide() const {
805 return flags&Slide;
806 }
807
808bool MoveAlgo::isInAir() const {
809 return flags&InAir;
810 }
811
812bool MoveAlgo::isJumpup() const {
813 return flags&JumpUp;
814 }
815
816bool MoveAlgo::isClimb() const {
817 return flags&ClimbUp;
818 }
819
821 return flags&InWater;
822 }
823
824bool MoveAlgo::isSwim() const {
825 return flags&Swim;
826 }
827
828bool MoveAlgo::isDive() const {
829 return flags&Dive;
830 }
831
832void MoveAlgo::setInAir(bool f) {
833 if(f==isInAir())
834 return;
835 if(!f)
836 setAsFalling(false);
837 if(f)
838 flags=Flags(flags|InAir); else
839 flags=Flags(flags&(~InAir));
840 }
841
842void MoveAlgo::setAsJumpup(bool f) {
843 if(f)
844 flags=Flags(flags|JumpUp); else
845 flags=Flags(flags&(~JumpUp));
846 }
847
848void MoveAlgo::setAsClimb(bool f) {
849 if(f)
850 flags=Flags(flags|ClimbUp); else
851 flags=Flags(flags&(~ClimbUp));
852 }
853
854void MoveAlgo::setAsSlide(bool f) {
855 if(f)
856 flags=Flags(flags|Slide); else
857 flags=Flags(flags&(~Slide));
858 }
859
860void MoveAlgo::setInWater(bool f) {
861 if(f)
862 flags=Flags(flags|InWater); else
863 flags=Flags(flags&(~InWater));
864 }
865
866void MoveAlgo::setAsSwim(bool f) {
867 if(f==isSwim())
868 return;
869
870 if(f)
871 flags=Flags(flags|Swim); else
872 flags=Flags(flags&(~Swim));
873
874 if(f) {
875 auto ws = npc.weaponState();
876 npc.setAnim(Npc::Anim::NoAnim);
878 npc.closeWeapon(true);
879 npc.dropTorch(true);
880 }
881 }
882
883void MoveAlgo::setAsDive(bool f) {
884 if(f==isDive())
885 return;
886 if(f) {
887 npc.setDirectionY(-40);
888 diveStart = npc.world().tickCount();
890 } else {
891 diveStart = npc.world().tickCount();
892 npc.setWalkMode(npc.walkMode() & (~WalkBit::WM_Dive));
893 }
894 if(f)
895 flags=Flags(flags|Dive); else
896 flags=Flags(flags&(~Dive));
897 }
898
899void MoveAlgo::setAsFalling(bool f) {
900 if(f)
901 flags=Flags(flags|Falling); else
902 flags=Flags(flags&(~Falling));
903 }
904
905bool MoveAlgo::slideDir() const {
906 float a = std::atan2(fallSpeed.x,fallSpeed.z)+float(M_PI/2);
907 float b = npc.rotationRad();
908
909 auto s = std::sin(a-b);
910 return s>0;
911 }
912
913bool MoveAlgo::testMoveDirection(const Tempest::Vec3& dp, const Tempest::Vec3& dir) const {
914 Tempest::Vec3 tr, tr2;
915 applyRotation(tr,dir);
916 tr2 = Tempest::Vec3::normalize(dp);
917 return Tempest::Vec3::dotProduct(tr,tr2)>0.8f;
918 }
919
920bool MoveAlgo::isForward(const Tempest::Vec3& dp) const {
921 return testMoveDirection(dp,{0,0, 1});
922 }
923
924bool MoveAlgo::isBackward(const Tempest::Vec3& dp) const {
925 return testMoveDirection(dp,{0,0,-1});
926 }
927
928void MoveAlgo::onMoveFailed(const Tempest::Vec3& dp, const DynamicWorld::CollisionTest& info, uint64_t dt) {
929 static float threshold = 0.4f;
930 static float speed = 360.f;
931
932 if(dp==Tempest::Vec3())
933 return;
934
935 const auto ortho = Tempest::Vec3::crossProduct(Tempest::Vec3::normalize(dp),Tempest::Vec3(0,1,0));
936 const float stp = speed*float(dt)/1000.f;
937 const float val = Tempest::Vec3::dotProduct(ortho,info.normal);
938 const bool forward = isForward(dp);
939
940 if(info.vob!=nullptr && forward && npc.interactive()!=info.vob) {
941 npc.setDetectedMob(info.vob);
942 npc.perceptionProcess(npc,nullptr,0,PERC_MOVEMOB);
943 if(npc.moveHint()==Npc::GT_No)
944 return;
945 }
946
947 if(forward && !info.preFall && npc.currentTarget==info.npc && npc.bodyStateMasked()==BS_HIT)
948 return;
949
951 lastBounce = npc.world().tickCount();
952
953 if(std::abs(val)>=threshold && !info.preFall) {
954 // emulate bouncing behaviour of original game
955 Tempest::Vec3 corr;
956 for(int i=5; i<=35; i+=5) {
957 for(float angle:{float(i),-float(i)}) {
958 applyRotation(corr,dp,float(angle*M_PI)/180.f);
959 if(npc.tryMove(corr)) {
960 if(forward)
961 npc.setDirection(npc.rotation()+angle);
962 return;
963 }
964 }
965 }
966 }
967
968 if(!forward)
969 return;
970
971 if(!info.preFall && npc.isAttackAnim())
972 return;
973
974 npc.setAnimRotate(0);
975 if(val<-threshold) {
976 npc.setDirection(npc.rotation()-stp);
977 }
978 else if(val>threshold) {
979 npc.setDirection(npc.rotation()+stp);
980 }
981 else switch(npc.moveHint()) {
982 case Npc::GT_No:
983 npc.setAnim(Npc::Anim::Idle);
984 break;
985 case Npc::GT_NextFp:
986 npc.clearGoTo();
987 break;
988 case Npc::GT_Item:
989 npc.setDirection(npc.rotation()+stp);
990 break;
991 case Npc::GT_EnemyA:
992 case Npc::GT_EnemyG:
993 case Npc::GT_Way:
994 case Npc::GT_Point: {
995 if(info.npcCol || info.preFall) {
996 npc.setDirection(npc.rotation()+stp);
997 } else {
998 auto jc = npc.tryJump();
999 if(jc.noClimb)
1000 npc.setDirection(npc.rotation()+stp); else
1001 if(!npc.startClimb(jc))
1002 npc.setDirection(npc.rotation()+stp);
1003 }
1004 break;
1005 }
1006 case Npc::GT_Flee:
1007 npc.setDirection(npc.rotation()+stp);
1008 break;
1009 }
1010 }
1011
1012void MoveAlgo::onGravityFailed(const DynamicWorld::CollisionTest& info, uint64_t dt) {
1013 auto norm = info.normal;
1014 const float normXZ = std::sqrt(norm.x*norm.x+norm.z*norm.z);
1015 const float normXZInv = std::sqrt(1.f - normXZ*normXZ);
1016
1017 if(normXZ>0.001f && normXZ<std::fabs(norm.y) && normXZInv>0.001f) {
1018 norm.x = normXZInv*norm.x/normXZ;
1019 norm.z = normXZInv*norm.z/normXZ;
1020 norm.y = normXZ *norm.y/normXZInv;
1021 }
1022
1023 if(Tempest::Vec3::dotProduct(fallSpeed,norm)<0.f || fallCount>0) {
1024 float len = fallSpeed.length()/std::max(1.f,fallCount);
1025 if(isInAir() && Tempest::Vec2::dotProduct({fallSpeed.x, fallSpeed.z}, {norm.x, norm.z})<0.f) {
1026 float lx = Tempest::Vec2({fallSpeed.x, fallSpeed.z}).length();
1027 lx *= 0.25f;
1028 fallSpeed.x = norm.x*lx;
1029 fallSpeed.z = norm.z*lx;
1030 //fallSpeed = Tempest::Vec3::normalize(fallSpeed)*len;
1031 } else {
1032 len *= 0.25f;
1033 len = std::max(len, 0.1f);
1034 fallSpeed = Tempest::Vec3::normalize(fallSpeed+norm)*len;
1035 }
1036 fallCount = 0;
1037 } else {
1038 fallSpeed += norm*gravity;
1039 }
1040 }
1041
1042float MoveAlgo::waterRay(const Tempest::Vec3& p, bool* hasCol) const {
1043 auto pos = p - Tempest::Vec3(0,waterPadd,0);
1044 if(std::fabs(cacheW.x-pos.x)>eps || std::fabs(cacheW.y-pos.y)>eps || std::fabs(cacheW.z-pos.z)>eps) {
1045 static_cast<DynamicWorld::RayWaterResult&>(cacheW) = npc.world().physic()->waterRay(pos);
1046 cacheW.x = pos.x;
1047 cacheW.y = pos.y;
1048 cacheW.z = pos.z;
1049 }
1050 if(hasCol!=nullptr)
1051 *hasCol = cacheW.hasCol;
1052 return cacheW.wdepth;
1053 }
1054
1055void MoveAlgo::rayMain(const Tempest::Vec3& pos) const {
1056 if(std::fabs(cache.x-pos.x)>eps || std::fabs(cache.y-pos.y)>eps || std::fabs(cache.z-pos.z)>eps) {
1057 float dy = waterDepthChest()+100; // 1 meter extra offset
1058 if(fallSpeed.y<0)
1059 dy = 0; // whole world
1060 static_cast<DynamicWorld::RayLandResult&>(cache) = npc.world().physic()->landRay(pos,dy);
1061 cache.x = pos.x;
1062 cache.y = pos.y;
1063 cache.z = pos.z;
1064 }
1065 }
1066
1067float MoveAlgo::dropRay(const Tempest::Vec3& pos, bool &hasCol) const {
1068 rayMain(pos);
1069 hasCol = cache.hasCol;
1070 return cache.v.y;
1071 }
1072
1073Tempest::Vec3 MoveAlgo::normalRay(const Tempest::Vec3& pos) const {
1074 rayMain(pos);
1075 return cache.n;
1076 }
1077
1078zenkit::MaterialGroup MoveAlgo::groundMaterial() const {
1079 const float stp = stepHeight();
1080 if(cacheW.wdepth+stp>cache.v.y)
1081 return zenkit::MaterialGroup::WATER;
1082 return cache.mat;
1083 }
1084
1085Tempest::Vec3 MoveAlgo::groundNormal() const {
1086 return cache.n;
1087 }
1088
1089std::string_view MoveAlgo::portalName() {
1090 return portal;
1091 }
1092
1093std::string_view MoveAlgo::formerPortalName() {
1094 return formerPortal;
1095 }
1096
static constexpr float gravity
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
float waterDepthKnee() const
Definition movealgo.cpp:663
auto groundNormal() const -> Tempest::Vec3
float waterDepthChest() const
Definition movealgo.cpp:668
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
MoveAlgo(Npc &unit)
Definition movealgo.cpp:15
bool isInWater() const
Definition movealgo.cpp:820
bool isJumpup() const
Definition movealgo.cpp:812
Definition npc.h:25
float qDistTo(const Tempest::Vec3 pos) const
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
bool startClimb(JumpStatus jump)
Definition npc.cpp:543
GoToHint moveHint() const
Definition npc.h:373
float rotation() const
Definition npc.cpp:658
float translateY() const
Definition npc.cpp:680
auto walkMode() const
Definition npc.h:113
bool setAnim(Anim a)
Definition npc.cpp:941
uint32_t guild() const
Definition npc.cpp:1223
bool tryTranslate(const Tempest::Vec3 &to)
void setWalkMode(WalkBit m)
Definition npc.cpp:535
auto animMoveSpeed(uint64_t dt) const -> Tempest::Vec3
Definition npc.cpp:834
bool isJumpAnim() const
Definition npc.cpp:1025
float rotationRad() const
Definition npc.cpp:662
void setDirection(const Tempest::Vec3 &pos)
Definition npc.cpp:436
void setAnimRotate(int rot)
Definition npc.cpp:961
bool setPosition(float x, float y, float z)
Definition npc.cpp:388
bool isInAir() const
Definition npc.cpp:1045
@ GT_EnemyG
Definition npc.h:36
@ GT_NextFp
Definition npc.h:32
@ GT_Way
Definition npc.h:31
@ GT_Point
Definition npc.h:35
@ GT_EnemyA
Definition npc.h:33
@ GT_Item
Definition npc.h:34
@ GT_No
Definition npc.h:30
@ GT_Flee
Definition npc.h:37
bool isDead() const
Definition npc.cpp:3962
bool testMove(const Tempest::Vec3 &pos)
Definition npc.cpp:4201
auto setAnimAngGet(Anim a) -> const Animation::Sequence *
Definition npc.cpp:945
void setDirectionY(float rotation)
Definition npc.cpp:446
JumpStatus tryJump()
Definition npc.cpp:4235
bool isDown() const
Definition npc.cpp:3974
auto interactive() const -> Interactive *
Definition npc.h:294
bool closeWeapon(bool noAnim)
Definition npc.cpp:3490
bool isPlayer() const
Definition npc.cpp:539
auto world() -> World &
Definition npc.cpp:624
auto position() const -> Tempest::Vec3
Definition npc.cpp:628
bool perceptionProcess(Npc &pl)
Definition npc.cpp:4023
bool isAttackAnim() const
Definition npc.cpp:3986
float rotationYRad() const
Definition npc.cpp:670
bool hasSwimAnimations() const
Definition npc.cpp:995
bool tryMove(const Tempest::Vec3 &dp)
auto processPolicy() const -> NpcProcessPolicy
Definition npc.h:109
void emitSoundEffect(std::string_view sound, float range, bool freeSlot)
Definition npc.cpp:2946
void clearGoTo()
Definition npc.cpp:4389
void setDetectedMob(Interactive *id)
Definition npc.cpp:4140
bool isFlyAnim() const
Definition npc.cpp:1029
BodyState bodyStateMasked() const
Definition npc.cpp:3161
void write(const Arg &... a)
Definition serialize.h:76
void read(Arg &... a)
Definition serialize.h:81
uint32_t useCounter() const
Definition waypoint.h:25
Tempest::Vec3 position() const
Definition waypoint.cpp:52
@ BS_SNEAK
Definition constants.h:156
@ BS_CLIMB
Definition constants.h:163
@ BS_JUMP
Definition constants.h:162
@ BS_FALL
Definition constants.h:164
@ BS_HIT
Definition constants.h:180
@ PERC_MOVEMOB
Definition constants.h:421
@ PERC_ASSESSENTERROOM
Definition constants.h:430
AnimationSolver::Anim anim
Definition movealgo.h:26