Skip to content

Commit

Permalink
Refactor senses and 'see'-functions
Browse files Browse the repository at this point in the history
npc_canseenpc/npc_canseenpcfreelos/npc_canseeitem are now based only on ray-cast

hnpc->senses affect only perceptions framework

#589
  • Loading branch information
Try committed Apr 8, 2024
1 parent f98ee46 commit fccda5b
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 66 deletions.
43 changes: 28 additions & 15 deletions game/game/gamescript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ void GameScript::initCommon() {
bindExternal("npc_getequippedrangedweapon", &GameScript::npc_getequippedrangedweapon);
bindExternal("npc_getequippedarmor", &GameScript::npc_getequippedarmor);
bindExternal("npc_canseenpc", &GameScript::npc_canseenpc);
bindExternal("npc_canseenpcfreelos", &GameScript::npc_canseenpcfreelos);
bindExternal("npc_canseeitem", &GameScript::npc_canseeitem);
bindExternal("npc_hasequippedweapon", &GameScript::npc_hasequippedweapon);
bindExternal("npc_hasequippedmeleeweapon", &GameScript::npc_hasequippedmeleeweapon);
bindExternal("npc_hasequippedrangedweapon", &GameScript::npc_hasequippedrangedweapon);
Expand All @@ -187,7 +189,6 @@ void GameScript::initCommon() {
bindExternal("npc_getactivespellcat", &GameScript::npc_getactivespellcat);
bindExternal("npc_setactivespellinfo", &GameScript::npc_setactivespellinfo);
bindExternal("npc_getactivespelllevel", &GameScript::npc_getactivespelllevel);
bindExternal("npc_canseenpcfreelos", &GameScript::npc_canseenpcfreelos);
bindExternal("npc_isinfightmode", &GameScript::npc_isinfightmode);
bindExternal("npc_settarget", &GameScript::npc_settarget);
bindExternal("npc_gettarget", &GameScript::npc_gettarget);
Expand Down Expand Up @@ -2163,13 +2164,35 @@ std::shared_ptr<zenkit::IItem> GameScript::npc_getequippedarmor(std::shared_ptr<
}

bool GameScript::npc_canseenpc(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef) {
// 'see' functions are intended as ray-cast, ignoring hnpc->senses mask
// https://discord.com/channels/989316194148433950/989333514543587339/1226664463697182760
auto other = findNpc(otherRef);
auto npc = findNpc(npcRef);
bool ret = false;

if(npc!=nullptr && other!=nullptr){
ret = npc->canSeeNpc(*other,false);
return npc->canSeeNpc(*other,false);
}
return ret;
return false;
}

bool GameScript::npc_canseenpcfreelos(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef) {
auto npc = findNpc(npcRef);
auto oth = findNpc(otherRef);

if(npc!=nullptr && oth!=nullptr){
return npc->canSeeNpc(*oth,true);
}
return false;
}

bool GameScript::npc_canseeitem(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::IItem> itemRef) {
auto npc = findNpc(npcRef);
auto itm = findItem(itemRef.get());

if(npc!=nullptr && itm!=nullptr){
return npc->canSeeItem(*itm,false);
}
return false;
}

bool GameScript::npc_hasequippedweapon(std::shared_ptr<zenkit::INpc> npcRef) {
Expand Down Expand Up @@ -2214,16 +2237,6 @@ bool GameScript::npc_getactivespellisscroll(std::shared_ptr<zenkit::INpc> npcRef
return true;
}

bool GameScript::npc_canseenpcfreelos(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef) {
auto npc = findNpc(npcRef);
auto oth = findNpc(otherRef);

if(npc!=nullptr && oth!=nullptr){
return npc->canSeeNpc(*oth,true);
}
return false;
}

bool GameScript::npc_isinfightmode(std::shared_ptr<zenkit::INpc> npcRef, int modeI) {
auto npc = findNpc(npcRef);
auto mode = FightMode(modeI);
Expand Down Expand Up @@ -2288,7 +2301,7 @@ bool GameScript::npc_getnexttarget(std::shared_ptr<zenkit::INpc> npcRef) {
dist*=dist;

world().detectNpc(npc->position(),float(npc->handle().senses_range),[&,npc](Npc& oth){
if(&oth!=npc && !oth.isDown() && oth.isEnemy(*npc) && npc->canSeeNpc(oth,true)){
if(&oth!=npc && !oth.isDown() && oth.isEnemy(*npc) && npc->canSenseNpc(oth,true)!=SensesBit::SENSE_NONE){
float qd = oth.qDistTo(*npc);
if(qd<dist){
dist=qd;
Expand Down
3 changes: 2 additions & 1 deletion game/game/gamescript.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,13 @@ class GameScript final {
std::shared_ptr<zenkit::IItem> npc_getequippedrangedweapon(std::shared_ptr<zenkit::INpc> npcRef);
std::shared_ptr<zenkit::IItem> npc_getequippedarmor(std::shared_ptr<zenkit::INpc> npcRef);
bool npc_canseenpc (std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef);
bool npc_canseenpcfreelos(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef);
bool npc_canseeitem (std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::IItem> itemRef);
bool npc_hasequippedweapon(std::shared_ptr<zenkit::INpc> npcRef);
bool npc_hasequippedmeleeweapon(std::shared_ptr<zenkit::INpc> npcRef);
bool npc_hasequippedrangedweapon(std::shared_ptr<zenkit::INpc> npcRef);
int npc_getactivespell (std::shared_ptr<zenkit::INpc> npcRef);
bool npc_getactivespellisscroll(std::shared_ptr<zenkit::INpc> npcRef);
bool npc_canseenpcfreelos(std::shared_ptr<zenkit::INpc> npcRef, std::shared_ptr<zenkit::INpc> otherRef);
bool npc_isinfightmode (std::shared_ptr<zenkit::INpc> npcRef, int modeI);
int npc_getactivespellcat(std::shared_ptr<zenkit::INpc> npcRef);
int npc_setactivespellinfo(std::shared_ptr<zenkit::INpc> npcRef, int v);
Expand Down
8 changes: 2 additions & 6 deletions game/world/objects/interactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -517,18 +517,14 @@ uint32_t Interactive::stateMask() const {
bool Interactive::canSeeNpc(const Npc& npc, bool freeLos) const {
for(auto& i:attPos){
auto pos = nodePosition(npc,i);
if(npc.canSeeNpc(pos.x,pos.y,pos.z,freeLos))
if(npc.canSeeNpc(pos,freeLos))
return true;
}

// graves
if(attPos.size()==0){
auto pos = displayPosition();

float x = pos.x;
float y = pos.y;
float z = pos.z;
if(npc.canSeeNpc(x,y,z,freeLos))
if(npc.canSeeNpc(pos,freeLos))
return true;
}
return false;
Expand Down
82 changes: 49 additions & 33 deletions game/world/objects/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@ float Npc::qDistTo(float x1, float y1, float z1) const {
return dx*dx+dy*dy+dz*dz;
}

float Npc::qDistTo(const Tempest::Vec3 pos) const {
return qDistTo(pos.x,pos.y,pos.z);
}

float Npc::qDistTo(const WayPoint *f) const {
if(f==nullptr)
return 0.f;
Expand All @@ -689,12 +693,12 @@ float Npc::qDistTo(const Npc &p) const {

float Npc::qDistTo(const Interactive &p) const {
auto pos = p.nearestPoint(*this);
return qDistTo(pos.x,pos.y,pos.z);
return qDistTo(pos);
}

float Npc::qDistTo(const Item& p) const {
auto pos = p.midPosition();
return qDistTo(pos.x,pos.y,pos.z);
return qDistTo(pos);
}

uint8_t Npc::calcAniComb() const {
Expand Down Expand Up @@ -1452,7 +1456,7 @@ bool Npc::implAttack(uint64_t dt) {
}

if(act==FightAlgo::MV_ATTACK || act==FightAlgo::MV_ATTACKL || act==FightAlgo::MV_ATTACKR) {
if(!canSeeNpc(*currentTarget,false)) {
if(canSenseNpc(*currentTarget,false)==SensesBit::SENSE_NONE) {
if(bs==BS_RUN)
setAnim(Npc::Anim::Idle); else
adjustAttackRotation(dt);
Expand Down Expand Up @@ -1703,7 +1707,7 @@ bool Npc::implAiFlee(uint64_t dt) {
return false;
if(p.underWater)
return false;
if(!canSeeNpc(p.x,p.y+10,p.z,true))
if(!canRayHitPoint(p.position() + Vec3(0,10,0),true))
return false;
if(wp==nullptr || oth.qDistTo(&p)>oth.qDistTo(wp))
wp = &p;
Expand Down Expand Up @@ -1907,7 +1911,7 @@ Npc *Npc::updateNearestEnemy() {
Npc* ret = nullptr;
float dist = std::numeric_limits<float>::max();
if(nearestEnemy!=nullptr &&
(!nearestEnemy->isDown() && (canSenseNpc(*nearestEnemy,true)&SensesBit::SENSE_SEE)!=SensesBit::SENSE_NONE)) {
(!nearestEnemy->isDown() && canSenseNpc(*nearestEnemy,true)!=SensesBit::SENSE_NONE)) {
ret = nearestEnemy;
dist = qDistTo(*ret);
}
Expand All @@ -1917,7 +1921,7 @@ Npc *Npc::updateNearestEnemy() {
return;

float d = qDistTo(n);
if(d<dist && (canSenseNpc(n,true)&SensesBit::SENSE_SEE)!=SensesBit::SENSE_NONE) {
if(d<dist && canSenseNpc(n,true)!=SensesBit::SENSE_NONE) {
ret = &n;
dist = d;
}
Expand All @@ -1938,7 +1942,7 @@ Npc* Npc::updateNearestBody() {
return;

float d = qDistTo(n);
if(d<dist && (canSenseNpc(n,true)&SensesBit::SENSE_SEE)!=SensesBit::SENSE_NONE) {
if(d<dist && canSenseNpc(n,true)!=SensesBit::SENSE_NONE) {
ret = &n;
dist = d;
}
Expand Down Expand Up @@ -2070,6 +2074,8 @@ void Npc::tickAnimationTags() {
}

void Npc::tick(uint64_t dt) {
// if(!isPlayer() && hnpc->id!=953)
// return;
tickAnimationTags();

if(!visual.pose().hasAnim())
Expand Down Expand Up @@ -4136,10 +4142,10 @@ void Npc::stopWalking() {

bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const {
const auto mid = oth.bounds().midTr;
if(canSeeNpc(mid.x,mid.y,mid.z,freeLos))
if(canSeeNpc(mid,freeLos))
return true;
const auto ppos = oth.physic.position();
if(oth.isDown() && canSeeNpc(ppos.x,ppos.y,ppos.z,freeLos)) {
if(oth.isDown() && canSeeNpc(ppos,freeLos)) {
// mid of dead npc may endedup inside a wall; extra check for physical center
return true;
}
Expand All @@ -4148,7 +4154,7 @@ bool Npc::canSeeNpc(const Npc &oth, bool freeLos) const {
if(oth.visual.visualSkeleton()->BIP01_HEAD==size_t(-1))
return false;
auto head = oth.visual.mapHeadBone();
if(canSeeNpc(head.x,head.y,head.z,freeLos))
if(canSeeNpc(head,freeLos))
return true;
return false;
}
Expand All @@ -4163,27 +4169,46 @@ bool Npc::canSeeSource() const {
return false;
}

bool Npc::canSeeNpc(float tx, float ty, float tz, bool freeLos) const {
SensesBit s = canSenseNpc(tx,ty,tz,freeLos,false);
return int32_t(s&SensesBit::SENSE_SEE)!=0;
bool Npc::canSeeNpc(const Vec3 pos, bool freeLos) const {
return canRayHitPoint(pos, freeLos);
}

bool Npc::canRayHitPoint(const Tempest::Vec3 pos, bool freeLos, 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();
if(freeLos) {
return !w->ray(head, pos).hasCol;
}

float dx = x-pos.x, dz=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)
return true;
}
return false;
}

SensesBit Npc::canSenseNpc(const Npc &oth, bool freeLos, float extRange) const {
const auto mid = oth.bounds().midTr;
const bool isNoisy = (oth.bodyStateMasked()!=BodyState::BS_SNEAK);
return canSenseNpc(mid.x,mid.y,mid.z,freeLos,isNoisy,extRange);
return canSenseNpc(mid,freeLos,isNoisy,extRange);
}

SensesBit Npc::canSenseNpc(float tx, float ty, float tz, bool freeLos, bool isNoisy, float extRange) const {
DynamicWorld* w = owner.physic();
static const double ref = std::cos(100*M_PI/180.0); // spec requires +-100 view angle range

SensesBit Npc::canSenseNpc(const Tempest::Vec3 pos, bool freeLos, bool isNoisy, float extRange) const {
const float range = float(hnpc->senses_range)+extRange;
if(qDistTo(tx,ty,tz)>range*range)
if(qDistTo(pos)>range*range)
return SensesBit::SENSE_NONE;

SensesBit ret=SensesBit::SENSE_NONE;
if(owner.roomAt({tx,ty,tz})==owner.roomAt({x,y,z})) {
if(owner.roomAt(pos)==owner.roomAt({x,y,z})) {
ret = ret | SensesBit::SENSE_SMELL;
}

Expand All @@ -4192,19 +4217,10 @@ SensesBit Npc::canSenseNpc(float tx, float ty, float tz, bool freeLos, bool isNo
ret = ret | SensesBit::SENSE_HEAR;
}

// npc eyesight height
auto head = visual.mapHeadBone();
if(!freeLos) {
float dx = x-tx, dz=z-tz;
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, Vec3(tx,ty,tz)).hasCol)
ret = ret | SensesBit::SENSE_SEE;
} else {
if(!w->ray(head, Vec3(tx,ty,tz)).hasCol)
ret = ret | SensesBit::SENSE_SEE;
if(canRayHitPoint(pos, freeLos, extRange)) {
ret = ret | SensesBit::SENSE_SEE;
}

return ret & SensesBit(hnpc->senses);
}

Expand All @@ -4214,7 +4230,7 @@ bool Npc::canSeeItem(const Item& it, bool freeLos) const {

const auto itMid = it.midPosition();
const float range = float(hnpc->senses_range);
if(qDistTo(itMid.x,itMid.y,itMid.z)>range*range)
if(qDistTo(itMid)>range*range)
return false;

if(!freeLos) {
Expand Down
14 changes: 8 additions & 6 deletions game/world/objects/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ class Npc final {
auto portalName() -> std::string_view;
auto formerPortalName() -> std::string_view;

float qDistTo(float x,float y,float z) const;
float qDistTo(float x, float y, float z) const;
float qDistTo(const Tempest::Vec3 pos) const;
float qDistTo(const WayPoint* p) const;
float qDistTo(const Npc& p) const;
float qDistTo(const Interactive& p) const;
Expand Down Expand Up @@ -372,13 +373,14 @@ class Npc final {
void clearGoTo();
void stopWalking();

bool canSeeNpc(const Npc& oth,bool freeLos) const;
bool canSeeNpc(const Npc& oth, bool freeLos) const;
bool canSeeNpc(const Tempest::Vec3 pos, bool freeLos) const;
bool canSeeItem(const Item& it, bool freeLos) const;
bool canSeeSource() const;
bool canSeeNpc(float x,float y,float z,bool freeLos) const;
auto canSenseNpc(const Npc& oth,bool freeLos, float extRange=0.f) const -> SensesBit;
auto canSenseNpc(float x,float y,float z,bool freeLos,bool isNoisy,float extRange=0.f) const -> SensesBit;
bool canRayHitPoint(const Tempest::Vec3 pos, bool freeLos = true, float extRange=0.f) const;

bool canSeeItem(const Item& it,bool freeLos) 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;

void setTarget(Npc* t);
Npc* target() const;
Expand Down
8 changes: 4 additions & 4 deletions game/world/world.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,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.canSeeNpc(wp.x,wp.y+10,wp.z,true))
if(!npc.canRayHitPoint(Tempest::Vec3(wp.x,wp.y+10,wp.z),true))
return false;
return true;
});
Expand All @@ -869,7 +869,7 @@ const WayPoint *World::findNextFreePoint(const Npc &npc, std::string_view name)
auto wp = wmatrix->findFreePoint(pos,name,[cur,&npc](const WayPoint& wp) -> bool {
if(wp.isLocked() || &wp==cur)
return false;
if(!npc.canSeeNpc(wp.x,wp.y+10,wp.z,true))
if(!npc.canRayHitPoint(Tempest::Vec3(wp.x,wp.y+10,wp.z),true))
return false;
return true;
});
Expand Down Expand Up @@ -911,7 +911,7 @@ WayPath World::wayTo(const Npc &npc, const WayPoint &end) const {
return wmatrix->wayTo(&begin,1,p,end);
}
auto near = wmatrix->findWayPoint(p, [&npc](const WayPoint &wp) {
if(!npc.canSeeNpc(wp.x,wp.y+10,wp.z,true))
if(!npc.canRayHitPoint(Tempest::Vec3(wp.x,wp.y+10,wp.z),true))
return false;
return true;
});
Expand All @@ -925,7 +925,7 @@ WayPath World::wayTo(const Npc &npc, const WayPoint &end) const {
wpoint.push_back(near);
for(auto& i:near->connections()) {
auto p = i.point->position();
if(npc.canSeeNpc(p.x,p.y+10,p.z,true))
if(npc.canRayHitPoint(Tempest::Vec3(p.x,p.y+10,p.z)))
wpoint.push_back(i.point);
}

Expand Down
2 changes: 1 addition & 1 deletion game/world/worldobjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ void WorldObjects::tick(uint64_t dt, uint64_t dtPlayer) {
if(r.self==&i)
continue;

const float distance = i.qDistTo(r.pos.x,r.pos.y,r.pos.z);
const float distance = i.qDistTo(r.pos);
const float range = float(owner.script().percRanges().at(PercType(r.what), i.handle().senses_range));

if(distance > range*range)
Expand Down

0 comments on commit fccda5b

Please sign in to comment.