diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index add7c6a32..9d90b1b89 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -2296,16 +2296,22 @@ void Npc::tickTimedEvt(Animation::EvCount& ev) { } void Npc::tickRegen(int32_t& v, const int32_t max, const int32_t chg, const uint64_t dt) { - uint64_t tick = owner.tickCount(); + const uint64_t tick = owner.tickCount(); if(tick
0 ? uint64_t(chg) : uint64_t(-int64_t(chg)); + const uint64_t period = amount*1000u; + const uint64_t prev = tick-dt; + const uint64_t n = tick/period-prev/period; + if(n==0) + return; - int32_t nextV = std::max(0,std::min(v+val1-val0,max)); + const int64_t step = chg>0 ? int64_t(n) : -int64_t(n); + const int64_t upper = std::max(0,max); + int32_t nextV = int32_t(std::clamp(int64_t(v)+step,0,upper)); if(v!=nextV) { v = nextV; // check health, in case of negative chg @@ -4662,16 +4668,26 @@ bool Npc::canRayHitPoint(const Tempest::Vec3 self, const Tempest::Vec3 pos, floa if(qDistTo(pos)>range*range) return false; - static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range + // The vector below points from the target back to this NPC. A >=100 degree separation from + // that reverse vector is the intended <=80 degree front arc. + static const double ref = std::cos(100*M_PI/180.0); const DynamicWorld* w = owner.physic(); bool freeLos = angOverride>=180.f; if(freeLos) { return !w->ray(self, pos).hasCol; } - float dx = self.x-pos.x, dz=self.z-pos.z; + // cone from the object position, not the animated head bone: in melee the head dives into + // the target and the direction to it collapses to noise. a false negative here reads to some + // mods as a point-blank stealth hit and triggers a monster self-heal. + float dx = x-pos.x, dz = z-pos.z; float dir = angleDir(dx,dz); - float da = float(M_PI)*(visual.viewDirection()-dir)/180.f; + + // Use the logical heading during normal movement/combat. While attached to a MOBSI the + // interaction animation owns the root rotation, so the visible pose remains authoritative. + // Flying monsters are not in that state and their model yaw must not flip the cone. + float viewAng = (interactive()!=nullptr) ? visual.viewDirection() : angle; + float da = float(M_PI)*(viewAng-dir)/180.f; auto ca = angOverride > 0 ? std::cos(angOverride*M_PI/180.0) : ref; if(double(std::cos(da))<=ca) { if(!w->ray(self, pos).hasCol)