mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 10:33:57 +00:00
Add disassemble/assemble emote (#13)
* Add stateful multi-part emote system with disassemble/reassemble Introduces a generalized multi-part emote framework where emotes can have two phases. The first trigger plays phase 1 and freezes the character at its last frame; the second trigger plays phase 2 to restore normal state. Movement is blocked for the entire duration of a multi-part emote (from phase 1 start through frozen state to phase 2 completion). The frozen state is synced to all peers via customizeFlags bits in PlayerStateMsg, so new joiners see disassembled players correctly. The emote table is now a 2D array (g_emoteAnims[][2]) where [1] is the phase-2 animation name (nullptr for one-shot emotes). Adding future multi-part emotes only requires a new row in the table. https://claude.ai/code/session_01L5FiuVFUqASR93iJcaXfEi * Fix emote movement blocking and frozen state sync Move movement blocking from CalculateTransform hook (which broke the camera by skipping p_transform output) to ThirdPersonCamera::Tick where it zeroes speed/velocity directly. Remove ShouldBlockMovement and ShouldInvertMovement hooks entirely. Rebuild frozen emote animation cache in InitAnimCaches when the frozen state was set before the ROI was available (state message arrived before world was ready). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Clean up emote branch: remove unused include, extract ClearFrozenState helper - Remove unused multiplayer.h include and using-directive from legopathactor.cpp - Extract ClearFrozenState() to DRY up 4 identical frozen state reset blocks - Clarify bit-encoding comment with mask value and emote ID limit Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
04730bcc97
commit
1a8c6c70ea
@ -75,6 +75,12 @@ class CharacterAnimator {
|
||||
// Emote state accessors
|
||||
bool IsEmoteActive() const { return m_emoteActive; }
|
||||
|
||||
// Multi-part emote state. Returns true when the player is in any phase of a multi-part
|
||||
// emote (playing phase 1, frozen at last frame, or playing phase 2). Movement is blocked.
|
||||
bool IsInMultiPartEmote() const { return m_frozenEmoteId >= 0 || (m_emoteActive && IsMultiPartEmote(m_currentEmoteId)); }
|
||||
int8_t GetFrozenEmoteId() const { return m_frozenEmoteId; }
|
||||
void SetFrozenEmoteId(int8_t p_emoteId, LegoROI* p_roi);
|
||||
|
||||
// Animation time (needed for vehicle ride tick in ThirdPersonCamera)
|
||||
float GetAnimTime() const { return m_animTime; }
|
||||
void SetAnimTime(float p_time) { m_animTime = p_time; }
|
||||
@ -84,6 +90,7 @@ class CharacterAnimator {
|
||||
using AnimCache = AnimUtils::AnimCache;
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
|
||||
void ClearFrozenState();
|
||||
|
||||
CharacterAnimatorConfig m_config;
|
||||
|
||||
@ -102,8 +109,15 @@ class CharacterAnimator {
|
||||
float m_emoteTime;
|
||||
float m_emoteDuration;
|
||||
bool m_emoteActive;
|
||||
uint8_t m_currentEmoteId;
|
||||
MxMatrix m_emoteParentTransform;
|
||||
|
||||
// Multi-part emote frozen state (-1 = not frozen)
|
||||
int8_t m_frozenEmoteId;
|
||||
AnimCache* m_frozenAnimCache;
|
||||
float m_frozenAnimDuration;
|
||||
MxMatrix m_frozenParentTransform;
|
||||
|
||||
// Click animation tracking (0 = none)
|
||||
MxU32 m_clickAnimObjectId;
|
||||
|
||||
|
||||
@ -162,9 +162,16 @@ extern const int g_walkAnimCount;
|
||||
extern const char* const g_idleAnimNames[];
|
||||
extern const int g_idleAnimCount;
|
||||
|
||||
extern const char* const g_emoteAnimNames[];
|
||||
// Emote animation table: [emoteId][phase]. Phase 0 = primary, phase 1 = phase-2 (nullptr for one-shot).
|
||||
extern const char* const g_emoteAnims[][2];
|
||||
extern const int g_emoteAnimCount;
|
||||
|
||||
// Returns true if the emote is a multi-part stateful emote (has a phase-2 animation).
|
||||
inline bool IsMultiPartEmote(uint8_t p_emoteId)
|
||||
{
|
||||
return p_emoteId < g_emoteAnimCount && g_emoteAnims[p_emoteId][1] != nullptr;
|
||||
}
|
||||
|
||||
extern const char* const g_vehicleROINames[VEHICLE_COUNT];
|
||||
extern const char* const g_rideAnimNames[VEHICLE_COUNT];
|
||||
extern const char* const g_rideVehicleROINames[VEHICLE_COUNT];
|
||||
|
||||
@ -47,6 +47,7 @@ class RemotePlayer {
|
||||
void StopClickAnimation();
|
||||
bool IsInVehicle() const { return m_animator.IsInVehicle(); }
|
||||
bool IsMoving() const { return m_animator.IsInVehicle() || m_targetSpeed > 0.01f; }
|
||||
bool IsInMultiPartEmote() const { return m_animator.IsInMultiPartEmote(); }
|
||||
|
||||
private:
|
||||
const char* GetDisplayActorName() const;
|
||||
|
||||
@ -38,6 +38,8 @@ class ThirdPersonCamera {
|
||||
void SetWalkAnimId(uint8_t p_walkAnimId);
|
||||
void SetIdleAnimId(uint8_t p_idleAnimId);
|
||||
void TriggerEmote(uint8_t p_emoteId);
|
||||
bool IsInMultiPartEmote() const;
|
||||
int8_t GetFrozenEmoteId() const;
|
||||
void SetDisplayActorIndex(uint8_t p_displayActorIndex);
|
||||
uint8_t GetDisplayActorIndex() const { return m_displayActorIndex; }
|
||||
LegoROI* GetDisplayROI() const { return m_displayROI; }
|
||||
|
||||
@ -20,7 +20,8 @@ using namespace Multiplayer;
|
||||
CharacterAnimator::CharacterAnimator(const CharacterAnimatorConfig& p_config)
|
||||
: m_config(p_config), m_walkAnimId(0), m_idleAnimId(0), m_walkAnimCache(nullptr), m_idleAnimCache(nullptr),
|
||||
m_animTime(0.0f), m_idleTime(0.0f), m_idleAnimTime(0.0f), m_wasMoving(false), m_emoteAnimCache(nullptr),
|
||||
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_clickAnimObjectId(0), m_rideAnim(nullptr),
|
||||
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_currentEmoteId(0), m_frozenEmoteId(-1),
|
||||
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_rideAnim(nullptr),
|
||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE),
|
||||
m_nameBubble(nullptr)
|
||||
{
|
||||
@ -71,10 +72,10 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
bool inVehicle = (m_currentVehicleType != VEHICLE_NONE);
|
||||
bool isMoving = inVehicle || p_isMoving;
|
||||
|
||||
// Movement interrupts click animations and emotes
|
||||
if (isMoving) {
|
||||
// Movement interrupts click animations and emotes (but not multi-part emotes)
|
||||
if (isMoving && m_frozenEmoteId < 0) {
|
||||
StopClickAnimation();
|
||||
if (m_emoteActive) {
|
||||
if (m_emoteActive && !IsMultiPartEmote(m_currentEmoteId)) {
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
}
|
||||
@ -104,16 +105,32 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else if (m_emoteActive && m_emoteAnimCache && m_emoteAnimCache->anim && m_emoteAnimCache->roiMap) {
|
||||
// Emote playback (one-shot)
|
||||
// Emote playback
|
||||
m_emoteTime += p_deltaTime * 1000.0f;
|
||||
|
||||
if (m_emoteTime >= m_emoteDuration) {
|
||||
// Emote completed -- return to stationary flow
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
if (IsMultiPartEmote(m_currentEmoteId) && m_frozenEmoteId < 0) {
|
||||
// Phase 1 completed -> freeze at last frame
|
||||
m_frozenEmoteId = (int8_t) m_currentEmoteId;
|
||||
m_frozenAnimCache = m_emoteAnimCache;
|
||||
m_frozenAnimDuration = m_emoteDuration;
|
||||
m_emoteActive = false;
|
||||
if (m_config.saveEmoteTransform) {
|
||||
m_frozenParentTransform = m_emoteParentTransform;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (IsMultiPartEmote(m_currentEmoteId) && m_frozenEmoteId >= 0) {
|
||||
// Phase 2 completed -> unfreeze
|
||||
ClearFrozenState();
|
||||
}
|
||||
// Emote completed -- return to stationary flow
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
MxMatrix transform(m_config.saveEmoteTransform ? m_emoteParentTransform : p_roi->GetLocal2World());
|
||||
@ -134,6 +151,24 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_frozenEmoteId >= 0 && m_frozenAnimCache && m_frozenAnimCache->anim && m_frozenAnimCache->roiMap) {
|
||||
// Frozen at last frame of a multi-part emote's phase-1 animation
|
||||
MxMatrix transform(m_config.saveEmoteTransform ? m_frozenParentTransform : p_roi->GetLocal2World());
|
||||
|
||||
LegoTreeNode* root = m_frozenAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) m_frozenAnimDuration,
|
||||
m_frozenAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
|
||||
if (m_config.saveEmoteTransform) {
|
||||
p_roi->WrappedSetLocal2WorldWithWorldDataUpdate(m_frozenParentTransform);
|
||||
}
|
||||
}
|
||||
else if (m_idleAnimCache && m_idleAnimCache->anim && m_idleAnimCache->roiMap) {
|
||||
// Idle animation
|
||||
if (m_wasMoving) {
|
||||
@ -199,18 +234,48 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
||||
return;
|
||||
}
|
||||
|
||||
// Only play emotes when stationary
|
||||
if (p_isMoving) {
|
||||
return;
|
||||
if (IsMultiPartEmote(p_emoteId)) {
|
||||
if (m_frozenEmoteId == (int8_t) p_emoteId) {
|
||||
// Phase 2: play the recovery animation to unfreeze
|
||||
AnimCache* cache = GetOrBuildAnimCache(p_roi, g_emoteAnims[p_emoteId][1]);
|
||||
if (!cache || !cache->anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
|
||||
m_currentEmoteId = p_emoteId;
|
||||
m_emoteAnimCache = cache;
|
||||
m_emoteTime = 0.0f;
|
||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||
m_emoteActive = true;
|
||||
|
||||
if (m_config.saveEmoteTransform) {
|
||||
m_emoteParentTransform = m_frozenParentTransform;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (m_frozenEmoteId >= 0) {
|
||||
// Already frozen in a different emote, ignore
|
||||
return;
|
||||
}
|
||||
// Phase 1: fall through to play the primary animation
|
||||
}
|
||||
else {
|
||||
// One-shot emote: block if moving or frozen in any multi-part emote
|
||||
if (p_isMoving || m_frozenEmoteId >= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
AnimCache* cache = GetOrBuildAnimCache(p_roi, g_emoteAnimNames[p_emoteId]);
|
||||
AnimCache* cache = GetOrBuildAnimCache(p_roi, g_emoteAnims[p_emoteId][0]);
|
||||
if (!cache || !cache->anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
|
||||
m_currentEmoteId = p_emoteId;
|
||||
m_emoteAnimCache = cache;
|
||||
m_emoteTime = 0.0f;
|
||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||
@ -295,6 +360,36 @@ void CharacterAnimator::InitAnimCaches(LegoROI* p_roi)
|
||||
{
|
||||
m_walkAnimCache = GetOrBuildAnimCache(p_roi, g_walkAnimNames[m_walkAnimId]);
|
||||
m_idleAnimCache = GetOrBuildAnimCache(p_roi, g_idleAnimNames[m_idleAnimId]);
|
||||
|
||||
// Rebuild frozen emote cache if the frozen state was set before the ROI was available
|
||||
// (e.g. state message arrived before world was ready, or world was re-enabled).
|
||||
if (m_frozenEmoteId >= 0 && !m_frozenAnimCache) {
|
||||
SetFrozenEmoteId(m_frozenEmoteId, p_roi);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::SetFrozenEmoteId(int8_t p_emoteId, LegoROI* p_roi)
|
||||
{
|
||||
if (p_emoteId >= 0 && p_emoteId < g_emoteAnimCount && IsMultiPartEmote((uint8_t) p_emoteId)) {
|
||||
AnimCache* cache = p_roi ? GetOrBuildAnimCache(p_roi, g_emoteAnims[p_emoteId][0]) : nullptr;
|
||||
m_frozenEmoteId = p_emoteId;
|
||||
m_frozenAnimCache = cache;
|
||||
m_frozenAnimDuration = (cache && cache->anim) ? (float) cache->anim->GetDuration() : 0.0f;
|
||||
m_emoteActive = false;
|
||||
if (m_config.saveEmoteTransform && p_roi) {
|
||||
m_frozenParentTransform = p_roi->GetLocal2World();
|
||||
}
|
||||
}
|
||||
else {
|
||||
ClearFrozenState();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearFrozenState()
|
||||
{
|
||||
m_frozenEmoteId = -1;
|
||||
m_frozenAnimCache = nullptr;
|
||||
m_frozenAnimDuration = 0.0f;
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearAnimCaches()
|
||||
@ -303,6 +398,7 @@ void CharacterAnimator::ClearAnimCaches()
|
||||
m_idleAnimCache = nullptr;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_emoteActive = false;
|
||||
ClearFrozenState();
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearAll()
|
||||
@ -318,6 +414,7 @@ void CharacterAnimator::ResetAnimState()
|
||||
m_idleAnimTime = 0.0f;
|
||||
m_wasMoving = false;
|
||||
m_emoteActive = false;
|
||||
ClearFrozenState();
|
||||
}
|
||||
|
||||
void CharacterAnimator::ApplyIdleFrame0(LegoROI* p_roi)
|
||||
|
||||
@ -330,6 +330,18 @@ void NetworkManager::BroadcastLocalState()
|
||||
m_thirdPersonCamera.GetCustomizeState().Pack(msg.customizeData);
|
||||
msg.customizeFlags = m_localAllowRemoteCustomize ? 0x01 : 0x00;
|
||||
|
||||
// Encode multi-part emote frozen state (0x02 = frozen, emote ID in bits 2-4, max 8 emotes)
|
||||
int8_t frozenId = m_thirdPersonCamera.GetFrozenEmoteId();
|
||||
if (frozenId >= 0) {
|
||||
msg.customizeFlags |= 0x02;
|
||||
msg.customizeFlags |= (frozenId & 0x07) << 2;
|
||||
}
|
||||
|
||||
// Zero speed when in any phase of a multi-part emote
|
||||
if (m_thirdPersonCamera.IsInMultiPartEmote()) {
|
||||
msg.speed = 0.0f;
|
||||
}
|
||||
|
||||
SendMessage(msg);
|
||||
}
|
||||
|
||||
@ -687,7 +699,7 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
||||
it->second->GetCustomizeState(),
|
||||
p_msg.changeType == CHANGE_MOOD
|
||||
);
|
||||
if (!it->second->IsMoving()) {
|
||||
if (!it->second->IsMoving() && !it->second->IsInMultiPartEmote()) {
|
||||
MxU32 clickAnimId =
|
||||
CharacterCustomizer::PlayClickAnimation(it->second->GetROI(), it->second->GetCustomizeState());
|
||||
it->second->SetClickAnimObjectId(clickAnimId);
|
||||
@ -719,8 +731,9 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
||||
p_msg.changeType == CHANGE_MOOD
|
||||
);
|
||||
|
||||
// Only play click animation in 3rd person (not visible in 1st person)
|
||||
if (m_thirdPersonCamera.GetDisplayROI() && !m_thirdPersonCamera.IsInVehicle()) {
|
||||
// Only play click animation in 3rd person (not visible in 1st person or multi-part emote)
|
||||
if (m_thirdPersonCamera.GetDisplayROI() && !m_thirdPersonCamera.IsInVehicle() &&
|
||||
!m_thirdPersonCamera.IsInMultiPartEmote()) {
|
||||
MxU32 clickAnimId = CharacterCustomizer::PlayClickAnimation(
|
||||
m_thirdPersonCamera.GetDisplayROI(),
|
||||
m_thirdPersonCamera.GetCustomizeState()
|
||||
|
||||
@ -26,11 +26,14 @@ const char* const g_idleAnimNames[] = {
|
||||
};
|
||||
const int g_idleAnimCount = sizeof(g_idleAnimNames) / sizeof(g_idleAnimNames[0]);
|
||||
|
||||
const char* const g_emoteAnimNames[] = {
|
||||
"CNs011xx", // 0: Wave
|
||||
"CNs012xx", // 1: Hat Tip
|
||||
// Emote animation table. Each entry is {phase1, phase2}.
|
||||
// phase2 is nullptr for one-shot emotes; non-null makes it a multi-part stateful emote.
|
||||
const char* const g_emoteAnims[][2] = {
|
||||
{"CNs011xx", nullptr}, // 0: Wave (one-shot)
|
||||
{"CNs012xx", nullptr}, // 1: Hat Tip (one-shot)
|
||||
{"BNsDis01", "BNsAss01"}, // 2: Disassemble / Reassemble (multi-part)
|
||||
};
|
||||
const int g_emoteAnimCount = sizeof(g_emoteAnimNames) / sizeof(g_emoteAnimNames[0]);
|
||||
const int g_emoteAnimCount = sizeof(g_emoteAnims) / sizeof(g_emoteAnims[0]);
|
||||
|
||||
// Vehicle model names (LOD names). The helicopter is a compound ROI ("copter")
|
||||
// with no standalone LOD; use its body part instead.
|
||||
|
||||
@ -172,6 +172,13 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
|
||||
// Update allow remote customize flag
|
||||
m_allowRemoteCustomize = (p_msg.customizeFlags & 0x01) != 0;
|
||||
|
||||
// Sync multi-part emote frozen state from remote
|
||||
bool isFrozen = (p_msg.customizeFlags & 0x02) != 0;
|
||||
int8_t frozenEmoteId = isFrozen ? (int8_t) ((p_msg.customizeFlags >> 2) & 0x07) : -1;
|
||||
if (frozenEmoteId != m_animator.GetFrozenEmoteId()) {
|
||||
m_animator.SetFrozenEmoteId(frozenEmoteId, m_roi);
|
||||
}
|
||||
|
||||
// Swap walk animation if changed
|
||||
if (p_msg.walkAnimId != m_animator.GetWalkAnimId() && p_msg.walkAnimId < g_walkAnimCount) {
|
||||
m_animator.SetWalkAnimId(p_msg.walkAnimId, m_roi);
|
||||
@ -191,7 +198,12 @@ void RemotePlayer::Tick(float p_deltaTime)
|
||||
|
||||
UpdateVehicleState();
|
||||
UpdateTransform(p_deltaTime);
|
||||
m_animator.Tick(p_deltaTime, m_roi, m_targetSpeed > 0.01f);
|
||||
|
||||
bool isMoving = m_targetSpeed > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
}
|
||||
m_animator.Tick(p_deltaTime, m_roi, isMoving);
|
||||
|
||||
// Update name bubble position and billboard orientation
|
||||
m_animator.UpdateNameBubble(m_roi);
|
||||
@ -249,7 +261,11 @@ void RemotePlayer::TriggerEmote(uint8_t p_emoteId)
|
||||
return;
|
||||
}
|
||||
|
||||
m_animator.TriggerEmote(p_emoteId, m_roi, m_targetSpeed > 0.01f);
|
||||
bool isMoving = m_targetSpeed > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
}
|
||||
m_animator.TriggerEmote(p_emoteId, m_roi, isMoving);
|
||||
}
|
||||
|
||||
void RemotePlayer::UpdateTransform(float p_deltaTime)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "islepathactor.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
@ -294,6 +295,11 @@ void ThirdPersonCamera::Tick(float p_deltaTime)
|
||||
|
||||
float speed = userActor->GetWorldSpeed();
|
||||
bool isMoving = SDL_fabsf(speed) > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
userActor->SetWorldSpeed(0.0f);
|
||||
NavController()->SetLinearVel(0.0f);
|
||||
}
|
||||
|
||||
m_animator.Tick(p_deltaTime, m_playerROI, isMoving);
|
||||
}
|
||||
@ -308,6 +314,16 @@ void ThirdPersonCamera::SetIdleAnimId(uint8_t p_idleAnimId)
|
||||
m_animator.SetIdleAnimId(p_idleAnimId, m_active ? m_playerROI : nullptr);
|
||||
}
|
||||
|
||||
bool ThirdPersonCamera::IsInMultiPartEmote() const
|
||||
{
|
||||
return m_animator.IsInMultiPartEmote();
|
||||
}
|
||||
|
||||
int8_t ThirdPersonCamera::GetFrozenEmoteId() const
|
||||
{
|
||||
return m_animator.GetFrozenEmoteId();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::TriggerEmote(uint8_t p_emoteId)
|
||||
{
|
||||
if (!m_active) {
|
||||
@ -319,7 +335,11 @@ void ThirdPersonCamera::TriggerEmote(uint8_t p_emoteId)
|
||||
return;
|
||||
}
|
||||
|
||||
m_animator.TriggerEmote(p_emoteId, m_playerROI, SDL_fabsf(userActor->GetWorldSpeed()) > 0.01f);
|
||||
bool isMoving = SDL_fabsf(userActor->GetWorldSpeed()) > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
}
|
||||
m_animator.TriggerEmote(p_emoteId, m_playerROI, isMoving);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user