From d3048906cbbed652accace0ac3d8e9055ac6c296 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Sat, 5 Jun 2010 00:49:14 +0000 Subject: [PATCH] # Unit animation improvements. Animate props much more sensibly. Move ammo code out of CUnit. Move animation logic out of CModel. Launch projectiles from the correct location. Use entity's speeds and sounds in actor viewer. Add -nosound option to disable audio, and allow audio by default in Atlas. Remove some obsolete options. This was SVN commit r7609. --- .../_test.sim/simulation/templates/unit.xml | 2 +- .../simulation/templates/unitobstruct.xml | 2 +- .../tools/atlas/scripts/section/object.js | 4 +- source/graphics/Model.cpp | 189 +++++++--------- source/graphics/Model.h | 64 ++++-- source/graphics/ObjectBase.h | 6 +- source/graphics/ObjectEntry.cpp | 92 ++++---- source/graphics/ObjectEntry.h | 10 +- source/graphics/SkeletonAnim.h | 3 +- source/graphics/Unit.cpp | 103 +-------- source/graphics/Unit.h | 31 +-- source/graphics/UnitAnimation.cpp | 207 ++++++++++++------ source/graphics/UnitAnimation.h | 24 ++ source/graphics/UnitManager.cpp | 4 +- source/graphics/UnitManager.h | 3 +- source/ps/GameSetup/Config.cpp | 13 +- source/ps/GameSetup/Config.h | 5 +- source/ps/GameSetup/GameSetup.cpp | 3 +- source/renderer/Scene.cpp | 16 +- .../components/CCmpProjectileManager.cpp | 18 +- .../components/CCmpTemplateManager.cpp | 8 +- .../simulation2/components/CCmpUnitMotion.cpp | 17 +- .../components/CCmpVisualActor.cpp | 14 +- .../simulation2/components/ICmpUnitMotion.h | 5 + source/simulation2/components/ICmpVisual.h | 7 + source/sound/SoundGroup.cpp | 3 +- .../tools/atlas/GameInterface/ActorViewer.cpp | 49 ++++- 27 files changed, 489 insertions(+), 413 deletions(-) diff --git a/binaries/data/mods/_test.sim/simulation/templates/unit.xml b/binaries/data/mods/_test.sim/simulation/templates/unit.xml index d1c587102e..8aa0c9c5e4 100644 --- a/binaries/data/mods/_test.sim/simulation/templates/unit.xml +++ b/binaries/data/mods/_test.sim/simulation/templates/unit.xml @@ -9,5 +9,5 @@ upright false - + diff --git a/binaries/data/mods/_test.sim/simulation/templates/unitobstruct.xml b/binaries/data/mods/_test.sim/simulation/templates/unitobstruct.xml index 33271416e9..307a00620f 100644 --- a/binaries/data/mods/_test.sim/simulation/templates/unitobstruct.xml +++ b/binaries/data/mods/_test.sim/simulation/templates/unitobstruct.xml @@ -9,7 +9,7 @@ upright false - + 1.0 diff --git a/binaries/data/tools/atlas/scripts/section/object.js b/binaries/data/tools/atlas/scripts/section/object.js index 3f0cd06ab7..b9ebc0faa4 100644 --- a/binaries/data/tools/atlas/scripts/section/object.js +++ b/binaries/data/tools/atlas/scripts/section/object.js @@ -244,8 +244,8 @@ function init(window, bottomWindow) actorViewer.controls.push(animationBoxBox); var animationBox = new wxStaticBoxSizer(animationBoxBox, wxOrientation.VERTICAL); var animationSelector = new wxChoice(bottomWindow, -1, wxDefaultPosition, wxDefaultSize, - [ "attack1", "attack2", "build", "corpse", "death", - "gather_fruit", "gather_grain", "gather_wood", "gather_stone", "gather_metal", + [ "build", "corpse", "death", + "gather_fruit", "gather_grain", "gather_meat", "gather_metal", "gather_stone", "gather_wood", "idle", "melee", "run", "walk" ] // TODO: this list should come from the actor ); animationSelector.stringSelection = "idle"; diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp index bfb1c7332e..04a5c58559 100644 --- a/source/graphics/Model.cpp +++ b/source/graphics/Model.cpp @@ -35,13 +35,13 @@ #include "ps/Profile.h" #include "ps/CLogger.h" -#define LOG_CATEGORY L"graphics" ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // Constructor CModel::CModel(CSkeletonAnimManager& skeletonAnimManager) : m_Parent(NULL), m_Flags(0), m_Anim(NULL), m_AnimTime(0), m_BoneMatrices(NULL), m_InverseBindBoneMatrices(NULL), + m_AmmoPropPoint(NULL), m_AmmoLoadedProp(0), m_PositionValid(false), m_ShadingColor(1,1,1,1), m_PlayerID((size_t)-1), m_SkeletonAnimManager(skeletonAnimManager) { @@ -223,8 +223,16 @@ CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const char* name, anim->m_Name = name; anim->m_AnimDef = def; anim->m_Speed = speed; - anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration(); - anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration(); + + if (actionpos == -1.f) + anim->m_ActionPos = -1.f; + else + anim->m_ActionPos = actionpos * anim->m_AnimDef->GetDuration(); + + if (actionpos2 == -1.f) + anim->m_ActionPos2 = -1.f; + else + anim->m_ActionPos2 = actionpos2 * anim->m_AnimDef->GetDuration(); anim->m_ObjectBounds.SetEmpty(); InvalidateBounds(); @@ -233,86 +241,20 @@ CSkeletonAnim* CModel::BuildAnimation(const VfsPath& pathname, const char* name, } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Update: update this model by the given time, in msec -void CModel::Update(float time) +// Update: update this model to the given time, in msec +void CModel::UpdateTo(float time) { - if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) - { - time *= m_Anim->m_Speed; + // update animation time, but don't calculate bone matrices - do that (lazily) when + // something requests them; that saves some calculation work for offscreen models, + // and also assures the world space, inverted bone matrices (required for normal + // skinning) are up to date with respect to m_Transform + m_AnimTime = time; - float oldAnimTime = m_AnimTime; + // mark vertices as dirty + SetDirty(RENDERDATA_UPDATE_VERTICES); - // update animation time, but don't calculate bone matrices - do that (lazily) when - // something requests them; that saves some calculation work for offscreen models, - // and also assures the world space, inverted bone matrices (required for normal - // skinning) are up to date with respect to m_Transform - m_AnimTime += time; - - float duration = m_Anim->m_AnimDef->GetDuration(); - if (m_AnimTime > duration) - { - if (m_Flags & MODELFLAG_NOLOOPANIMATION) - { - if (m_NextAnim) - SetAnimation(m_NextAnim); - else - { - // Changing to no animation - probably becoming a corpse. - // Make sure the last displayed frame is the final frame - // of the animation. - float nearlyEnd = duration - 1.f; // 1 msec - if (fabs(oldAnimTime - nearlyEnd) < 1.f) - SetAnimation(NULL); - else - m_AnimTime = nearlyEnd; - } - } - else - m_AnimTime = fmod(m_AnimTime, duration); - } - - // mark vertices as dirty - SetDirty(RENDERDATA_UPDATE_VERTICES); - - // mark matrices as dirty - InvalidatePosition(); - } - - // update props - for (size_t i = 0; i < m_Props.size(); ++i) - m_Props[i].m_Model->Update(time); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -bool CModel::NeedsNewAnim(float time) const -{ - // TODO: fix UnitAnimation so it correctly loops animated props - - if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) - { - time *= m_Anim->m_Speed; - - float duration = m_Anim->m_AnimDef->GetDuration(); - - if (m_AnimTime + time > duration) - return true; - } - - return false; -} - -void CModel::CheckActionTriggers(float time, bool& action, bool& action2) const -{ - if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) - { - time *= m_Anim->m_Speed; - - if (m_AnimTime <= m_Anim->m_ActionPos && m_AnimTime + time > m_Anim->m_ActionPos) - action = true; - - if (m_AnimTime <= m_Anim->m_ActionPos2 && m_AnimTime + time > m_Anim->m_ActionPos2) - action2 = true; - } + // mark matrices as dirty + InvalidatePosition(); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -382,34 +324,35 @@ void CModel::ValidatePosition() ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SetAnimation: set the given animation as the current animation on this model; // return false on error, else true -bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, CSkeletonAnim* next) +bool CModel::SetAnimation(CSkeletonAnim* anim, bool once) { - m_Anim=NULL; // in case something fails + m_Anim = NULL; // in case something fails - if (anim) { + if (anim) + { m_Flags &= ~MODELFLAG_NOLOOPANIMATION; if (once) - { m_Flags |= MODELFLAG_NOLOOPANIMATION; - m_NextAnim = next; - } - if (!m_BoneMatrices && anim->m_AnimDef) { + if (!m_BoneMatrices && anim->m_AnimDef) + { // not boned, can't animate return false; } - if (m_BoneMatrices && !anim->m_AnimDef) { + if (m_BoneMatrices && !anim->m_AnimDef) + { // boned, but animation isn't valid // (e.g. the default (static) idle animation on an animated unit) return false; } - if (anim->m_AnimDef && anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones()) { + if (anim->m_AnimDef && anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones()) + { // mismatch between model's skeleton and animation's skeleton - LOG(CLogger::Error, LOG_CATEGORY, L"Mismatch between model's skeleton and animation's skeleton (%lu model bones != %lu animation keys)", - (unsigned long)m_pModelDef->GetNumBones(), (unsigned long)anim->m_AnimDef->GetNumKeys()); + LOGERROR(L"Mismatch between model's skeleton and animation's skeleton (%lu model bones != %lu animation keys)", + (unsigned long)m_pModelDef->GetNumBones(), (unsigned long)anim->m_AnimDef->GetNumKeys()); return false; } @@ -431,7 +374,6 @@ bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, CSkeletonAnim* next) void CModel::CopyAnimationFrom(CModel* source) { m_Anim = source->m_Anim; - m_NextAnim = source->m_NextAnim; m_AnimTime = source->m_AnimTime; m_Flags &= ~MODELFLAG_CASTSHADOWS; @@ -457,16 +399,49 @@ void CModel::AddProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry m_Props.push_back(prop); } -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// RemoveProp: remove any props from the given point -void CModel::RemoveProp(SPropPoint* point) +void CModel::AddAmmoProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry) { - for (size_t i = 0; i < m_Props.size(); i++) { - if (m_Props[i].m_Point == point) { - delete m_Props[i].m_Model; - // (when a prop is removed it will automatically remove its prop point) - } + AddProp(point, model, objectentry); + m_AmmoPropPoint = point; + m_AmmoLoadedProp = m_Props.size() - 1; + m_Props[m_AmmoLoadedProp].m_Hidden = true; +} + +void CModel::ShowAmmoProp() +{ + if (m_AmmoPropPoint == NULL) + return; + + // Show the ammo prop, hide all others on the same prop point + for (size_t i = 0; i < m_Props.size(); ++i) + if (m_Props[i].m_Point == m_AmmoPropPoint) + m_Props[i].m_Hidden = (i != m_AmmoLoadedProp); +} + +void CModel::HideAmmoProp() +{ + if (m_AmmoPropPoint == NULL) + return; + + // Hide the ammo prop, show all others on the same prop point + for (size_t i = 0; i < m_Props.size(); ++i) + if (m_Props[i].m_Point == m_AmmoPropPoint) + m_Props[i].m_Hidden = (i == m_AmmoLoadedProp); +} + +CModel* CModel::FindFirstAmmoProp() +{ + if (m_AmmoPropPoint) + return m_Props[m_AmmoLoadedProp].m_Model; + + for (size_t i = 0; i < m_Props.size(); ++i) + { + CModel* model = m_Props[i].m_Model->FindFirstAmmoProp(); + if (model) + return model; } + + return NULL; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -482,10 +457,16 @@ CModel* CModel::Clone() const clone->SetMaterial(m_Material); clone->SetAnimation(m_Anim); clone->SetFlags(m_Flags); - for (size_t i=0;iAddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry); + if (m_AmmoPropPoint && i == m_AmmoLoadedProp) + clone->AddAmmoProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry); + else + clone->AddProp(m_Props[i].m_Point, m_Props[i].m_Model->Clone(), m_Props[i].m_ObjectEntry); } + return clone; } @@ -531,8 +512,6 @@ void CModel::SetPlayerColor(const CColor& colour) void CModel::SetShadingColor(const CColor& colour) { m_ShadingColor = colour; - for( std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it ) - { + for (std::vector::iterator it = m_Props.begin(); it != m_Props.end(); ++it) it->m_Model->SetShadingColor(colour); - } } diff --git a/source/graphics/Model.h b/source/graphics/Model.h index 76d2cccda1..34b1fb71d0 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -29,6 +29,7 @@ #include "RenderableObject.h" #include "Material.h" #include "ps/Overlay.h" + struct SPropPoint; class CObjectEntry; class CSkeletonAnim; @@ -45,17 +46,16 @@ class CModel : public CRenderableObject { NONCOPYABLE(CModel); - friend class CUnitAnimation; - // HACK - we should probably move the rest of this class's animation state - // into the animation class, then it wouldn't need friend access - public: - struct Prop { - Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0) {} + struct Prop + { + Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false) {} SPropPoint* m_Point; CModel* m_Model; CObjectEntry* m_ObjectEntry; + + bool m_Hidden; // temporarily removed from rendering }; public: @@ -68,14 +68,8 @@ public: bool InitModel(const CModelDefPtr& modeldef); // calculate the world space bounds of this model void CalcBounds(); - // update this model's state; 'time' is the time since the last update, in MS - void Update(float time); - // returns true if Update(time) will require a new animation (due to the - // current one ending) - bool NeedsNewAnim(float time) const; - // sets the bool& arguments if the given time update will cross the trigger points for those actions; - // otherwise leaves the arguments unchanged - void CheckActionTriggers(float time, bool& action, bool& action2) const; + // update this model's state; 'time' is the absolute time since the start of the animation, in MS + void UpdateTo(float time); // get the model's geometry data CModelDefPtr GetModelDef() { return m_pModelDef; } @@ -100,7 +94,7 @@ public: CColor GetShadingColor() { return m_ShadingColor; } // set the given animation as the current animation on this model - bool SetAnimation(CSkeletonAnim* anim, bool once = false, CSkeletonAnim* next = NULL); + bool SetAnimation(CSkeletonAnim* anim, bool once = false); // get the currently playing animation, if any CSkeletonAnim* GetAnimation() const { return m_Anim; } @@ -162,10 +156,32 @@ public: */ CSkeletonAnim* BuildAnimation(const VfsPath& pathname, const char* name, float speed, float actionpos, float actionpos2); - // add a prop to the model on the given point + /** + * Add a prop to the model on the given point. + */ void AddProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry); - // remove any props from the given point - void RemoveProp(SPropPoint* point); + + /** + * Add a prop to the model on the given point, and treat it as the ammo prop. + * The prop will be hidden by default. + */ + void AddAmmoProp(SPropPoint* point, CModel* model, CObjectEntry* objectentry); + + /** + * Show the ammo prop (if any), and hide any other props on that prop point. + */ + void ShowAmmoProp(); + + /** + * Hide the ammo prop (if any), and show any other props on that prop point. + */ + void HideAmmoProp(); + + /** + * Find the first prop used for ammo, by this model or its own props. + */ + CModel* FindFirstAmmoProp(); + // return prop list std::vector& GetProps() { return m_Props; } const std::vector& GetProps() const { return m_Props; } @@ -209,8 +225,6 @@ private: CBound m_ObjectBounds; // animation currently playing on this model, if any CSkeletonAnim* m_Anim; - // animation to switch back to when the current one finishes - CSkeletonAnim* m_NextAnim; // time (in MS) into the current animation float m_AnimTime; // current state of all bones on this model; null if associated modeldef isn't skeletal @@ -220,6 +234,16 @@ private: // list of current props on model std::vector m_Props; + /** + * The prop point to which the ammo prop is attached, or NULL if none + */ + SPropPoint* m_AmmoPropPoint; + + /** + * If m_AmmoPropPoint is not NULL, then the index in m_Props of the ammo prop + */ + size_t m_AmmoLoadedProp; + /** * true if both transform and and bone matrices are valid. */ diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index 9373e31ba3..693446f325 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -35,7 +35,7 @@ public: struct Anim { // constructor - Anim() : m_Speed(1), m_ActionPos(0.5), m_ActionPos2(0.0) {} + Anim() : m_Speed(1.f), m_ActionPos(-1.f), m_ActionPos2(-1.f) {} // name of the animation - "Idle", "Run", etc CStr m_AnimName; @@ -43,8 +43,8 @@ public: VfsPath m_FileName; // animation speed, as specified in XML actor file float m_Speed; - // fraction of the way through the animation that the interesting bit(s) - // happens + // fraction [0.0, 1.0] of the way through the animation that the interesting bit(s) + // happens, or -1.0 if unspecified float m_ActionPos; float m_ActionPos2; }; diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index f33362c018..ee62b6b6a1 100644 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -37,11 +37,8 @@ #include -#define LOG_CATEGORY L"graphics" - -CObjectEntry::CObjectEntry(CObjectBase* base) -: m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), - m_AmmunitionModel(NULL), m_AmmunitionPoint(NULL), m_Model(NULL) +CObjectEntry::CObjectEntry(CObjectBase* base) : + m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL) { } @@ -72,7 +69,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections str << variation.color; int r, g, b; if (! (str >> r >> g >> b)) // Any trailing data is ignored - LOG(CLogger::Error, LOG_CATEGORY, L"Invalid RGB colour '%hs'", variation.color.c_str()); + LOGERROR(L"Actor '%ls' has invalid RGB colour '%hs'", m_Base->m_ShortName.c_str(), variation.color.c_str()); else m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f); } @@ -93,7 +90,7 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections CModelDefPtr modeldef (objectManager.GetMeshManager().GetMesh(m_ModelName)); if (!modeldef) { - LOG(CLogger::Error, LOG_CATEGORY, L"CObjectEntry::BuildModel(): Model %ls failed to load", m_ModelName.string().c_str()); + LOGERROR(L"CObjectEntry::BuildVariation(): Model %ls failed to load", m_ModelName.string().c_str()); return false; } @@ -144,8 +141,8 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections else { // start up idling - if (! m_Model->SetAnimation(GetRandomAnimation("idle"))) - LOG(CLogger::Error, LOG_CATEGORY, L"Failed to set idle animation in model \"%ls\"", m_ModelName.string().c_str()); + if (!m_Model->SetAnimation(GetRandomAnimation("idle"))) + LOGERROR(L"Failed to set idle animation in model \"%ls\"", m_ModelName.string().c_str()); } // build props - TODO, RC - need to fix up bounds here @@ -164,31 +161,37 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.string().c_str(), selections); if (!oe) { - LOG(CLogger::Error, LOG_CATEGORY, L"Failed to build prop model \"%ls\" on actor \"%ls\"", prop.m_ModelName.string().c_str(), m_Base->m_ShortName.c_str()); + LOGERROR(L"Failed to build prop model \"%ls\" on actor \"%ls\"", prop.m_ModelName.string().c_str(), m_Base->m_ShortName.c_str()); continue; } - // Also pluck out the other special attachpoint 'loaded-' - if (prop.m_PropPointName.length() > 7 && prop.m_PropPointName.Left(7) == "loaded-") + // If we don't have a projectile but this prop does (e.g. it's our rider), then + // use that as our projectile too + if (m_ProjectileModelName.empty() && !oe->m_ProjectileModelName.empty()) + m_ProjectileModelName = oe->m_ProjectileModelName; + + CStr ppn = prop.m_PropPointName; + bool isAmmo = false; + + // Handle the special attachpoint 'loaded-' + if (ppn.Find("loaded-") == 0) { - CStr ppn = prop.m_PropPointName.substr(7); - m_AmmunitionModel = oe->m_Model; - m_AmmunitionPoint = modeldef->FindPropPoint(ppn); - if (! m_AmmunitionPoint) - LOG(CLogger::Error, LOG_CATEGORY, L"Failed to find matching prop point called \"%hs\" in model \"%ls\" for actor \"%ls\"", ppn.c_str(), m_ModelName.string().c_str(), m_Base->m_ShortName.c_str()); + ppn = prop.m_PropPointName.substr(7); + isAmmo = true; + } + + SPropPoint* proppoint = modeldef->FindPropPoint(ppn.c_str()); + if (proppoint) + { + CModel* propmodel = oe->m_Model->Clone(); + if (isAmmo) + m_Model->AddAmmoProp(proppoint, propmodel, oe); + else + m_Model->AddProp(proppoint, propmodel, oe); + propmodel->SetAnimation(oe->GetRandomAnimation("idle")); } else - { - SPropPoint* proppoint = modeldef->FindPropPoint(prop.m_PropPointName.c_str()); - if (proppoint) - { - CModel* propmodel = oe->m_Model->Clone(); - m_Model->AddProp(proppoint, propmodel, oe); - propmodel->SetAnimation(oe->GetRandomAnimation("idle")); - } - else - LOG(CLogger::Error, LOG_CATEGORY, L"Failed to find matching prop point called \"%hs\" in model \"%ls\" for actor \"%ls\"", prop.m_PropPointName.c_str(), m_ModelName.string().c_str(), m_Base->m_ShortName.c_str()); - } + LOGERROR(L"Failed to find matching prop point called \"%hs\" in model \"%ls\" for actor \"%ls\"", ppn.c_str(), m_ModelName.string().c_str(), m_Base->m_ShortName.c_str()); } // setup flags @@ -235,21 +238,26 @@ bool CObjectEntry::BuildVariation(const std::vector >& selections return true; } -CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) +CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const { - SkeletonAnimMap::iterator lower = m_Animations.lower_bound(animationName); - SkeletonAnimMap::iterator upper = m_Animations.upper_bound(animationName); + SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName); + SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); size_t count = std::distance(lower, upper); if (count == 0) - { -// LOG(CLogger::Warning, LOG_CATEGORY, L"Failed to find animation '%hs' for actor '%ls'", animationName.c_str(), m_ModelName.c_str()); return NULL; - } - else - { - // TODO: Do we care about network synchronisation of random animations? - size_t id = rand(0, count); - std::advance(lower, id); - return lower->second; - } + + size_t id = rand(0, count); + std::advance(lower, id); + return lower->second; +} + +std::vector CObjectEntry::GetAnimations(const CStr& animationName) const +{ + std::vector anims; + + SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName); + SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); + for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it) + anims.push_back(it->second); + return anims; } diff --git a/source/graphics/ObjectEntry.h b/source/graphics/ObjectEntry.h index 761918de22..1e721c7756 100644 --- a/source/graphics/ObjectEntry.h +++ b/source/graphics/ObjectEntry.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -57,12 +57,12 @@ public: CStrW m_ProjectileModelName; - CModel* m_AmmunitionModel; - SPropPoint* m_AmmunitionPoint; - // Returns a randomly-chosen animation matching the given name. // If none is found, returns NULL. - CSkeletonAnim* GetRandomAnimation(const CStr& animationName); + CSkeletonAnim* GetRandomAnimation(const CStr& animationName) const; + + // Returns all the animations matching the given name. + std::vector GetAnimations(const CStr& animationName) const; // corresponding model CModel* m_Model; diff --git a/source/graphics/SkeletonAnim.h b/source/graphics/SkeletonAnim.h index d4959b7346..78a4eb179b 100644 --- a/source/graphics/SkeletonAnim.h +++ b/source/graphics/SkeletonAnim.h @@ -40,7 +40,8 @@ public: // (treated as 0 if m_AnimDef == NULL) float m_Speed; // Times during the animation at which the interesting bits happen, - // as msec times in the range [0, AnimDef->GetDuration]. + // as msec times in the range [0, AnimDef->GetDuration], + // or special value -1 if unspecified. // ActionPos is used for melee hits, projectile launches, etc. // ActionPos2 is used for loading projectile ammunition. float m_ActionPos; diff --git a/source/graphics/Unit.cpp b/source/graphics/Unit.cpp index 3ab78e7250..1bef02bde7 100644 --- a/source/graphics/Unit.cpp +++ b/source/graphics/Unit.cpp @@ -29,7 +29,7 @@ #include "ps/Game.h" #include "ps/Player.h" -CUnit::CUnit(CObjectEntry* object, CEntity* entity, CObjectManager& objectManager, +CUnit::CUnit(CObjectEntry* object, CObjectManager& objectManager, const std::set& actorSelections) : m_Object(object), m_Model(object->m_Model->Clone()), m_ID(invalidUnitId), m_ActorSelections(actorSelections), @@ -44,8 +44,7 @@ CUnit::~CUnit() delete m_Model; } -CUnit* CUnit::Create(const CStrW& actorName, CEntity* entity, - const std::set& selections, CObjectManager& objectManager) +CUnit* CUnit::Create(const CStrW& actorName, const std::set& selections, CObjectManager& objectManager) { CObjectBase* base = objectManager.FindObjectBase(actorName); @@ -62,101 +61,7 @@ CUnit* CUnit::Create(const CStrW& actorName, CEntity* entity, if (! obj) return NULL; - return new CUnit(obj, entity, objectManager, actorSelections); -} - -void CUnit::ShowAmmunition() -{ - if (!m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint) - return; - // Remove any previous ammunition prop, in case there's still one left on there - m_Model->RemoveProp(m_Object->m_AmmunitionPoint); - // Then add the new prop - m_Model->AddProp(m_Object->m_AmmunitionPoint, m_Object->m_AmmunitionModel->Clone(), m_Object); -} - -void CUnit::HideAmmunition() -{ - if (!m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint) - return; - - // Remove the ammunition prop - m_Model->RemoveProp(m_Object->m_AmmunitionPoint); - // Restore the original props that were on the ammo attachpoint, by copying them from - // the base model - std::vector& props = m_Object->m_Model->GetProps(); - std::vector::iterator it; - for (it = props.begin(); it != props.end(); ++it) - { - if (it->m_Point == m_Object->m_AmmunitionPoint) - { - m_Model->AddProp(m_Object->m_AmmunitionPoint, it->m_Model->Clone(), m_Object); - } - } -} - - -static CSkeletonAnim* GetRandomAnimation(const CStr& name, CObjectEntry* object) -{ - CSkeletonAnim* anim = object->GetRandomAnimation(name); - - // Fall back to 'idle', if no matching animation is found - if (anim == NULL && name != "idle") - anim = object->GetRandomAnimation("idle"); - - // Every object should have an idle animation (even if it's a dummy static one) - debug_assert(anim != NULL); - - return anim; -} - -static bool SetRandomAnimation(const CStr& name, bool once, - CModel* model, CObjectEntry* object) -{ - CSkeletonAnim* anim = GetRandomAnimation(name, object); - if (anim) - { - model->SetAnimation(anim, once); - - // Recursively apply the animation name to props - const std::vector& props = model->GetProps(); - for (std::vector::const_iterator it = props.begin(); it != props.end(); ++it) - { - bool ok = SetRandomAnimation(name, once, it->m_Model, it->m_ObjectEntry); - if (! ok) - return false; - } - - return true; - } - else - { - // This shouldn't happen, since GetRandomAnimation tries to always - // return something valid - return false; - } -} - - - -bool CUnit::SetRandomAnimation(const CStr& name, bool once) -{ - return ::SetRandomAnimation(name, once, m_Model, m_Object); -} - -CSkeletonAnim* CUnit::GetRandomAnimation(const CStr& name) -{ - return ::GetRandomAnimation(name, m_Object); -} - -bool CUnit::HasAnimation(const CStr& name) -{ - return (m_Object->GetRandomAnimation(name) != NULL); -} - -bool CUnit::IsPlayingAnimation(const CStr& name) -{ - return (m_Model->GetAnimation() && m_Model->GetAnimation()->m_Name == name); + return new CUnit(obj, objectManager, actorSelections); } void CUnit::SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& soundgroup) @@ -217,5 +122,7 @@ void CUnit::ReloadObject() delete m_Model; m_Model = newModel; m_Object = newObject; + + m_Animation->ReloadUnit(); // TODO: maybe this should try to preserve animation state? } } diff --git a/source/graphics/Unit.h b/source/graphics/Unit.h index ab8c3deb9b..ad784aaf81 100644 --- a/source/graphics/Unit.h +++ b/source/graphics/Unit.h @@ -25,7 +25,6 @@ class CModel; class CObjectEntry; class CObjectManager; -class CEntity; class CSkeletonAnim; class CUnitAnimation; @@ -44,15 +43,14 @@ class CUnit NONCOPYABLE(CUnit); private: // Private constructor. Needs complete list of selections for the variation. - CUnit(CObjectEntry* object, CEntity* entity, CObjectManager& objectManager, + CUnit(CObjectEntry* object, CObjectManager& objectManager, const std::set& actorSelections); public: - // Attempt to create a unit with the given actor, attached to an entity - // (or NULL), with a set of suggested selections (with the rest being randomised). + // Attempt to create a unit with the given actor, with a set of + // suggested selections (with the rest being randomised). // Returns NULL on failure. - static CUnit* Create(const CStrW& actorName, CEntity* entity, - const std::set& selections, CObjectManager& objectManager); + static CUnit* Create(const CStrW& actorName, const std::set& selections, CObjectManager& objectManager); // destructor ~CUnit(); @@ -61,12 +59,6 @@ public: const CObjectEntry& GetObject() const { return *m_Object; } // get unit's model data CModel& GetModel() const { return *m_Model; } - // get actor's entity; can be NULL - CEntity* GetEntity() const { return NULL; } - - // Put here as it conveniently references both the model and the ObjectEntry - void ShowAmmunition(); - void HideAmmunition(); /// See CUnitAnimation::SetAnimationState void SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& soundgroup); @@ -80,16 +72,10 @@ public: */ void UpdateModel(float frameTime); - bool HasAnimation(const CStr& name); - // Sets the entity-selection, and updates the unit to use the new // actor variation. void SetEntitySelection(const CStr& selection); - // Returns whether the currently active animation is one of the ones - // matching 'name'. - bool IsPlayingAnimation(const CStr& name); - // Most units have a hopefully-unique ID number, so they can be referred to // persistently despite saving/loading maps. Default for new units is -1; should // usually be set to CUnitManager::GetNewID() after creation. @@ -120,15 +106,6 @@ private: // object manager which looks after this unit's objectentry CObjectManager& m_ObjectManager; - // Sets the animation a random one matching 'name'. If none is found, - // sets to idle instead. Applies recursively to props. - // SetEntitySelection(name) should typically be used before this. - bool SetRandomAnimation(const CStr& name, bool once = false); - - // Returns a random animation matching 'name'. If none is found, - // returns idle instead. - CSkeletonAnim* GetRandomAnimation(const CStr& name); - void ReloadObject(); friend class CUnitAnimation; diff --git a/source/graphics/UnitAnimation.cpp b/source/graphics/UnitAnimation.cpp index aa0f6f4573..a25c57a461 100644 --- a/source/graphics/UnitAnimation.cpp +++ b/source/graphics/UnitAnimation.cpp @@ -20,6 +20,7 @@ #include "UnitAnimation.h" #include "graphics/Model.h" +#include "graphics/ObjectEntry.h" #include "graphics/SkeletonAnim.h" #include "graphics/SkeletonAnimDef.h" #include "graphics/Unit.h" @@ -40,8 +41,43 @@ static float DesyncSpeed(float speed, float desync) } CUnitAnimation::CUnitAnimation(CUnit& unit) -: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(1.f), m_OriginalSpeed(1.f), m_Desync(0.f) +: m_Unit(unit), m_State("idle"), m_Looping(true), m_Speed(1.f), m_SyncRepeatTime(0.f), m_OriginalSpeed(1.f), m_Desync(0.f) { + ReloadUnit(); +} + +void CUnitAnimation::AddModel(CModel* model, const CObjectEntry* object) +{ + SModelAnimState state; + + state.anims = object->GetAnimations(m_State); + + if (state.anims.empty()) + state.anims = object->GetAnimations("idle"); + debug_assert(!state.anims.empty()); // there must always be an idle animation + + state.model = model; + state.animIdx = rand(0, state.anims.size()); + state.time = 0.f; + state.pastLoadPos = false; + state.pastActionPos = false; + + m_AnimStates.push_back(state); + + model->SetAnimation(state.anims[state.animIdx], !m_Looping); + + // Recursively add all props + const std::vector& props = model->GetProps(); + for (std::vector::const_iterator it = props.begin(); it != props.end(); ++it) + { + AddModel(it->m_Model, it->m_ObjectEntry); + } +} + +void CUnitAnimation::ReloadUnit() +{ + m_AnimStates.clear(); + AddModel(&m_Unit.GetModel(), &m_Unit.GetObject()); } void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, float desync, bool keepSelection, const CStrW& actionSound) @@ -56,96 +92,135 @@ void CUnitAnimation::SetAnimationState(const CStr& name, bool once, float speed, m_ActionSound = actionSound; m_Speed = DesyncSpeed(m_OriginalSpeed, m_Desync); + m_SyncRepeatTime = 0.f; if (! keepSelection) m_Unit.SetEntitySelection(name); - m_Unit.SetRandomAnimation(m_State, !m_Looping); + ReloadUnit(); } void CUnitAnimation::SetAnimationSync(float actionTime, float repeatTime) { - CModel& model = m_Unit.GetModel(); + m_SyncRepeatTime = repeatTime; - if (!model.m_Anim || !model.m_Anim->m_AnimDef) - return; + // Update all the synced prop models to each coincide with actionTime + for (std::vector::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it) + { + CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef; + if (animDef == NULL) + continue; // ignore static animations - float duration = model.m_Anim->m_AnimDef->GetDuration(); + float duration = animDef->GetDuration(); - // Set the speed so it loops once in repeatTime - float speed = duration / repeatTime; + float actionPos = it->anims[it->animIdx]->m_ActionPos; + bool hasActionPos = (actionPos != -1.f); - // Compensate for the animation's scale factor - if (model.m_Anim->m_Speed) - speed /= model.m_Anim->m_Speed; + if (!hasActionPos) + continue; - // Need to offset so that start+actionTime*speed = ActionPos - float start = model.m_Anim->m_ActionPos - actionTime*speed; - // Wrap it so that it's within the animation - start = fmodf(start, duration); - if (start < 0) - start += duration; + float speed = duration / repeatTime; - // Make the animation run at the computed timings - model.m_AnimTime = start; - m_Speed = m_OriginalSpeed = speed; - m_Desync = 0.f; + // Need to offset so that start+actionTime*speed = actionPos + float start = actionPos - actionTime*speed; + // Wrap it so that it's within the animation + start = fmodf(start, duration); + if (start < 0) + start += duration; + + it->time = start; + } } void CUnitAnimation::Update(float time) { - CModel& model = m_Unit.GetModel(); - - // Convert from real time to scaled animation time - float advance = time*m_Speed; - - // Check if the current animation will trigger the action events - bool action = false; - bool action2 = false; - model.CheckActionTriggers(advance, action, action2); - - // Choose a new random animation if we're going to loop - if (m_Looping && model.NeedsNewAnim(time)) + // Advance all of the prop models independently + for (std::vector::iterator it = m_AnimStates.begin(); it != m_AnimStates.end(); ++it) { - // Re-desynchronise the animation, to keep the irregular drifting between separate units - m_Speed = DesyncSpeed(m_OriginalSpeed, m_Desync); - advance = time*m_Speed; - // TODO: should subtract time remaining in previous animation from advance, to be a bit more precise + CSkeletonAnimDef* animDef = it->anims[it->animIdx]->m_AnimDef; + if (animDef == NULL) + continue; // ignore static animations - m_Unit.SetRandomAnimation(m_State, false); - // TODO: this really ought to interpolate smoothly into the new animation, - // instead of just cutting off the end of the previous one and jumping - // straight into the new. + float duration = animDef->GetDuration(); - // Check if the start of the new animation will trigger the action events - // (This is additive with the previous CheckActionTriggers) - model.CheckActionTriggers(advance, action, action2); - } + float actionPos = it->anims[it->animIdx]->m_ActionPos; + float loadPos = it->anims[it->animIdx]->m_ActionPos2; + bool hasActionPos = (actionPos != -1.f); + bool hasLoadPos = (loadPos != -1.f); - // TODO: props should get a new random animation once they loop, independent - // of the object they're propped onto, if we ever bother with animated props + // Find the current animation speed + float speed; + if (m_SyncRepeatTime && hasActionPos) + speed = duration / m_SyncRepeatTime; + else + speed = m_Speed * it->anims[it->animIdx]->m_Speed; - // If we're going to advance past the action point in this update, then perform the action - if (action) - { - m_Unit.HideAmmunition(); + // Convert from real time to scaled animation time + float advance = time * speed; - if (!m_ActionSound.empty()) + // If we're going to advance past the load point in this update, then load the ammo + if (hasLoadPos && !it->pastLoadPos && it->time + advance >= loadPos) { - CmpPtr cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); - if (!cmpSoundManager.null()) - cmpSoundManager->PlaySoundGroup(m_ActionSound, m_Unit.GetID()); + it->model->ShowAmmoProp(); + it->pastLoadPos = true; + } + + // If we're going to advance past the action point in this update, then perform the action + if (hasActionPos && !it->pastActionPos && it->time + advance >= actionPos) + { + if (hasLoadPos) + it->model->HideAmmoProp(); + + if (!m_ActionSound.empty()) + { + CmpPtr cmpSoundManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (!cmpSoundManager.null()) + cmpSoundManager->PlaySoundGroup(m_ActionSound, m_Unit.GetID()); + } + + it->pastActionPos = true; + } + + if (it->time + advance < duration) + { + // If we're still within the current animation, then simply update it + it->time += advance; + it->model->UpdateTo(it->time); + } + else if (m_Looping) + { + // If we've finished the current animation and want to loop... + + // Wrap the timer around + it->time = fmod(it->time + advance, duration); + + // If there's a choice of multiple animations, pick a new random one + if (it->anims.size() > 1) + { + size_t newAnimIdx = rand(0, it->anims.size()); + if (newAnimIdx != it->animIdx) + { + it->animIdx = newAnimIdx; + it->model->SetAnimation(it->anims[it->animIdx], !m_Looping); + } + } + + it->pastActionPos = false; + it->pastLoadPos = false; + + it->model->UpdateTo(it->time); + } + else + { + // If we've finished the current animation and don't want to loop... + + // Update to very nearly the end of the last frame (but not quite the end else we'll wrap around when skinning) + float nearlyEnd = duration - 1.f; + if (fabs(it->time - nearlyEnd) > 1.f) + { + it->time = nearlyEnd; + it->model->UpdateTo(it->time); + } } } - - // Ditto for the second action point - if (action2) - { - m_Unit.ShowAmmunition(); - } - - // TODO: some animations (e.g. wood chopping) have two action points that should trigger sounds, - // so we ought to support that somehow - - model.Update(advance); } diff --git a/source/graphics/UnitAnimation.h b/source/graphics/UnitAnimation.h index 7a13b02a24..2ff29a1a7b 100644 --- a/source/graphics/UnitAnimation.h +++ b/source/graphics/UnitAnimation.h @@ -21,6 +21,9 @@ #include "ps/CStr.h" class CUnit; +class CModel; +class CSkeletonAnim; +class CObjectEntry; /** * Deals with synchronisation issues between raw animation data (CModel, CSkeletonAnim) @@ -69,12 +72,33 @@ public: */ void Update(float time); + /** + * Regenerate internal animation state from the models in the current unit. + * This should be called whenever the unit is changed externally, to keep this in sync. + */ + void ReloadUnit(); + private: + struct SModelAnimState + { + CModel* model; + std::vector anims; + size_t animIdx; + float time; + bool pastLoadPos; + bool pastActionPos; + }; + + std::vector m_AnimStates; + + void AddModel(CModel* model, const CObjectEntry* object); + CUnit& m_Unit; CStr m_State; bool m_Looping; float m_OriginalSpeed; float m_Speed; + float m_SyncRepeatTime; float m_Desync; CStrW m_ActionSound; }; diff --git a/source/graphics/UnitManager.cpp b/source/graphics/UnitManager.cpp index 3de155fd0f..f3585b6e39 100644 --- a/source/graphics/UnitManager.cpp +++ b/source/graphics/UnitManager.cpp @@ -124,12 +124,12 @@ CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir, boo /////////////////////////////////////////////////////////////////////////////// // CreateUnit: create a new unit and add it to the world -CUnit* CUnitManager::CreateUnit(const CStrW& actorName, CEntity* entity, const std::set& selections) +CUnit* CUnitManager::CreateUnit(const CStrW& actorName, const std::set& selections) { if (! m_ObjectManager) return NULL; - CUnit* unit = CUnit::Create(actorName, entity, selections, *m_ObjectManager); + CUnit* unit = CUnit::Create(actorName, selections, *m_ObjectManager); if (unit) AddUnit(unit); return unit; diff --git a/source/graphics/UnitManager.h b/source/graphics/UnitManager.h index b0e4f15662..bfd62643b4 100644 --- a/source/graphics/UnitManager.h +++ b/source/graphics/UnitManager.h @@ -27,7 +27,6 @@ class CUnit; class CVector3D; -class CEntity; class CObjectManager; class CStr8; class CStrW; @@ -51,7 +50,7 @@ public: void DeleteAll(); // creates a new unit and adds it to the world - CUnit* CreateUnit(const CStrW& actorName, CEntity* entity, const std::set& selections); + CUnit* CreateUnit(const CStrW& actorName, const std::set& selections); // return the units const std::vector& GetUnits() const { return m_Units; } diff --git a/source/ps/GameSetup/Config.cpp b/source/ps/GameSetup/Config.cpp index 485501d761..c988d4462e 100644 --- a/source/ps/GameSetup/Config.cpp +++ b/source/ps/GameSetup/Config.cpp @@ -49,6 +49,7 @@ int g_xres, g_yres; bool g_VSync = false; bool g_Quickstart = false; +bool g_DisableAudio = false; // flag to switch on drawing terrain overlays bool g_ShowPathfindingOverlay = false; @@ -150,7 +151,13 @@ static void ProcessCommandLineArgs(const CmdLineArgs& args) g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = args.Get("profile"); if (args.Has("quickstart")) + { g_Quickstart = true; + g_DisableAudio = true; // do this for backward-compatibility with user expectations + } + + if (args.Has("nosound")) + g_DisableAudio = true; if (args.Has("shadows")) g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String = "true"; @@ -163,12 +170,6 @@ static void ProcessCommandLineArgs(const CmdLineArgs& args) if (args.Has("vsync")) g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String = "true"; - - if (args.Has("showOverlay")) - g_ShowPathfindingOverlay = true; - - if (args.Has("triPathfind")) - g_TriPathfind = true; } diff --git a/source/ps/GameSetup/Config.h b/source/ps/GameSetup/Config.h index 88b8d8e302..87c0be5cd3 100644 --- a/source/ps/GameSetup/Config.h +++ b/source/ps/GameSetup/Config.h @@ -60,14 +60,11 @@ extern int g_xres, g_yres; extern bool g_VSync; extern bool g_Quickstart; +extern bool g_DisableAudio; extern CStrW g_CursorName; class CmdLineArgs; extern void CONFIG_Init(const CmdLineArgs& args); -extern bool g_ShowPathfindingOverlay; - -extern bool g_TriPathfind; - #endif // INCLUDED_PS_GAMESETUP_CONFIG diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 0a1a3d64bd..f3862f3025 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -765,7 +765,8 @@ void Init(const CmdLineArgs& args, int flags) // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } - else + + if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). diff --git a/source/renderer/Scene.cpp b/source/renderer/Scene.cpp index 07c4fb3783..a1c88a238b 100644 --- a/source/renderer/Scene.cpp +++ b/source/renderer/Scene.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -15,7 +15,7 @@ * along with 0 A.D. If not, see . */ -/** +/* * File : Scene.cpp * Project : graphics * Description : This file contains default implementations and utilities @@ -23,7 +23,7 @@ * : classes. * * @note This file would fit just as well into the graphics/ subdirectory. - **/ + */ #include "precompiled.h" @@ -31,8 +31,6 @@ #include "renderer/Scene.h" - - /////////////////////////////////////////////////////////// // Default implementation traverses the model recursively and uses // SubmitNonRecursive for the actual work. @@ -41,9 +39,9 @@ void SceneCollector::SubmitRecursive(CModel* model) SubmitNonRecursive(model); const std::vector& props = model->GetProps(); - for (size_t i=0;i selections; Projectile projectile; - projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, NULL, selections); + projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, selections); if (!projectile.unit) { // The error will have already been logged return; } - CmpPtr sourcePos(GetSimContext(), source); - if (sourcePos.null()) - return; + CVector3D sourceVec(sourceVisual->GetProjectileLaunchPoint()); + if (!sourceVec) + { + // If there's no explicit launch point, take a guess based on the entity position - CVector3D sourceVec(sourcePos->GetPosition()); - sourceVec.Y += 3.f; // TODO: ought to exactly match the appropriate prop point + CmpPtr sourcePos(GetSimContext(), source); + if (sourcePos.null()) + return; + + CVector3D sourceVec(sourcePos->GetPosition()); + sourceVec.Y += 3.f; + } CVector3D targetVec; diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index 9d640e255a..bdb1f4fcfe 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -389,7 +389,13 @@ void CCmpTemplateManager::CopyPreviewSubset(CParamNode& out, const CParamNode& i permittedComponentTypes.insert("VisualActor"); permittedComponentTypes.insert("Footprint"); permittedComponentTypes.insert("Obstruction"); - // (This could be initialised once and reused, but it's not worth the effort) + + // Need these for the Actor Viewer: + permittedComponentTypes.insert("Attack"); + permittedComponentTypes.insert("UnitMotion"); + permittedComponentTypes.insert("Sound"); + + // (This set could be initialised once and reused, but it's not worth the effort) CParamNode::LoadXMLString(out, ""); out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes); diff --git a/source/simulation2/components/CCmpUnitMotion.cpp b/source/simulation2/components/CCmpUnitMotion.cpp index a591e9b4ea..e6eea80cd9 100644 --- a/source/simulation2/components/CCmpUnitMotion.cpp +++ b/source/simulation2/components/CCmpUnitMotion.cpp @@ -59,7 +59,8 @@ public: // Template state: - fixed m_Speed; // in units per second + fixed m_Speed; // in metres per second + fixed m_RunSpeed; entity_pos_t m_Radius; u8 m_PassClass; u8 m_CostClass; @@ -122,6 +123,15 @@ public: m_Speed = paramNode.GetChild("WalkSpeed").ToFixed(); + if (paramNode.GetChild("Run").IsOk()) + { + m_RunSpeed = paramNode.GetChild("Run").GetChild("Speed").ToFixed(); + } + else + { + m_RunSpeed = m_Speed; + } + CmpPtr cmpObstruction(context, GetEntityId()); if (!cmpObstruction.null()) m_Radius = cmpObstruction->GetUnitRadius(); @@ -197,6 +207,11 @@ public: return m_Speed; } + virtual fixed GetRunSpeed() + { + return m_RunSpeed; + } + virtual void SetDebugOverlay(bool enabled) { m_DebugOverlayEnabled = enabled; diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 4b923ae0e9..f28ccf80e3 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -103,7 +103,7 @@ public: m_ActorName = paramNode.GetChild("Actor").ToString(); std::set selections; - m_Unit = context.GetUnitManager().CreateUnit(m_ActorName, NULL, selections); + m_Unit = context.GetUnitManager().CreateUnit(m_ActorName, selections); if (!m_Unit) { // The error will have already been logged @@ -204,6 +204,16 @@ public: return m_Unit->GetObject().m_ProjectileModelName; } + virtual CVector3D GetProjectileLaunchPoint() + { + if (!m_Unit) + return CVector3D(); + CModel* ammo = m_Unit->GetModel().FindFirstAmmoProp(); + if (!ammo) + return CVector3D(); + return ammo->GetTransform().GetTranslation(); + } + virtual void SelectAnimation(std::string name, bool once, float speed, std::wstring soundgroup) { if (!m_Unit) @@ -249,7 +259,7 @@ public: return; std::set selections; - CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, NULL, selections); + CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, selections); if (!newUnit) return; diff --git a/source/simulation2/components/ICmpUnitMotion.h b/source/simulation2/components/ICmpUnitMotion.h index c361c96c67..0987f20563 100644 --- a/source/simulation2/components/ICmpUnitMotion.h +++ b/source/simulation2/components/ICmpUnitMotion.h @@ -65,6 +65,11 @@ public: */ virtual fixed GetSpeed() = 0; + /** + * Get the default speed that this unit will have when running, in metres per second. + */ + virtual fixed GetRunSpeed() = 0; + /** * Toggle the rendering of debug info. */ diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index e493906fc4..379374c0f1 100644 --- a/source/simulation2/components/ICmpVisual.h +++ b/source/simulation2/components/ICmpVisual.h @@ -53,6 +53,13 @@ public: */ virtual std::wstring GetProjectileActor() = 0; + /** + * Return the exact position where a projectile should be launched from (based on the actor's + * ammo prop points). + * Returns (0,0,0) if no point can be found. + */ + virtual CVector3D GetProjectileLaunchPoint() = 0; + /** * Start playing the given animation. If there are multiple possible animations then it will * pick one at random (not network-synchronised). diff --git a/source/sound/SoundGroup.cpp b/source/sound/SoundGroup.cpp index 0cc3ab8c2f..8fe8ee8944 100644 --- a/source/sound/SoundGroup.cpp +++ b/source/sound/SoundGroup.cpp @@ -35,6 +35,7 @@ #define LOG_CATEGORY L"audio" +static const bool DISABLE_INTENSITY = true; // disable for now since it's broken void CSoundGroup::SetGain(float gain) { @@ -124,7 +125,7 @@ static void HandleError(const std::wstring& message, const VfsPath& pathname, Li void CSoundGroup::PlayNext(const CVector3D& position) { - if(m_Intensity >= m_IntensityThreshold) + if(m_Intensity >= m_IntensityThreshold && !DISABLE_INTENSITY) { if(!is_playing(m_hReplacement)) { diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp index 6d91c316bb..d9a81ec656 100644 --- a/source/tools/atlas/GameInterface/ActorViewer.cpp +++ b/source/tools/atlas/GameInterface/ActorViewer.cpp @@ -37,8 +37,10 @@ #include "renderer/Renderer.h" #include "renderer/Scene.h" #include "renderer/SkyManager.h" +#include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpPosition.h" +#include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/components/ICmpVisual.h" struct ActorViewerImpl : public Scene @@ -178,20 +180,37 @@ void ActorViewer::SetActor(const CStrW& name, const CStrW& animation) { CStr anim = CStr(animation).LowerCase(); + // Emulate the typical simulation animation behaviour float speed; - // TODO: this is just copied from template_unit.xml and isn't the - // same for all units. We ought to get it from the entity definition - // (if there is one) + float repeattime = 0.f; if (anim == "walk") { - speed = 7.f; + CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); + if (!cmpUnitMotion.null()) + speed = cmpUnitMotion->GetSpeed().ToFloat(); + else + speed = 7.f; // typical unit speed + m.CurrentSpeed = speed; } else if (anim == "run") { - speed = 12.f; + CmpPtr cmpUnitMotion(m.Simulation2, m.Entity); + if (!cmpUnitMotion.null()) + speed = cmpUnitMotion->GetRunSpeed().ToFloat(); + else + speed = 12.f; // typical unit speed + m.CurrentSpeed = speed; } + else if (anim == "melee") + { + speed = 1.f; // speed will be ignored if we have a repeattime + m.CurrentSpeed = 0.f; + + CStr code = "var cmp = Engine.QueryInterface("+CStr(m.Entity)+", IID_Attack); if (cmp) cmp.GetTimers(cmp.GetBestAttack()).repeat; else 0;"; + m.Simulation2.GetScriptInterface().Eval(code.c_str(), repeattime); + } else { // Play the animation at normal speed, but movement speed is zero @@ -199,12 +218,28 @@ void ActorViewer::SetActor(const CStrW& name, const CStrW& animation) m.CurrentSpeed = 0.f; } + CStr sound; + if (anim == "melee") + sound = "attack"; + else if (anim == "build") + sound = "build"; + else if (anim.Find("gather_") == 0) + sound = anim; + + std::wstring soundgroup; + if (!sound.empty()) + { + CStr code = "var cmp = Engine.QueryInterface("+CStr(m.Entity)+", IID_Sound); if (cmp) cmp.GetSoundGroup('"+sound+"'); else '';"; + m.Simulation2.GetScriptInterface().Eval(code.c_str(), soundgroup); + } + CmpPtr cmpVisual(m.Simulation2, m.Entity); if (!cmpVisual.null()) { // TODO: SetEntitySelection(anim) - // TODO: maybe we could play soundgroups from entities in here, to help test timings? - cmpVisual->SelectAnimation(anim, false, speed, L""); + cmpVisual->SelectAnimation(anim, false, speed, soundgroup); + if (repeattime) + cmpVisual->SetAnimationSync(0.f, repeattime); } }