11const float MoveAlgo::eps = 2.f;
12const int32_t MoveAlgo::flyOverWaterHint = 999999;
13const float MoveAlgo::waterPadd = 15;
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));
26 portal = npc.
world().physic()->validateSectorName(str);
28 formerPortal = npc.
world().physic()->validateSectorName(str);
34 fout.
write(uint32_t(flags));
35 fout.
write(mulSpeed,fallSpeed,fallCount,climbStart,climbPos0,climbHeight);
36 fout.
write(uint8_t(jmp));
38 fout.
write(portal,formerPortal);
39 fout.
write(diveStart);
42void MoveAlgo::tickMobsi(uint64_t dt) {
53 applyRotation(ret,dp);
54 dp = Tempest::Vec3(ret.x, 0, ret.z);
69bool MoveAlgo::tryMove(
float x,
float y,
float z) {
71 return tryMove(x,y,z,out);
75 return npc.
tryMove({x,y,z},out);
78bool MoveAlgo::tickSlide(uint64_t dt) {
79 float fallThreshold = stepHeight();
82 auto norm = normalRay(pos+Tempest::Vec3(0,fallThreshold,0));
86 auto ground = dropRay (pos+Tempest::Vec3(0,fallThreshold,0), valid);
87 auto water = waterRay(pos);
95 if(dY>fallThreshold*1.1) {
102 if(norm.y<=0 || norm.y>=0.99f || !
testSlide(pos+Tempest::Vec3(0,fallThreshold,0),info,
true)) {
107 const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0));
108 const auto slide = Tempest::Vec3::crossProduct(norm, tangent);
110 auto dp = fallSpeed*float(dt);
111 if(tryMove(dp.x,dp.y,dp.z,info)) {
112 fallSpeed += slide*float(dt)*gravity;
115 else if(tryMove(dp.x,0.f,dp.z,info)) {
116 fallSpeed += Tempest::Vec3(slide.x, 0.f, slide.z)*float(dt)*gravity;
120 onGravityFailed(info,dt);
143void MoveAlgo::tickGravity(uint64_t dt) {
144 float fallThreshold = stepHeight();
147 fallSpeed/=fallCount;
156 auto ground = dropRay (pos+Tempest::Vec3(0,fallThreshold,0), valid);
157 auto water = waterRay(pos);
158 float fallStop = std::max(water-chest,ground);
160 const auto dp = fallSpeed*float(dt);
162 if(pY+dp.y>fallStop || dp.y>0) {
165 if(!tryMove(dp.x,dp.y,dp.z,info)) {
168 onGravityFailed(info,dt);
169 fallSpeed.y = std::max(fallSpeed.y, 0.f);
171 fallSpeed.y -= gravity*float(dt);
174 auto gl = npc.
guild();
175 auto h0 = float(npc.
world().script().guildVal().falldown_height[gl]);
177 float fallTime = fallSpeed.y/gravity;
178 float height = 0.5f*std::abs(gravity)*fallTime*fallTime;
181 if(height>h0 && !npc.
isDead()) {
191 if(ground+chest<water && !npc.
isDead()) {
197 tryMove(0.f,water-chest-pY,0.f);
201 emitWaterSplash(water);
203 npc.takeDrownDamage();
209 tryMove(0.f,ground-pY,0.f);
210 npc.takeFallDamage(fallSpeed);
217void MoveAlgo::tickJumpup(uint64_t dt) {
219 if(pos.y<climbHeight) {
220 pos.y += fallSpeed.y*float(dt);
221 fallSpeed.y -= gravity*float(dt);
223 pos.y = std::min(pos.y,climbHeight);
236 if(climb.anim==Npc::Anim::JumpHang) {
245void MoveAlgo::tickClimb(uint64_t dt) {
255 npc.
setPosition(Tempest::Vec3(climbPos0.x,climbHeight,climbPos0.z));
262 auto dp = animMoveSpeed(dt);
265 if(pos.y<climbHeight) {
267 pos.y = std::min(pos.y,climbHeight);
281void MoveAlgo::tickSwim(uint64_t dt) {
285 float fallThreshold = stepHeight();
290 auto ground = dropRay (pos+dp+Tempest::Vec3(0,fallThreshold,0), valid);
291 auto water = waterRay(pos+dp+Tempest::Vec3(0,fallThreshold,0), &validW);
299 if(chest==flyOverWaterHint) {
302 tryMove(dp.x,ground-pY,dp.z);
306 if(ground+chest>=water && !(!validW &&
isSwim())) {
308 if(
testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info))
312 tryMove(dp.x,ground-pY,dp.z);
316 if(
isDive() && pos.y+chest>water && validW) {
317 if(npc.
world().tickCount()-diveStart>2000) {
326 for(
int i=0; i<=50; i+=10) {
327 if(tryMove(dp.x,water-chest-pY+
float(i),dp.z))
333 if(!
isDive() && !validW) {
336 setInAir (ground<pos.y);
340 tryMove(dp.x,dp.y,dp.z);
343bool MoveAlgo::tickRun(uint64_t dt, MvFlags moveFlg) {
344 const auto dp = npcMoveSpeed(dt,moveFlg);
346 const float fallThreshold = stepHeight();
350 auto ground = dropRay (pos+dp+Tempest::Vec3(0,fallThreshold,0), valid);
351 auto water = waterRay(pos+dp);
352 float dY = pos.y-ground;
360 if(pos+dp==pos && dY==0)
363 if(-fallThreshold<dY && npc.
isFlyAnim()) {
365 tryMove(dp.x,dp.y,dp.z);
367 fallCount += float(dt);
371 else if(0.f<=dY && dY<fallThreshold) {
374 if(onGound &&
testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info)) {
379 onMoveFailed(dp,info,dt);
382 if(!tryMove(dp.x,-dY,dp.z,info))
383 onMoveFailed(dp,info,dt);
384 fallSpeed = Tempest::Vec3();
390 if(!tryMove(dp.x,-dY,dp.z)) {
391 if(!tryMove(dp.x,dp.y,dp.z,info))
392 onMoveFailed(dp,info,dt);
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);
405 if(!tryMove(dp.x,-dY,dp.z,info)) {
406 onMoveFailed(dp,info,dt);
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;
420 fallSpeed.z = 0.3f*dpCliff.z;
421 fallCount = float(dt);
424 if(npc.
testMove(pos + Tempest::Vec3{0,-fallThreshold*0.1f,0})) {
428 tryMove(dpCliff.x,0,dpCliff.z);
434 onMoveFailed(dp,info,dt);
442 implTick(dt,moveFlg);
444 if(cache.sector!=
nullptr && portal!=cache.sector) {
445 formerPortal = portal;
446 portal = cache.sector;
448 auto& w = npc.
world();
454void MoveAlgo::implTick(uint64_t dt, MvFlags moveFlg) {
456 return tickMobsi(dt);
459 return tickClimb(dt);
462 return tickJumpup(dt);
469 auto dp = npcMoveSpeed(dt,moveFlg);
470 tryMove(dp.x,dp.y,dp.z);
472 fallCount += float(dt);
475 return tickGravity(dt);
486 if(!tickRun(dt,moveFlg))
493 float fallThreshold = stepHeight();
496 auto ground = dropRay (pos1+Tempest::Vec3(0,fallThreshold,0), valid);
497 auto water = waterRay(pos1);
509 if(ground+chest<water) {
515 onMoveFailed(pos1-pos0,info,dt);
525 setInWater(
true);
else
538 float len = std::sqrt(dx*dx+dz*dz);
539 auto vec = Tempest::Vec3(dx,len*0.5f,dz);
540 vec = vec/vec.length();
548void MoveAlgo::applyRotation(Tempest::Vec3& out,
const Tempest::Vec3& dpos)
const {
549 float mul = mulSpeed;
552 float s = std::sin(rot), c = std::cos(rot);
554 out.y = -dpos.length()*s;
560 applyRotation(out,dpos,rot);
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);
571Tempest::Vec3 MoveAlgo::animMoveSpeed(uint64_t dt)
const {
574 applyRotation(ret,dp);
578Tempest::Vec3 MoveAlgo::npcMoveSpeed(uint64_t dt, MvFlags moveFlg) {
579 Tempest::Vec3 dp = animMoveSpeed(dt);
584 if(npc.currentTarget!=
nullptr && !npc.
isPlayer() && !npc.currentTarget->
isDown()) {
585 return go2NpcMoveSpeed(dp,*npc.currentTarget);
591 return go2NpcMoveSpeed(dp,*npc.go2.npc);
594 return go2WpMoveSpeed(dp,npc.go2.wp->position());
597 return go2WpMoveSpeed(dp,npc.go2.pos);
603Tempest::Vec3 MoveAlgo::go2NpcMoveSpeed(
const Tempest::Vec3& dp,
const Npc& tg) {
604 return go2WpMoveSpeed(dp,tg.
position());
607Tempest::Vec3 MoveAlgo::go2WpMoveSpeed(Tempest::Vec3 dp,
const Tempest::Vec3& to) {
609 float qLen = (d.x*d.x+d.z*d.z);
611 const float qSpeed = dp.x*dp.x+dp.z*dp.z;
613 float k = std::sqrt(std::min(qLen,qSpeed)/qSpeed);
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();
631 if(!(slideEnd<norm.y && norm.y<slideBegin)) {
635 const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0));
636 const auto slide = Tempest::Vec3::crossProduct(norm, tangent);
638 if(!npc.
testMove(pos+slide*gravity*0.15f))
643float MoveAlgo::stepHeight()
const {
644 auto gl = npc.
guild();
645 auto v = float(npc.
world().script().guildVal().step_height[gl]);
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);
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);
664 auto gl = npc.
guild();
665 return float(npc.
world().script().guildVal().water_depth_knee[gl]);
669 auto gl = npc.
guild();
670 return float(npc.
world().script().guildVal().water_depth_chest[gl]);
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;
681 uint64_t ticks = npc.
world().tickCount();
682 return lastBounce+1000<ticks;
685void MoveAlgo::emitWaterSplash(
float y) {
686 auto& world = npc.
world();
691 Tempest::Matrix4x4 at;
698 world.runEffect(std::move(e));
706 return int32_t(npc.
world().tickCount() - diveStart);
711 return (len<dist*dist);
734 return (len<dist*dist);
740 if(std::abs(dp.y)<250)
742 float len = dp.quadLength();
743 return (len<dist*dist);
751 climbStart = npc.
world().tickCount();
753 climbHeight = jump.
height;
755 const float dHeight = (jump.
height-climbPos0.y);
758 fallSpeed.y = dHeight/sq->totalTime();
762 if(jump.
anim==Npc::Anim::JumpUp && dHeight>0.f){
766 float t = std::sqrt(2.f*dHeight/gravity);
767 fallSpeed.y = gravity*t;
769 else if(jump.
anim==Npc::Anim::JumpUpMid ||
770 jump.
anim==Npc::Anim::JumpUpLow) {
788 if(npc.
world().tickCount()-diveStart>1000) {
794 auto water = waterRay(pos);
795 tryMove(0,water-chest-pY,0);
801 return flags&Falling;
817 return flags&ClimbUp;
821 return flags&InWater;
832void MoveAlgo::setInAir(
bool f) {
838 flags=Flags(flags|InAir);
else
839 flags=Flags(flags&(~InAir));
842void MoveAlgo::setAsJumpup(
bool f) {
844 flags=Flags(flags|JumpUp);
else
845 flags=Flags(flags&(~JumpUp));
848void MoveAlgo::setAsClimb(
bool f) {
850 flags=Flags(flags|ClimbUp);
else
851 flags=Flags(flags&(~ClimbUp));
854void MoveAlgo::setAsSlide(
bool f) {
856 flags=Flags(flags|Slide);
else
857 flags=Flags(flags&(~Slide));
860void MoveAlgo::setInWater(
bool f) {
862 flags=Flags(flags|InWater);
else
863 flags=Flags(flags&(~InWater));
866void MoveAlgo::setAsSwim(
bool f) {
871 flags=Flags(flags|Swim);
else
872 flags=Flags(flags&(~Swim));
876 npc.
setAnim(Npc::Anim::NoAnim);
883void MoveAlgo::setAsDive(
bool f) {
888 diveStart = npc.
world().tickCount();
891 diveStart = npc.
world().tickCount();
895 flags=Flags(flags|Dive);
else
896 flags=Flags(flags&(~Dive));
899void MoveAlgo::setAsFalling(
bool f) {
901 flags=Flags(flags|Falling);
else
902 flags=Flags(flags&(~Falling));
905bool MoveAlgo::slideDir()
const {
906 float a = std::atan2(fallSpeed.x,fallSpeed.z)+float(M_PI/2);
909 auto s = std::sin(a-b);
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;
920bool MoveAlgo::isForward(
const Tempest::Vec3& dp)
const {
921 return testMoveDirection(dp,{0,0, 1});
924bool MoveAlgo::isBackward(
const Tempest::Vec3& dp)
const {
925 return testMoveDirection(dp,{0,0,-1});
929 static float threshold = 0.4f;
930 static float speed = 360.f;
932 if(dp==Tempest::Vec3())
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);
951 lastBounce = npc.
world().tickCount();
953 if(std::abs(val)>=threshold && !info.
preFall) {
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);
978 else if(val>threshold) {
1014 const float normXZ = std::sqrt(norm.x*norm.x+norm.z*norm.z);
1015 const float normXZInv = std::sqrt(1.f - normXZ*normXZ);
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;
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();
1028 fallSpeed.x = norm.x*lx;
1029 fallSpeed.z = norm.z*lx;
1033 len = std::max(len, 0.1f);
1034 fallSpeed = Tempest::Vec3::normalize(fallSpeed+norm)*len;
1038 fallSpeed += norm*gravity;
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) {
1051 *hasCol = cacheW.hasCol;
1052 return cacheW.wdepth;
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) {
1067float MoveAlgo::dropRay(
const Tempest::Vec3& pos,
bool &hasCol)
const {
1069 hasCol = cache.hasCol;
1073Tempest::Vec3 MoveAlgo::normalRay(
const Tempest::Vec3& pos)
const {
1079 const float stp = stepHeight();
1080 if(cacheW.wdepth+stp>cache.v.y)
1081 return zenkit::MaterialGroup::WATER;
1094 return formerPortal;
static constexpr float gravity
static const float closeToPointThreshold
zenkit::MaterialGroup groundMaterial() const
bool canFlyOverWater() const
float waterDepthKnee() const
auto groundNormal() const -> Tempest::Vec3
float waterDepthChest() const
auto formerPortalName() -> std::string_view
void load(Serialize &fin)
bool testSlide(const Tempest::Vec3 &p, DynamicWorld::CollisionTest &out, bool cont=false) const
auto portalName() -> std::string_view
void save(Serialize &fout) const
static const float climbMove
bool startClimb(JumpStatus ani)
void accessDamFly(float dx, float dz)
bool checkLastBounce() const
static bool isClose(const Npc &npc, const Npc &p, float dist)
void tick(uint64_t dt, MvFlags fai=NoFlag)
float qDistTo(const Tempest::Vec3 pos) const
auto weaponState() const -> WeaponState
bool startClimb(JumpStatus jump)
GoToHint moveHint() const
bool tryTranslate(const Tempest::Vec3 &to)
void setWalkMode(WalkBit m)
auto animMoveSpeed(uint64_t dt) const -> Tempest::Vec3
float rotationRad() const
void setDirection(const Tempest::Vec3 &pos)
void setAnimRotate(int rot)
bool setPosition(float x, float y, float z)
bool testMove(const Tempest::Vec3 &pos)
auto setAnimAngGet(Anim a) -> const Animation::Sequence *
void setDirectionY(float rotation)
auto interactive() const -> Interactive *
bool closeWeapon(bool noAnim)
auto position() const -> Tempest::Vec3
bool perceptionProcess(Npc &pl)
bool isAttackAnim() const
float rotationYRad() const
bool hasSwimAnimations() const
bool tryMove(const Tempest::Vec3 &dp)
auto processPolicy() const -> NpcProcessPolicy
void emitSoundEffect(std::string_view sound, float range, bool freeSlot)
void setDetectedMob(Interactive *id)
BodyState bodyStateMasked() const
void write(const Arg &... a)
uint32_t useCounter() const
Tempest::Vec3 position() const
AnimationSolver::Anim anim