Skip to content

Commit

Permalink
Implement function AI_WhirlAround
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mmind committed Feb 8, 2025
1 parent 258df8b commit ad71db2
Show file tree
Hide file tree
Showing 13 changed files with 104 additions and 32 deletions.
3 changes: 2 additions & 1 deletion game/game/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,8 @@ enum Action:uint32_t {
AI_PointAt,
AI_StopPointAt,
AI_PrintScreen,
AI_LookAt
AI_LookAt,
AI_WhirlToNpc,
};


Expand Down
8 changes: 8 additions & 0 deletions game/game/gamescript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -2924,6 +2925,13 @@ void GameScript::ai_turntonpc(std::shared_ptr<zenkit::INpc> selfRef, std::shared
self->aiPush(AiQueue::aiTurnToNpc(npc));
}

void GameScript::ai_whirlaround(std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> npcRef) {
auto npc = findNpc(npcRef);
auto self = findNpc(selfRef);
if(self!=nullptr)
self->aiPush(AiQueue::aiWhirlToNpc(npc));
}

void GameScript::ai_outputsvm(std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> targetRef, std::string_view name) {
auto target = findNpc(targetRef);
auto self = findNpc(selfRef);
Expand Down
1 change: 1 addition & 0 deletions game/game/gamescript.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ class GameScript final {
void ai_removeweapon (std::shared_ptr<zenkit::INpc> npcRef);
void ai_unreadyspell (std::shared_ptr<zenkit::INpc> npcRef);
void ai_turntonpc (std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> npcRef);
void ai_whirlaround (std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> npcRef);
void ai_outputsvm (std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> targetRef, std::string_view name);
void ai_outputsvm_overlay(std::shared_ptr<zenkit::INpc> selfRef, std::shared_ptr<zenkit::INpc> targetRef, std::string_view name);
void ai_startstate (std::shared_ptr<zenkit::INpc> selfRef, int func, int state, std::string_view wp);
Expand Down
2 changes: 1 addition & 1 deletion game/game/playercontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down
6 changes: 5 additions & 1 deletion game/graphics/mdlvisual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions game/graphics/mdlvisual.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
6 changes: 6 additions & 0 deletions game/graphics/mesh/animationsolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 8 additions & 4 deletions game/graphics/mesh/pose.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion game/graphics/mesh/pose.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "game/constants.h"
#include "animation.h"
#include "animationsolver.h"
#include "resources.h"

class Skeleton;
Expand Down Expand Up @@ -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);

Expand Down
7 changes: 7 additions & 0 deletions game/world/aiqueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions game/world/aiqueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
78 changes: 57 additions & 21 deletions game/world/objects/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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)<oth.qDistTo(*this)) {
auto dx = oth.x-x;
auto dz = oth.z-z;
if(implTurnTo(-dx,-dz,(go2.flag!=GT_No),dt))
if(implTurnTo(-dx,-dz,anim,dt))
return (go2.flag==GT_Flee);
} else {
auto dx = 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);
}

Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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));
}
}
Expand Down Expand Up @@ -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);

Expand All @@ -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);
Expand Down Expand Up @@ -3603,7 +3639,7 @@ bool Npc::tickCast(uint64_t dt) {
}

if(!isPlayer() && currentTarget!=nullptr) {
implTurnTo(*currentTarget,true,dt);
implTurnTo(*currentTarget,AnimationSolver::TurnType::None,dt);
}
}

Expand Down
8 changes: 5 additions & 3 deletions game/world/objects/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*;

Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit ad71db2

Please sign in to comment.