diff --git a/game/game/constants.h b/game/game/constants.h index 266f89370..183d3436b 100644 --- a/game/game/constants.h +++ b/game/game/constants.h @@ -386,6 +386,7 @@ enum Action:uint32_t { AI_LookAt, AI_WhirlToNpc, AI_TurnAway, + AI_QuickLook, }; diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index 5a7fe1a71..0d565ee56 100644 --- a/game/game/gamescript.cpp +++ b/game/game/gamescript.cpp @@ -238,6 +238,7 @@ void GameScript::initCommon() { bindExternal("ai_standupquick", &GameScript::ai_standupquick); bindExternal("ai_continueroutine", &GameScript::ai_continueroutine); bindExternal("ai_stoplookat", &GameScript::ai_stoplookat); + bindExternal("ai_quicklook", &GameScript::ai_quicklook); bindExternal("ai_lookat", &GameScript::ai_lookat); bindExternal("ai_lookatnpc", &GameScript::ai_lookatnpc); bindExternal("ai_removeweapon", &GameScript::ai_removeweapon); @@ -2896,6 +2897,13 @@ void GameScript::ai_stoplookat(std::shared_ptr selfRef) { self->aiPush(AiQueue::aiStopLookAt()); } +void GameScript::ai_quicklook(std::shared_ptr selfRef, std::shared_ptr npcRef) { + auto npc = findNpc(npcRef); + auto self = findNpc(selfRef); + if(self!=nullptr) + self->aiPush(AiQueue::aiQuickLook(npc)); + } + void GameScript::ai_lookat(std::shared_ptr selfRef, std::string_view waypoint) { auto self = findNpc(selfRef); auto to = world().findPoint(waypoint); diff --git a/game/game/gamescript.h b/game/game/gamescript.h index 2f5f1ea63..21a8ecc8e 100644 --- a/game/game/gamescript.h +++ b/game/game/gamescript.h @@ -370,6 +370,7 @@ class GameScript final { void ai_standupquick (std::shared_ptr selfRef); void ai_continueroutine (std::shared_ptr selfRef); void ai_stoplookat (std::shared_ptr selfRef); + void ai_quicklook (std::shared_ptr selfRef, std::shared_ptr npcRef); void ai_lookat (std::shared_ptr selfRef, std::string_view waypoint); void ai_lookatnpc (std::shared_ptr selfRef, std::shared_ptr npcRef); void ai_removeweapon (std::shared_ptr npcRef); diff --git a/game/world/aiqueue.cpp b/game/world/aiqueue.cpp index aeb81e17b..d57d4f6c3 100644 --- a/game/world/aiqueue.cpp +++ b/game/world/aiqueue.cpp @@ -79,6 +79,13 @@ AiQueue::AiAction AiQueue::aiLookAt(const WayPoint* to) { return a; } +AiQueue::AiAction AiQueue::aiQuickLook(Npc* other) { + AiAction a; + a.act = AI_QuickLook; + a.target = other; + return a; + } + AiQueue::AiAction AiQueue::aiLookAtNpc(Npc* other) { AiAction a; a.act = AI_LookAtNpc; diff --git a/game/world/aiqueue.h b/game/world/aiqueue.h index 416595d5c..554ccd653 100644 --- a/game/world/aiqueue.h +++ b/game/world/aiqueue.h @@ -43,6 +43,7 @@ class AiQueue { void onWldItemRemoved(const Item& itm); static AiAction aiLookAt(const WayPoint* to); + static AiAction aiQuickLook(Npc* other); static AiAction aiLookAtNpc(Npc* other); static AiAction aiStopLookAt(); static AiAction aiRemoveWeapon(); diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index e1d679ac2..46bec50f8 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -1274,17 +1274,19 @@ bool Npc::implPointAt(const Tempest::Vec3& to) { } bool Npc::implLookAtWp(uint64_t dt) { - if(currentLookAt==nullptr) + // nothing to look at, or a quicklook is handled in implLookAtNpc + if(currentLookAt==nullptr || quickLookNpc!=nullptr) return false; auto dvec = currentLookAt->position(); return implLookAt(dvec.x,dvec.y,dvec.z,dt); } bool Npc::implLookAtNpc(uint64_t dt) { - if(currentLookAtNpc==nullptr) + if(currentLookAtNpc==nullptr && quickLookNpc == nullptr) return false; auto selfHead = visual.mapHeadBone(); - auto otherHead = currentLookAtNpc->visual.mapHeadBone(); + auto other = (quickLookNpc!=nullptr) ? quickLookNpc : currentLookAtNpc; + auto otherHead = other->visual.mapHeadBone(); auto dvec = otherHead - selfHead; return implLookAt(dvec.x,dvec.y,dvec.z,dt); } @@ -2132,6 +2134,16 @@ void Npc::tick(uint64_t dt) { if(dbg && !isPlayer() && hnpc->id!=kId) return; + // Unset QuickLook after the timeout + if(quickLookEnd > 0 && owner.tickCount() > quickLookEnd) { + quickLookEnd = 0; + quickLookNpc = nullptr; + + // if nothing else to look at, re-set head rotation + if(currentLookAt==nullptr && currentLookAtNpc==nullptr) + visual.setHeadRotation(0,0); + } + tickAnimationTags(); if(!visual.pose().hasAnim()) @@ -2214,11 +2226,19 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { case AI_LookAtNpc:{ currentLookAt=nullptr; currentLookAtNpc=act.target; + quickLookEnd=0; + quickLookNpc=nullptr; + break; + } + case AI_QuickLook:{ + // Nothing to do, as the QuickLook just works in parallel break; } case AI_LookAt:{ currentLookAtNpc=nullptr; currentLookAt=act.point; + quickLookEnd=0; + quickLookNpc=nullptr; break; } case AI_TurnAway: { @@ -2304,6 +2324,8 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { case AI_StopLookAt: currentLookAtNpc=nullptr; currentLookAt=nullptr; + quickLookEnd=0; + quickLookNpc=nullptr; visual.setHeadRotation(0,0); break; case AI_RemoveWeapon: @@ -3091,9 +3113,19 @@ void Npc::setToFistMode() { } void Npc::aiPush(AiQueue::AiAction&& a) { - if(a.act==AI_OutputSvmOverlay) - aiQueueOverlay.pushBack(std::move(a)); else - aiQueue.pushBack(std::move(a)); + switch(a.act) { + case AI_OutputSvmOverlay: + aiQueueOverlay.pushBack(std::move(a)); + break; + case AI_QuickLook: + // QuickLook is supposed to last for 2 seconds + // and should not interrupt other AI actions like walking + quickLookEnd = owner.tickCount()+2000; + quickLookNpc = a.target; + default: + aiQueue.pushBack(std::move(a)); + break; + } } void Npc::resumeAiRoutine() { @@ -4221,6 +4253,9 @@ bool Npc::isAiBusy() const { void Npc::clearAiQueue() { currentLookAt = nullptr; currentLookAtNpc = nullptr; + quickLookEnd = 0; + quickLookNpc = nullptr; + visual.setHeadRotation(0,0); aiQueue.clear(); diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 267605ccb..0594ef4b9 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -596,6 +596,9 @@ class Npc final { Npc* currentOther =nullptr; Npc* currentVictim =nullptr; + Npc* quickLookNpc=nullptr; + uint64_t quickLookEnd=0; + const WayPoint* currentLookAt=nullptr; Npc* currentLookAtNpc=nullptr; Npc* currentTarget =nullptr;