Add intent-driven strafe animation selection and movement state hooks

This commit is contained in:
Kelsi 2026-02-03 19:29:11 -08:00
parent 871172d63e
commit dfc29cad10
5 changed files with 105 additions and 18 deletions

View file

@ -42,6 +42,10 @@ public:
bool isJumping() const { return !grounded && verticalVelocity > 0.0f; }
bool isFalling() const { return !grounded && verticalVelocity <= 0.0f; }
bool isSprinting() const;
bool isMovingForward() const { return moveForwardActive; }
bool isMovingBackward() const { return moveBackwardActive; }
bool isStrafingLeft() const { return strafeLeftActive; }
bool isStrafingRight() const { return strafeRightActive; }
bool isRightMouseHeld() const { return rightMouseDown; }
bool isSitting() const { return sitting; }
bool isSwimming() const { return swimming; }
@ -136,6 +140,10 @@ private:
bool wasTurningRight = false;
bool wasJumping = false;
bool wasFalling = false;
bool moveForwardActive = false;
bool moveBackwardActive = false;
bool strafeLeftActive = false;
bool strafeRightActive = false;
// Movement callback
MovementCallback movementCallback;

View file

@ -64,6 +64,7 @@ public:
void setInstanceVisible(uint32_t instanceId, bool visible);
void removeInstance(uint32_t instanceId);
bool getAnimationState(uint32_t instanceId, uint32_t& animationId, float& animationTimeMs, float& animationDurationMs) const;
bool hasAnimation(uint32_t instanceId, uint32_t animationId) const;
/** Attach a weapon model to a character instance at the given attachment point. */
bool attachWeapon(uint32_t charInstanceId, uint32_t attachmentId,

View file

@ -752,6 +752,10 @@ void CameraController::update(float deltaTime) {
wasMovingBackward = nowBackward;
wasStrafingLeft = nowStrafeLeft;
wasStrafingRight = nowStrafeRight;
moveForwardActive = nowForward;
moveBackwardActive = nowBackward;
strafeLeftActive = nowStrafeLeft;
strafeRightActive = nowStrafeRight;
wasTurningLeft = nowTurnLeft;
wasTurningRight = nowTurnRight;
wasJumping = nowJump;
@ -827,6 +831,10 @@ void CameraController::reset() {
wasJumping = false;
wasFalling = false;
wasSwimming = false;
moveForwardActive = false;
moveBackwardActive = false;
strafeLeftActive = false;
strafeRightActive = false;
glm::vec3 spawnPos = defaultPosition;

View file

@ -1165,6 +1165,26 @@ bool CharacterRenderer::getAnimationState(uint32_t instanceId, uint32_t& animati
return true;
}
bool CharacterRenderer::hasAnimation(uint32_t instanceId, uint32_t animationId) const {
auto it = instances.find(instanceId);
if (it == instances.end()) {
return false;
}
auto modelIt = models.find(it->second.modelId);
if (modelIt == models.end()) {
return false;
}
const auto& sequences = modelIt->second.data.sequences;
for (const auto& seq : sequences) {
if (seq.id == animationId) {
return true;
}
}
return false;
}
bool CharacterRenderer::attachWeapon(uint32_t charInstanceId, uint32_t attachmentId,
const pipeline::M2Model& weaponModel, uint32_t weaponModelId,
const std::string& texturePath) {

View file

@ -299,6 +299,12 @@ void Renderer::updateCharacterAnimation() {
constexpr uint32_t ANIM_STAND = 0;
constexpr uint32_t ANIM_WALK = 4;
constexpr uint32_t ANIM_RUN = 5;
// Candidate locomotion clips by common WotLK IDs.
constexpr uint32_t ANIM_STRAFE_RUN_RIGHT = 92;
constexpr uint32_t ANIM_STRAFE_RUN_LEFT = 93;
constexpr uint32_t ANIM_STRAFE_WALK_LEFT = 11;
constexpr uint32_t ANIM_STRAFE_WALK_RIGHT = 12;
constexpr uint32_t ANIM_BACKPEDAL = 13;
constexpr uint32_t ANIM_JUMP_START = 37;
constexpr uint32_t ANIM_JUMP_MID = 38;
constexpr uint32_t ANIM_JUMP_END = 39;
@ -310,6 +316,11 @@ void Renderer::updateCharacterAnimation() {
CharAnimState newState = charAnimState;
bool moving = cameraController->isMoving();
bool movingBackward = cameraController->isMovingBackward();
bool strafeLeft = cameraController->isStrafingLeft();
bool strafeRight = cameraController->isStrafingRight();
bool anyStrafeLeft = strafeLeft && !strafeRight;
bool anyStrafeRight = strafeRight && !strafeLeft;
bool grounded = cameraController->isGrounded();
bool jumping = cameraController->isJumping();
bool sprinting = cameraController->isSprinting();
@ -442,24 +453,61 @@ void Renderer::updateCharacterAnimation() {
if (newState != charAnimState) {
charAnimState = newState;
}
uint32_t animId = ANIM_STAND;
bool loop = true;
switch (charAnimState) {
case CharAnimState::IDLE: animId = ANIM_STAND; loop = true; break;
case CharAnimState::WALK: animId = ANIM_WALK; loop = true; break;
case CharAnimState::RUN: animId = ANIM_RUN; loop = true; break;
case CharAnimState::JUMP_START: animId = ANIM_JUMP_START; loop = false; break;
case CharAnimState::JUMP_MID: animId = ANIM_JUMP_MID; loop = false; break;
case CharAnimState::JUMP_END: animId = ANIM_JUMP_END; loop = false; break;
case CharAnimState::SIT_DOWN: animId = ANIM_SIT_DOWN; loop = false; break;
case CharAnimState::SITTING: animId = ANIM_SITTING; loop = true; break;
case CharAnimState::EMOTE: animId = emoteAnimId; loop = emoteLoop; break;
case CharAnimState::SWIM_IDLE: animId = ANIM_SWIM_IDLE; loop = true; break;
case CharAnimState::SWIM: animId = ANIM_SWIM; loop = true; break;
auto pickFirstAvailable = [&](std::initializer_list<uint32_t> candidates, uint32_t fallback) -> uint32_t {
for (uint32_t id : candidates) {
if (characterRenderer->hasAnimation(characterInstanceId, id)) {
return id;
}
}
return fallback;
};
uint32_t animId = ANIM_STAND;
bool loop = true;
switch (charAnimState) {
case CharAnimState::IDLE: animId = ANIM_STAND; loop = true; break;
case CharAnimState::WALK:
if (movingBackward) {
animId = pickFirstAvailable({ANIM_BACKPEDAL}, ANIM_WALK);
} else if (anyStrafeLeft) {
animId = pickFirstAvailable({ANIM_STRAFE_WALK_LEFT, ANIM_STRAFE_RUN_LEFT}, ANIM_WALK);
} else if (anyStrafeRight) {
animId = pickFirstAvailable({ANIM_STRAFE_WALK_RIGHT, ANIM_STRAFE_RUN_RIGHT}, ANIM_WALK);
} else {
animId = ANIM_WALK;
}
loop = true;
break;
case CharAnimState::RUN:
if (movingBackward) {
animId = pickFirstAvailable({ANIM_BACKPEDAL}, ANIM_WALK);
} else if (anyStrafeLeft) {
animId = pickFirstAvailable({ANIM_STRAFE_RUN_LEFT}, ANIM_RUN);
} else if (anyStrafeRight) {
animId = pickFirstAvailable({ANIM_STRAFE_RUN_RIGHT}, ANIM_RUN);
} else {
animId = ANIM_RUN;
}
loop = true;
break;
case CharAnimState::JUMP_START: animId = ANIM_JUMP_START; loop = false; break;
case CharAnimState::JUMP_MID: animId = ANIM_JUMP_MID; loop = false; break;
case CharAnimState::JUMP_END: animId = ANIM_JUMP_END; loop = false; break;
case CharAnimState::SIT_DOWN: animId = ANIM_SIT_DOWN; loop = false; break;
case CharAnimState::SITTING: animId = ANIM_SITTING; loop = true; break;
case CharAnimState::EMOTE: animId = emoteAnimId; loop = emoteLoop; break;
case CharAnimState::SWIM_IDLE: animId = ANIM_SWIM_IDLE; loop = true; break;
case CharAnimState::SWIM: animId = ANIM_SWIM; loop = true; break;
}
uint32_t currentAnimId = 0;
float currentAnimTimeMs = 0.0f;
float currentAnimDurationMs = 0.0f;
bool haveState = characterRenderer->getAnimationState(characterInstanceId, currentAnimId, currentAnimTimeMs, currentAnimDurationMs);
if (!haveState || currentAnimId != animId) {
characterRenderer->playAnimation(characterInstanceId, animId, loop);
}
}
@ -600,9 +648,11 @@ void Renderer::update(float deltaTime) {
if (characterInstanceId > 0 && characterRenderer && cameraController && cameraController->isThirdPerson()) {
characterRenderer->setInstancePosition(characterInstanceId, characterPosition);
// Only rotate character to face camera direction when right-click is held
// Left-click orbits camera without turning the character
if (cameraController->isRightMouseHeld() || cameraController->isMoving()) {
// Keep facing decoupled from lateral movement:
// face camera when RMB is held, or with forward/back intent.
if (cameraController->isRightMouseHeld() ||
cameraController->isMovingForward() ||
cameraController->isMovingBackward()) {
characterYaw = cameraController->getYaw();
} else if (targetPosition && !emoteActive && !cameraController->isMoving()) {
// Face target when idle