# Unit animation improvements.
Animate props much more sensibly. Move ammo code out of CUnit. Move animation logic out of CModel. Launch projectiles from the correct location. Use entity's speeds and sounds in actor viewer. Add -nosound option to disable audio, and allow audio by default in Atlas. Remove some obsolete options. This was SVN commit r7609.
This commit is contained in:
parent
a18aa24fe3
commit
d3048906cb
@ -9,5 +9,5 @@
|
||||
<Anchor>upright</Anchor>
|
||||
<Floating>false</Floating>
|
||||
</Position>
|
||||
<UnitMotion/>
|
||||
<UnitAI/>
|
||||
</Entity>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<Anchor>upright</Anchor>
|
||||
<Floating>false</Floating>
|
||||
</Position>
|
||||
<UnitMotion/>
|
||||
<UnitAI/>
|
||||
<Footprint>
|
||||
<Circle radius="4"/>
|
||||
<Height>1.0</Height>
|
||||
|
@ -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";
|
||||
|
@ -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;i<m_Props.size();i++) {
|
||||
|
||||
for (size_t i = 0; i < m_Props.size(); i++)
|
||||
{
|
||||
// eek! TODO, RC - need to investigate shallow clone here
|
||||
clone->AddProp(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<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it )
|
||||
{
|
||||
for (std::vector<Prop>::iterator it = m_Props.begin(); it != m_Props.end(); ++it)
|
||||
it->m_Model->SetShadingColor(colour);
|
||||
}
|
||||
}
|
||||
|
@ -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<Prop>& GetProps() { return m_Props; }
|
||||
const std::vector<Prop>& 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<Prop> 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.
|
||||
*/
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 <sstream>
|
||||
|
||||
#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<std::set<CStr> >& 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<std::set<CStr> >& 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<std::set<CStr> >& 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<std::set<CStr> >& 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-<proppoint>'
|
||||
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-<proppoint>'
|
||||
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<std::set<CStr> >& 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<CSkeletonAnim*> CObjectEntry::GetAnimations(const CStr& animationName) const
|
||||
{
|
||||
std::vector<CSkeletonAnim*> 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;
|
||||
}
|
||||
|
@ -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<CSkeletonAnim*> GetAnimations(const CStr& animationName) const;
|
||||
|
||||
// corresponding model
|
||||
CModel* m_Model;
|
||||
|
@ -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;
|
||||
|
@ -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<CStr>& 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<CStr>& selections, CObjectManager& objectManager)
|
||||
CUnit* CUnit::Create(const CStrW& actorName, const std::set<CStr>& 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<CModel::Prop>& props = m_Object->m_Model->GetProps();
|
||||
std::vector<CModel::Prop>::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<CModel::Prop>& props = model->GetProps();
|
||||
for (std::vector<CModel::Prop>::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?
|
||||
}
|
||||
}
|
||||
|
@ -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<CStr>& 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<CStr>& selections, CObjectManager& objectManager);
|
||||
static CUnit* Create(const CStrW& actorName, const std::set<CStr>& 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;
|
||||
|
@ -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<CModel::Prop>& props = model->GetProps();
|
||||
for (std::vector<CModel::Prop>::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<SModelAnimState>::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<SModelAnimState>::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<ICmpSoundManager> 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<ICmpSoundManager> 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);
|
||||
}
|
||||
|
@ -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<CSkeletonAnim*> anims;
|
||||
size_t animIdx;
|
||||
float time;
|
||||
bool pastLoadPos;
|
||||
bool pastActionPos;
|
||||
};
|
||||
|
||||
std::vector<SModelAnimState> 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;
|
||||
};
|
||||
|
@ -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<CStr>& selections)
|
||||
CUnit* CUnitManager::CreateUnit(const CStrW& actorName, const std::set<CStr>& 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;
|
||||
|
@ -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<CStr8>& selections);
|
||||
CUnit* CreateUnit(const CStrW& actorName, const std::set<CStr8>& selections);
|
||||
|
||||
// return the units
|
||||
const std::vector<CUnit*>& GetUnits() const { return m_Units; }
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
/*
|
||||
* 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<CModel::Prop>& props = model->GetProps();
|
||||
for (size_t i=0;i<props.size();i++) {
|
||||
SubmitRecursive(props[i].m_Model);
|
||||
for (size_t i = 0; i < props.size(); i++)
|
||||
{
|
||||
if (!props[i].m_Hidden)
|
||||
SubmitRecursive(props[i].m_Model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -143,19 +143,25 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D
|
||||
std::set<CStr> 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<ICmpPosition> 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<ICmpPosition> sourcePos(GetSimContext(), source);
|
||||
if (sourcePos.null())
|
||||
return;
|
||||
|
||||
CVector3D sourceVec(sourcePos->GetPosition());
|
||||
sourceVec.Y += 3.f;
|
||||
}
|
||||
|
||||
CVector3D targetVec;
|
||||
|
||||
|
@ -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, "<Entity/>");
|
||||
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
|
||||
|
@ -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<ICmpObstruction> 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;
|
||||
|
@ -103,7 +103,7 @@ public:
|
||||
m_ActorName = paramNode.GetChild("Actor").ToString();
|
||||
|
||||
std::set<CStr> 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<CStr> selections;
|
||||
CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, NULL, selections);
|
||||
CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, selections);
|
||||
|
||||
if (!newUnit)
|
||||
return;
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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).
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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<ICmpUnitMotion> 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<ICmpUnitMotion> 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<ICmpVisual> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user