From 101f4a59da26716e6374fafbc0a19b29bb610e94 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 15 Jan 2025 22:20:49 +0100 Subject: [PATCH] Implement ai_quicklook The quicklook function is meant to have the Npc just move its head to the player for two seconds. Testcase in vanilla Gothic 1 was at the exchange place, while Diego walks back to the old camp. Stand next to where he is walking and pick up one of the berries, arrows, etc. Diego will continue walking but turn his head over for those 2 seconds. So the implementation does the same, not changing the currentLookAtNpc but storing the temporary one in a separate variable for the duration of the action. That way this cinematic should not influence any other parts or save/load mechanisms. --- game/game/constants.h | 1 + game/game/gamescript.cpp | 8 +++++++ game/game/gamescript.h | 1 + game/world/aiqueue.cpp | 7 ++++++ game/world/aiqueue.h | 1 + game/world/objects/npc.cpp | 47 +++++++++++++++++++++++++++++++++----- game/world/objects/npc.h | 3 +++ 7 files changed, 62 insertions(+), 6 deletions(-) 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;