From ad71db21a4563416255230f63e23e3096705ef1f Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 9 Jan 2025 18:00:05 +0100 Subject: [PATCH] Implement function AI_WhirlAround It is supposed to be a faster version of AI_TurnToNpc and seemlingly used in Gothic 1 in a number of places. It's for example used when the player draws a weapon behind an NPC but also with the Firedaemon in Xardas' tower. --- game/game/constants.h | 3 +- game/game/gamescript.cpp | 8 +++ game/game/gamescript.h | 1 + game/game/playercontrol.cpp | 2 +- game/graphics/mdlvisual.cpp | 6 ++- game/graphics/mdlvisual.h | 1 + game/graphics/mesh/animationsolver.h | 6 +++ game/graphics/mesh/pose.cpp | 12 +++-- game/graphics/mesh/pose.h | 3 +- game/world/aiqueue.cpp | 7 +++ game/world/aiqueue.h | 1 + game/world/objects/npc.cpp | 78 ++++++++++++++++++++-------- game/world/objects/npc.h | 8 +-- 13 files changed, 104 insertions(+), 32 deletions(-) diff --git a/game/game/constants.h b/game/game/constants.h index b19198f7c..be76e7e2a 100644 --- a/game/game/constants.h +++ b/game/game/constants.h @@ -383,7 +383,8 @@ enum Action:uint32_t { AI_PointAt, AI_StopPointAt, AI_PrintScreen, - AI_LookAt + AI_LookAt, + AI_WhirlToNpc, }; diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index c9baa94ee..59822e432 100644 --- a/game/game/gamescript.cpp +++ b/game/game/gamescript.cpp @@ -243,6 +243,7 @@ void GameScript::initCommon() { bindExternal("ai_removeweapon", &GameScript::ai_removeweapon); bindExternal("ai_unreadyspell", &GameScript::ai_unreadyspell); bindExternal("ai_turntonpc", &GameScript::ai_turntonpc); + bindExternal("ai_whirlaround", &GameScript::ai_whirlaround); bindExternal("ai_outputsvm", &GameScript::ai_outputsvm); bindExternal("ai_outputsvm_overlay", &GameScript::ai_outputsvm_overlay); bindExternal("ai_startstate", &GameScript::ai_startstate); @@ -2924,6 +2925,13 @@ void GameScript::ai_turntonpc(std::shared_ptr selfRef, std::shared self->aiPush(AiQueue::aiTurnToNpc(npc)); } +void GameScript::ai_whirlaround(std::shared_ptr selfRef, std::shared_ptr npcRef) { + auto npc = findNpc(npcRef); + auto self = findNpc(selfRef); + if(self!=nullptr) + self->aiPush(AiQueue::aiWhirlToNpc(npc)); + } + void GameScript::ai_outputsvm(std::shared_ptr selfRef, std::shared_ptr targetRef, std::string_view name) { auto target = findNpc(targetRef); auto self = findNpc(selfRef); diff --git a/game/game/gamescript.h b/game/game/gamescript.h index f1efcc998..42dd1ec8e 100644 --- a/game/game/gamescript.h +++ b/game/game/gamescript.h @@ -375,6 +375,7 @@ class GameScript final { void ai_removeweapon (std::shared_ptr npcRef); void ai_unreadyspell (std::shared_ptr npcRef); void ai_turntonpc (std::shared_ptr selfRef, std::shared_ptr npcRef); + void ai_whirlaround (std::shared_ptr selfRef, std::shared_ptr npcRef); void ai_outputsvm (std::shared_ptr selfRef, std::shared_ptr targetRef, std::string_view name); void ai_outputsvm_overlay(std::shared_ptr selfRef, std::shared_ptr targetRef, std::string_view name); void ai_startstate (std::shared_ptr selfRef, int func, int state, std::string_view wp); diff --git a/game/game/playercontrol.cpp b/game/game/playercontrol.cpp index 62deb7f3a..aeacb8c34 100644 --- a/game/game/playercontrol.cpp +++ b/game/game/playercontrol.cpp @@ -1087,7 +1087,7 @@ void PlayerControl::processAutoRotate(Npc& pl, float& rot, uint64_t dt) { float step = float(pl.world().script().guildVal().turn_speed[gl]); if(actrl[ActGeneric]) step*=2.f; - pl.rotateTo(dp.x,dp.z,step,false,dt); + pl.rotateTo(dp.x,dp.z,step,AnimationSolver::TurnType::Std,dt); rot = pl.rotation(); } } diff --git a/game/graphics/mdlvisual.cpp b/game/graphics/mdlvisual.cpp index 8ae305355..35e1412be 100644 --- a/game/graphics/mdlvisual.cpp +++ b/game/graphics/mdlvisual.cpp @@ -743,7 +743,11 @@ bool MdlVisual::startAnim(Npc &npc, WeaponState st) { } void MdlVisual::setAnimRotate(Npc &npc, int dir) { - skInst->setAnimRotate(solver,npc,fgtMode,dir); + skInst->setAnimRotate(solver,npc,fgtMode,AnimationSolver::TurnType::Std,dir); + } + +void MdlVisual::setAnimWhirl(Npc &npc, int dir) { + skInst->setAnimRotate(solver,npc,fgtMode,AnimationSolver::TurnType::Whirl,dir); } void MdlVisual::interrupt() { diff --git a/game/graphics/mdlvisual.h b/game/graphics/mdlvisual.h index abbf73ae9..244b40764 100644 --- a/game/graphics/mdlvisual.h +++ b/game/graphics/mdlvisual.h @@ -100,6 +100,7 @@ class MdlVisual final { bool hasAnim (std::string_view scheme) const; void stopWalkAnim (Npc &npc); void setAnimRotate (Npc &npc, int dir); + void setAnimWhirl (Npc &npc, int dir); void interrupt(); WeaponState fightMode() const { return fgtMode; } diff --git a/game/graphics/mesh/animationsolver.h b/game/graphics/mesh/animationsolver.h index c4bc193ae..ce248a0d1 100644 --- a/game/graphics/mesh/animationsolver.h +++ b/game/graphics/mesh/animationsolver.h @@ -72,6 +72,12 @@ class AnimationSolver final { MagNoMana }; + enum TurnType : uint8_t { + Std = 0, + None, + Whirl, + }; + struct Overlay final { const Skeleton* skeleton=nullptr; uint64_t time =0; diff --git a/game/graphics/mesh/pose.cpp b/game/graphics/mesh/pose.cpp index 78831fcb6..8afd2aca9 100644 --- a/game/graphics/mesh/pose.cpp +++ b/game/graphics/mesh/pose.cpp @@ -812,7 +812,7 @@ Vec2 Pose::headRotation() const { return Vec2(headRotX,headRotY); } -void Pose::setAnimRotate(const AnimationSolver &solver, Npc &npc, WeaponState fightMode, int dir) { +void Pose::setAnimRotate(const AnimationSolver &solver, Npc &npc, WeaponState fightMode, AnimationSolver::TurnType turn, int dir) { const Animation::Sequence *sq = nullptr; if(dir==0) { if(rotation!=nullptr) { @@ -823,11 +823,15 @@ void Pose::setAnimRotate(const AnimationSolver &solver, Npc &npc, WeaponState fi } if(bodyState()!=BS_STAND) return; - if(dir<0) { - sq = solver.solveAnim(AnimationSolver::Anim::RotL,fightMode,npc.walkMode(),*this); + + enum AnimationSolver::Anim ani; + if(turn==AnimationSolver::TurnType::Whirl) { + ani = (dir<0)?AnimationSolver::Anim::WhirlL:AnimationSolver::Anim::WhirlR; } else { - sq = solver.solveAnim(AnimationSolver::Anim::RotR,fightMode,npc.walkMode(),*this); + ani = (dir<0)?AnimationSolver::Anim::RotL:AnimationSolver::Anim::RotR; } + + sq = solver.solveAnim(ani,fightMode,npc.walkMode(),*this); if(rotation!=nullptr) { if(sq!=nullptr && rotation->name==sq->name) return; diff --git a/game/graphics/mesh/pose.h b/game/graphics/mesh/pose.h index dcbd58191..b05c90faf 100644 --- a/game/graphics/mesh/pose.h +++ b/game/graphics/mesh/pose.h @@ -7,6 +7,7 @@ #include "game/constants.h" #include "animation.h" +#include "animationsolver.h" #include "resources.h" class Skeleton; @@ -84,7 +85,7 @@ class Pose final { void setHeadRotation(float dx, float dz); Tempest::Vec2 headRotation() const; - void setAnimRotate(const AnimationSolver &solver, Npc &npc, WeaponState fightMode, int dir); + void setAnimRotate(const AnimationSolver &solver, Npc &npc, WeaponState fightMode, AnimationSolver::TurnType turn, int dir); auto setAnimItem(const AnimationSolver &solver, Npc &npc, std::string_view scheme, int state) -> const Animation::Sequence*; bool stopItemStateAnim(const AnimationSolver &solver, uint64_t tickCount); diff --git a/game/world/aiqueue.cpp b/game/world/aiqueue.cpp index 350b7dd3b..7a8fb7ccc 100644 --- a/game/world/aiqueue.cpp +++ b/game/world/aiqueue.cpp @@ -105,6 +105,13 @@ AiQueue::AiAction AiQueue::aiTurnToNpc(Npc *other) { return a; } +AiQueue::AiAction AiQueue::aiWhirlToNpc(Npc *other) { + AiAction a; + a.act = AI_WhirlToNpc; + a.target = other; + return a; + } + AiQueue::AiAction AiQueue::aiGoToNpc(Npc *other) { AiAction a; a.act = AI_GoToNpc; diff --git a/game/world/aiqueue.h b/game/world/aiqueue.h index 3d4ba6a08..ded5677a7 100644 --- a/game/world/aiqueue.h +++ b/game/world/aiqueue.h @@ -47,6 +47,7 @@ class AiQueue { static AiAction aiStopLookAt(); static AiAction aiRemoveWeapon(); static AiAction aiTurnToNpc(Npc *other); + static AiAction aiWhirlToNpc(Npc *other); static AiAction aiGoToNpc (Npc *other); static AiAction aiGoToNextFp(std::string_view fp); static AiAction aiStartState(ScriptFn stateFn, int behavior, Npc *other, Npc* victim, std::string_view wp); diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 3ee6ed53a..ad3ec8bbd 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -1334,21 +1334,25 @@ bool Npc::implTurnTo(const Npc &oth, uint64_t dt) { return true; auto dx = oth.x-x; auto dz = oth.z-z; - return implTurnTo(dx,dz,false,dt); + return implTurnTo(dx,dz,AnimationSolver::TurnType::Std,dt); } -bool Npc::implTurnTo(const Npc& oth, bool noAnim, uint64_t dt) { +bool Npc::implTurnTo(const Npc& oth, AnimationSolver::TurnType anim, uint64_t dt) { if(&oth==this) return true; auto dx = oth.x-x; auto dz = oth.z-z; - return implTurnTo(dx,dz,noAnim,dt); + return implTurnTo(dx,dz,anim,dt); } -bool Npc::implTurnTo(float dx, float dz, bool noAnim, uint64_t dt) { +bool Npc::implTurnTo(float dx, float dz, AnimationSolver::TurnType anim, uint64_t dt) { auto gl = guild(); float step = float(owner.script().guildVal().turn_speed[gl]); - return rotateTo(dx,dz,step,noAnim,dt); + return rotateTo(dx,dz,step,anim,dt); + } + +bool Npc::implWhirlTo(const Npc &oth, uint64_t dt) { + return implTurnTo(oth,AnimationSolver::TurnType::Whirl,dt); } bool Npc::implGoTo(uint64_t dt) { @@ -1396,7 +1400,7 @@ bool Npc::implGoTo(uint64_t dt, float destDist) { } } if(finished) { - if(go2.flag==Npc::GT_NextFp && implTurnTo(go2.wp->dirX,go2.wp->dirZ,false,dt)) + if(go2.flag==Npc::GT_NextFp && implTurnTo(go2.wp->dirX,go2.wp->dirZ,AnimationSolver::TurnType::Std,dt)) return true; clearGoTo(); } @@ -1405,7 +1409,7 @@ bool Npc::implGoTo(uint64_t dt, float destDist) { mvAlgo.tick(dt); return true; } - if(mvAlgo.checkLastBounce() && implTurnTo(dpos.x,dpos.z,false,dt)) { + if(mvAlgo.checkLastBounce() && implTurnTo(dpos.x,dpos.z,AnimationSolver::TurnType::Std,dt)) { mvAlgo.tick(dt); return true; } @@ -1537,7 +1541,7 @@ bool Npc::implAttack(uint64_t dt) { if(shootBow()) { fghAlgo.consumeAction(); } else { - if(!implTurnTo(*currentTarget,true,dt)) { + if(!implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt)) { if(!aimBow()) setAnim(Anim::Idle); } @@ -1656,10 +1660,10 @@ void Npc::adjustAttackRotation(uint64_t dt) { if(currentTarget!=nullptr && !currentTarget->isDown()) { auto ws = weaponState(); if(ws!=WeaponState::NoWeapon) { - bool noAnim = !hasAutoroll(); + enum AnimationSolver::TurnType anim = !hasAutoroll() ? AnimationSolver::TurnType::None : AnimationSolver::TurnType::Std; if(ws==WeaponState::Bow || ws==WeaponState::CBow || ws==WeaponState::Mage) - noAnim = true; - implTurnTo(*currentTarget,noAnim,dt); + anim = AnimationSolver::TurnType::None; + implTurnTo(*currentTarget,anim,dt); } } } @@ -1752,15 +1756,16 @@ bool Npc::implAiFlee(uint64_t dt) { setAnim(Anim::Idle); } + auto anim = (go2.flag!=GT_No)?AnimationSolver::TurnType::None:AnimationSolver::TurnType::Std; if(wp==nullptr || oth.qDistTo(wp)x-x; auto dz = wp->z-z; - if(implTurnTo(dx,dz,(go2.flag!=GT_No),dt)) + if(implTurnTo(dx,dz,anim,dt)) return (go2.flag==GT_Flee); } @@ -2219,6 +2224,17 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { // currentLookAtNpc = nullptr; break; } + case AI_WhirlToNpc: { + if(!prepareTurn()) { + queue.pushFront(std::move(act)); + break; + } + if(act.target!=nullptr && implWhirlTo(*act.target,dt)) { + queue.pushFront(std::move(act)); + break; + } + break; + } case AI_GoToNpc: if(!setInteraction(nullptr)) { queue.pushFront(std::move(act)); @@ -2555,7 +2571,7 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { case AI_AlignToFp:{ if(auto fp = currentFp){ if(fp->dirX!=0.f || fp->dirZ!=0.f){ - if(implTurnTo(fp->dirX,fp->dirZ,false,dt)) + if(implTurnTo(fp->dirX,fp->dirZ,AnimationSolver::TurnType::Std,dt)) queue.pushFront(std::move(act)); } } @@ -3223,10 +3239,10 @@ Vec3 Npc::mapBone(std::string_view bone) const { } bool Npc::turnTo(float dx, float dz, bool noAnim, uint64_t dt) { - return implTurnTo(dx,dz,noAnim,dt); + return implTurnTo(dx,dz,noAnim?AnimationSolver::TurnType::None:AnimationSolver::TurnType::Std,dt); } -bool Npc::rotateTo(float dx, float dz, float step, bool noAnim, uint64_t dt) { +bool Npc::rotateTo(float dx, float dz, float step, AnimationSolver::TurnType anim, uint64_t dt) { //step *= (float(dt)/1000.f)*60.f/100.f; step *= (float(dt)/1000.f); @@ -3241,23 +3257,43 @@ bool Npc::rotateTo(float dx, float dz, float step, bool noAnim, uint64_t dt) { float a = angleDir(dx,dz); float da = a-angle; - if(noAnim || std::cos(double(da)*M_PI/180.0)>0) { + if(anim == AnimationSolver::TurnType::None || std::cos(double(da)*M_PI/180.0)>0) { if(float(std::abs(int(da)%180))<=(step*2.f)) { setAnimRotate(0); setDirection(a); return false; } } else { - stopWalkAnimation(); + visual.stopWalkAnim(*this); } const auto sgn = std::sin(double(da)*M_PI/180.0); if(sgn<0) { - setAnimRotate(noAnim ? 0 : +1); + switch(anim) { + case AnimationSolver::TurnType::Std: + setAnimRotate(+1); + break; + case AnimationSolver::TurnType::None: + setAnimRotate(0); + break; + case AnimationSolver::TurnType::Whirl: + visual.setAnimWhirl(*this,+1); + break; + } setDirection(angle-step); } else if(sgn>0) { - setAnimRotate(noAnim ? 0 : -1); + switch(anim) { + case AnimationSolver::TurnType::Std: + setAnimRotate(-1); + break; + case AnimationSolver::TurnType::None: + setAnimRotate(0); + break; + case AnimationSolver::TurnType::Whirl: + visual.setAnimWhirl(*this,-1); + break; + } setDirection(angle+step); } else { setAnimRotate(0); @@ -3603,7 +3639,7 @@ bool Npc::tickCast(uint64_t dt) { } if(!isPlayer() && currentTarget!=nullptr) { - implTurnTo(*currentTarget,true,dt); + implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt); } } diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 15dc548ab..6a9add5a1 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -356,7 +356,8 @@ class Npc final { auto mapBone(std::string_view bone) const -> Tempest::Vec3; bool turnTo (float dx, float dz, bool noAnim, uint64_t dt); - bool rotateTo(float dx, float dz, float speed, bool anim, uint64_t dt); + bool rotateTo(float dx, float dz, float speed, AnimationSolver::TurnType anim, uint64_t dt); + bool whirlTo(float dx, float dz, float step, uint64_t dt); bool isRotationAllowed() const; auto playAnimByName(std::string_view name, BodyState bs) -> const Animation::Sequence*; @@ -472,8 +473,9 @@ class Npc final { bool implLookAtNpc(uint64_t dt); bool implLookAt (float dx, float dy, float dz, uint64_t dt); bool implTurnTo (const Npc& oth, uint64_t dt); - bool implTurnTo (const Npc& oth, bool noAnim, uint64_t dt); - bool implTurnTo (float dx, float dz, bool noAnim, uint64_t dt); + bool implTurnTo (const Npc& oth, AnimationSolver::TurnType anim, uint64_t dt); + bool implTurnTo (float dx, float dz, AnimationSolver::TurnType anim, uint64_t dt); + bool implWhirlTo(const Npc& oth, uint64_t dt); bool implGoTo (uint64_t dt); bool implGoTo (uint64_t dt, float destDist); bool implAttack (uint64_t dt);