1
0
forked from 0ad/0ad

# 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:
Ykkrosh 2010-06-05 00:49:14 +00:00
parent a18aa24fe3
commit d3048906cb
27 changed files with 489 additions and 413 deletions

View File

@ -9,5 +9,5 @@
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<UnitMotion/>
<UnitAI/>
</Entity>

View File

@ -9,7 +9,7 @@
<Anchor>upright</Anchor>
<Floating>false</Floating>
</Position>
<UnitMotion/>
<UnitAI/>
<Footprint>
<Circle radius="4"/>
<Height>1.0</Height>

View File

@ -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";

View File

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

View File

@ -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.
*/

View File

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

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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?
}
}

View File

@ -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;

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -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

View File

@ -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).

View File

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

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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;

View File

@ -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.
*/

View File

@ -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).

View File

@ -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))
{

View File

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