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