Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions game/world/objects/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<dt || chg==0)
return;
int32_t time0 = int32_t(tick%1000);
int32_t time1 = time0+int32_t(dt);

int32_t val0 = (time0*chg)/1000;
int32_t val1 = (time1*chg)/1000;
// ATR_REGENERATE* is an interval: 1 point per 'chg' seconds.
// treating it as points-per-second made monsters in mods with hp-regeneration (L'Hiver)
// heal faster than the player can deal damage
const uint64_t amount = chg>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<int64_t>(0,max);
int32_t nextV = int32_t(std::clamp<int64_t>(int64_t(v)+step,0,upper));
if(v!=nextV) {
v = nextV;
// check health, in case of negative chg
Expand Down Expand Up @@ -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)
Expand Down