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); } }