diff --git a/game/camera.cpp b/game/camera.cpp index 071e2c5bb..9b6b9659c 100644 --- a/game/camera.cpp +++ b/game/camera.cpp @@ -753,9 +753,14 @@ void Camera::tick(uint64_t dt) { auto pl = isFree() ? nullptr : world->player(); auto& physic = *world->physic(); - if(pl!=nullptr && !pl->isInWater()) { + if(pl!=nullptr && pl->isSwim()) { + inWater = (angles.x < -8.f); + } + else if(pl!=nullptr && (pl->isInAir() || pl->isJump()) && !pl->isDead()) { + // NOTE: not quite correct inWater = physic.cameraRay(inter.target, origin).waterCol % 2; - } else { + } + else { // NOTE: find a way to avoid persistent tracking inWater = inWater ^ (physic.cameraRay(prev, origin).waterCol % 2); } diff --git a/game/game/movealgo.cpp b/game/game/movealgo.cpp index 74c92ecd3..7b33ddd0b 100644 --- a/game/game/movealgo.cpp +++ b/game/game/movealgo.cpp @@ -1,5 +1,7 @@ #include "movealgo.h" +#include + #include "world/objects/npc.h" #include "world/objects/interactive.h" #include "world/world.h" @@ -62,8 +64,7 @@ void MoveAlgo::tickMobsi(uint64_t dt) { auto pos = npc.position(); npc.setPosition(pos+dp); } - setAsSlide(false); - setInAir (false); + setState(Run); } bool MoveAlgo::tryMove(float x, float y, float z) { @@ -75,147 +76,17 @@ bool MoveAlgo::tryMove(float x,float y,float z, DynamicWorld::CollisionTest& out return npc.tryMove({x,y,z},out); } -bool MoveAlgo::tickSlide(uint64_t dt) { - float fallThreshold = stepHeight(); - auto pos = npc.position(); - - auto norm = normalRay(pos+Tempest::Vec3(0,fallThreshold,0)); - // check ground - float pY = pos.y; - bool valid = false; - auto ground = dropRay (pos+Tempest::Vec3(0,fallThreshold,0), valid); - auto water = waterRay(pos); - float dY = pY-ground; - - if(ground+waterDepthChest()fallThreshold*1.1) { - setInAir (true); - setAsSlide(false); - return false; - } - - DynamicWorld::CollisionTest info; - if(norm.y<=0 || norm.y>=0.99f || !testSlide(pos+Tempest::Vec3(0,fallThreshold,0),info,true)) { - setAsSlide(false); - return false; - } - - const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0)); - const auto slide = Tempest::Vec3::crossProduct(norm, tangent); - - auto dp = fallSpeed*float(dt); - if(tryMove(dp.x,dp.y,dp.z,info)) { - fallSpeed += slide*float(dt)*gravity; - fallCount = 1; - } - else if(tryMove(dp.x,0.f,dp.z,info)) { - fallSpeed += Tempest::Vec3(slide.x, 0.f, slide.z)*float(dt)*gravity; - fallCount = 1; - } - else { - onGravityFailed(info,dt); - } - - /* - if(fallCount>0 && !tryMove(dp.x,dp.y,dp.z,info)) { - onGravityFailed(info,dt); - } else { - fallSpeed += slide*float(dt)*gravity; - fallCount = 1; - }*/ - - npc.setAnimRotate(0); - if(!npc.isDown()) { - if(slideDir()) - npc.setAnim(AnimationSolver::SlideA); else - npc.setAnim(AnimationSolver::SlideB); - } - - setInAir (false); - setAsSlide(true); - return true; +bool MoveAlgo::tryMove(const Tempest::Vec3& dp, DynamicWorld::CollisionTest& out) { + return npc.tryMove(dp,out); } -void MoveAlgo::tickGravity(uint64_t dt) { - float fallThreshold = stepHeight(); - // falling - if(0.ffallStop || dp.y>0) { - // continue falling - DynamicWorld::CollisionTest info; - if(!tryMove(dp.x,dp.y,dp.z,info)) { - if(!npc.isDead()) - npc.setAnim(AnimationSolver::Fall); - onGravityFailed(info,dt); - fallSpeed.y = std::max(fallSpeed.y, 0.f); - } else { - fallSpeed.y -= gravity*float(dt); - } + if(climb.anim==Npc::Anim::JumpHang) + climbHeight = pos.y; - auto gl = npc.guild(); - auto h0 = float(npc.world().script().guildVal().falldown_height[gl]); - float gravity = DynamicWorld::gravity; - float fallTime = fallSpeed.y/gravity; - float height = 0.5f*std::abs(gravity)*fallTime*fallTime; - auto bs = npc.bodyStateMasked(); - - if(height>h0 && !npc.isDead()) { - npc.setAnim(AnimationSolver::FallDeep); - npc.setAnimRotate(0); - setAsFalling(true); - } else - if(fallSpeed.y<-0.3f && !npc.isDead() && bs!=BS_JUMP && bs!=BS_FALL) { - npc.setAnim(AnimationSolver::Fall); - npc.setAnimRotate(0); - } - } else { - if(ground+chest=water && !(!validW && isSwim())) { - DynamicWorld::CollisionTest info; - if(testSlide(pos+dp+Tempest::Vec3(0,fallThreshold,0),info)) - return; - setAsSwim(false); - setAsDive(false); - tryMove(dp.x,ground-pY,dp.z); + if(!implTick(dt,moveFlg)) return; - } - if(isDive() && pos.y+chest>water && validW) { - if(npc.world().tickCount()-diveStart>2000) { - setAsDive(false); - return; + if(cache.sector!=nullptr && portal!=cache.sector) { + formerPortal = portal; + portal = cache.sector; + if(npc.isPlayer() && npc.bodyStateMasked()!=BS_SNEAK) { + auto& w = npc.world(); + w.sendImmediatePerc(npc,npc,npc,PERC_ASSESSENTERROOM); } } + } - // swim on top of water - if(!isDive() && validW) { - // Khorinis port hack - for(int i=0; i<=50; i+=10) { - if(tryMove(dp.x,water-chest-pY+float(i),dp.z)) - break; +bool MoveAlgo::implTick(uint64_t dt, MvFlags moveFlg) { + if(flags==ClimbUp) { + tickClimb(dt); //fixup: collision + return true; + } + if(flags==JumpUp) { + tickJumpup(dt); + return true; + } + + const auto state = flags; + const bool dead = npc.isDead(); + const bool swim = (state==Swim); + const bool dive = (state==Dive); + const bool grav = (state==InAir || state==Falling || state==JumpUp); + const auto bs = npc.bodyStateMasked(); + const auto pos0 = npc.position(); + const auto dp = (!grav && state!=Slide) ? npcMoveSpeed(dt,moveFlg) : npcFallSpeed(dt); + const bool walk = bool(npc.walkMode() & WalkBit::WM_Walk); + + DynamicWorld::CollisionTest info; + if(!tryMove(dp,info)) { + info.preFall = false; + if(state==Slide) { + onGravityFailed(info,dt); + setState(InAir); + } + else if(grav) { + onGravityFailed(info,dt); + } + else if(swim) { + // Khorinis port hack + /* + for(int i=0; i<=50; i+=10) { + if(tryMove(Tempest::Vec3(dp.x,dp.y+float(i),dp.z), info)) + break; + if(i==50) { + onMoveFailed(dp,info,dt); + return false; + } + } + */ + onMoveFailed(dp,info,dt); + return false; + } + else { + onMoveFailed(dp,info,dt); + return false; } - return; } - if(!isDive() && !validW) { - setAsDive(false); - setAsSwim(false); - setInAir (ground=climbHeight) { + // } } - else if(-fallThreshold= 1.f; + setState(Swim); + if(splash) + emitWaterSplash(water); + if(!npc.hasSwimAnimations()) + npc.takeDrownDamage(); + clearSpeed(); + return true; } - } else { - DynamicWorld::CollisionTest info; - info.normal = dp; - info.preFall = true; - onMoveFailed(dp,info,dt); + } + else if(gpos + knee <= water && state!=InAir && state!=Slide && state!=Dive) { + // swimming toward cliff-slide + if(swim && testSlide(pos,info)) { + npc.setPosition(pos0); + onMoveFailed(dp,info,dt); + return false; + } + if(state==Swim) { + setState(Run); + } + } + else if(state==InWater || state==Swim) { + setState(Run); } } - return true; - } - -void MoveAlgo::tick(uint64_t dt, MvFlags moveFlg) { - implTick(dt,moveFlg); - - if(cache.sector!=nullptr && portal!=cache.sector) { - formerPortal = portal; - portal = cache.sector; - if(npc.isPlayer() && npc.bodyStateMasked()!=BS_SNEAK) { - auto& w = npc.world(); - w.sendImmediatePerc(npc,npc,npc,PERC_ASSESSENTERROOM); + if(swim) { + if(dead || !std::isfinite(water)) { + setState(Run); } + return true; } - } -void MoveAlgo::implTick(uint64_t dt, MvFlags moveFlg) { - if(npc.interactive()!=nullptr) - return tickMobsi(dt); + if(dive) { + if(pos.y+chest > water && std::isfinite(water)) { + npc.tryTranslate(Tempest::Vec3(pos.x, water-chest, pos.z)); + if(npc.world().tickCount()-diveStart>2000) { + setState(Swim); + } + } + return true; + } - if(isClimb()) - return tickClimb(dt); + // above ground/void + if(!gValid || (pos.y>ground && dY >= stickThreshold && state!=InWater)) { + if(!gValid && swim) { + // sea monster condition? + } + if(walk || swim) { + npc.setPosition(pos0); - if(isJumpup()) - return tickJumpup(dt); + info.normal = dp; + info.preFall = true; + onMoveFailed(dp,info,dt); + return false; + } + if(!swim && !dive && !dead) { + // fall animations + const float h0 = falldownHeight(); - if(isSwim()) - return tickSwim(dt); + float fallTime = fallSpeed.y/gravity; + float height = 0.5f*std::abs(gravity)*fallTime*fallTime; - if(isInAir()) { - if(npc.isJumpAnim()) { - auto dp = npcMoveSpeed(dt,moveFlg); - tryMove(dp.x,dp.y,dp.z); - fallSpeed += dp; - fallCount += float(dt); - return; + if(height>h0) { + npc.setAnim(AnimationSolver::FallDeep); + npc.setAnimRotate(0); + setState(Falling); + } + else if(fallSpeed.y<-0.3f && bs!=BS_JUMP && bs!=BS_FALL) { + npc.setAnim(AnimationSolver::Fall); + npc.setAnimRotate(0); + setState(InAir); + } + else if(state==InWater) { + npc.setAnimRotate(0); + setState(Swim); + } + else { + npc.setAnimRotate(0); + setState(InAir); + } } - return tickGravity(dt); - } - - if(isSlide()) { - if(tickSlide(dt)) - return; - if(isInAir()) - return; + return true; } - const auto pos0 = npc.position(); - if(!tickRun(dt,moveFlg)) - return; - - if(npc.isDead() || npc.bodyStateMasked()==BS_JUMP) - return; + if(state==Slide && !npc.isDown()) { + if(!testSlide(pos,info)) { + setState(Run); + return true; + } + const auto norm = normalRay(pos); + const auto tangent = Tempest::Vec3::crossProduct(norm, Tempest::Vec3(0,1,0)); + const auto slide = Tempest::Vec3::crossProduct(norm, tangent); - auto pos1 = npc.position(); - float fallThreshold = stepHeight(); + fallSpeed += slide*float(dt)*gravity; + //fallCount = 1; + if(gValid && std::abs(dY)=pos.y) { + // same as wall + npc.setPosition(pos0); + info.preFall = false; + onMoveFailed(dp,info,dt); + return false; + } + if(walk) { + npc.setPosition(pos0); + + info.normal = dp; info.preFall = true; - onMoveFailed(pos1-pos0,info,dt); - return; + onMoveFailed(dp,info,dt); + return false; + } + fallSpeed = Tempest::Vec3(); + fallCount = 0; + setState(Slide); + return true; + } + + if(gValid && dY <= stickThreshold) { + const float gpos = std::max(npc.position().y, ground); + if(gpos + knee <= water) { + setState(InWater); + } else { + setState(Run); + } + if(ground==pos.y) + return true; + if(ground<=pos.y) { + // step up + // npc.setPosition(adjPos); + npc.tryMove(Tempest::Vec3(0,-dY,0)); + return true; + } + if(ground>=pos.y) { + // inside ground + // npc.setPosition(adjPos); + npc.tryMove(Tempest::Vec3(0,-dY,0)); + return true; } - setInAir(false); - setInWater(true); - setAsSwim(true); - return; } - if(pos1.y+waterDepthKnee()0.f){ - setAsJumpup(true); - setInAir(true); - + setState(JumpUp); float t = std::sqrt(2.f*dHeight/gravity); fallSpeed.y = gravity*t; } else if(jump.anim==Npc::Anim::JumpUpMid || jump.anim==Npc::Anim::JumpUpLow) { - setAsJumpup(false); - setAsClimb(true); - setInAir(true); + setState(ClimbUp); } - else if(isJumpup() && jump.anim==Npc::Anim::JumpHang) { - setAsJumpup(false); - setAsClimb(true); - setInAir(true); + else if(isJumpUp() && jump.anim==Npc::Anim::JumpHang) { + setState(ClimbUp); } else { return false; @@ -773,122 +736,113 @@ bool MoveAlgo::startClimb(JumpStatus jump) { } void MoveAlgo::startDive() { - if(isSwim() && !isDive()) { - if(npc.world().tickCount()-diveStart>1000) { - setAsDive(true); - - auto pos = npc.position(); - float pY = pos.y; - float chest = canFlyOverWater() ? 0 : waterDepthChest(); - auto water = waterRay(pos); - tryMove(0,water-chest-pY,0); - } + if(!isSwim()) + return; + + if(npc.world().tickCount()-diveStart>1000) { + setState(Dive); + + auto pos = npc.position(); + float pY = pos.y; + float chest = canFlyOverWater() ? 0 : waterDepthChest(); + auto water = waterRay(pos); + tryMove(0,water-chest-pY,0); } } bool MoveAlgo::isFalling() const { - return flags&Falling; + return flags==Falling; } bool MoveAlgo::isSlide() const { - return flags&Slide; + return flags==Slide; } bool MoveAlgo::isInAir() const { - return flags&InAir; + return flags==InAir; } -bool MoveAlgo::isJumpup() const { - return flags&JumpUp; +bool MoveAlgo::isJumpUp() const { + return flags==JumpUp; } bool MoveAlgo::isClimb() const { - return flags&ClimbUp; + return flags==ClimbUp; } bool MoveAlgo::isInWater() const { - return flags&InWater; + return flags==InWater; } bool MoveAlgo::isSwim() const { - return flags&Swim; + return flags==Swim; } bool MoveAlgo::isDive() const { - return flags&Dive; + return flags==Dive; } -void MoveAlgo::setInAir(bool f) { - if(f==isInAir()) +void MoveAlgo::setState(State f) { + if(f==flags) return; - if(!f) - setAsFalling(false); - if(f) - flags=Flags(flags|InAir); else - flags=Flags(flags&(~InAir)); - } -void MoveAlgo::setAsJumpup(bool f) { - if(f) - flags=Flags(flags|JumpUp); else - flags=Flags(flags&(~JumpUp)); - } - -void MoveAlgo::setAsClimb(bool f) { - if(f) - flags=Flags(flags|ClimbUp); else - flags=Flags(flags&(~ClimbUp)); - } +#ifndef NDEBUG + assertStateChange(f); +#endif -void MoveAlgo::setAsSlide(bool f) { - if(f) - flags=Flags(flags|Slide); else - flags=Flags(flags&(~Slide)); - } - -void MoveAlgo::setInWater(bool f) { - if(f) - flags=Flags(flags|InWater); else - flags=Flags(flags&(~InWater)); - } - -void MoveAlgo::setAsSwim(bool f) { - if(f==isSwim()) - return; - - if(f) - flags=Flags(flags|Swim); else - flags=Flags(flags&(~Swim)); - - if(f) { + if((f==Swim) && !(flags==Swim)) { auto ws = npc.weaponState(); npc.setAnim(Npc::Anim::NoAnim); if(ws!=WeaponState::NoWeapon && ws!=WeaponState::Fist) npc.closeWeapon(true); npc.dropTorch(true); } - } -void MoveAlgo::setAsDive(bool f) { - if(f==isDive()) - return; - if(f) { + if((f==Dive) && !(flags==Dive)) { npc.setDirectionY(-40); + } + if((f==Dive) != (flags==Dive)) { diveStart = npc.world().tickCount(); - npc.setWalkMode(npc.walkMode() | WalkBit::WM_Dive); - } else { - diveStart = npc.world().tickCount(); - npc.setWalkMode(npc.walkMode() & (~WalkBit::WM_Dive)); } - if(f) - flags=Flags(flags|Dive); else - flags=Flags(flags&(~Dive)); + + // handle fly-speed here? + + flags = f; } -void MoveAlgo::setAsFalling(bool f) { - if(f) - flags=Flags(flags|Falling); else - flags=Flags(flags&(~Falling)); +void MoveAlgo::assertStateChange(State f) { + // assert possible transitions + switch(flags) { + case Run: + assert(f!=Falling); + break; + case InAir: + assert(f==Run || f==Falling || f==InWater || f==Swim || f==Dive); + break; + case Falling: + assert(f==Run || f==InAir || f==InWater || f==Swim || f==Dive); + break; + case Slide: + break; + case Jump: + assert(f==Run || f==InAir || f==Falling || f==Swim || f==Dive); + break; + case JumpUp: + assert(f==Run || f==InAir || f==ClimbUp); + break; + case ClimbUp: + assert(f==Run); + break; + case InWater: + assert(f==Run || f==Slide || f==JumpUp || f==Swim || f==Dive); + break; + case Swim: + assert(f==Run || f==InAir || f==InWater || f==Dive); + break; + case Dive: + assert(f==InAir || f==Swim || f==InWater); + break; + } } bool MoveAlgo::slideDir() const { @@ -939,13 +893,13 @@ void MoveAlgo::onMoveFailed(const Tempest::Vec3& dp, const DynamicWorld::Collisi if(npc.processPolicy()!=NpcProcessPolicy::Player) lastBounce = npc.world().tickCount(); - if(std::abs(val)>=threshold && !info.preFall) { + if(std::abs(val)>=threshold && !info.preFall && checkLastBounce()) { // emulate bouncing behaviour of original game Tempest::Vec3 corr; for(int i=5; i<=35; i+=5) { for(float angle:{float(i),-float(i)}) { applyRotation(corr,dp,float(angle*M_PI)/180.f); - if(npc.tryMove(corr)) { + if(npc.testMove(corr)) { if(forward) npc.setDirection(npc.rotation()+angle); return; @@ -981,7 +935,7 @@ void MoveAlgo::onMoveFailed(const Tempest::Vec3& dp, const DynamicWorld::Collisi case Npc::GT_EnemyG: case Npc::GT_Way: case Npc::GT_Point: { - if(info.npcCol || info.preFall) { + if(info.npc!=nullptr || info.preFall) { npc.setDirection(npc.rotation()+stp); } else { auto jc = npc.tryJump(); @@ -1024,29 +978,30 @@ void MoveAlgo::onGravityFailed(const DynamicWorld::CollisionTest& info, uint64_t } fallCount = 0; } else { - fallSpeed += norm*gravity; + fallSpeed += 10.f*norm*gravity*float(dt); } } -float MoveAlgo::waterRay(const Tempest::Vec3& p, bool* hasCol) const { - auto pos = p - Tempest::Vec3(0,waterPadd,0); +float MoveAlgo::waterRay(const Tempest::Vec3& pos) const { if(std::fabs(cacheW.x-pos.x)>eps || std::fabs(cacheW.y-pos.y)>eps || std::fabs(cacheW.z-pos.z)>eps) { - static_cast(cacheW) = npc.world().physic()->waterRay(pos); + const float threshold = -0.1f; + const auto spos = Tempest::Vec3(pos.x, pos.y+threshold, pos.z); + static_cast(cacheW) = npc.world().physic()->waterRay(spos); cacheW.x = pos.x; cacheW.y = pos.y; cacheW.z = pos.z; } - if(hasCol!=nullptr) - *hasCol = cacheW.hasCol; return cacheW.wdepth; } void MoveAlgo::rayMain(const Tempest::Vec3& pos) const { if(std::fabs(cache.x-pos.x)>eps || std::fabs(cache.y-pos.y)>eps || std::fabs(cache.z-pos.z)>eps) { - float dy = waterDepthChest()+100; // 1 meter extra offset - if(fallSpeed.y<0) + float threshold = npc.physic.groundOffset() + 1.f; + float dy = threshold+100; // 1 meter extra offset + if(fallSpeed.y<0 || false) dy = 0; // whole world - static_cast(cache) = npc.world().physic()->landRay(pos,dy); + const auto spos = Tempest::Vec3(pos.x, pos.y+threshold, pos.z); + static_cast(cache) = npc.world().physic()->landRay(spos,dy); cache.x = pos.x; cache.y = pos.y; cache.z = pos.z; diff --git a/game/game/movealgo.h b/game/game/movealgo.h index be3d6c19d..f07ec41f7 100644 --- a/game/game/movealgo.h +++ b/game/game/movealgo.h @@ -34,6 +34,19 @@ class MoveAlgo final { WaitMove = 1<<1, }; + enum State : uint32_t { + Run = 0, + InAir, + Falling, + Slide, + Jump, + JumpUp, + ClimbUp, + InWater, + Swim, + Dive, + }; + static bool isClose(const Npc& npc, const Npc& p, float dist); static bool isClose(const Npc& npc, const WayPoint& p); static bool isClose(const Npc& npc, const WayPoint& p, float dist); @@ -48,7 +61,7 @@ class MoveAlgo final { void clearSpeed(); void accessDamFly(float dx,float dz); - bool testSlide(const Tempest::Vec3& p, DynamicWorld::CollisionTest& out, bool cont = false) const; + bool testSlide(const Tempest::Vec3& p, DynamicWorld::CollisionTest& out) const; bool startClimb(JumpStatus ani); void startDive(); @@ -56,12 +69,14 @@ class MoveAlgo final { bool isFalling() const; bool isSlide() const; bool isInAir() const; - bool isJumpup() const; + bool isJumpUp() const; bool isClimb() const; bool isInWater() const; bool isSwim() const; bool isDive() const; + auto state() const { return flags; } + zenkit::MaterialGroup groundMaterial() const; auto groundNormal() const -> Tempest::Vec3; @@ -71,42 +86,24 @@ class MoveAlgo final { float waterDepthKnee() const; float waterDepthChest() const; + float falldownHeight() const; bool canFlyOverWater() const; + bool canFallByGravity() const; bool checkLastBounce() const; private: - void tickMobsi (uint64_t dt); - bool tickSlide (uint64_t dt); - void tickGravity(uint64_t dt); - void tickSwim (uint64_t dt); - void tickClimb (uint64_t dt); - void tickJumpup (uint64_t dt); - bool tickRun(uint64_t dt, MvFlags moveFlg); - - bool tryMove (float x, float y, float z); - bool tryMove (float x, float y, float z, DynamicWorld::CollisionTest& out); - - enum Flags : uint32_t { - NoFlags = 0, - InAir = 1<<1, - Falling = 1<<2, - Slide = 1<<3, - JumpUp = 1<<4, - ClimbUp = 1<<5, - InWater = 1<<6, - Swim = 1<<7, - Dive = 1<<8, - }; + void tickMobsi (uint64_t dt); + void tickClimb (uint64_t dt); + void tickJumpup(uint64_t dt); + bool implTick (uint64_t dt, MvFlags fai); + + bool tryMove (float x, float y, float z); + bool tryMove (float x, float y, float z, DynamicWorld::CollisionTest& out); + bool tryMove (const Tempest::Vec3& dp, DynamicWorld::CollisionTest& out); - void setInAir (bool f); - void setAsJumpup (bool f); - void setAsClimb (bool f); - void setAsSlide (bool f); - void setInWater (bool f); - void setAsSwim (bool f); - void setAsDive (bool f); - void setAsFalling(bool f); + void setState(State f); + void assertStateChange(State f); bool slideDir() const; bool isForward(const Tempest::Vec3& dp) const; @@ -116,9 +113,9 @@ class MoveAlgo final { void applyRotation(Tempest::Vec3& out, const Tempest::Vec3& in, float radians) const; auto animMoveSpeed(uint64_t dt) const -> Tempest::Vec3; auto npcMoveSpeed (uint64_t dt, MvFlags moveFlg) -> Tempest::Vec3; + auto npcFallSpeed (uint64_t dt) -> Tempest::Vec3; auto go2NpcMoveSpeed (const Tempest::Vec3& dp, const Npc &tg) -> Tempest::Vec3; auto go2WpMoveSpeed (Tempest::Vec3 dp, const Tempest::Vec3& to) -> Tempest::Vec3; - void implTick(uint64_t dt,MvFlags fai=NoFlag); void onMoveFailed(const Tempest::Vec3& dp, const DynamicWorld::CollisionTest& info, uint64_t dt); void onGravityFailed(const DynamicWorld::CollisionTest& info, uint64_t dt); @@ -131,7 +128,7 @@ class MoveAlgo final { void rayMain (const Tempest::Vec3& pos) const; float dropRay (const Tempest::Vec3& pos, bool& hasCol) const; - float waterRay (const Tempest::Vec3& pos, bool* hasCol = nullptr) const; + float waterRay (const Tempest::Vec3& pos) const; auto normalRay(const Tempest::Vec3& pos) const -> Tempest::Vec3; struct CacheLand : DynamicWorld::RayLandResult { @@ -147,7 +144,7 @@ class MoveAlgo final { std::string_view portal; std::string_view formerPortal; - Flags flags = NoFlags; + State flags = Run; float mulSpeed =1.f; Tempest::Vec3 fallSpeed ={}; diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 5aa89b4c9..4102adb30 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -700,7 +700,7 @@ void PlayerControl::implMove(uint64_t dt) { } pl.setDirectionY(rotY); - if(pl.isFalling() || pl.isSlide() || pl.isInAir()){ + if(pl.isFalling() || pl.isSlide() || pl.isInAir() || pl.isJump() || pl.isJumpUp()){ pl.setDirection(rot); runAngleDest = 0; return; diff --git a/game/graphics/bounds.cpp b/game/graphics/bounds.cpp index 36cf5777d..d4ee7aef1 100644 --- a/game/graphics/bounds.cpp +++ b/game/graphics/bounds.cpp @@ -4,14 +4,9 @@ using namespace Tempest; -Bounds::Bounds(){ - } - void Bounds::assign(const Vec3& cen, float sizeSz) { bbox[0] = cen-Vec3(sizeSz,sizeSz,sizeSz); bbox[1] = cen+Vec3(sizeSz,sizeSz,sizeSz); - midTr = cen; - mid = cen; calcR(); } @@ -22,34 +17,28 @@ void Bounds::assign(const Bounds& a, const Bounds& b) { bbox[1].x = std::max(a.bbox[1].x,b.bbox[1].x); bbox[1].y = std::max(a.bbox[1].y,b.bbox[1].y); bbox[1].z = std::max(a.bbox[1].z,b.bbox[1].z); - mid = (bbox[0]+bbox[1])/2; - midTr = mid; calcR(); } void Bounds::assign(const Vec3* src) { + if(src==nullptr) { + *this = Bounds(); + return; + } bbox[0] = src[0]; bbox[1] = src[1]; - mid = (bbox[0]+bbox[1])/2; - midTr = mid; calcR(); } void Bounds::assign(const std::pair& src) { bbox[0] = src.first; bbox[1] = src.second; - mid = (bbox[0]+bbox[1])/2; - midTr = mid; calcR(); } void Bounds::assign(const std::vector& vbo) { if(vbo.size()==0) { - bbox[0] = Vec3(); - bbox[1] = Vec3(); - mid = Vec3(); - midTr = Vec3(); - r = 0; + *this = Bounds(); return; } bbox[0].x = vbo[0].pos[0]; @@ -64,27 +53,21 @@ void Bounds::assign(const std::vector& vbo) { bbox[1].y = std::max(bbox[1].y,i.pos[1]); bbox[1].z = std::max(bbox[1].z,i.pos[2]); } - mid = (bbox[0]+bbox[1])/2; - midTr = mid; calcR(); } void Bounds::assign(const std::vector& vbo, const std::vector& ibo, size_t iboOffset, size_t iboLenght) { if(ibo.size()==0){ - bbox[0] = Vec3(); - bbox[1] = Vec3(); - mid = Vec3(); - midTr = Vec3(); - r = 0; + *this = Bounds(); return; } - bbox[0].x = vbo[ibo[0]].pos[0]; - bbox[0].y = vbo[ibo[0]].pos[1]; - bbox[0].z = vbo[ibo[0]].pos[2]; + bbox[0].x = vbo[ibo[iboOffset]].pos[0]; + bbox[0].y = vbo[ibo[iboOffset]].pos[1]; + bbox[0].z = vbo[ibo[iboOffset]].pos[2]; bbox[1] = bbox[0]; - for(size_t id=0; id& vbo, const std::vector bbox[1].y = std::max(bbox[1].y,i.pos[1]); bbox[1].z = std::max(bbox[1].z,i.pos[2]); } - mid = (bbox[0]+bbox[1])/2; - midTr = mid; calcR(); } -void Bounds::setObjMatrix(const Matrix4x4& m) { - // transformBbox(m); - midTr = mid; - m.project(midTr); - } - -void Bounds::transformBbox(const Matrix4x4& m) { - auto* b = bbox; - Vec3 pt[8] = { - {b[0].x,b[0].y,b[0].z}, - {b[1].x,b[0].y,b[0].z}, - {b[1].x,b[1].y,b[0].z}, - {b[0].x,b[1].y,b[0].z}, - - {b[0].x,b[0].y,b[1].z}, - {b[1].x,b[0].y,b[1].z}, - {b[1].x,b[1].y,b[1].z}, - {b[0].x,b[1].y,b[1].z}, - }; - - for(auto& i:pt) - m.project(i.x,i.y,i.z); - - midTr = mid; - m.project(midTr); - } - void Bounds::calcR() { - float dx = std::fabs(bbox[0].x-bbox[1].x); - float dy = std::fabs(bbox[0].y-bbox[1].y); - float dz = std::fabs(bbox[0].z-bbox[1].z); - r = std::sqrt(dx*dx+dy*dy+dz*dz)*0.5f; + // float dx = std::fabs(bbox[0].x-bbox[1].x); + // float dy = std::fabs(bbox[0].y-bbox[1].y); + // float dz = std::fabs(bbox[0].z-bbox[1].z); + // r = std::sqrt(dx*dx+dy*dy+dz*dz)*0.5f; float x = std::max(std::abs(bbox[0].x),std::abs(bbox[1].x)); float y = std::max(std::abs(bbox[0].y),std::abs(bbox[1].y)); diff --git a/game/graphics/bounds.h b/game/graphics/bounds.h index 60f5f1a88..c81b423f7 100644 --- a/game/graphics/bounds.h +++ b/game/graphics/bounds.h @@ -9,7 +9,7 @@ class Bounds final { public: - Bounds(); + Bounds() = default; void assign(const Tempest::Vec3& cen, float sizeSz); void assign(const Bounds& a, const Bounds& b); @@ -17,16 +17,11 @@ class Bounds final { void assign(const std::pair& bbox); void assign(const std::vector& vbo); void assign(const std::vector& vbo, const std::vector& ibo, size_t iboOffset, size_t iboLenght); - void setObjMatrix(const Tempest::Matrix4x4& m); Tempest::Vec3 bbox[2]; - Tempest::Vec3 midTr; - float r = 0, rConservative = 0; + float rConservative = 0; private: - void transformBbox(const Tempest::Matrix4x4& m); void calcR(); - - Tempest::Vec3 mid; }; diff --git a/game/graphics/mesh/animationsolver.cpp b/game/graphics/mesh/animationsolver.cpp index 8a7eb6b0b..6a3fddc83 100644 --- a/game/graphics/mesh/animationsolver.cpp +++ b/game/graphics/mesh/animationsolver.cpp @@ -357,6 +357,10 @@ const Animation::Sequence* AnimationSolver::implSolveAnim(AnimationSolver::Anim return solveFrm("T_STUMBLE"); if(a==Anim::StumbleB) return solveFrm("T_STUMBLEB"); + + if((a==Anim::DeadA || a==Anim::DeadB) && bool(wlkMode & WalkBit::WM_Dive)){ + return solveFrm("S_DROWNED"); + } if(a==Anim::DeadA) { if(pose.isInAnim("S_WOUNDED") || pose.isInAnim("T_STAND_2_WOUNDED") || pose.isInAnim("S_WOUNDEDB") || pose.isInAnim("T_STAND_2_WOUNDEDB")) diff --git a/game/graphics/mesh/pose.cpp b/game/graphics/mesh/pose.cpp index f76a20942..0ead07d24 100644 --- a/game/graphics/mesh/pose.cpp +++ b/game/graphics/mesh/pose.cpp @@ -183,7 +183,7 @@ bool Pose::startAnim(const AnimationSolver& solver, const Animation::Sequence *s return false; } const Animation::Sequence* tr=nullptr; - if(i.seq->shortName!=nullptr && sq->shortName!=nullptr) { + if(i.seq->shortName!=nullptr && sq->shortName!=nullptr && i.sAnim!=tickCount) { string_frm tansition("T_",i.seq->shortName,"_2_",sq->shortName); tr = solver.solveFrm(tansition); } @@ -396,7 +396,7 @@ bool Pose::updateFrame(const Animation::Sequence &s, BodyState bs, uint64_t sBle smp.position.y = trY; else if(bs==BS_SWIM || bs==BS_DIVE) smp.position.y = trY; - else if(s.isFly()) + else if(bs==BS_JUMP) //else if(s.isFly()) smp.position.y = trY; //d.translate.y; } diff --git a/game/graphics/mesh/skeleton.cpp b/game/graphics/mesh/skeleton.cpp index 18f994943..8a16ca91e 100644 --- a/game/graphics/mesh/skeleton.cpp +++ b/game/graphics/mesh/skeleton.cpp @@ -8,12 +8,17 @@ using namespace Tempest; Skeleton::Skeleton(const zenkit::ModelHierarchy& src, const Animation* anim, std::string_view name) :fileName(name), anim(anim) { + bbox[0] = {src.bbox.min.x, src.bbox.min.y, src.bbox.min.z}; + bbox[1] = {src.bbox.max.x, src.bbox.max.y, src.bbox.max.z}; + +#if 1 bboxCol[0] = {src.collision_bbox.min.x, src.collision_bbox.min.y, src.collision_bbox.min.z}; bboxCol[1] = {src.collision_bbox.max.x, src.collision_bbox.max.y, src.collision_bbox.max.z}; - - // bbox size apears to be halfed in source file - bboxCol[0] *= 2.f; - bboxCol[1] *= 2.f; +#else + //NOTE: 'collision_bbox' doesn't match marvin view + bboxCol[0] = {src.bbox.min.x, src.bbox.min.y, src.bbox.min.z}; + bboxCol[1] = {src.bbox.max.x, src.bbox.max.y, src.bbox.max.z}; +#endif nodes.resize(src.nodes.size()); tr.resize(src.nodes.size()); @@ -88,7 +93,7 @@ std::string_view Skeleton::defaultMesh() const { float Skeleton::colisionHeight() const { // scale by 0.5, to be compatible with old behaviour for now - return std::fabs(bboxCol[1].y-bboxCol[0].y) * 0.5f; + return std::fabs(bboxCol[1].y-bboxCol[0].y); } void Skeleton::mkSkeleton() { diff --git a/game/graphics/mesh/skeleton.h b/game/graphics/mesh/skeleton.h index e8510d210..d1121fb6c 100644 --- a/game/graphics/mesh/skeleton.h +++ b/game/graphics/mesh/skeleton.h @@ -26,6 +26,7 @@ class Skeleton final { size_t BIP01_HEAD = size_t(-1); + Tempest::Vec3 bbox[2] ={}; Tempest::Vec3 bboxCol[2]={}; size_t findNode(std::string_view name, size_t def=size_t(-1)) const; diff --git a/game/mainwindow.cpp b/game/mainwindow.cpp index f58339f41..924521453 100644 --- a/game/mainwindow.cpp +++ b/game/mainwindow.cpp @@ -607,16 +607,18 @@ void MainWindow::paintFocus(Painter& p, const Focus& focus, const Matrix4x4& vp) auto tr = vp; tr.mul(focus.npc->transform()); - auto b = focus.npc->bounds(); + const auto b = focus.npc->bounds(); + const auto bbox = b.bbox; //focus.npc->bBox(); + Vec3 bx[] = { - {b.bbox[0].x,b.bbox[0].y,b.bbox[0].z}, - {b.bbox[1].x,b.bbox[0].y,b.bbox[0].z}, - {b.bbox[1].x,b.bbox[1].y,b.bbox[0].z}, - {b.bbox[0].x,b.bbox[1].y,b.bbox[0].z}, - {b.bbox[0].x,b.bbox[0].y,b.bbox[1].z}, - {b.bbox[1].x,b.bbox[0].y,b.bbox[1].z}, - {b.bbox[1].x,b.bbox[1].y,b.bbox[1].z}, - {b.bbox[0].x,b.bbox[1].y,b.bbox[1].z}, + {bbox[0].x, bbox[0].y, bbox[0].z}, + {bbox[1].x, bbox[0].y, bbox[0].z}, + {bbox[1].x, bbox[1].y, bbox[0].z}, + {bbox[0].x, bbox[1].y, bbox[0].z}, + {bbox[0].x, bbox[0].y, bbox[1].z}, + {bbox[1].x, bbox[0].y, bbox[1].z}, + {bbox[1].x, bbox[1].y, bbox[1].z}, + {bbox[0].x, bbox[1].y, bbox[1].z}, }; int min[2]={ix,iy-20}, max[2]={ix,iy-20}; diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index 5ca191fe5..e6e220ab3 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -16,20 +16,21 @@ #include "utils/dbgpainter.h" -const float DynamicWorld::ghostPadding=50-22.5f; -const float DynamicWorld::ghostHeight =140; -const float DynamicWorld::worldHeight =20000; +//#include "BulletCollision/CollisionShapes/btCylinderShape.h" + +const float DynamicWorld::ghostPadding = 90;//55.f; //50-22.5f; +const float DynamicWorld::worldHeight = 20000; struct DynamicWorld::HumShape:btCapsuleShape { - HumShape(btScalar radius, btScalar height):btCapsuleShape( - CollisionWorld::toMeters(height<=0.f ? 0.f : radius), - CollisionWorld::toMeters(height)) {} + //NOTE: total height is height+2*radius + HumShape(btScalar radius, btScalar height):btCapsuleShape(CollisionWorld::toMeters(radius), + CollisionWorld::toMeters(height)) {} // "human" object mush have identyty scale/rotation matrix. Only translation allowed. void getAabb(const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const override { const btScalar rad = getRadius(); btVector3 extent(rad,rad,rad); - extent[m_upAxis] = rad + getHalfHeight(); + extent[m_upAxis] = getHalfHeight() + rad; btVector3 center = t.getOrigin(); aabbMin = center - extent; @@ -43,22 +44,38 @@ struct DynamicWorld::NpcBody : btRigidBody { delete m_collisionShape; } - Tempest::Vec3 pos={}; - float r=0, h=0, rX=0, rZ=0; - bool enable=true; - size_t frozen=size_t(-1); - uint64_t lastMove=0; + Tempest::Vec3 pos = {}; + float r = 0; + float h = 0; + float gPadd = 0.f; + float stepSz = 0.f; + bool enable = true; + size_t frozen = size_t(-1); + uint64_t lastMove = 0; Npc* toNpc() { return reinterpret_cast(getUserPointer()); } + auto centerPosition() const { + return Tempest::Vec3(pos.x, pos.y+h*0.5f, pos.z); + } + + auto ellipsoidSize() const { + return Tempest::Vec3(r, h*0.5f, r); + } + + auto groundOffset() const { + const float extPadding = 10.f; // Khorinis port hack + return h*0.5f + ghostPadding*0.5f + extPadding; + } + void setPosition(const Tempest::Vec3& p) { - auto m = CollisionWorld::toMeters(p+Tempest::Vec3(0,(h-r-ghostPadding)*0.5f+r+ghostPadding,0)); + auto m = p + Tempest::Vec3(0,groundOffset(),0); pos = p; btTransform trans; trans.setIdentity(); - trans.setOrigin(m); + trans.setOrigin(CollisionWorld::toMeters(m)); setWorldTransform(trans); } }; @@ -75,17 +92,17 @@ struct DynamicWorld::NpcBodyList final { } NpcBody* create(const Tempest::Vec3 &min, const Tempest::Vec3 &max) { - static const float dimMax = 45.f; - - float dx = max.x-min.x; - float dz = max.z-min.z; - float dim = (dx+dz)*0.5f; // npc-to-landscape collision size - float height = max.y-min.y; + auto size = max - min; + float radius = std::min(size.y*0.5f, std::min(size.x, size.z))*0.5f; // npc-to-landscape collision size + float height = size.y; - if(dim>dimMax) - dim = dimMax; + float ghostPadding = height*0.5f; + float cHeight = std::max(height-2.f*radius-ghostPadding, 0.f); - btCollisionShape* shape = new HumShape(dim*0.5f, std::max(height-ghostPadding,0.f)*0.5f); + //NOTE: it seem vanilla uses elipsoids at some point, at least for npc-2-npc collisions + btCollisionShape* shape = new HumShape(radius, cHeight); + //btCollisionShape* shape = new btCylinderShape(CollisionWorld::toMeters(Tempest::Vec3(radius, height*0.5f, radius))); + //btCollisionShape* shape = new btCapsuleShape(CollisionWorld::toMeters(radius), CollisionWorld::toMeters(height)); NpcBody* obj = new NpcBody(shape); btTransform trans; @@ -94,8 +111,15 @@ struct DynamicWorld::NpcBodyList final { obj->setUserIndex(C_Ghost); obj->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE); + // obj->r = radius * 2.f; + // obj->r = std::max(size.x, size.z) * 0.5f; + obj->r = std::min(size.x, size.z); // best so far + obj->h = height; + obj->gPadd = ghostPadding; + obj->stepSz = std::min(height*0.5f, radius); // safe tunneling size + maxR = std::max(maxR, obj->r); + add(obj); - resize(*obj,height,dx,dz); return obj; } @@ -116,7 +140,7 @@ struct DynamicWorld::NpcBodyList final { return false; } - bool del(void* b,std::vector& arr){ + bool del(void* b, std::vector& arr){ for(size_t i=0;i R*R) return false; if(npc.hupdateAabbs(); if(maxDy==0) maxDy = worldHeight; - return ray(Tempest::Vec3(from.x,from.y+ghostPadding,from.z), Tempest::Vec3(from.x,from.y-maxDy,from.z)); + return ray(from, Tempest::Vec3(from.x,from.y-maxDy,from.z)); } DynamicWorld::RayWaterResult DynamicWorld::waterRay(const Tempest::Vec3& from) const { @@ -543,7 +579,7 @@ DynamicWorld::RayWaterResult DynamicWorld::implWaterRay(const Tempest::Vec3& fro float waterY = callback.m_hitPointWorld.y()*100.f; auto cave = ray(from,Tempest::Vec3(to.x,waterY,to.z)); if(cave.hasCol && cave.v.y::infinity(); ret.hasCol = false; } else { ret.wdepth = waterY; @@ -552,7 +588,7 @@ DynamicWorld::RayWaterResult DynamicWorld::implWaterRay(const Tempest::Vec3& fro return ret; } - ret.wdepth = from.y-worldHeight; + ret.wdepth = -std::numeric_limits::infinity(); ret.hasCol = false; return ret; } @@ -632,7 +668,10 @@ DynamicWorld::RayLandResult DynamicWorld::ray(const Tempest::Vec3& from, const T hitNorm.y = callback.m_hitNormalWorld.y(); hitNorm.z = callback.m_hitNormalWorld.z(); } + } else { + hitPos.y = -std::numeric_limits::infinity(); } + RayLandResult ret; ret.v = hitPos; ret.n = hitNorm; @@ -701,13 +740,11 @@ float DynamicWorld::soundOclusion(const Tempest::Vec3& from, const Tempest::Vec3 DynamicWorld::NpcItem DynamicWorld::ghostObj(std::string_view visual) { Tempest::Vec3 min={0,0,0}, max={0,0,0}; if(auto sk = Resources::loadSkeleton(visual)) { - // scale by 0.5, to be compatible with old behaviour for now - min = sk->bboxCol[0] * 0.5f; - max = sk->bboxCol[1] * 0.5f; + min = sk->bboxCol[0]; + max = sk->bboxCol[1]; } - auto obj = npcList->create(min,max); - float dim = std::max(obj->rX,obj->rZ); - return NpcItem(this,obj,dim*0.5f); + auto obj = npcList->create(min,max); + return NpcItem(this,obj); } DynamicWorld::Item DynamicWorld::staticObj(const PhysicMeshShape *shape, const Tempest::Matrix4x4 &m) { @@ -969,12 +1006,19 @@ std::string_view DynamicWorld::validateSectorName(std::string_view name) const { } bool DynamicWorld::hasCollision(const NpcItem& it, CollisionTest& out) { + bool ret = false; if(npcList->hasCollision(it,out.normal,out.npc)){ - out.normal /= out.normal.length(); - out.npcCol = true; - return true; + ret = true; + } + if(world->hasCollision(*it.obj,out.normal,out.vob)) { + out.landCol = true; + ret = true; } - return world->hasCollision(*it.obj,out.normal,out.vob); + + if(!ret) + return false; + out.normal /= out.normal.length(); + return true; } DynamicWorld::NpcItem::~NpcItem() { @@ -1026,25 +1070,15 @@ float DynamicWorld::NpcItem::centerY() const { return 0; } +float DynamicWorld::NpcItem::groundOffset() const { + return obj->groundOffset(); + } + const Tempest::Vec3& DynamicWorld::NpcItem::position() const { return obj->pos; } void DynamicWorld::NpcItem::debugDraw(DbgPainter& p) const { - p.setBrush(Tempest::Color(0,1,0)); - p.drawPoint(obj->pos); - - const auto cen = Tempest::Vec3(obj->pos.x, centerY(), obj->pos.z); - p.setBrush(Tempest::Color(0,0,1)); - p.drawPoint(cen); - - p.setPen(Tempest::Color(1,1,1)); - p.drawLine(cen, cen+Tempest::Vec3(0,25,0)); - p.setPen(Tempest::Color(1,1,0)); - p.drawLine(cen, cen+Tempest::Vec3(25,0,0)); - p.setPen(Tempest::Color(1,0.5f,0)); - p.drawLine(cen, cen+Tempest::Vec3(0,0,25)); - btVector3 aabb0, aabb1; obj->getAabb(aabb0, aabb1); @@ -1100,7 +1134,7 @@ DynamicWorld::MoveCode DynamicWorld::NpcItem::tryMove(const Tempest::Vec3& to, C DynamicWorld::MoveCode DynamicWorld::NpcItem::implTryMove(const Tempest::Vec3& to, const Tempest::Vec3& pos0, CollisionTest& out) { auto initial = pos0; - auto r = obj->r; + auto r = obj->stepSz; int count = 1; auto dp = to-initial; @@ -1111,26 +1145,42 @@ DynamicWorld::MoveCode DynamicWorld::NpcItem::implTryMove(const Tempest::Vec3& t count = std::max(countXZ,countY); } - auto prev = initial; + bool skipNpc = false; + bool skipLnd = false; + bool secondPass = false; for(int i=1; i<=count; ++i) { - auto pos = initial+(dp*float(i))/float(count); + const auto pos = initial+(dp*float(i))/float(count); implSetPosition(pos); - if(owner->hasCollision(*this,out)) { - if(i>1) { - // moved a bit - out.partial = prev; - return MoveCode::MC_Partial; - } - implSetPosition(initial); - if(owner->hasCollision(*this,out)) { - // was in collision from the start - implSetPosition(to); - return MoveCode::MC_OK; + + if(!owner->hasCollision(*this,out)) + continue; + + if((out.npc==nullptr || skipNpc) && (!out.landCol || skipLnd)) + continue; + + if(i>1) { + // moved a bit + out.partial = initial+(dp*float(i-1))/float(count); + implSetPosition(out.partial); + return MoveCode::MC_Partial; + } + + implSetPosition(initial); + if(i==1 && !secondPass) { + // maybe we were stuck into something(npc) from the start? + CollisionTest tmpOut = {}; + if(!owner->hasCollision(*this,tmpOut)) { + return MoveCode::MC_Fail; } - return MoveCode::MC_Fail; + skipNpc = tmpOut.npc!=nullptr; + skipLnd = tmpOut.landCol; + secondPass = true; + i = 0; + continue; } - } + return MoveCode::MC_Fail; + } return MoveCode::MC_OK; } diff --git a/game/physics/dynamicworld.h b/game/physics/dynamicworld.h index d236f8d57..684d4005a 100644 --- a/game/physics/dynamicworld.h +++ b/game/physics/dynamicworld.h @@ -64,16 +64,17 @@ class DynamicWorld final { struct CollisionTest { Tempest::Vec3 partial = {}; Tempest::Vec3 normal = {}; - bool npcCol = false; bool preFall = false; + Interactive* vob = nullptr; Npc* npc = nullptr; + bool landCol = false; }; struct NpcItem { public: NpcItem()=default; - NpcItem(DynamicWorld* owner,NpcBody* obj,float r):owner(owner),obj(obj){} + NpcItem(DynamicWorld* owner, NpcBody* obj):owner(owner),obj(obj){} NpcItem(NpcItem&& it):owner(it.owner),obj(it.obj){it.obj=nullptr;} ~NpcItem(); @@ -93,6 +94,7 @@ class DynamicWorld final { auto center() const -> Tempest::Vec3; float centerY() const; + float groundOffset() const; bool testMove(const Tempest::Vec3& to, CollisionTest& out); bool testMove(const Tempest::Vec3& to, const Tempest::Vec3& from, CollisionTest& out); @@ -299,6 +301,5 @@ class DynamicWorld final { std::unique_ptr bulletList; std::unique_ptr bboxList; - static const float ghostHeight; static const float worldHeight; }; diff --git a/game/world/objects/item.cpp b/game/world/objects/item.cpp index 78a09cf7b..b1cb2df99 100644 --- a/game/world/objects/item.cpp +++ b/game/world/objects/item.cpp @@ -189,11 +189,11 @@ void Item::setPhysicsEnable(const MeshObjects::Mesh& view) { } void Item::setPhysicsEnable(const ProtoMesh* mesh) { - if(mesh==nullptr) + if(bBox()==nullptr) return; auto& p = *world.physic(); Bounds b; - b.assign(mesh->bbox); + b.assign(bBox()); physic = p.dynamicObj(transform(),b,zenkit::MaterialGroup(hitem->material)); physic.setItem(this); } diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 36ffa8960..dd635ed24 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -450,7 +450,7 @@ void Npc::setDirectionY(float rotation) { if(rotation<-90) rotation = -90; rotation = std::fmod(rotation,360.f); - if(!mvAlgo.isSwim() && !(interactive()!=nullptr && interactive()->isLadder())) + if(!mvAlgo.isDive() && !(interactive()!=nullptr && interactive()->isLadder())) return; angleY = rotation; durtyTranform |= TR_Rot; @@ -596,7 +596,7 @@ void Npc::onNoHealth(bool death, HitSound sndMask) { // Note: clear perceptions for William in Jarkentar for(size_t i=0;ivoice>0 && sndMask!=HS_NoSound) { + if(hnpc->voice>0 && sndMask!=HS_NoSound && !isDive()) { emitSoundSVM(svm); } @@ -673,9 +673,13 @@ float Npc::rotationYRad() const { } Bounds Npc::bounds() const { - auto b = visual.bounds(); - b.setObjMatrix(transform()); - return b; + return visual.bounds(); + } + +auto Npc::bBox() const -> const Vec3* { + if(visual.visualSkeleton()==nullptr) + return nullptr; + return visual.visualSkeleton()->bboxCol; } Vec3 Npc::centerPosition() const { @@ -1034,19 +1038,27 @@ bool Npc::isFlyAnim() const { } bool Npc::isFalling() const { - return mvAlgo.isFalling(); + return mvAlgo.state()==MoveAlgo::Falling; } bool Npc::isFallingDeep() const { - return mvAlgo.isInAir() && (visual.pose().isInAnim("S_FALL") || visual.pose().isInAnim("S_FALLB")); + return mvAlgo.isFalling() && (visual.pose().isInAnim("S_FALL") || visual.pose().isInAnim("S_FALLB")); } bool Npc::isSlide() const { - return mvAlgo.isSlide(); + return mvAlgo.state()==MoveAlgo::Slide; } bool Npc::isInAir() const { - return mvAlgo.isInAir(); + return mvAlgo.state()==MoveAlgo::InAir; + } + +bool Npc::isJump() const { + return mvAlgo.state()==MoveAlgo::Jump; + } + +bool Npc::isJumpUp() const { + return mvAlgo.state()==MoveAlgo::JumpUp; } void Npc::invalidateTalentOverlays() { @@ -1504,7 +1516,7 @@ bool Npc::implAttack(uint64_t dt) { const auto act = fghAlgo.nextFromQueue(*this,*currentTarget,owner.script()); // vanilla behavior, required for orcs in G1 orcgraveyard - if(ws==WeaponState::NoWeapon && isAiQueueEmpty()) { + if(ws==WeaponState::NoWeapon && isAiQueueEmpty() && mvAlgo.state()==MoveAlgo::Run) { drawWeaponMelee(); return true; } @@ -1948,7 +1960,7 @@ void Npc::takeDamage(Npc& other, const Bullet* b, const CollideMask bMask, int32 } if(hitResult.hasHit) { - if(bodyStateMasked()!=BS_UNCONSCIOUS && interactive()==nullptr && !isSwim() && !mvAlgo.isClimb()) { + if(bodyStateMasked()!=BS_UNCONSCIOUS && interactive()==nullptr && !mvAlgo.isSwim() && !mvAlgo.isClimb()) { const bool noInter = (hnpc->bodystate_interruptable_override!=0); if(!noInter) { //NOTE: kepp rotation animation: this results in more accurate fight with trolls @@ -2211,8 +2223,10 @@ void Npc::tick(uint64_t dt) { if(tickSz>0) { t-=v; int dmg = t/tickSz - (t-int(dt))/tickSz; - if(dmg>0) + if(dmg>0) { + lastHit = nullptr; changeAttribute(ATR_HITPOINTS,-dmg,false); + } } } } @@ -3604,7 +3618,7 @@ bool Npc::drawMage(uint8_t slot) { } bool Npc::drawSpell(int32_t spell) { - if(isFalling() || mvAlgo.isSwim() || bodyStateMasked()==BS_CASTING) + if(mvAlgo.isFalling() || mvAlgo.isSwim() || bodyStateMasked()==BS_CASTING) return false; auto weaponSt=weaponState(); if(weaponSt!=WeaponState::NoWeapon && weaponSt!=WeaponState::Mage) { @@ -4263,7 +4277,7 @@ Npc::JumpStatus Npc::tryJump() { JumpStatus ret; DynamicWorld::CollisionTest info; - if(!isInAir() && physic.testMove(pos0+dp,info)) { + if(!mvAlgo.isJumpUp() && physic.testMove(pos0+dp,info)) { // jump forward ret.anim = Anim::Jump; ret.noClimb = true; @@ -4288,7 +4302,7 @@ Npc::JumpStatus Npc::tryJump() { if(!physic.testMove(pos1,pos0,info) || !physic.testMove(pos2,pos1,info)) { // check approximate path of climb failed - ret.anim = Anim::Jump; + ret.anim = Anim::JumpUp; ret.noClimb = true; return ret; } @@ -4308,14 +4322,14 @@ Npc::JumpStatus Npc::tryJump() { return ret; } - if(isInAir() && dY<=jumpLow + visual.pose().translateY()) { + if(mvAlgo.isJumpUp() && dY<=jumpLow + visual.pose().translateY()) { // jumpup -> climb ret.anim = Anim::JumpHang; ret.height = jumpY; return ret; } - if(isInAir()) { + if(mvAlgo.isJumpUp()) { ret.anim = Anim::Idle; ret.noClimb = true; return ret; @@ -4514,7 +4528,7 @@ SensesBit Npc::canSenseNpc(const Npc &oth, bool freeLos, float extRange) const { // NOTE2: interacting with chest(lockpicking) or some MOBSI should not produce 'noise' // NOTE3: seem npc can't hear player in general case, and hearing relevant only for sendImmediatePerc cases const bool isNoisy = false; - const auto mid = oth.bounds().midTr; + const auto mid = oth.centerPosition(); return canSenseNpc(mid,freeLos,isNoisy,extRange); } diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 53945ac7b..2e5ac4ed8 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -103,6 +103,7 @@ class Npc final { float runAngle() const { return runAng; } float fatness() const { return bdFatness; } Bounds bounds() const; + auto bBox() const -> const Tempest::Vec3*; void stopDlgAnim(); void clearSpeed(); @@ -186,6 +187,8 @@ class Npc final { bool isFallingDeep() const; bool isSlide() const; bool isInAir() const; + bool isJump() const; + bool isJumpUp() const; bool isStanding() const; bool isSwim() const; bool isInWater() const;