Refactored actor variation system, and added support for entity-level selections (controlled by the current animation).

Avoided tooltip error message.
Avoided noisy warnings when textures fail to load.

This was SVN commit r3653.
This commit is contained in:
Ykkrosh 2006-03-17 03:59:49 +00:00
parent 6eda8c2209
commit d3f57744d9
27 changed files with 499 additions and 437 deletions

View File

@ -205,7 +205,8 @@ int CMapReader::ApplyData()
// loaded on demand)
}
CUnit* unit = g_UnitMan.CreateUnit(m_ObjectTypes.at(m_Objects[i].m_ObjectIndex), NULL);
std::set<CStrW> selections; // TODO: read from file
CUnit* unit = g_UnitMan.CreateUnit(m_ObjectTypes.at(m_Objects[i].m_ObjectIndex), NULL, selections);
if (unit)
{
@ -428,7 +429,9 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
LOG(ERROR, LOG_CATEGORY, "Failed to load entity template '%ls'", TemplateName.c_str());
else
{
HEntity ent = g_EntityManager.create(base, Position, Orientation);
std::set<CStrW> selections; // TODO: read from file
HEntity ent = g_EntityManager.create(base, Position, Orientation, selections);
if (! ent)
LOG(ERROR, LOG_CATEGORY, "Failed to create entity of type '%ls'", TemplateName.c_str());
@ -492,7 +495,9 @@ int CXMLReader::ReadNonEntities(XMBElement parent, double end_time)
debug_warn("Invalid XML data - DTD shouldn't allow this");
}
CUnit* unit = g_UnitMan.CreateUnit(ActorName, NULL);
std::set<CStrW> selections; // TODO: read from file
CUnit* unit = g_UnitMan.CreateUnit(ActorName, NULL, selections);
if (unit)
{

View File

@ -133,7 +133,7 @@ static CVector3D SkinPoint(const CVector3D& pos,const SVertexBlend& blend,
void CModel::CalcBounds()
{
// Need to calculate the object bounds first, if that hasn't already been done
if (! m_Anim)
if (! (m_Anim && m_Anim->m_AnimDef))
CalcObjectBounds();
else
{
@ -238,7 +238,7 @@ CSkeletonAnim* CModel::BuildAnimation(const char* filename, const char* name, fl
// Update: update this model by the given time, in seconds
void CModel::Update(float time)
{
if (m_Anim && m_BoneMatrices) {
if (m_Anim && m_Anim->m_AnimDef && m_BoneMatrices) {
// adjust for animation speed
float animtime=time*m_AnimSpeed;
@ -251,13 +251,10 @@ void CModel::Update(float time)
float duration=m_Anim->m_AnimDef->GetDuration();
if (m_AnimTime > duration) {
if( m_Flags & MODELFLAG_NOLOOPANIMATION )
{
SetAnimation( m_NextAnim );
}
if (m_Flags & MODELFLAG_NOLOOPANIMATION)
SetAnimation(m_NextAnim);
else
m_AnimTime=(float) fmod(m_AnimTime,duration);
m_AnimTime = (float) fmod(m_AnimTime, duration);
}
// mark vertices as dirty
@ -366,18 +363,25 @@ bool CModel::SetAnimation(CSkeletonAnim* anim, bool once, float speed, CSkeleton
if (anim) {
m_Flags &= ~MODELFLAG_NOLOOPANIMATION;
if (once)
{
m_Flags |= MODELFLAG_NOLOOPANIMATION;
m_NextAnim = next;
}
if (!m_BoneMatrices) {
if (!m_BoneMatrices && anim->m_AnimDef) {
// not boned, can't animate
return false;
}
if (anim->m_AnimDef->GetNumKeys() != m_pModelDef->GetNumBones()) {
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()) {
// mismatch between model's skeleton and animation's skeleton
LOG(ERROR, LOG_CATEGORY, "Mismatch between model's skeleton and animation's skeleton (%d model bones != %d animation keys)",
m_pModelDef->GetNumBones(), anim->m_AnimDef->GetNumKeys());

View File

@ -55,7 +55,8 @@ public:
void SetTexture(const CTexture& tex) { m_Texture=tex; }
// set the model's material
void SetMaterial(const CMaterial &material);
// set the model's player ID, recursively through props
// set the model's player ID, recursively through props. CUnit::SetPlayerID
// should normally be used instead.
void SetPlayerID(int id);
// set the model's player colour
void SetPlayerColor(CColor& colour);
@ -69,7 +70,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, float speed = 1000.0f, CSkeletonAnim* next = NULL );
bool SetAnimation(CSkeletonAnim* anim, bool once = false, float speed = 1000.0f, CSkeletonAnim* next = NULL);
// get the currently playing animation, if any
CSkeletonAnim* GetAnimation() { return m_Anim; }

View File

@ -209,17 +209,19 @@ bool CObjectBase::Load(const char* filename)
return true;
}
void CObjectBase::CalculateVariation(std::set<CStr>& strings, variation_key& choices)
std::vector<u8> CObjectBase::CalculateVariationKey(const std::vector<std::set<CStrW> >& selections)
{
// Calculate a complete list of choices, one per group. In each group,
// if one of the variants has a name matching a string in 'strings', use
// that one. If more than one matches, choose randomly from those matching
// ones. If none match, choose randomly from all variants.
//
// When choosing randomly, make use of each variant's frequency. If all
// variants have frequency 0, treat them as if they were 1.
// Calculate a complete list of choices, one per group, based on the
// supposedly-complete selections (i.e. not making random choices at this
// stage).
// In each group, if one of the variants has a name matching a string in the
// first 'selections', set use that one.
// Otherwise, try with the next (lower priority) selections set, and repeat.
// Otherwise, choose the first variant (arbitrarily).
choices.clear();
std::vector<u8> choices;
std::map<CStr, CStr> chosenProps;
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_Variants.begin();
grp != m_Variants.end();
@ -230,103 +232,175 @@ void CObjectBase::CalculateVariation(std::set<CStr>& strings, variation_key& cho
if (grp->size() == 0)
continue;
int match = -1; // -1 => none found yet
// If there's only a single variant, choose that one
if (grp->size() == 1)
{
choices.push_back(0);
continue;
match = 0;
}
// Determine the variants that match the provided strings:
std::vector<u8> matches;
typedef std::vector<u8>::const_iterator Iter;
debug_assert(grp->size() < 256); // else they won't fit in the vector
for (uint i = 0; i < grp->size(); ++i)
if (strings.count((*grp)[i].m_VariantName))
matches.push_back((u8)i); // "protected" by debug_assert
// If there's only one match, choose that one
if (matches.size() == 1)
else
{
choices.push_back(matches[0]);
continue;
}
// Otherwise, choose randomly from the others.
// Determine the first variant that matches the provided strings,
// starting with the highest priority selections set:
// If none matched the specified strings, choose from all the variants
if (matches.size() == 0)
for (uint i = 0; i < grp->size(); ++i)
matches.push_back((u8)i); // "protected" by debug_assert
// Sum the frequencies:
int totalFreq = 0;
for (Iter it = matches.begin(); it != matches.end(); ++it)
totalFreq += (*grp)[*it].m_Frequency;
// Someone might be silly and set all variants to have freq==0, in
// which case we just pretend they're all 1
bool allZero = false;
if (totalFreq == 0)
{
totalFreq = (int)matches.size();
allZero = true;
}
// Choose a random number in the interval [0..totalFreq).
// (It shouldn't be necessary to use a network-synchronised RNG,
// since actors are meant to have purely visual manifestations.)
int randNum = rand(0, totalFreq);
// and use that to choose one of the variants
for (Iter it = matches.begin(); it != matches.end(); ++it)
{
randNum -= (allZero ? 1 : (*grp)[*it].m_Frequency);
if (randNum < 0)
for (std::vector<std::set<CStrW> >::const_iterator selset = selections.begin(); selset < selections.end(); ++selset)
{
choices.push_back(*it);
break;
debug_assert(grp->size() < 256); // else they won't fit in 'choices'
for (size_t i = 0; i < grp->size(); ++i)
{
if (selset->count((*grp)[i].m_VariantName))
{
match = (u8)i;
break;
}
}
// Stop after finding the first match
if (match != -1)
break;
}
// If no match, just choose the first
if (match == -1)
match = 0;
}
debug_assert(randNum < 0);
// This should always happen; otherwise it
// wouldn't have chosen any of the variants.
}
choices.push_back(match);
debug_assert(choices.size() == m_Variants.size());
// Also, make choices for all props:
// Work out which props have been chosen
std::map<CStr, CStr> chosenProps;
CObjectBase::variation_key::const_iterator choice_it = choices.begin();
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_Variants.begin();
grp != m_Variants.end();
++grp)
{
CObjectBase::Variant& var (grp->at(*(choice_it++)));
// Remember which props were chosen. (Later-defined props override
// earlier props at the same prop point.)
CObjectBase::Variant& var ((*grp)[match]);
for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
{
chosenProps[it->m_PropPointName] = it->m_ModelName;
}
}
// Load each prop, and call CalculateVariation on them:
// Load each prop, and add their CalculateVariationKey to our key:
for (std::map<CStr, CStr>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
{
CObjectBase* prop = g_ObjMan.FindObjectBase(it->second);
if (prop)
{
variation_key propChoices;
prop->CalculateVariation(strings, propChoices);
std::vector<u8> propChoices = prop->CalculateVariationKey(selections);
choices.insert(choices.end(), propChoices.begin(), propChoices.end());
}
}
// (TODO: This seems rather fragile, e.g. if props fail to load)
return choices;
}
std::set<CStrW> CObjectBase::CalculateRandomVariation(const std::set<CStrW>& initialSelections)
{
std::set<CStrW> selections = initialSelections;
std::map<CStr, CStr> chosenProps;
// Calculate a complete list of selections, so there is at least one
// (and in most cases only one) per group.
// In each group, if one of the variants has a name matching a string in
// 'selections', use that one.
// If more than one matches, choose randomly from those matching ones.
// If none match, choose randomly from all variants.
//
// When choosing randomly, make use of each variant's frequency. If all
// variants have frequency 0, treat them as if they were 1.
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_Variants.begin();
grp != m_Variants.end();
++grp)
{
// Ignore groups with nothing inside. (A warning will have been
// emitted by the loading code.)
if (grp->size() == 0)
continue;
int match = -1; // -1 => none found yet
// If there's only a single variant, choose that one
if (grp->size() == 1)
{
match = 0;
}
else
{
// See if a variant (or several, but we only care about the first)
// is already matched by the selections we've made
for (size_t i = 0; i < grp->size(); ++i)
{
if (selections.count((*grp)[i].m_VariantName))
{
match = (int)i;
break;
}
}
// If there was one, we don't need to do anything now because there's
// already something to choose. Otherwise, choose randomly from the others.
if (match == -1)
{
// Sum the frequencies
int totalFreq = 0;
for (size_t i = 0; i < grp->size(); ++i)
totalFreq += (*grp)[i].m_Frequency;
// Someone might be silly and set all variants to have freq==0, in
// which case we just pretend they're all 1
bool allZero = (totalFreq == 0);
if (allZero) totalFreq = (int)grp->size();
// Choose a random number in the interval [0..totalFreq).
// (It shouldn't be necessary to use a network-synchronised RNG,
// since actors are meant to have purely visual manifestations.)
int randNum = rand(0, totalFreq);
// and use that to choose one of the variants
for (size_t i = 0; i < grp->size(); ++i)
{
randNum -= (allZero ? 1 : (*grp)[i].m_Frequency);
if (randNum < 0)
{
selections.insert((*grp)[i].m_VariantName);
// (If this change to 'selections' interferes with earlier
// choices, then we'll get some non-fatal inconsistencies
// that just break the randomness. But that shouldn't
// happen, much.)
match = (int)i;
break;
}
}
debug_assert(randNum < 0);
// This should always succeed; otherwise it
// wouldn't have chosen any of the variants.
}
}
// Remember which props were chosen. (Later-defined props override
// earlier props at the same prop point.)
CObjectBase::Variant& var ((*grp)[match]);
for (std::vector<CObjectBase::Prop>::iterator it = var.m_Props.begin(); it != var.m_Props.end(); ++it)
{
chosenProps[it->m_PropPointName] = it->m_ModelName;
}
}
// Load each prop, and add their required selections to ours:
for (std::map<CStr, CStr>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
{
CObjectBase* prop = g_ObjMan.FindObjectBase(it->second);
if (prop)
{
std::set<CStrW> propSelections = prop->CalculateRandomVariation(selections);
std::set<CStrW> newSelections;
std::set_union(propSelections.begin(), propSelections.end(),
selections.begin(), selections.end(),
std::inserter(newSelections, newSelections.begin()));
selections.swap(newSelections);
}
}
return selections;
}

View File

@ -52,9 +52,9 @@ public:
std::vector< std::vector<Variant> > m_Variants;
typedef std::vector<u8> variation_key;
std::vector<u8> CalculateVariationKey(const std::vector<std::set<CStrW> >& selections);
void CalculateVariation(std::set<CStr>& strings, variation_key& variation);
std::set<CStrW> CalculateRandomVariation(const std::set<CStrW>& initialSelections);
bool Load(const char* filename);

View File

@ -36,47 +36,61 @@ CObjectEntry::~CObjectEntry()
delete m_Model;
}
bool CObjectEntry::BuildRandomVariant(const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it)
{
// vars_it is passed by reference so that the caller's iterator
// can be incremented by the appropriate amount, to point to the
// next object's set of variant choices (for propped models).
bool CObjectEntry::BuildVariation(const std::vector<std::set<CStrW> >& selections)
{
CStr chosenTexture;
CStr chosenModel;
CStr chosenColor;
std::map<CStr, CObjectBase::Prop> chosenProps;
std::multimap<CStr, CObjectBase::Anim> chosenAnims;
// For each group in m_Base->m_Variants, take whichever variant is specified
// by 'vars', and then store its data into the 'chosen' variables. If data
// is specified more than once, the last value overrides all previous ones.
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_Base->m_Variants.begin();
grp != m_Base->m_Variants.end();
++grp)
{
if (vars_it == vars.end())
// Ignore groups with nothing inside. (A warning will have been
// emitted by the loading code.)
if (grp->size() == 0)
continue;
int match = -1; // -1 => none found yet
// If there's only a single variant, choose that one
if (grp->size() == 1)
{
debug_warn("BuildRandomVariant is using too many vars");
return false;
match = 0;
}
else
{
// Determine the first variant that matches the provided strings,
// starting with the highest priority selections set:
for (std::vector<std::set<CStrW> >::const_iterator selset = selections.begin(); selset < selections.end(); ++selset)
{
debug_assert(grp->size() < 256); // else they won't fit in 'choices'
for (size_t i = 0; i < grp->size(); ++i)
{
if (selset->count((*grp)[i].m_VariantName))
{
match = (u8)i;
break;
}
}
// Stop after finding the first match
if (match != -1)
break;
}
// If no match, just choose the first
if (match == -1)
match = 0;
}
// Get the correct variant
u8 var_id = *vars_it++;
if (var_id >= grp->size())
{
LOG(ERROR, LOG_CATEGORY, "Internal error (BuildRandomVariant: %d not in 0..%d)", var_id, grp->size()-1);
// Carry on as best we can, by using some arbitrary variant (rather
// than choosing none, else we might end up with no model or texture)
if (grp->size())
var_id = 0;
else
// ... unless there aren't any variants in this group, in which
// case just give up and try the next group
continue;
}
CObjectBase::Variant& var ((*grp)[var_id]);
// Get the matched variant
CObjectBase::Variant& var ((*grp)[match]);
// Apply its data:
@ -124,12 +138,9 @@ bool CObjectEntry::BuildRandomVariant(const CObjectBase::variation_key& vars, CO
for (std::map<CStr, CObjectBase::Prop>::iterator it = chosenProps.begin(); it != chosenProps.end(); ++it)
props.push_back(it->second);
// TODO: This is all wrong, since it breaks the order (which vars_it relies on)
// Build the model:
// get the root directory of this object
CStr dirname = g_ObjMan.m_ObjectTypes[m_Type].m_Name;
@ -176,9 +187,26 @@ bool CObjectEntry::BuildRandomVariant(const CObjectBase::variation_key& vars, CO
}
}
// start up idling
if (! m_Model->SetAnimation(GetRandomAnimation("idle")))
LOG(ERROR, LOG_CATEGORY, "Failed to set idle animation in model \"%s\"", modelfilename);
// ensure there's always an idle animation
if (m_Animations.find("idle") == m_Animations.end())
{
CSkeletonAnim* anim = new CSkeletonAnim();
anim->m_Name = "idle";
anim->m_AnimDef = NULL;
anim->m_Speed = 0.f;
anim->m_ActionPos = 0.f;
anim->m_ActionPos2 = 0.f;
m_Animations.insert(std::make_pair("idle", anim));
// Ignore errors, since they're probably saying this is a non-animated model
m_Model->SetAnimation(anim);
}
else
{
// start up idling
if (! m_Model->SetAnimation(GetRandomAnimation("idle")))
LOG(ERROR, LOG_CATEGORY, "Failed to set idle animation in model \"%s\"", modelfilename);
}
// build props - TODO, RC - need to fix up bounds here
// TODO: Make sure random variations get handled correctly when a prop fails
@ -186,7 +214,7 @@ bool CObjectEntry::BuildRandomVariant(const CObjectBase::variation_key& vars, CO
{
const CObjectBase::Prop& prop = props[p];
CObjectEntry* oe = g_ObjMan.FindObjectVariation(prop.m_ModelName, vars, vars_it);
CObjectEntry* oe = g_ObjMan.FindObjectVariation(prop.m_ModelName, selections);
if (!oe)
{
LOG(ERROR, LOG_CATEGORY, "Failed to build prop model \"%s\" on actor \"%s\"", (const char*)prop.m_ModelName, (const char*)m_Base->m_ShortName);
@ -265,7 +293,6 @@ bool CObjectEntry::BuildRandomVariant(const CObjectBase::variation_key& vars, CO
return true;
}
CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName)
{
SkeletonAnimMap::iterator lower = m_Animations.lower_bound(animationName);
@ -279,7 +306,7 @@ CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName)
else
{
// TODO: Do we care about network synchronisation of random animations?
int id = rand() % (int)count;
int id = rand(0, (int)count);
std::advance(lower, id);
return lower->second;
}

View File

@ -18,7 +18,7 @@ public:
CObjectEntry(int type, CObjectBase* base);
~CObjectEntry();
bool BuildRandomVariant(const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it);
bool BuildVariation(const std::vector<std::set<CStrW> >& selections);
// Base actor. Contains all the things that don't change between
// different variations of the actor.

View File

@ -15,6 +15,7 @@
#include "Model.h"
#include "Unit.h"
#include "Matrix3D.h"
#include "ps/Profile.h"
#define LOG_CATEGORY "graphics"
@ -77,37 +78,28 @@ CObjectBase* CObjectManager::FindObjectBase(const char* objectname)
CObjectEntry* CObjectManager::FindObject(const char* objname)
{
CObjectBase* base = FindObjectBase(objname);
if (! base)
return NULL;
std::set<CStr> choices;
// TODO: Fill in these choices from somewhere, e.g.:
//choices.insert("whatever");
CObjectBase::variation_key var;
base->CalculateVariation(choices, var);
CObjectBase::variation_key::const_iterator vars_it=var.begin();
return FindObjectVariation(base, var, vars_it);
std::vector<std::set<CStrW> > selections; // TODO - should this really be empty?
return FindObjectVariation(objname, selections);
}
CObjectEntry* CObjectManager::FindObjectVariation(const char* objname, const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it)
CObjectEntry* CObjectManager::FindObjectVariation(const char* objname, const std::vector<std::set<CStrW> >& selections)
{
CObjectBase* base = FindObjectBase(objname);
if (! base)
return NULL;
return FindObjectVariation(base, vars, vars_it);
return FindObjectVariation(base, selections);
}
CObjectEntry* CObjectManager::FindObjectVariation(CObjectBase* base, const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it)
CObjectEntry* CObjectManager::FindObjectVariation(CObjectBase* base, const std::vector<std::set<CStrW> >& selections)
{
PROFILE( "object variation loading" );
// Look to see whether this particular variation has already been loaded
ObjectKey key (base->m_Name, vars);
std::vector<u8> choices = base->CalculateVariationKey(selections);
ObjectKey key (base->m_Name, choices);
std::map<ObjectKey, CObjectEntry*>::iterator it = m_ObjectTypes[0].m_Objects.find(key);
if (it != m_ObjectTypes[0].m_Objects.end())
@ -117,7 +109,7 @@ CObjectEntry* CObjectManager::FindObjectVariation(CObjectBase* base, const CObje
CObjectEntry* obj = new CObjectEntry(0, base); // TODO: type ?
if (! obj->BuildRandomVariant(vars, vars_it))
if (! obj->BuildVariation(selections))
{
DeleteObject(obj);
return NULL;
@ -185,8 +177,6 @@ void CObjectManager::UnloadObjects()
}
//////////////////////////////////////////////////////////////////////////
// For ScEd:
static void GetObjectName_ThunkCb(const char* path, const DirEnt* UNUSED(ent), void* context)
{
@ -194,80 +184,15 @@ static void GetObjectName_ThunkCb(const char* path, const DirEnt* UNUSED(ent), v
CStr name (path);
names->push_back(name.AfterFirst("actors/"));
}
void CObjectManager::GetAllObjectNames(std::vector<CStr>& names)
{
VFSUtil::EnumDirEnts("art/actors/", VFSUtil::RECURSIVE, "*.xml",
GetObjectName_ThunkCb, &names);
}
void CObjectManager::GetPropObjectNames(std::vector<CStr>& names)
{
VFSUtil::EnumDirEnts("art/actors/props/", VFSUtil::RECURSIVE, "*.xml",
GetObjectName_ThunkCb, &names);
}
struct CObjectThing_Entity : public CObjectThing
{
CObjectThing_Entity(CBaseEntity* b) : base(b), ent(NULL), obj(g_ObjMan.FindObject((CStr)b->m_actorName)) {}
~CObjectThing_Entity() {}
CBaseEntity* base;
CEntity* ent;
CObjectEntry* obj;
void Create(CMatrix3D& transform, int playerID)
{
CVector3D orient = transform.GetIn();
CVector3D position = transform.GetTranslation();
ent = g_EntityManager.create(base, position, atan2(-orient.X, -orient.Z));
ent->SetPlayer(g_Game->GetPlayer(playerID));
}
void SetTransform(CMatrix3D& transform)
{
CVector3D orient = transform.GetIn();
CVector3D position = transform.GetTranslation();
// This looks quite yucky, but nothing else seems to actually work:
ent->m_position =
ent->m_position_previous =
ent->m_graphics_position = position;
ent->teleport();
ent->m_orientation =
ent->m_orientation_previous =
ent->m_graphics_orientation = atan2(-orient.X, -orient.Z);
ent->reorient();
}
CObjectEntry* GetObjectEntry()
{
return obj;
}
};
struct CObjectThing_Object : public CObjectThing
{
CObjectThing_Object(CObjectEntry* o) : obj(o) {}
~CObjectThing_Object() {}
CObjectEntry* obj;
CUnit* unit;
void Create(CMatrix3D& transform, int UNUSED(playerID))
{
unit = new CUnit(obj, obj->m_Model->Clone());
unit->GetModel()->SetTransform(transform);
g_UnitMan.AddUnit(unit);
}
void SetTransform(CMatrix3D& transform)
{
unit->GetModel()->SetTransform(transform);
}
CObjectEntry* GetObjectEntry()
{
return obj;
}
};
void CObjectManager::SetSelectedEntity(CBaseEntity* thing)
{
delete m_SelectedThing;
m_SelectedThing = (thing ? new CObjectThing_Entity(thing) : NULL);
}
void CObjectManager::SetSelectedObject(CObjectEntry* thing)
{
delete m_SelectedThing;
m_SelectedThing = (thing ? new CObjectThing_Object(thing) : NULL);
}

View File

@ -31,11 +31,11 @@ class CObjectManager : public Singleton<CObjectManager>
public:
struct ObjectKey
{
ObjectKey(const CStr& name, const CObjectBase::variation_key& var)
ObjectKey(const CStr& name, const std::vector<u8>& var)
: ActorName(name), ActorVariation(var) {}
CStr ActorName;
CObjectBase::variation_key ActorVariation;
std::vector<u8> ActorVariation;
};
@ -68,17 +68,13 @@ public:
CObjectBase* FindObjectBase(const char* objname);
CObjectEntry* FindObjectVariation(const char* objname, const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it);
CObjectEntry* FindObjectVariation(CObjectBase* base, const CObjectBase::variation_key& vars, CObjectBase::variation_key::const_iterator& vars_it);
CObjectEntry* FindObjectVariation(const char* objname, const std::vector<std::set<CStrW> >& selections);
CObjectEntry* FindObjectVariation(CObjectBase* base, const std::vector<std::set<CStrW> >& selections);
// Get all names, quite slowly. (Intended only for ScEd.)
void GetAllObjectNames(std::vector<CStr>& names);
void GetPropObjectNames(std::vector<CStr>& names);
//CBaseEntity* m_SelectedEntity;
void SetSelectedEntity(CBaseEntity* thing);
void SetSelectedObject(CObjectEntry* thing);
std::vector<SObjectType> m_ObjectTypes;
};

View File

@ -70,7 +70,8 @@ CSkeletonAnimDef* CSkeletonAnimDef::Load(const char* filename)
// unpack the data
CSkeletonAnimDef* anim=new CSkeletonAnimDef;
try {
unpacker.UnpackString(anim->m_Name);
CStr name; // unused - just here to maintain compatibility with the animation files
unpacker.UnpackString(name);
unpacker.UnpackRaw(&anim->m_FrameTime,sizeof(anim->m_FrameTime));
unpacker.UnpackRaw(&anim->m_NumKeys,sizeof(anim->m_NumKeys));
unpacker.UnpackRaw(&anim->m_NumFrames,sizeof(anim->m_NumFrames));
@ -91,7 +92,7 @@ void CSkeletonAnimDef::Save(const char* filename,const CSkeletonAnimDef* anim)
CFilePacker packer(FILE_VERSION, "PSSA");
// pack up all the data
packer.PackString(CStr(anim->m_Name));
packer.PackString("");
packer.PackRaw(&anim->m_FrameTime,sizeof(anim->m_FrameTime));
packer.PackRaw(&anim->m_NumKeys,sizeof(anim->m_NumKeys));
packer.PackRaw(&anim->m_NumFrames,sizeof(anim->m_NumFrames));

View File

@ -69,8 +69,6 @@ public:
static void Save(const char* filename, const CSkeletonAnimDef* anim);
public:
// name of the animation
CStr m_Name; // TODO: this doesn't seem to be used, so it's just a waste of memory...
// frame time - time between successive frames, in ms
float m_FrameTime;
// number of keys in each frame - should match number of bones in the skeleton

View File

@ -3,46 +3,60 @@
#include "Unit.h"
#include "Model.h"
#include "ObjectEntry.h"
#include "ObjectManager.h"
#include "SkeletonAnimDef.h"
CUnit::~CUnit() {
CUnit::CUnit(CObjectEntry* object, CEntity* entity, const std::set<CStrW>& actorSelections)
: m_Object(object), m_Model(object->m_Model->Clone()), m_Entity(entity),
m_ID(-1), m_ActorSelections(actorSelections)
{
}
CUnit::~CUnit()
{
delete m_Model;
}
void CUnit::ShowAmmunition()
{
if( !m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint )
if (!m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint)
return;
m_Model->AddProp( m_Object->m_AmmunitionPoint, m_Object->m_AmmunitionModel->Clone() );
m_Model->AddProp(m_Object->m_AmmunitionPoint, m_Object->m_AmmunitionModel->Clone());
}
void CUnit::HideAmmunition()
{
if( !m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint )
if (!m_Object->m_AmmunitionModel || !m_Object->m_AmmunitionPoint)
return;
// Find out what the usual prop is:
// Find out what the usual prop is:
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 )
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_Model->AddProp(m_Object->m_AmmunitionPoint, it->m_Model->Clone());
return;
}
}
// No usual prop.
m_Model->RemoveProp( m_Object->m_AmmunitionPoint );
m_Model->RemoveProp(m_Object->m_AmmunitionPoint);
}
bool CUnit::SetRandomAnimation(const CStr& name, bool once)
bool CUnit::SetRandomAnimation(const CStr& name, bool once, float speed)
{
CSkeletonAnim* anim = GetRandomAnimation(name);
if (anim)
{
m_Model->SetAnimation(anim, once);
m_Model->SetAnimation(anim, once, speed ? speed*anim->m_AnimDef->GetDuration() : 1000.f);
return true;
}
else
{
// TODO - report an error?
// This shouldn't happen, since GetRandomAnimation tries to always
// return something valid
return false;
}
}
@ -50,14 +64,61 @@ bool CUnit::SetRandomAnimation(const CStr& name, bool once)
CSkeletonAnim* CUnit::GetRandomAnimation(const CStr& name)
{
CSkeletonAnim* anim = m_Object->GetRandomAnimation(name);
// Fall back to 'idle', if no matching animation is found
if (anim == NULL && name != "idle")
anim = m_Object->GetRandomAnimation("idle");
// Every object should have an idle animation (even if it's a dummy static one)
debug_assert(anim != NULL);
return anim;
}
bool CUnit::IsPlayingAnimation(const CStr& name)
{
return (m_Model->GetAnimation()->m_Name == name);
return (m_Model->GetAnimation() && m_Model->GetAnimation()->m_Name == name);
}
void CUnit::SetPlayerID(int id)
{
m_PlayerID = id;
m_Model->SetPlayerID(m_PlayerID);
}
void CUnit::SetEntitySelection(const CStrW& selection)
{
// If we've already selected this, don't do anything
if (m_EntitySelections.find(selection) != m_EntitySelections.end())
return;
// Just allow one selection at a time
m_EntitySelections.clear();
m_EntitySelections.insert(selection);
ReloadObject();
}
void CUnit::ReloadObject()
{
std::vector<std::set<CStrW> > selections;
// TODO: push world selections (seasons, etc) (and reload whenever they're changed)
selections.push_back(m_EntitySelections);
selections.push_back(m_ActorSelections);
// If these selections give a different object, change this unit to use it
CObjectEntry* newObject = g_ObjMan.FindObjectVariation(m_Object->m_Base, selections);
if (newObject != m_Object)
{
CModel* newModel = newObject->m_Model->Clone();
// Copy old settings to the new model
newModel->SetPlayerID(m_PlayerID);
newModel->SetTransform(m_Model->GetTransform());
// TODO: preserve selection of animation, anim offset, etc?
delete m_Model;
m_Model = newModel;
m_Object = newObject;
}
}

View File

@ -14,17 +14,7 @@ class CStr8;
class CUnit
{
public:
// constructor - unit invalid without a model and object
CUnit(CObjectEntry* object, CModel* model)
: m_Object(object), m_Model(model), m_Entity(NULL), m_ID(-1)
{
debug_assert(object && model);
}
CUnit(CObjectEntry* object, CModel* model, CEntity* entity)
: m_Object(object), m_Model(model), m_Entity(entity), m_ID(-1)
{
debug_assert(object && model);
}
CUnit(CObjectEntry* object, CEntity* entity, const std::set<CStrW>& actorSelections);
// destructor
~CUnit();
@ -43,16 +33,23 @@ public:
// Sets the animation a random one matching 'name'. If none is found,
// sets to idle instead.
bool SetRandomAnimation(const CStr8& name, bool once = false);
bool SetRandomAnimation(const CStr8& name, bool once = false, float speed = 0.0f);
// Returns the animation a random one matching 'name'. If none is found,
// Returns a random animation matching 'name'. If none is found,
// returns idle instead.
CSkeletonAnim* GetRandomAnimation(const CStr8& name);
// Sets the entity-selection, and updates the unit to use the new
// actor variation.
void SetEntitySelection(const CStrW& selection);
// Returns whether the currently active animation is one of the ones
// matching 'name'.
bool IsPlayingAnimation(const CStr8& name);
// Set player ID of this unit
void SetPlayerID(int id);
int GetID() const { return m_ID; }
void SetID(int id) { m_ID = id; }
@ -63,9 +60,19 @@ private:
CModel* m_Model;
// the entity that this actor represents, if any
CEntity* m_Entity;
// player id of this unit (only used for graphical effects)
int m_PlayerID;
// unique (per map) ID number for units created in the editor, as a
// permanent way of referencing them. -1 for non-editor units.
int m_ID;
// actor-level selections for this unit
std::set<CStrW> m_ActorSelections;
// entity-level selections for this unit
std::set<CStrW> m_EntitySelections;
void ReloadObject();
};
#endif

View File

@ -119,14 +119,24 @@ CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir) con
///////////////////////////////////////////////////////////////////////////////
// CreateUnit: create a new unit and add it to the world
CUnit* CUnitManager::CreateUnit(const CStr& actorName, CEntity* entity)
CUnit* CUnitManager::CreateUnit(const CStr& actorName, CEntity* entity, const std::set<CStrW>& selections)
{
CObjectEntry* obj = g_ObjMan.FindObject(actorName);
CObjectBase* base = g_ObjMan.FindObjectBase(actorName);
if (! base)
return NULL;
std::set<CStrW> actorSelections = base->CalculateRandomVariation(selections);
std::vector<std::set<CStrW> > selectionsVec;
selectionsVec.push_back(actorSelections);
CObjectEntry* obj = g_ObjMan.FindObjectVariation(base, selectionsVec);
if (! obj)
return NULL;
CUnit* unit = new CUnit(obj, obj->m_Model->Clone(), entity);
CUnit* unit = new CUnit(obj, entity, actorSelections);
AddUnit(unit);
return unit;
}

View File

@ -40,7 +40,7 @@ public:
void DeleteAll();
// creates a new unit and adds it to the world
CUnit* CreateUnit(const CStr& actorName, CEntity* entity);
CUnit* CreateUnit(const CStr& actorName, CEntity* entity, const std::set<CStrW>& selections);
// return the units
const std::vector<CUnit*>& GetUnits() const { return m_Units; }

View File

@ -89,10 +89,14 @@ static bool GetTooltip(IGUIObject* obj, CStr &style)
return false;
}
void ShowTooltip(IGUIObject* obj, CPos pos, CStr& style, CGUI* gui)
void GUITooltip::ShowTooltip(IGUIObject* obj, CPos pos, const CStr& style, CGUI* gui)
{
debug_assert(obj);
// Ignore attempts to use tooltip ""
if (style.Length() == 0)
return;
// Get the object referenced by 'tooltip_style'
IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
if (! tooltipobj)
@ -144,8 +148,12 @@ void ShowTooltip(IGUIObject* obj, CPos pos, CStr& style, CGUI* gui)
usedobj->HandleMessage(SGUIMessage(GUIM_SETTINGS_UPDATED, "caption"));
}
void HideTooltip(CStr& style, CGUI* gui)
void GUITooltip::HideTooltip(const CStr& style, CGUI* gui)
{
// Ignore attempts to use tooltip ""
if (style.Length() == 0)
return;
IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
if (! tooltipobj)
{

View File

@ -15,6 +15,9 @@ public:
private:
static void ShowTooltip(IGUIObject* obj, CPos pos, const CStr& style, CGUI* gui);
static void HideTooltip(const CStr& style, CGUI* gui);
int m_State;
IGUIObject* m_PreviousObject;

View File

@ -130,10 +130,7 @@ class IGUIObject
friend class CGUI;
friend class CInternalCGUIAccessorBase;
friend class IGUIScrollBar;
// Allow ShowTooltip/HideTooltip (GUITooltip.cpp) to access HandleMessage
friend void ShowTooltip(IGUIObject*, CPos, CStr&, CGUI*);
friend void HideTooltip(CStr&, CGUI*);
friend class GUITooltip;
// Allow getProperty to access things like GetParent()
friend JSBool JSI_IGUIObject::getProperty(JSContext* cx, JSObject* obj, jsval id, jsval* vp);

View File

@ -1179,8 +1179,10 @@ bool CBuildingPlacer::activate(CStrW& templateName)
// m_actor
CStr actorName ( base->m_actorName ); // convert CStrW->CStr8
m_actor = g_UnitMan.CreateUnit( actorName, 0 );
m_actor->GetModel()->SetPlayerID(g_Game->GetLocalPlayer()->GetPlayerID());
std::set<CStrW> selections;
m_actor = g_UnitMan.CreateUnit( actorName, 0, selections );
m_actor->SetPlayerID(g_Game->GetLocalPlayer()->GetPlayerID());
// m_bounds
if( base->m_bound_type == CBoundingObject::BOUND_CIRCLE )

View File

@ -294,7 +294,7 @@ void WriteBigScreenshot(const char* extension, int tiles)
}
}
// Restor the buffer settings
// Restore the buffer settings
glDrawBuffer(oldDrawBuffer);
glReadBuffer(oldReadBuffer);

View File

@ -1076,6 +1076,11 @@ void CRenderer::BindTexture(int unit,GLuint tex)
void CRenderer::SetTexture(int unit,CTexture* texture)
{
Handle h = texture? texture->GetHandle() : 0;
// Errored textures will give a handle of -1
if (h == -1)
h = 0;
ogl_tex_bind(h, unit);
}

View File

@ -28,7 +28,7 @@ extern int g_xres, g_yres;
#include <algorithm>
using namespace std;
CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation, CStrW building )
CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation, const std::set<CStrW>& actorSelections, CStrW building )
{
m_position = position;
m_orientation = orientation;
@ -94,7 +94,8 @@ CEntity::CEntity( CBaseEntity* base, CVector3D position, float orientation, CStr
m_base = base;
loadBase();
m_actorSelections = actorSelections;
loadBase();
if( m_bounds )
m_bounds->setPosition( m_position.X, m_position.Z );
@ -166,7 +167,8 @@ void CEntity::loadBase()
}
CStr actorName ( m_base->m_actorName ); // convert CStrW->CStr8
m_actor = g_UnitMan.CreateUnit( actorName, this );
m_actor = g_UnitMan.CreateUnit( actorName, this, m_actorSelections );
// Set up our instance data
@ -217,7 +219,7 @@ void CEntity::SetPlayer(CPlayer *pPlayer)
// Store the ID of the player in the associated model
if( m_actor )
m_actor->GetModel()->SetPlayerID( m_player->GetPlayerID() );
m_actor->SetPlayerID( m_player->GetPlayerID() );
}
void CEntity::updateActorTransforms()
@ -449,10 +451,16 @@ void CEntity::update( size_t timestep )
if( m_extant )
{
if( ( m_lastState != -1 ) || !m_actor->GetModel()->GetAnimation() )
{
m_actor->SetEntitySelection( L"idle" );
m_actor->SetRandomAnimation( "idle" );
}
}
else if( !m_actor->GetModel()->GetAnimation() )
{
m_actor->SetEntitySelection( L"corpse" );
m_actor->SetRandomAnimation( "corpse" );
}
}
if( m_lastState != -1 )
@ -709,7 +717,7 @@ void CEntity::teleport()
void CEntity::playerChanged()
{
if( m_actor )
m_actor->GetModel()->SetPlayerID( m_player->GetPlayerID() );
m_actor->SetPlayerID( m_player->GetPlayerID() );
}
void CEntity::checkSelection()
@ -1164,7 +1172,8 @@ JSBool CEntity::Construct( JSContext* cx, JSObject* UNUSED(obj), uint argc, jsva
}
}
HEntity handle = g_EntityManager.create( baseEntity, position, orientation );
std::set<CStrW> selections; // TODO: let scripts specify selections?
HEntity handle = g_EntityManager.create( baseEntity, position, orientation, selections );
*rval = ToJSVal<CEntity>( *handle );
return( JS_TRUE );
@ -1346,7 +1355,10 @@ bool CEntity::Kill( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(arg
g_EntityManager.SetDeath(true);
if( m_actor )
{
m_actor->SetEntitySelection( L"death" );
m_actor->SetRandomAnimation( "death", true );
}
return( true );
}

View File

@ -166,6 +166,7 @@ public:
CScriptObject m_EventHandlers[EVENT_LAST];
CUnit* m_actor;
std::set<CStrW> m_actorSelections;
// State transition in the FSM (animations should be reset)
bool m_transition;
@ -184,7 +185,7 @@ public:
CEntity* m_currentListener;
private:
CEntity( CBaseEntity* base, CVector3D position, float orientation, CStrW building = L"" );
CEntity( CBaseEntity* base, CVector3D position, float orientation, const std::set<CStrW>& actorSelections, CStrW building = L"" );
uint processGotoHelper( CEntityOrder* current, size_t timestep_milli, HEntity& collide );
@ -201,6 +202,8 @@ private:
bool processPatrol( CEntityOrder* current, size_t timestep_milli );
float processChooseMovement( float distance );
public:
~CEntity();

View File

@ -68,7 +68,7 @@ void CEntityManager::deleteAll()
m_extant = true;
}
HEntity CEntityManager::create( CBaseEntity* base, CVector3D position, float orientation )
HEntity CEntityManager::create( CBaseEntity* base, CVector3D position, float orientation, const std::set<CStrW>& actorSelections )
{
debug_assert( base );
if( !base )
@ -77,24 +77,24 @@ HEntity CEntityManager::create( CBaseEntity* base, CVector3D position, float ori
while( m_entities[m_nextalloc].m_refcount )
{
m_nextalloc++;
if(m_nextalloc == MAX_HANDLES)
if(m_nextalloc >= MAX_HANDLES)
{
debug_warn("Ran out of entity handles!");
return HEntity();
}
}
m_entities[m_nextalloc].m_entity = new CEntity( base, position, orientation );
m_entities[m_nextalloc].m_entity = new CEntity( base, position, orientation, actorSelections );
if( m_collisionPatches)
m_entities[m_nextalloc].m_entity->updateCollisionPatch();
m_entities[m_nextalloc].m_entity->me = HEntity( m_nextalloc );
return( HEntity( m_nextalloc++ ) );
}
HEntity CEntityManager::create( const CStrW templateName, CVector3D position, float orientation )
HEntity CEntityManager::create( const CStrW templateName, CVector3D position, float orientation, const std::set<CStrW>& actorSelections )
{
CBaseEntity* templateObj = g_EntityTemplateCollection.getTemplate( templateName );
return( create( templateObj, position, orientation ) );
return( create( templateObj, position, orientation, actorSelections ) );
}
HEntity CEntityManager::createFoundation( CStrW templateName, CVector3D position, float orientation )
@ -103,8 +103,11 @@ HEntity CEntityManager::createFoundation( CStrW templateName, CVector3D position
debug_assert( base );
if( !base )
return HEntity();
std::set<CStrW> selections;
if( base->m_foundation == L"" )
return create( base, position, orientation ); // Entity has no foundation, so just create it
return create( base, position, orientation, selections ); // Entity has no foundation, so just create it
CBaseEntity* foundation = g_EntityTemplateCollection.getTemplate( base->m_foundation );
debug_assert( foundation );
@ -114,14 +117,14 @@ HEntity CEntityManager::createFoundation( CStrW templateName, CVector3D position
while( m_entities[m_nextalloc].m_refcount )
{
m_nextalloc++;
if(m_nextalloc == MAX_HANDLES)
if(m_nextalloc >= MAX_HANDLES)
{
debug_warn("Ran out of entity handles!");
return HEntity();
}
}
m_entities[m_nextalloc].m_entity = new CEntity( foundation, position, orientation, templateName );
m_entities[m_nextalloc].m_entity = new CEntity( foundation, position, orientation, selections, templateName );
if( m_collisionPatches)
m_entities[m_nextalloc].m_entity->updateCollisionPatch();
m_entities[m_nextalloc].m_entity->me = HEntity( m_nextalloc );

View File

@ -48,8 +48,8 @@ public:
CEntityManager();
~CEntityManager();
HEntity create( CBaseEntity* base, CVector3D position, float orientation );
HEntity create( CStrW templateName, CVector3D position, float orientation );
HEntity create( CBaseEntity* base, CVector3D position, float orientation, const std::set<CStrW>& actorSelections );
HEntity create( CStrW templateName, CVector3D position, float orientation, const std::set<CStrW>& actorSelections );
HEntity createFoundation( CStrW templateName, CVector3D position, float orientation );

View File

@ -29,6 +29,45 @@ enum EGotoSituation
WOULD_LEAVE_MAP
};
float CEntity::processChooseMovement( float distance )
{
// Should we run or walk
if (m_shouldRun && m_staminaCurr > 0 && distance < m_run.m_MaxRange &&
( distance > m_run.m_MinRange || m_isRunning ) )
{
if ( m_actor )
{
if ( !m_actor->IsPlayingAnimation( "run" ) )
{
m_actor->SetEntitySelection( L"run" );
m_actor->SetRandomAnimation( "run", false, m_run.m_Speed );
// Animation desync
m_actor->GetModel()->Update( rand( 0, 1000 ) / 1000.0f );
m_isRunning = true;
}
}
return m_run.m_Speed;
}
else
{
if ( m_actor )
{
// Should we update animation?
if ( !m_actor->IsPlayingAnimation( "walk" ) )
{
m_actor->SetEntitySelection( L"walk" );
m_actor->SetRandomAnimation( "walk", false, m_speed );
// Animation desync
m_actor->GetModel()->Update( rand( 0, 1000 ) / 1000.0f );
m_isRunning = false;
}
}
return m_speed;
}
}
// Does all the shared processing for line-of-sight gotos
uint CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis, HEntity& collide )
@ -47,48 +86,8 @@ uint CEntity::processGotoHelper( CEntityOrder* current, size_t timestep_millis,
// Curve smoothing.
// Here there be trig.
float scale;
float scale = processChooseMovement( len ) * timestep;
//Should we run or walk
if (m_shouldRun && m_staminaCurr > 0 && len < m_run.m_MaxRange &&
( len > m_run.m_MinRange || m_isRunning ) )
{
scale = m_run.m_Speed * timestep;
if ( m_actor )
{
if ( !m_actor->IsPlayingAnimation( "run" ) )
{
CSkeletonAnim* run = m_actor->GetRandomAnimation( "run" );
if ( run )
m_actor->GetModel()->SetAnimation( run, false, m_run.m_Speed * run->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = true;
}
}
}
else
{
scale = m_speed * timestep;
if ( m_actor )
{
//Should we update animation?
if ( !m_actor->IsPlayingAnimation( "walk" ) )
{
CSkeletonAnim* walk = m_actor->GetRandomAnimation( "walk" );
if ( walk )
m_actor->GetModel()->SetAnimation( walk, false, m_speed * walk->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = false;
}
}
}
// Note: Easy optimization: flag somewhere that this unit
// is already pointing the way, and don't do this
@ -338,38 +337,7 @@ bool CEntity::processContactAction( CEntityOrder* current, size_t UNUSED(timeste
return( true );
}
if ( m_actor )
{
//Should we run or walk
if (m_shouldRun && m_staminaCurr > 0 && Distance < m_run.m_MaxRange &&
( Distance > m_run.m_MinRange || m_isRunning ) )
{
if ( !m_actor->IsPlayingAnimation( "run" ) )
{
CSkeletonAnim* run = m_actor->GetRandomAnimation( "run" );
if ( run )
m_actor->GetModel()->SetAnimation( run, false, m_run.m_Speed * run->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = true;
}
}
else
{
//Should we update animation?
if ( !m_actor->IsPlayingAnimation( "walk" ) )
{
CSkeletonAnim* walk = m_actor->GetRandomAnimation( "walk" );
if ( walk )
m_actor->GetModel()->SetAnimation( walk, false, m_speed * walk->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = false;
}
}
}
processChooseMovement( Distance );
// The pathfinder will push its result back into this unit's queue and
// add back the current order at the end with the transition type.
@ -391,7 +359,12 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
// few hundred ms, at the 'action point' of the animation we're
// now setting.
m_isRunning = false;
m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_fsm_animation );
// TODO: this is set to be looping, because apparently it otherwise
// plays one frame of 'idle' after e.g. attacks. But this way means
// animations sometimes play ~1.5 times then repeat, which looks
// broken too.
//m_actor->GetModel()->SetAnimation( m_fsm_animation, true, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed, m_actor->GetRandomAnimation( "idle" ) );
m_actor->GetModel()->SetAnimation( m_fsm_animation, false, 1000.0f * m_fsm_animation->m_AnimDef->GetDuration() / (float)action->m_Speed );
}
if( ( m_fsm_cyclepos <= m_fsm_anipos2 ) &&
( nextpos > m_fsm_anipos2 ) )
@ -408,7 +381,8 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
m_orderQueue.pop_front();
m_isRunning = false;
m_shouldRun = false;
m_actor->SetRandomAnimation("idle");
m_actor->SetEntitySelection( L"idle" );
m_actor->SetRandomAnimation( "idle" );
return( false );
}
}
@ -460,39 +434,8 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
// We're aiming to end up at a location just inside our maximum range
// (is this good enough?)
delta = delta.normalize() * ( adjRange - m_bounds->m_radius );
if ( m_actor )
{
//Should we run or walk
if (m_shouldRun && m_staminaCurr > 0 && deltaLength < m_run.m_MaxRange &&
( deltaLength > m_run.m_MinRange || m_isRunning ) )
{
if ( !m_actor->IsPlayingAnimation( "run" ) )
{
CSkeletonAnim* run = m_actor->GetRandomAnimation( "run" );
if ( run )
m_actor->GetModel()->SetAnimation( run, false, m_run.m_Speed * run->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = true;
}
}
else
{
//Should we update animation?
if ( !m_actor->IsPlayingAnimation( "walk" ) )
{
CSkeletonAnim* walk = m_actor->GetRandomAnimation( "walk" );
if ( walk )
m_actor->GetModel()->SetAnimation( walk, false, m_speed * walk->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = false;
}
}
}
processChooseMovement(deltaLength);
current->m_data[0].location = (CVector2D)current->m_data[0].entity->m_position - delta;
@ -553,6 +496,7 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
}
// Pick our animation, calculate the time to play it, and start the timer.
m_actor->SetEntitySelection( animation );
m_fsm_animation = m_actor->GetRandomAnimation( animation );
// Here's the idea - we want to be at that animation's event point
@ -575,7 +519,10 @@ bool CEntity::processContactActionNoPathing( CEntityOrder* current, size_t times
// If we've just transitioned, play idle. Otherwise, let the previous animation complete, if it
// hasn't already.
if( m_transition )
{
// (don't change actor's entity-selection)
m_actor->SetRandomAnimation( "idle" );
}
}
// Load time needs to be animation->m_ActionPos2 ms after the start of the animation.
@ -635,38 +582,7 @@ bool CEntity::processGoto( CEntityOrder* current, size_t UNUSED(timestep_millis)
return( false );
}
if ( m_actor )
{
//Should we run or walk
if (m_shouldRun && m_staminaCurr > 0 && Distance < m_run.m_MaxRange &&
( Distance > m_run.m_MinRange || m_isRunning ) )
{
if ( !m_actor->IsPlayingAnimation( "run" ) )
{
CSkeletonAnim* run = m_actor->GetRandomAnimation( "run" );
if ( run )
m_actor->GetModel()->SetAnimation( run, false, m_run.m_Speed * run->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = true;
}
}
else
{
//Should we update animation?
if ( !m_actor->IsPlayingAnimation( "walk" ) )
{
CSkeletonAnim* walk = m_actor->GetRandomAnimation( "walk" );
if ( walk )
m_actor->GetModel()->SetAnimation( walk, false, m_speed * walk->m_AnimDef->GetDuration() );
// Animation desync
m_actor->GetModel()->Update( ( rand() * 1000.0f ) / 1000.0f );
m_isRunning = false;
}
}
}
processChooseMovement( Distance );
// The pathfinder will push its result back into this unit's queue.

View File

@ -164,20 +164,22 @@ MESSAGEHANDLER(ObjectPreview)
CStrW name;
if (ParseObjectName(msg->id, isEntity, name))
{
std::set<CStrW> selections; // TODO: get selections from user
// Create new unit
if (isEntity)
{
CBaseEntity* base = g_EntityTemplateCollection.getTemplate(name);
if (base) // (ignore errors)
{
g_PreviewUnit = g_UnitMan.CreateUnit(base->m_actorName, NULL);
g_PreviewUnit->GetModel()->SetPlayerID(msg->player);
g_PreviewUnit = g_UnitMan.CreateUnit(base->m_actorName, NULL, selections);
g_PreviewUnit->SetPlayerID(msg->player);
// TODO: variations
}
}
else
{
g_PreviewUnit = g_UnitMan.CreateUnit(CStr(name), NULL);
g_PreviewUnit = g_UnitMan.CreateUnit(CStr(name), NULL, selections);
}
}
@ -249,6 +251,8 @@ BEGIN_COMMAND(CreateObject)
CStrW name;
if (ParseObjectName(d->id, isEntity, name))
{
std::set<CStrW> selections;
if (isEntity)
{
CBaseEntity* base = g_EntityTemplateCollection.getTemplate(name);
@ -256,7 +260,7 @@ BEGIN_COMMAND(CreateObject)
LOG(ERROR, LOG_CATEGORY, "Failed to load entity template '%ls'", name.c_str());
else
{
HEntity ent = g_EntityManager.create(base, m_Pos, m_Angle);
HEntity ent = g_EntityManager.create(base, m_Pos, m_Angle, selections);
if (! ent)
LOG(ERROR, LOG_CATEGORY, "Failed to create entity of type '%ls'", name.c_str());
@ -270,7 +274,7 @@ BEGIN_COMMAND(CreateObject)
}
else
{
CUnit* unit = g_UnitMan.CreateUnit(CStr(name), NULL);
CUnit* unit = g_UnitMan.CreateUnit(CStr(name), NULL, selections);
if (! unit)
LOG(ERROR, LOG_CATEGORY, "Failed to load nonentity actor '%ls'", name.c_str());
else