diff --git a/binaries/data/mods/public/art/actors/actor.rnc b/binaries/data/mods/public/art/actors/actor.rnc index 7c046a233e..1aa6becdf0 100644 --- a/binaries/data/mods/public/art/actors/actor.rnc +++ b/binaries/data/mods/public/art/actors/actor.rnc @@ -38,6 +38,7 @@ element actor { element animations { element animation { attribute name { text } & + attribute id { text }? & attribute frequency { xsd:nonNegativeInteger }? & attribute file { text }? & attribute speed { xsd:nonNegativeInteger } & diff --git a/binaries/data/mods/public/art/actors/actor.rng b/binaries/data/mods/public/art/actors/actor.rng index f689a3a06f..ffdc2cfad4 100644 --- a/binaries/data/mods/public/art/actors/actor.rng +++ b/binaries/data/mods/public/art/actors/actor.rng @@ -93,6 +93,9 @@ + + + diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index b8fdb1a02b..ee7303c891 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -255,7 +255,7 @@ const CBoundingBoxAligned CModel::GetObjectSelectionBoundsRec() ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // BuildAnimation: load raw animation frame animation from given file, and build a // animation specific to this model -CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos) +CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos) { CSkeletonAnimDef* def = m_SkeletonAnimManager.GetAnimation(pathname); if (!def) @@ -263,6 +263,7 @@ CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const CStr& name, CSkeletonAnim* anim = new CSkeletonAnim(); anim->m_Name = name; + anim->m_ID = ID; anim->m_Frequency = frequency; anim->m_AnimDef = def; anim->m_Speed = speed; diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 7de37b0755..8bf254fb87 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -201,6 +201,7 @@ public: * animation specific to this model. * @param pathname animation file to load * @param name animation name (e.g. "idle") + * @param ID specific ID of the animation, to sync with props * @param frequency influences the random choices * @param speed animation speed as a factor of the default animation speed * @param actionpos offset of 'action' event, in range [0, 1] @@ -208,7 +209,7 @@ public: * @param sound offset of 'sound' event, in range [0, 1] * @return new animation, or NULL on error */ - CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, int frequency, float speed, float actionpos, float actionpos2, float soundpos); + CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const CStr& name, const CStr& ID, int frequency, float speed, float actionpos, float actionpos2, float soundpos); /** * Add a prop to the model on the given point. diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index d65b35c265..41f581e8a8 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -60,6 +60,7 @@ void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& vari AT(event); AT(file); AT(frequency); + AT(id); AT(load); AT(maxheight); AT(minheight); @@ -163,6 +164,8 @@ void CObjectBase::LoadVariant(const CXeromyces& XeroFile, const XMBElement& vari { if (ae.Name == at_name) anim.m_AnimName = ae.Value; + else if (ae.Name == at_id) + anim.m_ID = ae.Value; else if (ae.Name == at_frequency) anim.m_Frequency = ae.Value.ToInt(); else if (ae.Name == at_file) diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index e55919b1ef..df296b66bf 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -45,6 +45,8 @@ public: Anim() : m_Frequency(0), m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f), m_SoundPos(-1.f) {} // name of the animation - "Idle", "Run", etc CStr m_AnimName; + // ID of the animation: if not empty, something specific to sync with props. + CStr m_ID = ""; int m_Frequency; // filename of the animation - manidle.psa, manrun.psa, etc VfsPath m_FileName; diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index bd2d3df01e..73f39088dc 100644 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -164,6 +164,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections CSkeletonAnim* anim = model->BuildAnimation( it->second.m_FileName, name, + it->second.m_ID, it->second.m_Frequency, it->second.m_Speed, it->second.m_ActionPos, @@ -178,6 +179,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections { CSkeletonAnim* anim = new CSkeletonAnim(); anim->m_Name = "idle"; + anim->m_ID = ""; anim->m_AnimDef = NULL; anim->m_Frequency = 0; anim->m_Speed = 0.f; @@ -253,9 +255,9 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections return true; } -CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const +CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName, const CStr& ID) const { - std::vector anims = GetAnimations(animationName); + std::vector anims = GetAnimations(animationName, ID); int totalFreq = 0; for (CSkeletonAnim* anim : anims) @@ -271,11 +273,10 @@ CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const if (r < 0) return anim; } - LOGERROR("No animation found for name %s", animationName); return NULL; } -std::vector CObjectEntry::GetAnimations(const CStr& animationName) const +std::vector CObjectEntry::GetAnimations(const CStr& animationName, const CStr& ID) const { std::vector anims; @@ -283,12 +284,10 @@ std::vector CObjectEntry::GetAnimations(const CStr& animationNam SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it) - anims.push_back(it->second); - - if (anims.empty()) - for (const std::pair& anim : m_Animations) - if (anim.second->m_Frequency > 0) - anims.push_back(anim.second); + { + if (ID.empty() || it->second->m_ID == ID) + anims.push_back(it->second); + } if (anims.empty()) { diff --git a/source/graphics/ObjectEntry.h b/source/graphics/ObjectEntry.h index da266c6343..371fb9a12b 100644 --- a/source/graphics/ObjectEntry.h +++ b/source/graphics/ObjectEntry.h @@ -64,20 +64,18 @@ public: std::wstring m_ProjectileModelName; /** - * Returns a randomly-chosen animation matching the given name. + * Returns a randomly-chosen animation matching the given ID, or animationName if ID is empty. * The chosen animation is picked randomly from the GetAnimations list * with the frequencies as weights (if there are any defined). * This method should always return an animation */ - CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const; + CSkeletonAnim* GetRandomAnimation(const CStr& animationName, const CStr& ID = "") const; /** - * Returns all the animations matching the given name. - * - Prefers the animations names like the animationName - * - Second choice are animations with a frequency - * - Last choice are the Idle animations (which are always added) + * Returns all the animations matching the given ID or animationName if ID is empty. + * If none found returns Idle animations (which are always added) */ - std::vector GetAnimations(const CStr& animationName) const; + std::vector GetAnimations(const CStr& animationName, const CStr& ID = "") const; // corresponding model CModelAbstract* m_Model; @@ -87,6 +85,7 @@ public: bool m_Outdated; private: + CSimulation2& m_Simulation; typedef std::multimap SkeletonAnimMap; diff --git a/source/graphics/SkeletonAnim.h b/source/graphics/SkeletonAnim.h index 9713143e09..c3b1d32de2 100644 --- a/source/graphics/SkeletonAnim.h +++ b/source/graphics/SkeletonAnim.h @@ -34,6 +34,8 @@ class CSkeletonAnim public: // the name of the action which uses this animation (e.g. "idle") CStr m_Name; + // the ID of this animation, to sync between props. + CStr m_ID = ""; // frequency of the animation int m_Frequency; // the raw animation frame data; may be NULL if this is a static 'animation' diff --git a/source/graphics/UnitAnimation.cpp b/source/graphics/UnitAnimation.cpp index 8ad1ce9b5d..1cb8e4bd34 100644 --- a/source/graphics/UnitAnimation.cpp +++ b/source/graphics/UnitAnimation.cpp @@ -41,7 +41,7 @@ static float DesyncSpeed(float speed, float desync) } CUnitAnimation::CUnitAnimation(entity_id_t ent, CModel* model, CObjectEntry* object) - : m_Entity(ent), m_State("idle"), m_AnimationName("idle"), m_Looping(true), + : m_Entity(ent), m_State("idle"), m_Looping(true), m_Speed(1.f), m_SyncRepeatTime(0.f), m_OriginalSpeed(1.f), m_Desync(0.f) { ReloadUnit(model, object); @@ -58,7 +58,7 @@ void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object) state.model = model; state.object = object; - state.anim = object->GetRandomAnimation(m_State); + state.anim = object->GetRandomAnimation(m_State, m_AnimationID); state.time = 0.f; state.pastLoadPos = false; state.pastActionPos = false; @@ -108,7 +108,7 @@ void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, if (name != m_State) { m_State = name; - m_AnimationName = name; + UpdateAnimationID(); ReloadUnit(m_Model, m_Object); } @@ -236,23 +236,17 @@ void CUnitAnimation::Update(float time) { // we're handling the root model // choose animations from the complete state - anim = it->object->GetRandomAnimation(m_State); - // if we use a new animation name, - // update the animations of all non-root models - // sync with the root model, unless the root model could - // only resort to the "idle" state. - if (anim->m_Name != "idle") - m_AnimationName = anim->m_Name; - else - m_AnimationName = m_State; - if (it->anim->m_Name != m_AnimationName) + CStr oldID = m_AnimationID; + UpdateAnimationID(); + anim = it->object->GetRandomAnimation(m_State, m_AnimationID); + if (oldID != m_AnimationID) for (SModelAnimState animState : m_AnimStates) if (animState.model != m_Model) - animState.model->SetAnimation(animState.object->GetRandomAnimation(m_AnimationName)); + animState.model->SetAnimation(animState.object->GetRandomAnimation(m_State, m_AnimationID)); } else // choose animations that match the root - anim = it->object->GetRandomAnimation(m_AnimationName); + anim = it->object->GetRandomAnimation(m_State, m_AnimationID); if (anim != it->anim) { @@ -280,3 +274,9 @@ void CUnitAnimation::Update(float time) } } } + +void CUnitAnimation::UpdateAnimationID() +{ + CStr& ID = m_Object->GetRandomAnimation(m_State)->m_ID; + m_AnimationID = ID; +} diff --git a/source/graphics/UnitAnimation.h b/source/graphics/UnitAnimation.h index d9d7d228dc..c7c227b03b 100644 --- a/source/graphics/UnitAnimation.h +++ b/source/graphics/UnitAnimation.h @@ -92,6 +92,12 @@ public: void ReloadUnit(CModel* model, const CObjectEntry* object); private: + + /** + * Picks a new animation ID from our current state + */ + void UpdateAnimationID(); + struct SModelAnimState { CModel* model; @@ -117,7 +123,7 @@ private: CModel* m_Model; const CObjectEntry* m_Object; CStr m_State; - CStr m_AnimationName; + CStr m_AnimationID = ""; bool m_Looping; float m_OriginalSpeed; float m_Speed;