diff --git a/game/game/fightalgo.cpp b/game/game/fightalgo.cpp index 08e0cd423..5640b23a6 100644 --- a/game/game/fightalgo.cpp +++ b/game/game/fightalgo.cpp @@ -235,6 +235,14 @@ void FightAlgo::onTakeHit() { i = MV_NULL; } +float FightAlgo::qDistTo(const Npc& npc, const Npc& tg) const { + return (npc.collosionCenter() - tg.collosionCenter()).quadLength(); + } + +auto FightAlgo::distVec(const Npc& npc, const Npc& tg) const -> Tempest::Vec3 { + return npc.collosionCenter() - tg.collosionCenter(); + } + float FightAlgo::baseDistance(const Npc& npc, const Npc& tg, GameScript &owner) const { auto& gv = owner.guildVal(); float baseTg = float(gv.fight_range_base[tg .guild()]); @@ -272,8 +280,8 @@ bool FightAlgo::isInAttackRange(const Npc &npc, const Npc &tg, GameScript &owner // tested in vanilla on Bloofly's: // 60 weapon range (Spiked club) is not enough to hit // 70 weapon range (Rusty Sword) is good to hit - auto dist = npc.qDistTo(tg); - auto pd = prefferedAttackDistance(npc,tg,owner); + auto dist = qDistTo(npc, tg); + auto pd = prefferedAttackDistance(npc,tg,owner); static float padding = 0; if(npc.hasState(BS_RUN)) pd += padding; // padding, for wolf @@ -281,27 +289,47 @@ bool FightAlgo::isInAttackRange(const Npc &npc, const Npc &tg, GameScript &owner } bool FightAlgo::isInFinishRange(const Npc& npc, const Npc& tg, GameScript& owner) const { - auto dist = npc.qDistTo(tg); - auto pd = attackFinishDistance(owner); + auto dist = qDistTo(npc, tg); + auto pd = attackFinishDistance(owner); + return (dist<=pd*pd); + } + +bool FightAlgo::isInBaseRange(const Npc& npc, const Npc& tg, GameScript& owner) const { + auto dist = qDistTo(npc, tg); + auto pd = baseDistance(npc,tg,owner); return (dist<=pd*pd); } bool FightAlgo::isInWRange(const Npc& npc, const Npc& tg, GameScript& owner) const { - auto dist = npc.qDistTo(tg); + auto dist = qDistTo(npc, tg); auto pd = prefferedAttackDistance(npc,tg,owner); return (dist<=pd*pd); } bool FightAlgo::isInGRange(const Npc &npc, const Npc &tg, GameScript &owner) const { - auto dist = npc.qDistTo(tg); - auto pd = prefferedGDistance(npc,tg,owner); + auto dist = qDistTo(npc, tg); + auto pd = prefferedGDistance(npc,tg,owner); return (dist<=pd*pd); } bool FightAlgo::isInFocusAngle(const Npc &npc, const Npc &tg) const { static const float maxAngle = std::cos(float(30.0*M_PI/180.0)); - const auto dpos = tg.centerPosition() - npc.centerPosition(); + const auto dpos = distVec(tg, npc); + const float plAng = npc.rotationRad(); + + const float da = plAng-std::atan2(dpos.z,dpos.x); + const float c = std::cos(da); + + if(c - #include +#include class Npc; class GameScript; @@ -43,6 +43,8 @@ class FightAlgo final { bool hasInstructions() const; bool fetchInstructions(Npc &npc, Npc &tg, GameScript& owner); + float qDistTo(const Npc& npc, const Npc& tg) const; + auto distVec(const Npc& npc, const Npc& tg) const -> Tempest::Vec3; float baseDistance (const Npc &npc, const Npc &tg, GameScript &owner) const; float prefferedAttackDistance(const Npc &npc, const Npc &tg, GameScript &owner) const; @@ -51,9 +53,11 @@ class FightAlgo final { bool isInAttackRange (const Npc &npc, const Npc &tg, GameScript &owner) const; bool isInFinishRange (const Npc &npc, const Npc &tg, GameScript &owner) const; + bool isInBaseRange (const Npc &npc, const Npc &tg, GameScript &owner) const; bool isInWRange (const Npc &npc, const Npc &tg, GameScript &owner) const; bool isInGRange (const Npc &npc, const Npc &tg, GameScript &owner) const; bool isInFocusAngle (const Npc &npc, const Npc &tg) const; + bool isInFocusAngle (const Npc &npc, const Npc &tg, float ang) const; bool isInJumpBackAngle (const Npc &npc, const Npc &tg) const; private: diff --git a/game/game/movealgo.cpp b/game/game/movealgo.cpp index 40ae0efee..ebe32efad 100644 --- a/game/game/movealgo.cpp +++ b/game/game/movealgo.cpp @@ -917,8 +917,12 @@ void MoveAlgo::onMoveFailed(const Tempest::Vec3& dp, const DynamicWorld::Collisi return; } - if(forward && !info.preFall && npc.currentTarget==info.npc && npc.bodyStateMasked()==BS_HIT) + if(forward && !info.preFall && npc.currentTarget==info.npc && npc.bodyStateMasked()==BS_HIT) { + // if charge attack run into npc, FAI should be reengaged + // makes ai behaviour more consistent with vanilla, when fighting field-raiders + npc.implFaiWait(0); return; + } if(npc.processPolicy()!=NpcProcessPolicy::Player) lastBounce = npc.world().tickCount(); diff --git a/game/graphics/effect.cpp b/game/graphics/effect.cpp index e6f31a81e..434503ab8 100644 --- a/game/graphics/effect.cpp +++ b/game/graphics/effect.cpp @@ -359,12 +359,13 @@ void Effect::setupCollision(World& owner) { auto n = origin; auto sId = splId; auto b = bullet; - pfx.setPhysicsEnable(owner,[vfx,n,sId,b](Npc& npc){ - if(n!=&npc) { + pfx.setPhysicsEnable(owner,[vfx,n,sId,b](Npc& npc) { + if(b!=nullptr) { + b->onEffectCollide(npc); + } + else if(n!=&npc) { auto src = (n!=nullptr ? n : &npc); npc.takeDamage(*src,b,vfx,sId); - if(b!=nullptr) - b->setFlags(Bullet::Stopped); } }); } diff --git a/game/graphics/mdlvisual.cpp b/game/graphics/mdlvisual.cpp index 26cf2b153..1400fc03c 100644 --- a/game/graphics/mdlvisual.cpp +++ b/game/graphics/mdlvisual.cpp @@ -2,7 +2,6 @@ #include "graphics/mesh/skeleton.h" #include "game/serialize.h" -#include "utils/string_frm.h" #include "world/objects/npc.h" #include "world/objects/interactive.h" #include "world/objects/item.h" @@ -579,10 +578,6 @@ bool MdlVisual::stopItemStateAnim(Npc& npc) { return true; } -bool MdlVisual::hasAnim(std::string_view scheme) const { - return solver.solveFrm(scheme)!=nullptr; - } - void MdlVisual::stopWalkAnim(Npc &npc) { skInst->stopWalkAnim(); if(!skInst->hasAnim()) @@ -593,9 +588,12 @@ bool MdlVisual::isStanding() const { return skInst->isStanding(); } -bool MdlVisual::isAnimExist(std::string_view name) const { - const Animation::Sequence *sq = solver.solveFrm(name); - return sq!=nullptr; +bool MdlVisual::hasAnim(std::string_view scheme) const { + return solver.solveFrm(scheme)!=nullptr; + } + +bool MdlVisual::hasAnim(AnimationSolver::Anim a, WeaponState st, WalkBit wlk) const { + return solver.solveAnim(a,st,wlk,*skInst)!=nullptr; } const Animation::Sequence* MdlVisual::startAnimAndGet(std::string_view name, uint64_t tickCount, bool forceAnim) { diff --git a/game/graphics/mdlvisual.h b/game/graphics/mdlvisual.h index a6d4dfbe6..dadfd6ddc 100644 --- a/game/graphics/mdlvisual.h +++ b/game/graphics/mdlvisual.h @@ -3,7 +3,6 @@ #include #include "graphics/mesh/animationsolver.h" -#include "graphics/pfx/pfxobjects.h" #include "game/constants.h" #include "meshobjects.h" #include "effect.h" @@ -84,7 +83,9 @@ class MdlVisual final { bool isStanding() const; - bool isAnimExist(std::string_view name) const; + bool hasAnim(AnimationSolver::Anim a, WeaponState st, WalkBit wlk) const; + bool hasAnim(std::string_view scheme) const; + const Animation::Sequence* startAnimAndGet(std::string_view name, uint64_t tickCount, bool forceAnim = false); const Animation::Sequence* startAnimAndGet(Npc& npc, std::string_view name, uint8_t comb, BodyState bs); const Animation::Sequence* startAnimAndGet(Npc& npc, AnimationSolver::Anim a, uint8_t comb, WeaponState st, WalkBit wlk); @@ -98,7 +99,6 @@ class MdlVisual final { void stopDlgAnim (Npc& npc); void stopAnim (Npc& npc, std::string_view anim); bool stopItemStateAnim(Npc &npc); - bool hasAnim (std::string_view scheme) const; void stopWalkAnim (Npc &npc); void setAnimRotate (Npc &npc, int dir); void setAnimWhirl (Npc &npc, int dir); diff --git a/game/graphics/objvisual.cpp b/game/graphics/objvisual.cpp index f742045ba..8a40b8236 100644 --- a/game/graphics/objvisual.cpp +++ b/game/graphics/objvisual.cpp @@ -260,9 +260,9 @@ const Animation::Sequence* ObjVisual::startAnimAndGet(std::string_view name, uin return nullptr; } -bool ObjVisual::isAnimExist(std::string_view name) const { +bool ObjVisual::hasAnim(std::string_view name) const { if(type==M_Mdl) - return mdl.view.isAnimExist(name); + return mdl.view.hasAnim(name); return false; } diff --git a/game/graphics/objvisual.h b/game/graphics/objvisual.h index 413620b6f..4e4c7d8b4 100644 --- a/game/graphics/objvisual.h +++ b/game/graphics/objvisual.h @@ -27,7 +27,7 @@ class ObjVisual { void setInteractive(Interactive* it); const Animation::Sequence* startAnimAndGet(std::string_view name, uint64_t tickCount, bool force = false); - bool isAnimExist(std::string_view name) const; + bool hasAnim(std::string_view name) const; bool updateAnimation(Npc* npc, Interactive* mobsi, World& world, uint64_t dt, bool force); void processLayers(World& world); diff --git a/game/physics/collisionworld.cpp b/game/physics/collisionworld.cpp index 186e8d54c..d16a655d4 100644 --- a/game/physics/collisionworld.cpp +++ b/game/physics/collisionworld.cpp @@ -136,6 +136,7 @@ bool CollisionWorld::hasCollision(btRigidBody& it, Tempest::Vec3& normal, Intera struct rCallBack : public btCollisionWorld::ContactResultCallback { int count = 0; Tempest::Vec3 norm = {}; + float dist = 0; // collision depth btCollisionObject* src = nullptr; Interactive* vob = nullptr; @@ -155,9 +156,10 @@ bool CollisionWorld::hasCollision(btRigidBody& it, Tempest::Vec3& normal, Intera btScalar addSingleResult(btManifoldPoint& p, const btCollisionObjectWrapper* proxy0, int, int, const btCollisionObjectWrapper* proxy1, int, int) override { - norm.x+=p.m_normalWorldOnB.x(); - norm.y+=p.m_normalWorldOnB.y(); - norm.z+=p.m_normalWorldOnB.z(); + dist = std::min(p.getDistance(), dist); + norm.x += p.m_normalWorldOnB.x(); + norm.y += p.m_normalWorldOnB.y(); + norm.z += p.m_normalWorldOnB.z(); ++count; auto obj = proxy1->getCollisionObject(); if(obj->getUserIndex()==DynamicWorld::C_Object) { @@ -165,10 +167,6 @@ bool CollisionWorld::hasCollision(btRigidBody& it, Tempest::Vec3& normal, Intera } return 0; } - - void normalize() { - norm /= norm.length(); - } }; rCallBack callback{&it}; @@ -177,8 +175,7 @@ bool CollisionWorld::hasCollision(btRigidBody& it, Tempest::Vec3& normal, Intera contactTest(&it, callback); if(callback.count>0){ - callback.normalize(); - normal = callback.norm; + normal = Tempest::Vec3::normalize(callback.norm); vob = callback.vob; } return callback.count>0; diff --git a/game/physics/dynamicworld.cpp b/game/physics/dynamicworld.cpp index 1d44313a3..70be5189e 100644 --- a/game/physics/dynamicworld.cpp +++ b/game/physics/dynamicworld.cpp @@ -39,12 +39,9 @@ struct DynamicWorld::HumShape:btCapsuleShape { struct DynamicWorld::NpcBody : btRigidBody { NpcBody(btCollisionShape* shape):btRigidBody(0,nullptr,shape){} - ~NpcBody() { - delete m_collisionShape; - } + ~NpcBody() { delete m_collisionShape; } Tempest::Vec3 pos = {}; - float r = 0; float h = 0; float gPadd = 0.f; float stepSz = 0.f; @@ -54,11 +51,15 @@ struct DynamicWorld::NpcBody : btRigidBody { // relevant for npc-to-npc collison float angle = 0; - Tempest::Vec3 bbox[2] = {}; - Tempest::Vec3 dvec = {}; + Tempest::Vec3 bboxCen = {}; + Tempest::Vec3 bboxSize = {}; + float maxRXZ = 0; + float maxRY = 0; + + float maxR = 0; Tempest::Vec3 offsetCenter(float c, float s) const{ - auto off = (bbox[1]+bbox[0])*0.5f; + auto off = bboxCen; return Tempest::Vec3(off.x*c - off.z*s, off.y, off.x*s + off.z*c); @@ -67,7 +68,7 @@ struct DynamicWorld::NpcBody : btRigidBody { float ellipsoidRadius(Tempest::Vec3 dir, float c, float s) const { s = -s; // need to cancel object rotation - Tempest::Vec3 radius = (bbox[1]-bbox[0])*0.5f; + Tempest::Vec3 radius = bboxSize; dir = Tempest::Vec3(dir.x*c - dir.z*s, dir.y, dir.x*s + dir.z*c); @@ -78,10 +79,6 @@ struct DynamicWorld::NpcBody : btRigidBody { return reinterpret_cast(getUserPointer()); } - auto ellipsoidSize() const { - return Tempest::Vec3(r, h*0.5f, r); - } - auto groundOffset() const { const float extPadding = 15.f; // Khorinis port hack return h*0.5f + gPadd*0.5f + extPadding; @@ -93,8 +90,9 @@ struct DynamicWorld::NpcBody : btRigidBody { } void setPosition(const Tempest::Vec3& p) { - auto m = p + Tempest::Vec3(0,groundOffset(),0); pos = p; + + auto m = p + Tempest::Vec3(0,groundOffset(),0); btTransform trans; trans.setIdentity(); trans.setOrigin(CollisionWorld::toMeters(m)); @@ -134,14 +132,31 @@ struct DynamicWorld::NpcBodyList final { obj->setUserIndex(C_Ghost); obj->setCollisionFlags(btCollisionObject::CF_NO_CONTACT_RESPONSE); - 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); - obj->bbox[0] = min; //NOTE: better to have offset/size - obj->bbox[1] = max; + obj->bboxCen = (max+min)*0.5f; + obj->bboxSize = (max-min)*0.5f; + + // safe tunneling size + obj->stepSz = radius; + obj->stepSz = std::min(obj->stepSz, std::abs(max.x)); + obj->stepSz = std::min(obj->stepSz, std::abs(min.x)); + obj->stepSz = std::min(obj->stepSz, std::abs(max.z)); + obj->stepSz = std::min(obj->stepSz, std::abs(min.z)); + + // searchable radius, where we use logica-position(obj->pos), instead of physical center + obj->maxRXZ = std::max(obj->maxRXZ, std::abs(max.x)); + obj->maxRXZ = std::max(obj->maxRXZ, std::abs(min.x)); + obj->maxRXZ = std::max(obj->maxRXZ, std::abs(max.z)); + obj->maxRXZ = std::max(obj->maxRXZ, std::abs(min.z)); + obj->maxRY = std::max(obj->maxRY, std::abs(max.y)); + obj->maxRY = std::max(obj->maxRY, std::abs(min.y)); + + maxRXZ = std::max(maxRXZ, obj->maxRXZ); + maxRY = std::max(maxRY, obj->maxRY); + + obj->maxR = std::max(obj->maxRXZ, obj->maxRY); add(obj); return obj; @@ -154,108 +169,143 @@ struct DynamicWorld::NpcBodyList final { body.push_back(r); } - bool del(void* b){ - if(del(b,body)) + bool del(void* b) { + if(del(b,body,false)) return true; - if(del(b,frozen)){ - srt=false; + if(del(b,frozen,true)) return true; - } return false; } - bool del(void* b, std::vector& arr){ + bool del(void* b, std::vector& arr, bool preserveOrder) { for(size_t i=0;i& arr){ - auto& fr = arr[b->frozen]; + void onMove(NpcBody& n){ + if(n.frozen==size_t(-1)) { + n.lastMove = tick; + return; + } + + auto& fr = frozen[n.frozen]; const float x = fr.x; - if((b->frozen==0 || arr[b->frozen-1].xfrozen+1==arr.size() || xfrozen+1].x)) { + if((n.frozen==0 || frozen[n.frozen-1].xpos.x; - return false; - } else { - fr.body = nullptr; - return true; + return; } + + fr.body = nullptr; + + Record r; + r.body = &n; + body.push_back(r); + + n.lastMove = tick; + n.frozen = size_t(-1); } - void onMove(NpcBody& n){ - if(n.frozen!=size_t(-1)) { - if(delMisordered(&n,frozen)){ - n.lastMove = tick; - n.frozen = size_t(-1); - - Record r; - r.body = &n; - body.push_back(r); - } - } else { - n.lastMove = tick; - } + float raySphereTest(const Tempest::Vec3& origin, const Tempest::Vec3& dir, const Tempest::Vec3& sphere, float R) const { + auto oc = origin - sphere; + + // Quadratic coefficients for (ray.origin + t*ray.direction - center)^2 = radius^2 + float a = 1.f; // Always 1 if direction is normalized + float b = 2.0f * Tempest::Vec3::dotProduct(oc, dir); + float c = Tempest::Vec3::dotProduct(oc,oc) - R*R; + + float discriminant = b * b - 4 * a * c; + if(discriminant < 0) + return -1; + // Find the nearest intersection point (smallest positive t) + float t0 = (-b - std::sqrt(discriminant)) / (2.0f * a); + float t1 = (-b + std::sqrt(discriminant)) / (2.0f * a); + + if(t0 > 0) + return t0; + if(t1 > 0) + return t1; + return -1; // Intersection is behind the ray } - bool rayTest(NpcBody& npc, const Tempest::Vec3& s, const Tempest::Vec3& e, float extR, float& proj) { - if(!npc.enable) - return false; - auto ln = e - s; - auto at = npc.pos - s; + float rayEllipseTest(const Tempest::Vec3& origin, const Tempest::Vec3& dir, const Tempest::Vec3& cen, const Tempest::Vec3& size) const { + if(size.x<=0.001f || size.y<=0.001f || size.z<=0.001f) + return -1.f; + + auto ori = origin - cen; + auto oc = Tempest::Vec3(ori.x/size.x, ori.y/size.y, ori.z/size.z); + auto dx = Tempest::Vec3(dir.x/size.x, dir.y/size.y, dir.z/size.z); + auto ndx = Tempest::Vec3::normalize(dx); - float lenL = ln.length(); + float t = raySphereTest(oc, ndx, Tempest::Vec3(0.f), 1.f); + if(t<=0) + return t; - float dot = Tempest::Vec3::dotProduct(ln,at); + auto hit = (ndx*t); + hit.x *= size.x; + hit.y *= size.y; + hit.z *= size.z; - proj = dot/(lenL<=0 ? 1.f : (lenL*lenL)); - proj = std::max(0.f,std::min(proj,1.f)); + return hit.length(); + } - // TODO: ray-box intersection - auto& tr = npc.getWorldTransform(); - auto pos = CollisionWorld::toCentimeters(tr.getOrigin()); + bool rayTest(NpcBody& npc, const Tempest::Vec3& s, const Tempest::Vec3& dir, float tMax, float extR, float& proj) { + if(!npc.enable) + return false; - auto nr = ln*proj + s; - auto dp = nr - pos; - float R = npc.r + extR; - if(dp.x*dp.x+dp.z*dp.z > R*R) + if(raySphereTest(s, dir, npc.pos, npc.maxR+extR)<0.f) return false; - if(npc.h=tMax) return false; + proj = t; return true; } auto rayTest(const Tempest::Vec3& s, const Tempest::Vec3& e, float extR, const Npc* except) { NpcBody* ret = nullptr; - float minProj = 2; + auto dir = Tempest::Vec3::normalize(e-s); + auto tMax = (e-s).length(); + float tHit = tMax; for(auto i:body) { float proj = 0; - if(i.body->toNpc()!=except && rayTest(*i.body, s, e, extR, proj)) { - if(projtoNpc()!=except && rayTest(*i.body, s, dir, tMax, extR, proj)) { + if(projtoNpc()!=except && rayTest(*i.body, s, e, extR, proj)) { - if(projtoNpc()!=except && rayTest(*i.body, s, dir, tMax, extR, proj)) { + if(proj(obj.obj); if(pn==nullptr) return false; - const NpcBody& n = *pn; - if(srt){ - if(hasCollision(n,frozen,normal,npc,true)) - return true; - return hasCollision(n,body,normal,npc,false); - } else { - if(hasCollision(n,body,normal,npc,false)) - return true; - //adjustSort(); - return hasCollision(n,frozen,normal,npc,false); - } + const NpcBody& n = *pn; + if(hasCollision(n,frozen,normal,npc,colDepth,true)) + return true; + return hasCollision(n,body,normal,npc,colDepth,false); } - bool hasCollision(const NpcBody& n, const std::vector& arr, Tempest::Vec3& normal, Npc*& npc, bool sorted) { + bool hasCollision(const NpcBody& n, const std::vector& arr, Tempest::Vec3& normal, Npc*& npc, float& colDepth, bool sorted) const { auto l = arr.begin(); auto r = arr.end(); if(sorted) { - const float dX = maxR+n.r; + const float dX = maxRXZ + n.maxRXZ; l = std::lower_bound(arr.begin(),arr.end(),n.pos.x-dX,[](const Record& b,float x){ return b.xenable && hasCollision(n,*v.body,normal)) { + if(v.body!=nullptr && v.body->enable && hasCollision(n,*v.body,normal,colDepth)) { npc = v.body->toNpc(); ret = true; } @@ -302,36 +345,41 @@ struct DynamicWorld::NpcBodyList final { return ret; } - bool hasCollision(const NpcBody& a, const NpcBody& b, Tempest::Vec3& normal){ - // fixme: asymetric npc-npc collision - if(&a==&b) + bool hasCollision(const NpcBody& a, const NpcBody& b, Tempest::Vec3& normal, float& colDepth) const { + const float objMaxRXZ = a.maxRXZ + b.maxRXZ; + const float objMaxRY = a.maxRY + b.maxRY; + + if((Tempest::Vec2(a.pos.x,a.pos.z)-Tempest::Vec2(b.pos.x,b.pos.z)).quadLength() > objMaxRXZ*objMaxRXZ) return false; - auto direction = a.centerPosition() - b.centerPosition(); - if(direction.quadLength()<1.f) - direction.x += 5.f; // avoid zero based distance + if(std::abs(a.pos.y-b.pos.y) > objMaxRY) + return false; - auto R = a.r+b.r, H = a.h+b.h; - if(std::abs(direction.x)>R || std::abs(direction.z)>R || std::abs(direction.y)>H*0.5f) + if(&a==&b) return false; - auto ellipsoidRadius = [](Tempest::Vec3 radius, Tempest::Vec3 direction) -> float { - return (direction * radius).length(); - }; + const float acos = std::cos(a.angle), asin = std::sin(a.angle); + const float bcos = std::cos(b.angle), bsin = std::sin(b.angle); + + const auto apos = a.pos + a.offsetCenter(acos, asin); + const auto bpos = b.pos + b.offsetCenter(bcos, bsin); + const auto dir = bpos - apos; + + auto dlen = dir.length(); + auto ndir = Tempest::Vec3::normalize(dir); + const float rA = a.ellipsoidRadius( ndir, acos, asin); + const float rB = b.ellipsoidRadius(-ndir, bcos, bsin); - float distance = direction.length(); - auto normDir = Tempest::Vec3::normalize(direction); - auto radiusA = ellipsoidRadius(a.ellipsoidSize(), normDir); - auto radiusB = ellipsoidRadius(b.ellipsoidSize(), normDir); - if(distance < radiusA + radiusB) { - normal += normDir; + if(dlen < rA + rB) { + float depth = (rA + rB - dlen)*0.5f; + colDepth = std::max(colDepth, depth); + normal -= ndir*depth; return true; } return false; } void adjustSort() { - srt=true; std::sort(frozen.begin(),frozen.end(),[](Record& a,Record& b){ return a.x < b.x; }); @@ -373,61 +421,12 @@ struct DynamicWorld::NpcBodyList final { tick++; } - void tickBroadphase(uint64_t /*dt*/) { - static bool disabled = true; - if(disabled) - return; - - for(size_t i=0; idvec = Tempest::Vec3(); - } - for(size_t i=0; idvec = Tempest::Vec3(); - } - - for(size_t i=0; i body, frozen; - bool srt=false; - uint64_t tick=0; - float maxR=0; + + uint64_t tick = 0; + float maxRXZ = 0; + float maxRY = 0; }; struct DynamicWorld::BulletsList final { @@ -460,7 +459,7 @@ struct DynamicWorld::BulletsList final { void onMoveNpc(NpcBody& npc, NpcBodyList& list){ for(auto& i:body) { float proj = 0; - if(i.cb!=nullptr && list.rayTest(npc,i.lastPos,i.pos,i.tgRange,proj)) { + if(i.cb!=nullptr && list.rayTest(npc, i.pos, Tempest::Vec3(0,1,0), 0.f, i.tgRange, proj)) { i.cb->onCollide(*npc.toNpc()); } } @@ -976,7 +975,6 @@ void DynamicWorld::moveBullet(BulletBody &b, const Tempest::Vec3& dir, uint64_t void DynamicWorld::tick(uint64_t dt) { npcList ->tickAabbs(); - npcList ->tickBroadphase(dt); bulletList->tick(dt); world ->tick(dt); } @@ -1077,7 +1075,9 @@ 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 = Tempest::Vec3(); + if(npcList->hasCollision(it,out.normal,out.npc,out.npcCol)){ ret = true; } if(world->hasCollision(*it.obj,out.normal,out.vob)) { @@ -1087,7 +1087,7 @@ bool DynamicWorld::hasCollision(const NpcItem& it, CollisionTest& out) { if(!ret) return false; - out.normal /= out.normal.length(); + out.normal = Tempest::Vec3::normalize(out.normal); return true; } @@ -1140,6 +1140,11 @@ float DynamicWorld::NpcItem::centerY() const { return 0; } +auto DynamicWorld::NpcItem::centerAsym() const -> Tempest::Vec3 { + const float acos = std::cos(obj->angle), asin = std::sin(obj->angle); + return obj->offsetCenter(acos, asin); + } + float DynamicWorld::NpcItem::groundOffset() const { return obj->groundOffset(); } @@ -1164,6 +1169,19 @@ void DynamicWorld::NpcItem::debugDraw(DbgPainter& p) const { const auto max = Tempest::Vec3(aabb1.x()*100.f, aabb1.y()*100.f, aabb1.z()*100.f); p.setPen(Tempest::Color(1,0.5f,0)); p.drawAabb(min, max); + /* + auto tr = Tempest::Matrix4x4::mkIdentity(); + tr.translate(obj->pos); + tr.rotateOY(-float(obj->angle*180.f/M_PI)); + + p.setPen(Tempest::Color(1,0,1)); + p.drawObb(tr, obj->bbox); + + auto& a = *obj; + const float acos = std::cos(a.angle), asin = std::sin(a.angle); + const auto apos = a.pos + a.offsetCenter(acos, asin); + p.drawPoint(apos, 10); + */ } bool DynamicWorld::NpcItem::testMove(const Tempest::Vec3& to, CollisionTest& out) { @@ -1211,7 +1229,8 @@ 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; + const auto initial = pos0; // avoid self-reference + auto r = obj->stepSz; int count = 1; auto dp = to-initial; @@ -1223,22 +1242,22 @@ DynamicWorld::MoveCode DynamicWorld::NpcItem::implTryMove(const Tempest::Vec3& t count = std::max(countXZ,countY); } - bool skipNpc = false; - bool skipLnd = false; - bool secondPass = false; + float skipNpc = 0; + bool skipLnd = false; + bool secondPass = false; for(int i=1; i<=count; ++i) { - const auto pos = initial+(dp*float(i))/float(count); + const auto pos = initial + dp*(float(i)/float(count)); implSetPosition(pos); if(!owner->hasCollision(*this,out)) continue; - if((out.npc==nullptr || skipNpc) && (!out.landCol || skipLnd)) + if((skipNpc>=out.npcCol) && (!out.landCol || skipLnd)) continue; if(i>1) { // moved a bit - out.partial = initial+(dp*float(i-1))/float(count); + out.partial = initial + dp*(float(i-1)/float(count)); implSetPosition(out.partial); return MoveCode::MC_Partial; } @@ -1250,7 +1269,7 @@ DynamicWorld::MoveCode DynamicWorld::NpcItem::implTryMove(const Tempest::Vec3& t if(!owner->hasCollision(*this,tmpOut)) { return MoveCode::MC_Fail; } - skipNpc = tmpOut.npc!=nullptr; + skipNpc = tmpOut.npcCol; skipLnd = tmpOut.landCol; secondPass = true; i = 0; diff --git a/game/physics/dynamicworld.h b/game/physics/dynamicworld.h index 37159df09..b3dedd51f 100644 --- a/game/physics/dynamicworld.h +++ b/game/physics/dynamicworld.h @@ -68,6 +68,7 @@ class DynamicWorld final { Interactive* vob = nullptr; Npc* npc = nullptr; + float npcCol = 0; bool landCol = false; }; @@ -98,6 +99,7 @@ class DynamicWorld final { auto center() const -> Tempest::Vec3; float centerY() const; + auto centerAsym() const -> Tempest::Vec3; float groundOffset() const; bool testMove(const Tempest::Vec3& to, CollisionTest& out); @@ -161,7 +163,7 @@ class DynamicWorld final { int waterCol = 0; }; - struct RayQueryResult : RayLandResult{ + struct RayQueryResult : RayLandResult { Npc* npcHit = nullptr; }; diff --git a/game/world/bullet.cpp b/game/world/bullet.cpp index a1660ad15..d452fd4cd 100644 --- a/game/world/bullet.cpp +++ b/game/world/bullet.cpp @@ -107,6 +107,10 @@ float Bullet::pathLength() const { return obj->pathLength(); } +bool Bullet::onEffectCollide(Npc& other) { + return onCollide(other); + } + void Bullet::onStop() { flg = Flg(flg|Stopped); updateMatrix(); diff --git a/game/world/bullet.h b/game/world/bullet.h index dfb89dcb5..ee0f31c6e 100644 --- a/game/world/bullet.h +++ b/game/world/bullet.h @@ -43,7 +43,6 @@ class Bullet final : public DynamicWorld::BulletCallback { void setTarget(const Npc* n); Flg flags() const { return flg; } - void setFlags(Flg f) { flg=f; } ItemMaterial itemMaterial() const; auto damage() const -> const DamageCalculator::Damage& { return dmg; } @@ -57,6 +56,8 @@ class Bullet final : public DynamicWorld::BulletCallback { bool isFinished() const; float pathLength() const; + bool onEffectCollide(Npc& other); + protected: void onStop() override; void onMove() override; diff --git a/game/world/objects/interactive.cpp b/game/world/objects/interactive.cpp index 9cd256b64..c0f49c463 100644 --- a/game/world/objects/interactive.cpp +++ b/game/world/objects/interactive.cpp @@ -205,7 +205,7 @@ void Interactive::drawVobRay(DbgPainter& p, const Npc& npc) const { float tHit = DynamicWorld::rayBox(head, dir, tMax, transform(), boxMin, boxMax, 0.1f); bool accessable = true; - if(!npc.canRayHitPoint(head+dir*tHit, true)) + if(!npc.canRayHitPoint(head+dir*tHit)) accessable = false; p.setPen(accessable ? Tempest::Color(0,1,0) : Tempest::Color(1,0,0)); p.drawLine(head, head+dir*tHit); @@ -488,7 +488,7 @@ bool Interactive::setMobState(std::string_view scheme, int32_t st) { return ret; string_frm name("S_S",st); - if(visual.startAnimAndGet(name,world.tickCount())!=nullptr || !visual.isAnimExist(name)) { + if(visual.startAnimAndGet(name,world.tickCount())!=nullptr || !visual.hasAnim(name)) { setState(st); return ret; } diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 256395c07..da2838d02 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -50,6 +50,8 @@ Vec3 Npc::GoTo::target() const { } bool Npc::GoTo::isClose(const Npc& self, float dist) const { + if(flag==GT_EnemyA) + return self.fghAlgo.isInWRange(self, *npc, self.owner.script()); //need to be consistent with implAttack if(npc!=nullptr) return MoveAlgo::isClose(self, *npc, dist); if(wp!=nullptr) @@ -699,7 +701,9 @@ Vec3 Npc::centerPosition() const { } Vec3 Npc::collosionCenter() const { - return physic.center(); + auto p = position(); + p += physic.centerAsym(); + return p; } Npc* Npc::lookAtTarget() const { @@ -742,7 +746,7 @@ float Npc::qDistTo(const Item& p) const { uint8_t Npc::calcAniComb() const { if(currentTarget==nullptr) return 0; - auto dpos = currentTarget->position()-position(); + auto dpos = currentTarget->position() - position(); return Pose::calcAniComb(dpos,angle); } @@ -1009,6 +1013,18 @@ bool Npc::hasAnim(std::string_view scheme) const { return visual.hasAnim(scheme); } +bool Npc::hasAnim(Anim a) const { + auto st = weaponState(); + auto wlk = walkMode(); + if(mvAlgo.isDive()) + wlk = WalkBit::WM_Dive; + else if(mvAlgo.isSwim()) + wlk = WalkBit::WM_Swim; + else if(mvAlgo.isInWater()) + wlk = WalkBit::WM_Water; + return visual.hasAnim(a,st,wlk); + } + bool Npc::hasSwimAnimations() const { return hasAnim("S_SWIM") && hasAnim("S_SWIMF"); } @@ -1385,6 +1401,20 @@ bool Npc::implTurnAway(const Npc &oth, uint64_t dt) { return rotateTo(dx,dz,step,AnimationSolver::TurnType::Std,dt); } +bool Npc::implTurnToFai(const Npc& oth, uint64_t dt) { + if(&oth==this || oth.isDown()) + return false; + auto ws = weaponState(); + if(ws==WeaponState::NoWeapon) + return false; + auto anim = !hasAutoroll() ? AnimationSolver::TurnType::None : AnimationSolver::TurnType::Std; + if(ws==WeaponState::Bow || ws==WeaponState::CBow || ws==WeaponState::Mage) { + anim = AnimationSolver::TurnType::None; + } + const auto dpos = currentTarget->collosionCenter() - collosionCenter(); + return implTurnTo(dpos.x,dpos.z,anim,dt); + } + bool Npc::implTurnTo(const Npc &oth, uint64_t dt) { if(&oth==this) return false; @@ -1421,7 +1451,6 @@ bool Npc::implGoTo(uint64_t dt) { float dist = 0; if(go2.npc) { dist = fghAlgo.prefferedAttackDistance(*this,*go2.npc,owner.script()); - // dist = fghAlgo.baseDistance(*this,*go2.npc,owner.script()); } else { // use smaller threshold, to avoid edge-looping in script dist = MoveAlgo::closeToPointThreshold*0.5f; @@ -1502,10 +1531,17 @@ bool Npc::implAttack(uint64_t dt) { return false; } + const auto bs = bodyStateMasked(); + if(bs==BS_HIT) { + // NOTE: 'storm' attack has BS_RUN state and not meant to be auto-rotated + implTurnToFai(*currentTarget,dt); + mvAlgo.tick(dt,MoveAlgo::FaiMove); + return true; + } + if(!fghAlgo.hasInstructions()) return false; - const auto bs = bodyStateMasked(); if(bs==BS_LIE) { setAnim(Npc::Anim::Idle); mvAlgo.tick(dt,MoveAlgo::FaiMove); @@ -1517,7 +1553,7 @@ bool Npc::implAttack(uint64_t dt) { } if(faiWaitTime>=owner.tickCount() || waitTime>=owner.tickCount()) { - adjustAttackRotation(dt); + implTurnToFai(*currentTarget,dt); mvAlgo.tick(dt,MoveAlgo::FaiMove); return true; } @@ -1525,6 +1561,11 @@ bool Npc::implAttack(uint64_t dt) { const auto ws = weaponState(); const auto act = fghAlgo.nextFromQueue(*this,*currentTarget,owner.script()); + // NOTE: in original-game, this behaviour seem to be hardcoded + // test case: wolf jump-back quite often when close, but programmed to jump only if attacked + // so far promoting wait to jump seem to work best + const bool jmp = act==FightAlgo::MV_WAIT && fghAlgo.isInBaseRange(*this,*currentTarget,owner.script()); + // vanilla behavior, required for orcs in G1 orcgraveyard if(ws==WeaponState::NoWeapon && isAiQueueEmpty() && canSwitchWeapon()) { drawWeaponMelee(); @@ -1558,12 +1599,13 @@ bool Npc::implAttack(uint64_t dt) { if(act==FightAlgo::MV_ATTACK || act==FightAlgo::MV_ATTACKL || act==FightAlgo::MV_ATTACKR) { //NOTE: FIGHT_DIST_CANCEL in scipts is often longer, than senses_range of npc - const auto tgPos = currentTarget->centerPosition(); - const auto sense = canRayHitPoint(tgPos,true,MaxFightRange); + //const auto tgPos = currentTarget->centerPosition(); + //const auto sense = canRayHitPoint(centerPosition(),tgPos,5.f,MaxFightRange); + const auto sense = fghAlgo.isInFocusAngle(*this,*currentTarget,5.f); if(!sense) { if(bs==BS_RUN) setAnim(Npc::Anim::Idle); else - adjustAttackRotation(dt); + implTurnToFai(*currentTarget,dt); mvAlgo.tick(dt,MoveAlgo::FaiMove); return true; } @@ -1617,7 +1659,7 @@ bool Npc::implAttack(uint64_t dt) { if(shootBow()) { fghAlgo.consumeAction(); } else { - if(!implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt)) { + if(!implTurnToFai(*currentTarget,dt)) { if(!aimBow()) setAnim(Anim::Idle); } @@ -1634,7 +1676,7 @@ bool Npc::implAttack(uint64_t dt) { implAniWait(aniTime); fghAlgo.consumeAction(); } else { - adjustAttackRotation(dt); + implTurnToFai(*currentTarget,dt); } } else { @@ -1656,6 +1698,11 @@ bool Npc::implAttack(uint64_t dt) { implFaiWait(visual.pose().animationTotalTime()); fghAlgo.consumeAction(); } + else if(!hasAnim(Npc::Anim::MoveL)) { + // avoid soft-locks + visual.setAnimRotate(*this,0); + fghAlgo.consumeAction(); + } return true; } @@ -1665,6 +1712,11 @@ bool Npc::implAttack(uint64_t dt) { implFaiWait(visual.pose().animationTotalTime()); fghAlgo.consumeAction(); } + else if(!hasAnim(Npc::Anim::MoveR)) { + // avoid soft-locks + visual.setAnimRotate(*this,0); + fghAlgo.consumeAction(); + } return true; } @@ -1676,7 +1728,7 @@ bool Npc::implAttack(uint64_t dt) { return true; } - if(act==FightAlgo::MV_JUMPBACK) { + if(act==FightAlgo::MV_JUMPBACK || jmp) { if(isSwim()) { fghAlgo.consumeAction(); return true; @@ -1694,6 +1746,7 @@ bool Npc::implAttack(uint64_t dt) { if(setAnim(Npc::Anim::MoveBack)) { implFaiWait(visual.pose().animationTotalTime()); fghAlgo.consumeAction(); + aiState.loopNextTime = owner.tickCount(); // force ZS_MM_Attack_Loop call } return true; } @@ -1701,10 +1754,10 @@ bool Npc::implAttack(uint64_t dt) { if(act==FightAlgo::MV_MOVEA || act==FightAlgo::MV_MOVEG || act==FightAlgo::MV_TURNA || act==FightAlgo::MV_TURNG) { const bool prGRange = fghAlgo.isInGRange(*this, *currentTarget, owner.script()); + const auto prBs = bs; if(!(mvAlgo.checkLastBounce() && implTurnTo(*currentTarget,dt))) { - go2.set(currentTarget,(act==FightAlgo::MV_MOVEG || act==FightAlgo::MV_TURNG) ? - GoToHint::GT_EnemyG : GoToHint::GT_EnemyA); + go2.set(currentTarget, GoToHint::GT_EnemyA); implGoTo(dt); go2.clear(); } @@ -1713,7 +1766,7 @@ bool Npc::implAttack(uint64_t dt) { const bool isWRange = fghAlgo.isInWRange(*this, *currentTarget, owner.script()); const bool isFocus = fghAlgo.isInFocusAngle(*this, *currentTarget); - if((isWRange || (isGRange!=prGRange)) && isFocus) { + if((isWRange || (isGRange!=prGRange) || prBs!=bodyStateMasked()) && isFocus) { visual.setAnimRotate(*this, 0); fghAlgo.consumeAction(); aiState.loopNextTime = owner.tickCount(); // force ZS_MM_Attack_Loop call @@ -1748,18 +1801,6 @@ bool Npc::implAttack(uint64_t dt) { return true; } -void Npc::adjustAttackRotation(uint64_t dt) { - if(currentTarget!=nullptr && !currentTarget->isDown()) { - auto ws = weaponState(); - if(ws!=WeaponState::NoWeapon) { - enum AnimationSolver::TurnType anim = !hasAutoroll() ? AnimationSolver::TurnType::None : AnimationSolver::TurnType::Std; - if(ws==WeaponState::Bow || ws==WeaponState::CBow || ws==WeaponState::Mage) - anim = AnimationSolver::TurnType::None; - implTurnTo(*currentTarget,anim,dt); - } - } - } - bool Npc::implAiTick(uint64_t dt) { // Note AI-action queue takes priority, test case: Vatras pray at night if(aiQueue.size()==0) { @@ -2258,8 +2299,9 @@ void Npc::tick(uint64_t dt) { } if(waitTime>=owner.tickCount() || aniWaitTime>=owner.tickCount() || outWaitTime>owner.tickCount()) { - if(!isPlayer() && go2.flag!=GT_Flee && faiWaitTimetotalTime())); } else { - if(visual.isAnimExist(act.s0)) + if(visual.hasAnim(act.s0)) queue.pushFront(std::move(act)); } break; @@ -2431,7 +2473,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { if(auto sq = playAnimByName(act.s0,bs)) { implAniWait(uint64_t(sq->totalTime())); } else { - if(visual.isAnimExist(act.s0)) { + if(visual.hasAnim(act.s0)) { queue.pushFront(std::move(act)); } else { /* ZS_MM_Rtn_Sleep will set NPC_WALK mode and run T_STAND_2_SLEEP animation. @@ -2906,7 +2948,7 @@ void Npc::tickRoutine() { const bool fastPath = (aiPolicy==NpcProcessPolicy::AiFar2 && routines.empty()); //HACK: don't process far away Npc if(aiState.loopNextTime<=owner.tickCount()) { - aiState.loopNextTime = owner.tickCount() + 1000; // one tick per second? + aiState.loopNextTime = owner.tickCount() + perceptionTimeClampt(); int loop = LOOP_CONTINUE; if(aiState.funcLoop.isValid()) { static const float MAX_DIST = 300; @@ -3832,7 +3874,7 @@ bool Npc::tickCast(uint64_t dt) { if(active!=nullptr) { auto ani = owner.script().spellCastAnim(*this,*active); bool g2 = owner.version().game==2; - if(g2 || visual.isAnimExist(string_frm("T_MAGRUN_2_",ani,"CAST"))) + if(g2 || visual.hasAnim(string_frm("T_MAGRUN_2_",ani,"CAST"))) if(!visual.startAnimSpell(*this,ani,false)) return true; } @@ -4519,23 +4561,36 @@ bool Npc::canSeeSource() const { } bool Npc::canRayHitPoint(const Tempest::Vec3 pos, bool freeLos, float extRange) const { + float ang = freeLos ? 180.f : -1; + return canRayHitPoint(pos, ang, extRange); + } + +bool Npc::canRayHitPoint(const Tempest::Vec3 pos, float angOverride, float extRange) const { + const float range = float(hnpc->senses_range) + extRange; + if(qDistTo(pos)>range*range) + return false; + // npc eyesight height by default + return canRayHitPoint(visual.mapHeadBone(), pos, angOverride, extRange); + } + +bool Npc::canRayHitPoint(const Tempest::Vec3 self, const Tempest::Vec3 pos, float angOverride, float extRange) const { const float range = float(hnpc->senses_range) + extRange; if(qDistTo(pos)>range*range) return false; static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range const DynamicWorld* w = owner.physic(); - // npc eyesight height - auto head = visual.mapHeadBone(); + bool freeLos = angOverride>=180.f; if(freeLos) { - return !w->ray(head, pos).hasCol; + return !w->ray(self, pos).hasCol; } - float dx = x-pos.x, dz=z-pos.z; + float dx = self.x-pos.x, dz=self.z-pos.z; float dir = angleDir(dx,dz); float da = float(M_PI)*(visual.viewDirection()-dir)/180.f; - if(double(std::cos(da))<=ref) { - if(!w->ray(head, pos).hasCol) + auto ca = angOverride > 0 ? std::cos(angOverride*M_PI/180.0) : ref; + if(double(std::cos(da))<=ca) { + if(!w->ray(self, pos).hasCol) return true; } return false; diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 1ab895549..6e8c69995 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -33,7 +33,7 @@ class Npc final { GT_EnemyA, GT_Item, GT_Point, - GT_EnemyG, + GT_EnemyG, //deprecated GT_Flee, }; @@ -177,6 +177,7 @@ class Npc final { void startFaceAnim(std::string_view anim, float intensity, uint64_t duration); bool stopItemStateAnim(); bool hasAnim(std::string_view scheme) const; + bool hasAnim(Anim a) const; bool hasSwimAnimations() const; bool isFinishingMove() const; @@ -385,6 +386,8 @@ class Npc final { bool canSeeItem(const Item& it, bool freeLos) const; bool canSeeSource() const; bool canRayHitPoint(const Tempest::Vec3 pos, bool freeLos = true, float extRange=0.f) const; + bool canRayHitPoint(const Tempest::Vec3 pos, float angOverride, float extRange=0.f) const; + bool canRayHitPoint(const Tempest::Vec3 self, const Tempest::Vec3 pos, float angOverride, float extRange=0.f) const; auto canSenseNpc(const Npc& oth, bool freeLos, float extRange=0.f) const -> SensesBit; auto canSenseNpc(const Tempest::Vec3 pos, bool freeLos, bool isNoisy, float extRange=0.f) const -> SensesBit; @@ -484,6 +487,7 @@ class Npc final { bool implLookAtNpc(uint64_t dt); bool implLookAt (float dx, float dy, float dz, uint64_t dt); bool implTurnAway(const Npc& oth, uint64_t dt); + bool implTurnToFai(const Npc& oth, uint64_t dt); bool implTurnTo (const Npc& oth, uint64_t dt); bool implTurnTo (const Npc& oth, AnimationSolver::TurnType anim, uint64_t dt); bool implTurnTo (const WayPoint* wp, AnimationSolver::TurnType anim, uint64_t dt); @@ -492,7 +496,6 @@ class Npc final { bool implGoTo (uint64_t dt); bool implGoTo (uint64_t dt, float destDist); bool implAttack (uint64_t dt); - void adjustAttackRotation(uint64_t dt); bool implAiTick (uint64_t dt); void implAiWait (uint64_t dt); void implAniWait(uint64_t dt); diff --git a/game/world/world.cpp b/game/world/world.cpp index 4eec28ea9..6a7e49b03 100644 --- a/game/world/world.cpp +++ b/game/world/world.cpp @@ -914,7 +914,7 @@ const WayPoint* World::findFreePoint(const Npc &npc, std::string_view name) cons return wmatrix->findFreePoint(pos,name,[&npc](const WayPoint& wp) -> bool { if(wp.isLocked()) return false; - if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z),true)) + if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z))) return false; return true; }); @@ -937,7 +937,7 @@ const WayPoint* World::findNextFreePoint(const Npc &npc, std::string_view name) auto filter = [&](const WayPoint& wp) { if(wp.isLocked() || &wp==cur) return false; - if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z),true)) + if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z))) return false; return true; }; @@ -952,7 +952,7 @@ const WayPoint* World::findNextWayPoint(const Npc &npc) const { nearest = findWayPoint(pos); } auto filter = [&](const WayPoint& wp) { - if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z),true)) + if(!npc.canRayHitPoint(Tempest::Vec3(wp.pos.x,wp.pos.y+10,wp.pos.z))) return false; return nearest != ℘ }; @@ -999,7 +999,7 @@ WayPath World::wayTo(const Npc &npc, const WayPoint &end) const { return wmatrix->wayTo(&begin,1,npcPos,end); } auto near = wmatrix->findWayPoint(npcPos, [&npc](const WayPoint &wp) { - if(!npc.canRayHitPoint(wp.pos,true)) + if(!npc.canRayHitPoint(wp.pos)) return false; return true; });