2005-03-19 20:10:52 +01:00
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
#include "ObjectBase.h"
|
|
|
|
|
2005-04-03 07:02:00 +02:00
|
|
|
#include "ObjectManager.h"
|
2005-03-19 20:10:52 +01:00
|
|
|
#include "Xeromyces.h"
|
|
|
|
#include "CLogger.h"
|
|
|
|
|
|
|
|
#define LOG_CATEGORY "graphics"
|
|
|
|
|
|
|
|
CObjectBase::CObjectBase()
|
|
|
|
{
|
|
|
|
m_Properties.m_CastShadows = true;
|
|
|
|
m_Properties.m_AutoFlatten = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CObjectBase::Load(const char* filename)
|
|
|
|
{
|
|
|
|
m_Variants.clear();
|
|
|
|
|
2005-03-29 22:50:04 +02:00
|
|
|
CStr filePath ("art/actors/");
|
|
|
|
filePath += filename;
|
|
|
|
|
2005-03-19 20:10:52 +01:00
|
|
|
CXeromyces XeroFile;
|
2005-03-29 22:50:04 +02:00
|
|
|
if (XeroFile.Load(filePath) != PSRETURN_OK)
|
2005-03-19 20:10:52 +01:00
|
|
|
return false;
|
|
|
|
|
2005-03-29 22:50:04 +02:00
|
|
|
m_Name = filename;
|
2005-03-19 20:10:52 +01:00
|
|
|
|
|
|
|
XMBElement root = XeroFile.getRoot();
|
|
|
|
|
|
|
|
if (root.getNodeName() == XeroFile.getElementID("object"))
|
|
|
|
{
|
|
|
|
//// Old-format actor file ////
|
|
|
|
|
|
|
|
// Define all the elements and attributes used in the XML file
|
|
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
|
|
|
#define AT(x) int at_##x = XeroFile.getAttributeID(#x)
|
|
|
|
EL(name);
|
|
|
|
EL(modelname);
|
|
|
|
EL(texturename);
|
|
|
|
EL(material);
|
|
|
|
EL(animations);
|
|
|
|
EL(props);
|
|
|
|
EL(properties);
|
|
|
|
AT(attachpoint);
|
|
|
|
AT(model);
|
|
|
|
AT(name);
|
|
|
|
AT(file);
|
|
|
|
AT(speed);
|
|
|
|
AT(autoflatten);
|
|
|
|
AT(castshadows);
|
|
|
|
#undef AT
|
|
|
|
#undef EL
|
|
|
|
|
|
|
|
m_Variants.resize(1);
|
|
|
|
m_Variants.back().resize(1);
|
|
|
|
m_Variants.back().back().m_VariantName = "Base";
|
|
|
|
|
|
|
|
XERO_ITER_EL(root, child)
|
|
|
|
{
|
|
|
|
int element_name = child.getNodeName();
|
|
|
|
CStr element_value (child.getText());
|
|
|
|
|
|
|
|
if (element_name == el_name)
|
2005-03-29 22:50:04 +02:00
|
|
|
m_ShortName = element_value;
|
2005-03-19 20:10:52 +01:00
|
|
|
|
|
|
|
else if (element_name == el_modelname)
|
|
|
|
m_Variants.back().back().m_ModelFilename = element_value;
|
|
|
|
|
|
|
|
else if (element_name == el_texturename)
|
|
|
|
m_Variants.back().back().m_TextureFilename = element_value;
|
|
|
|
|
|
|
|
else if(element_name == el_material)
|
|
|
|
m_Material = element_value;
|
|
|
|
|
|
|
|
else if (element_name == el_properties)
|
|
|
|
{
|
|
|
|
|
|
|
|
XERO_ITER_ATTR(child, attrib)
|
|
|
|
{
|
|
|
|
int attrib_name = attrib.Name;
|
|
|
|
|
|
|
|
if (attrib_name == at_autoflatten)
|
|
|
|
{
|
|
|
|
CStr str (attrib.Value);
|
|
|
|
m_Properties.m_AutoFlatten = str.ToInt() ? true : false;
|
|
|
|
}
|
|
|
|
else if (attrib_name == at_castshadows)
|
|
|
|
{
|
|
|
|
CStr str = (attrib.Value);
|
|
|
|
m_Properties.m_CastShadows = str.ToInt() ? true : false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
else if (element_name == el_animations)
|
|
|
|
{
|
|
|
|
XERO_ITER_EL(child, anim_element)
|
|
|
|
{
|
|
|
|
XMBAttributeList attributes = anim_element.getAttributes();
|
|
|
|
|
|
|
|
if (attributes.Count)
|
|
|
|
{
|
|
|
|
Anim anim;
|
|
|
|
|
|
|
|
anim.m_AnimName = attributes.getNamedItem(at_name);
|
|
|
|
anim.m_FileName = attributes.getNamedItem(at_file);
|
|
|
|
CStr speedstr = attributes.getNamedItem(at_speed);
|
|
|
|
|
|
|
|
anim.m_Speed=float(speedstr.ToInt())/100.0f;
|
|
|
|
if (anim.m_Speed<=0.0) anim.m_Speed=1.0f;
|
|
|
|
|
|
|
|
m_Variants.back().back().m_Anims.push_back(anim);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (element_name == el_props)
|
|
|
|
{
|
|
|
|
XERO_ITER_EL(child, prop_element)
|
|
|
|
{
|
|
|
|
|
|
|
|
XMBAttributeList attributes = prop_element.getAttributes();
|
|
|
|
if (attributes.Count)
|
|
|
|
{
|
|
|
|
Prop prop;
|
|
|
|
|
|
|
|
prop.m_PropPointName = attributes.getNamedItem(at_attachpoint);
|
|
|
|
prop.m_ModelName = attributes.getNamedItem(at_model);
|
|
|
|
|
|
|
|
m_Variants.back().back().m_Props.push_back(prop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (root.getNodeName() == XeroFile.getElementID("actor"))
|
|
|
|
{
|
|
|
|
//// New-format actor file ////
|
|
|
|
|
|
|
|
// Use the filename for the model's name
|
2005-03-29 22:50:04 +02:00
|
|
|
m_ShortName = CStr(filename).AfterLast("/").BeforeLast(".xml");
|
2005-03-19 20:10:52 +01:00
|
|
|
|
|
|
|
// Define all the elements used in the XML file
|
|
|
|
#define EL(x) int el_##x = XeroFile.getElementID(#x)
|
2005-03-26 01:22:42 +01:00
|
|
|
#define AT(x) int at_##x = XeroFile.getAttributeID(#x)
|
2005-03-19 20:10:52 +01:00
|
|
|
EL(castshadow);
|
|
|
|
EL(material);
|
|
|
|
EL(group);
|
|
|
|
EL(variant);
|
|
|
|
EL(animations);
|
|
|
|
EL(animation);
|
|
|
|
EL(props);
|
|
|
|
EL(prop);
|
|
|
|
EL(mesh);
|
|
|
|
EL(texture);
|
2005-04-07 06:29:07 +02:00
|
|
|
EL(color);
|
2005-03-26 01:22:42 +01:00
|
|
|
AT(file);
|
|
|
|
AT(name);
|
|
|
|
AT(speed);
|
|
|
|
AT(attachpoint);
|
|
|
|
AT(actor);
|
|
|
|
AT(frequency);
|
2005-04-07 06:29:07 +02:00
|
|
|
AT(rgb);
|
2005-03-26 01:22:42 +01:00
|
|
|
#undef AT
|
2005-03-19 20:10:52 +01:00
|
|
|
#undef EL
|
|
|
|
|
|
|
|
// (This code is rather worryingly verbose...)
|
|
|
|
|
|
|
|
XERO_ITER_EL(root, child)
|
|
|
|
{
|
2005-03-22 14:54:37 +01:00
|
|
|
int child_name = child.getNodeName();
|
2005-03-19 20:10:52 +01:00
|
|
|
|
2005-03-22 14:54:37 +01:00
|
|
|
if (child_name == el_group)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
|
|
|
m_Variants.resize(m_Variants.size()+1);
|
|
|
|
|
|
|
|
XERO_ITER_EL(child, variant)
|
|
|
|
{
|
|
|
|
m_Variants.back().resize(m_Variants.back().size()+1);
|
|
|
|
|
2005-03-26 01:22:42 +01:00
|
|
|
XERO_ITER_ATTR(variant, attr)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
2005-03-26 01:22:42 +01:00
|
|
|
if (attr.Name == at_name)
|
|
|
|
m_Variants.back().back().m_VariantName = attr.Value;
|
|
|
|
|
|
|
|
else if (attr.Name == at_frequency)
|
|
|
|
m_Variants.back().back().m_Frequency = CStr(attr.Value).ToInt();
|
|
|
|
}
|
2005-03-19 20:10:52 +01:00
|
|
|
|
|
|
|
|
2005-03-26 01:22:42 +01:00
|
|
|
XERO_ITER_EL(variant, option)
|
|
|
|
{
|
|
|
|
int option_name = option.getNodeName();
|
2005-03-19 20:10:52 +01:00
|
|
|
|
2005-03-26 01:22:42 +01:00
|
|
|
if (option_name == el_mesh)
|
2005-03-26 00:04:36 +01:00
|
|
|
m_Variants.back().back().m_ModelFilename = "art/meshes/" + CStr(option.getText());
|
2005-03-19 20:10:52 +01:00
|
|
|
|
|
|
|
else if (option_name == el_texture)
|
2005-03-26 01:22:42 +01:00
|
|
|
m_Variants.back().back().m_TextureFilename = "art/textures/skins/" + CStr(option.getText());
|
2005-03-19 20:10:52 +01:00
|
|
|
|
2005-04-07 06:29:07 +02:00
|
|
|
else if (option_name == el_color)
|
|
|
|
m_Variants.back().back().m_Color = CStr(option.getAttributes().getNamedItem(at_rgb));
|
|
|
|
|
2005-03-19 20:10:52 +01:00
|
|
|
else if (option_name == el_animations)
|
|
|
|
{
|
|
|
|
XERO_ITER_EL(option, anim_element)
|
|
|
|
{
|
|
|
|
Anim anim;
|
|
|
|
|
2005-03-26 01:22:42 +01:00
|
|
|
XERO_ITER_ATTR(anim_element, ae)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
2005-03-26 01:22:42 +01:00
|
|
|
if (ae.Name == at_name)
|
|
|
|
anim.m_AnimName = ae.Value;
|
|
|
|
else if (ae.Name == at_file)
|
|
|
|
anim.m_FileName = "art/animation/" + CStr(ae.Value);
|
|
|
|
else if (ae.Name == at_speed)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
2005-03-26 01:22:42 +01:00
|
|
|
anim.m_Speed = CStr(ae.Value).ToInt() / 100.f;
|
2005-03-19 20:10:52 +01:00
|
|
|
if (anim.m_Speed <= 0.0) anim.m_Speed = 1.0f;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
; // unrecognised element
|
|
|
|
}
|
|
|
|
m_Variants.back().back().m_Anims.push_back(anim);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (option_name == el_props)
|
|
|
|
{
|
|
|
|
XERO_ITER_EL(option, prop_element)
|
|
|
|
{
|
|
|
|
Prop prop;
|
|
|
|
|
2005-03-26 01:22:42 +01:00
|
|
|
XERO_ITER_ATTR(prop_element, pe)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
2005-03-26 01:22:42 +01:00
|
|
|
if (pe.Name == at_attachpoint)
|
|
|
|
prop.m_PropPointName = pe.Value;
|
|
|
|
else if (pe.Name == at_actor)
|
|
|
|
prop.m_ModelName = pe.Value;
|
2005-03-19 20:10:52 +01:00
|
|
|
else
|
|
|
|
; // unrecognised element
|
|
|
|
}
|
|
|
|
m_Variants.back().back().m_Props.push_back(prop);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
; // unrecognised element
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_Variants.back().size() == 0)
|
|
|
|
{
|
|
|
|
LOG(ERROR, LOG_CATEGORY, "Actor group has zero variants ('%s')", filename);
|
|
|
|
m_Variants.pop_back();
|
|
|
|
}
|
|
|
|
}
|
2005-03-22 14:54:37 +01:00
|
|
|
else if (child_name == el_material)
|
2005-03-19 20:10:52 +01:00
|
|
|
{
|
2005-03-26 00:04:36 +01:00
|
|
|
m_Material = "art/materials/" + CStr(child.getText());
|
2005-03-19 20:10:52 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
; // unrecognised element
|
|
|
|
|
|
|
|
// TODO: castshadow, etc
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LOG(ERROR, LOG_CATEGORY, "Invalid actor format (unrecognised root element '%s')", XeroFile.getElementString(root.getNodeName()));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CObjectBase::CalculateVariation(std::set<CStr>& strings, variation_key& choices)
|
|
|
|
{
|
|
|
|
// 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.
|
|
|
|
|
|
|
|
choices.clear();
|
|
|
|
|
|
|
|
for (std::vector<std::vector<CObjectBase::Variant> >::iterator grp = m_Variants.begin();
|
|
|
|
grp != m_Variants.end();
|
|
|
|
++grp)
|
|
|
|
{
|
|
|
|
assert(grp->size() > 0);
|
|
|
|
|
|
|
|
// If there's only a single variant, choose that one
|
|
|
|
if (grp->size() == 1)
|
|
|
|
{
|
|
|
|
choices.push_back(0);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the variants that match the provided strings:
|
|
|
|
|
|
|
|
std::vector<u8> matches;
|
|
|
|
typedef std::vector<u8>::const_iterator Iter;
|
|
|
|
|
|
|
|
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(i);
|
|
|
|
|
|
|
|
// If there's only one match, choose that one
|
|
|
|
if (matches.size() == 1)
|
|
|
|
{
|
|
|
|
choices.push_back(matches[0]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, choose randomly from the others.
|
|
|
|
|
|
|
|
// 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(i);
|
|
|
|
|
|
|
|
// 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 = (int)( ((float)rand() / RAND_MAX) * totalFreq );
|
|
|
|
|
|
|
|
assert(randNum < 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)
|
|
|
|
{
|
|
|
|
choices.push_back(*it);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-03 07:02:00 +02:00
|
|
|
assert(randNum < 0);
|
|
|
|
// This should always happen; otherwise it
|
|
|
|
// wouldn't have chosen any of the variants.
|
2005-03-19 20:10:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
assert(choices.size() == m_Variants.size());
|
2005-04-03 07:02:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
// 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++)));
|
|
|
|
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:
|
|
|
|
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);
|
|
|
|
choices.insert(choices.end(), propChoices.begin(), propChoices.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// (TODO: This seems rather fragile, e.g. if props fail to load)
|
2005-03-19 20:10:52 +01:00
|
|
|
}
|