From 76bf92aa5a108dd962f6df761a91866baa289b1f Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Sun, 19 Jan 2025 23:01:23 +0100 Subject: [PATCH 1/4] Add whirl animation types All characters (except only the humans in Gothic 2) have T_SURPRISE_CW and T_SURPRISE_CCW animations used in the whirl-around type of movement. This is mainly used in Gothic 1 to portray a different style of turning to an enemy or surprising event. This adds the animation to the solver. Normally the whirl animation should not happen in water, but keep the case around just to be on the save side. --- game/graphics/mdlvisual.cpp | 5 ++++- game/graphics/mesh/animationsolver.cpp | 28 ++++++++++++++++++++++++++ game/graphics/mesh/animationsolver.h | 2 ++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/game/graphics/mdlvisual.cpp b/game/graphics/mdlvisual.cpp index 1c560183f..8ae305355 100644 --- a/game/graphics/mdlvisual.cpp +++ b/game/graphics/mdlvisual.cpp @@ -588,7 +588,8 @@ const Animation::Sequence* MdlVisual::startAnimAndGet(Npc& npc, AnimationSolver: uint8_t comb, WeaponState st, WalkBit wlk) { // for those use MdlVisual::setRotation - assert(a!=AnimationSolver::Anim::RotL && a!=AnimationSolver::Anim::RotR); + assert(a!=AnimationSolver::Anim::RotL && a!=AnimationSolver::Anim::RotR && + a!=AnimationSolver::Anim::WhirlL && a!=AnimationSolver::Anim::WhirlR); if(a==AnimationSolver::InteractIn || a==AnimationSolver::InteractOut || @@ -651,6 +652,8 @@ const Animation::Sequence* MdlVisual::startAnimAndGet(Npc& npc, AnimationSolver: bs = BS_RUN; case AnimationSolver::Anim::RotL: case AnimationSolver::Anim::RotR: + case AnimationSolver::Anim::WhirlL: + case AnimationSolver::Anim::WhirlR: break; case AnimationSolver::Anim::Fall: case AnimationSolver::Anim::FallDeep: diff --git a/game/graphics/mesh/animationsolver.cpp b/game/graphics/mesh/animationsolver.cpp index 6a88177df..8a7eb6b0b 100644 --- a/game/graphics/mesh/animationsolver.cpp +++ b/game/graphics/mesh/animationsolver.cpp @@ -277,6 +277,34 @@ const Animation::Sequence* AnimationSolver::implSolveAnim(AnimationSolver::Anim return solveFrm("T_%sWALKWTURNR",st); return solveFrm("T_%sRUNTURNR",st); } + // Whirl around + if(a==WhirlL) { + // Whirling in water should not happen, but just to make sure + if(bool(wlkMode & WalkBit::WM_Dive)) + return solveFrm("T_DIVETURNL"); + if(bool(wlkMode & WalkBit::WM_Swim)) + return solveFrm("T_SWIMTURNL"); + + // whirling is only used in G1, but the script function is present + // in all games. In G2 all models except the human also have the + // needed animation. + const Animation::Sequence* s = solveFrm("T_SURPRISE_CCW"); + if(s==nullptr) + s = solveFrm("T_%sRUNTURNL",st); + return s; + } + if(a==WhirlR) { + // Whirling in water should not happen, but just to make sure + if(bool(wlkMode & WalkBit::WM_Dive)) + return solveFrm("T_DIVETURNR"); + if(bool(wlkMode & WalkBit::WM_Swim)) + return solveFrm("T_SWIMTURNR"); + + const Animation::Sequence* s = solveFrm("T_SURPRISE_CW"); + if(s==nullptr) + s = solveFrm("T_%sRUNTURNR",st); + return s; + } // Jump regular if(a==Jump) return solveFrm("S_JUMP"); diff --git a/game/graphics/mesh/animationsolver.h b/game/graphics/mesh/animationsolver.h index 54e8ebd89..c4bc193ae 100644 --- a/game/graphics/mesh/animationsolver.h +++ b/game/graphics/mesh/animationsolver.h @@ -28,6 +28,8 @@ class AnimationSolver final { MoveR, RotL, RotR, + WhirlL, + WhirlR, Fall, FallDeep, FallDeepA, From e54173eab2bbc130086d9b4fb7a07650eaba8bcf Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 29 Jan 2025 11:28:06 +0100 Subject: [PATCH 2/4] Move turn-preparation to a separate function There are multiple turning variants (regular, whirl, away) that all will need the same preparation steps, so move them to a separate function for re-use. --- game/world/objects/npc.cpp | 23 +++++++++++++++-------- game/world/objects/npc.h | 1 + 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index cd80af61f..2785c14ba 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -2172,6 +2172,20 @@ void Npc::tick(uint64_t dt) { implAiTick(dt); } +bool Npc::prepareTurn() { + const auto st = bodyStateMasked(); + if(interactive()==nullptr && (st==BS_WALK || st==BS_SNEAK)) { + visual.stopWalkAnim(*this); + setAnimRotate(0); + return false; + } + if(interactive()==nullptr) { + visual.stopWalkAnim(*this); + visual.stopDlgAnim(*this); + } + return true; +} + void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { if(isInAir()) return; @@ -2191,17 +2205,10 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { break; } case AI_TurnToNpc: { - const auto st = bodyStateMasked(); - if(interactive()==nullptr && (st==BS_WALK || st==BS_SNEAK)) { - visual.stopWalkAnim(*this); - setAnimRotate(0); + if(!prepareTurn()) { queue.pushFront(std::move(act)); break; } - if(interactive()==nullptr) { - visual.stopWalkAnim(*this); - visual.stopDlgAnim(*this); - } if(act.target!=nullptr && implTurnTo(*act.target,dt)) { queue.pushFront(std::move(act)); break; diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 026e9506d..15dc548ab 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -495,6 +495,7 @@ class Npc final { bool checkHealth(bool onChange, bool forceKill); void onNoHealth(bool death, HitSound sndMask); bool hasAutoroll() const; + bool prepareTurn(); void stopWalkAnimation(); void takeDamage(Npc& other, const Bullet* b, const CollideMask bMask, int32_t splId, bool isSpell); void takeFallDamage(const Tempest::Vec3& fallSpeed); From 3b09302224fad1001a50b21fbf53595b5fe99d47 Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Thu, 9 Jan 2025 18:00:05 +0100 Subject: [PATCH 3/4] 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 | 76 ++++++++++++++++++---------- game/world/objects/npc.h | 8 +-- 13 files changed, 96 insertions(+), 38 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 3303d4a7e..0c4bf8edb 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); @@ -2927,6 +2928,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 2785c14ba..95b030ae4 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,26 +3257,32 @@ 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); - setDirection(angle-step); - } else - if(sgn>0) { - setAnimRotate(noAnim ? 0 : -1); - setDirection(angle+step); - } else { + if(sgn==0) { setAnimRotate(0); + } else { + switch(anim) { + case AnimationSolver::TurnType::Std: + setAnimRotate((sgn<0)?+1:-1); + break; + case AnimationSolver::TurnType::None: + setAnimRotate(0); + break; + case AnimationSolver::TurnType::Whirl: + visual.setAnimWhirl(*this,(sgn<0)?+1:-1); + break; + } + setDirection((sgn<0)?angle-step:angle+step); } return true; } @@ -3603,7 +3625,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); From 59ae819358bcc3f2ccd24eb0ca1718198ce72aef Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Tue, 14 Jan 2025 21:18:36 +0100 Subject: [PATCH 4/4] Implement function ai_turnaway Defined as 'npc' turns away from 'other', just like turnTo but +180 degrees. One user is for example Baal Netbek in the swamp where the player character ends the dialog with turning away saying "This guy won't be of help", the other example is the fire demon in Xardas' tower, where the player first turns away before whirling back to the demon. --- 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 | 23 +++++++++++++++++++++++ game/world/objects/npc.h | 1 + 7 files changed, 42 insertions(+) diff --git a/game/game/constants.h b/game/game/constants.h index be76e7e2a..266f89370 100644 --- a/game/game/constants.h +++ b/game/game/constants.h @@ -385,6 +385,7 @@ enum Action:uint32_t { AI_PrintScreen, AI_LookAt, AI_WhirlToNpc, + AI_TurnAway, }; diff --git a/game/game/gamescript.cpp b/game/game/gamescript.cpp index 0c4bf8edb..5a7fe1a71 100644 --- a/game/game/gamescript.cpp +++ b/game/game/gamescript.cpp @@ -242,6 +242,7 @@ void GameScript::initCommon() { bindExternal("ai_lookatnpc", &GameScript::ai_lookatnpc); bindExternal("ai_removeweapon", &GameScript::ai_removeweapon); bindExternal("ai_unreadyspell", &GameScript::ai_unreadyspell); + bindExternal("ai_turnaway", &GameScript::ai_turnaway); bindExternal("ai_turntonpc", &GameScript::ai_turntonpc); bindExternal("ai_whirlaround", &GameScript::ai_whirlaround); bindExternal("ai_outputsvm", &GameScript::ai_outputsvm); @@ -2921,6 +2922,13 @@ void GameScript::ai_unreadyspell(std::shared_ptr npcRef) { npc->aiPush(AiQueue::aiRemoveWeapon()); } +void GameScript::ai_turnaway(std::shared_ptr selfRef, std::shared_ptr npcRef) { + auto npc = findNpc(npcRef); + auto self = findNpc(selfRef); + if(self!=nullptr) + self->aiPush(AiQueue::aiTurnAway(npc)); + } + void GameScript::ai_turntonpc(std::shared_ptr selfRef, std::shared_ptr npcRef) { auto npc = findNpc(npcRef); auto self = findNpc(selfRef); diff --git a/game/game/gamescript.h b/game/game/gamescript.h index 42dd1ec8e..2f5f1ea63 100644 --- a/game/game/gamescript.h +++ b/game/game/gamescript.h @@ -374,6 +374,7 @@ class GameScript final { void ai_lookatnpc (std::shared_ptr selfRef, std::shared_ptr npcRef); void ai_removeweapon (std::shared_ptr npcRef); void ai_unreadyspell (std::shared_ptr npcRef); + void ai_turnaway (std::shared_ptr selfRef, 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); diff --git a/game/world/aiqueue.cpp b/game/world/aiqueue.cpp index 7a8fb7ccc..aeb81e17b 100644 --- a/game/world/aiqueue.cpp +++ b/game/world/aiqueue.cpp @@ -98,6 +98,13 @@ AiQueue::AiAction AiQueue::aiRemoveWeapon() { return a; } +AiQueue::AiAction AiQueue::aiTurnAway(Npc *other) { + AiAction a; + a.act = AI_TurnAway; + a.target = other; + return a; + } + AiQueue::AiAction AiQueue::aiTurnToNpc(Npc *other) { AiAction a; a.act = AI_TurnToNpc; diff --git a/game/world/aiqueue.h b/game/world/aiqueue.h index ded5677a7..416595d5c 100644 --- a/game/world/aiqueue.h +++ b/game/world/aiqueue.h @@ -46,6 +46,7 @@ class AiQueue { static AiAction aiLookAtNpc(Npc* other); static AiAction aiStopLookAt(); static AiAction aiRemoveWeapon(); + static AiAction aiTurnAway (Npc *other); static AiAction aiTurnToNpc(Npc *other); static AiAction aiWhirlToNpc(Npc *other); static AiAction aiGoToNpc (Npc *other); diff --git a/game/world/objects/npc.cpp b/game/world/objects/npc.cpp index 95b030ae4..1e55a3d8a 100644 --- a/game/world/objects/npc.cpp +++ b/game/world/objects/npc.cpp @@ -1329,6 +1329,18 @@ bool Npc::implLookAt(float dx, float dy, float dz, uint64_t dt) { return false; } +bool Npc::implTurnAway(const Npc &oth, uint64_t dt) { + if(&oth==this) + return true; + + // turn npc's back to oth, so calculate direction from oth to npc + auto dx = x-oth.x; + auto dz = z-oth.z; + auto gl = guild(); + float step = float(owner.script().guildVal().turn_speed[gl]); + return rotateTo(dx,dz,step,AnimationSolver::TurnType::Std,dt); + } + bool Npc::implTurnTo(const Npc &oth, uint64_t dt) { if(&oth==this) return true; @@ -2209,6 +2221,17 @@ void Npc::nextAiAction(AiQueue& queue, uint64_t dt) { currentLookAt=act.point; break; } + case AI_TurnAway: { + if(!prepareTurn()) { + queue.pushFront(std::move(act)); + break; + } + if(act.target!=nullptr && implTurnAway(*act.target,dt)) { + queue.pushFront(std::move(act)); + break; + } + break; + } case AI_TurnToNpc: { if(!prepareTurn()) { queue.pushFront(std::move(act)); diff --git a/game/world/objects/npc.h b/game/world/objects/npc.h index 6a9add5a1..267605ccb 100644 --- a/game/world/objects/npc.h +++ b/game/world/objects/npc.h @@ -472,6 +472,7 @@ class Npc final { bool implLookAtWp(uint64_t dt); bool implLookAtNpc(uint64_t dt); bool implLookAt (float dx, float dy, float dz, uint64_t dt); + bool implTurnAway(const Npc& oth, uint64_t dt); bool implTurnTo (const Npc& oth, 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);