0ad/source/simulation/EntityTemplate.cpp
Alexander 7eaa6d3454 Territorial limits applied
This was SVN commit r5244.
2007-07-14 06:15:19 +00:00

490 lines
17 KiB
C++

#include "precompiled.h"
#include "EntityTemplate.h"
#include "EntityTemplateCollection.h"
#include "graphics/ObjectManager.h"
#include "ps/CStr.h"
#include "ps/Player.h"
#include "scripting/ScriptableComplex.inl"
#include "ps/XML/Xeromyces.h"
#include "ps/CLogger.h"
#define LOG_CATEGORY "entity"
STL_HASH_SET<CStr, CStr_hash_compare> CEntityTemplate::scriptsLoaded;
// Utility functions used in reading XML
namespace {
/**
* Converts the given string, consisting of underscores and letters,
* to camelCase: the first letter of each underscore-separated "word"
* is changed to lowercase. For example, the string MiniMap_Colour
* is converted to miniMap_colour.
*
* @param const std::string & str The string to convert.
* @return std::string The given string in camelCase.
**/
std::string toCamelCase( const std::string& str )
{
std::string ret = str;
for( size_t i=0; i<ret.size(); i++ )
{
if( i==0 || ret[i-1] == '_' )
{
ret[i] = tolower( ret[i] );
}
}
return ret;
}
}
CEntityTemplate::CEntityTemplate( CPlayer* player )
{
m_player = player;
m_base = NULL;
AddProperty( L"tag", &m_Tag, false );
AddProperty( L"parent", &m_base, false );
AddProperty( L"unmodified", &m_unmodified, false );
for( int t = 0; t < EVENT_LAST; t++ )
{
AddProperty( EventNames[t], &m_EventHandlers[t], false );
AddHandler( t, &m_EventHandlers[t] );
}
// Initialize, make life a little easier on the scriptors
m_speed = m_turningRadius = 0.0f;
//m_extant = true;
m_foundation = CStrW();
m_socket = CStrW();
m_passThroughAllies = false;
m_sectorDivs = 4;
// Sentinel values for stamina and health (so scripts can check if an entity has no stamina or no HP).
m_speed = 0;
m_staminaMax = 0;
m_healthMax = 0;
m_bound_type = CBoundingObject::BOUND_NONE;
m_bound_circle = NULL;
m_bound_box = NULL;
// If these aren't set, we can get an infinite loop in CEntity::interpolate, which is nasty; they
// should be set in template_entity, but do this in case some entity forgets to inherit from that
m_anchorConformX = 0;
m_anchorConformZ = 0;
}
CEntityTemplate::~CEntityTemplate()
{
if( m_bound_box )
delete( m_bound_box );
if( m_bound_circle )
delete( m_bound_circle );
}
void CEntityTemplate::LoadBase()
{
// Copy the parent's bounds, unless we're providing a replacement.
if( m_bound_type == CBoundingObject::BOUND_NONE )
{
if( m_base->m_bound_type == CBoundingObject::BOUND_CIRCLE )
{
m_bound_circle = new CBoundingCircle();
m_bound_circle->SetRadius( m_base->m_bound_circle->m_radius );
m_bound_circle->SetHeight( m_base->m_bound_circle->m_height );
}
else if( m_base->m_bound_type == CBoundingObject::BOUND_OABB )
{
m_bound_box = new CBoundingBox();
m_bound_box->SetDimensions( m_base->m_bound_box->GetWidth(), m_base->m_bound_box->GetDepth() );
m_bound_box->SetHeight( m_base->m_bound_box->m_height );
}
m_bound_type = m_base->m_bound_type;
}
// Initialize sound group table from the parent's sound group table
for(SoundGroupTable::iterator it = m_base->m_SoundGroupTable.begin();
it != m_base->m_SoundGroupTable.end(); ++it)
{
m_SoundGroupTable[it->first] = it->second;
}
SetBase( m_base );
m_classes.SetParent( &( m_base->m_classes ) );
SetNextObject( m_base );
}
jsval CEntityTemplate::GetClassSet()
{
CStrW result = m_classes.GetMemberList();
return( ToJSVal( result ) );
}
void CEntityTemplate::SetClassSet( jsval value )
{
CStr memberCmdList = ToPrimitive<CStrW>( value );
m_classes.SetFromMemberList(memberCmdList);
RebuildClassSet();
}
void CEntityTemplate::RebuildClassSet()
{
m_classes.Rebuild();
InheritorsList::iterator it;
for( it = m_Inheritors.begin(); it != m_Inheritors.end(); it++ )
(*it)->RebuildClassSet();
}
bool CEntityTemplate::LoadXml( const CStr& filename )
{
CXeromyces XeroFile;
if (XeroFile.Load(filename) != PSRETURN_OK)
// Fail
return false;
// 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)
// Only the ones we can't load using normal methods.
EL(Entity);
EL(Script);
EL(Event);
EL(Traits);
EL(Footprint);
EL(Depth);
EL(Height);
EL(Radius);
EL(Width);
EL(SoundGroups);
AT(Parent);
AT(On);
AT(File);
AT(Function);
#undef AT
#undef EL
XMBElement Root = XeroFile.GetRoot();
if( Root.GetNodeName() != el_Entity )
{
LOG( ERROR, LOG_CATEGORY, "CEntityTemplate::LoadXml: XML root was not \"Entity\" in file %s. Load failed.", filename.c_str() );
return( false );
}
XMBElementList RootChildren = Root.GetChildNodes();
m_Tag = CStr(filename).AfterLast("/").BeforeLast(".xml");
m_Base_Name = Root.GetAttributes().GetNamedItem( at_Parent );
// Load our parent, if we have one
if( ! m_Base_Name.empty() )
{
CEntityTemplate* base = g_EntityTemplateCollection.GetTemplate( m_Base_Name, m_player );
if( base )
{
m_base = base;
LoadBase();
}
else
{
LOG( WARNING, LOG_CATEGORY, "Parent template \"%ls\" does not exist in template \"%ls\"", m_Base_Name.c_str(), m_Tag.c_str() );
// (The requested entity will still be returned, but with no parent.
// Is this a reasonable thing to do?)
}
}
for (int i = 0; i < RootChildren.Count; ++i)
{
XMBElement Child = RootChildren.Item(i);
int ChildName = Child.GetNodeName();
if( ChildName == el_Script )
{
CStr Include = Child.GetAttributes().GetNamedItem( at_File );
if( !Include.empty() && scriptsLoaded.find( Include ) == scriptsLoaded.end() )
{
scriptsLoaded.insert( Include );
g_ScriptingHost.RunScript( Include );
}
CStr Inline = Child.GetText();
if( !Inline.empty() )
{
g_ScriptingHost.RunMemScript( Inline.c_str(), Inline.length(), filename.c_str(), Child.GetLineNumber() );
}
}
else if (ChildName == el_Traits)
{
XMBElementList TraitChildren = Child.GetChildNodes();
for(int j = 0; j < TraitChildren.Count; ++j)
{
XMBElement TraitChild = TraitChildren.Item(j);
int TraitChildName = TraitChild.GetNodeName();
if( TraitChildName == el_Footprint )
{
XMBElementList FootprintChildren = TraitChild.GetChildNodes();
float radius=0, height=0, width=0, depth=0;
bool hadRadius = false, hadDepth = false;
for(int k = 0; k < FootprintChildren.Count; ++k)
{
XMBElement FootprintChild = FootprintChildren.Item(k);
int FootprintChildName = FootprintChild.GetNodeName();
if( FootprintChildName == el_Radius )
{
hadRadius = true;
radius = CStrW( FootprintChild.GetText() ).ToFloat();
}
else if( FootprintChildName == el_Width )
{
width = CStrW( FootprintChild.GetText() ).ToFloat();
}
else if( FootprintChildName == el_Height )
{
height = CStrW( FootprintChild.GetText() ).ToFloat();
}
else if( FootprintChildName == el_Depth )
{
hadDepth = true;
depth = CStrW( FootprintChild.GetText() ).ToFloat();
}
}
if( hadRadius )
{
// Specifying a circular footprint
if( !m_bound_circle )
m_bound_circle = new CBoundingCircle();
m_bound_circle->SetRadius( radius );
m_bound_circle->SetHeight( height );
m_bound_type = CBoundingObject::BOUND_CIRCLE;
}
else if( hadDepth )
{
// Specifying a rectangular footprint
if( !m_bound_box )
m_bound_box = new CBoundingBox();
m_bound_box->SetDimensions( width, depth );
m_bound_box->SetHeight( height );
m_bound_type = CBoundingObject::BOUND_OABB;
}
// Else, entity has no footprint.
}
}
// important so that scripts can see traits
XMLLoadProperty( XeroFile, Child, CStrW() );
}
else if( ChildName == el_Event )
{
// Action...On for consistency with the GUI.
CStrW EventName = L"on" + (CStrW)Child.GetAttributes().GetNamedItem( at_On );
CStrW Code (Child.GetText());
utf16string ExternalFunction = Child.GetAttributes().GetNamedItem( at_Function );
// Does a property with this name already exist?
for( uint eventID = 0; eventID < EVENT_LAST; eventID++ )
{
if( CStrW( EventNames[eventID] ) == EventName )
{
if( ExternalFunction != utf16string() )
{
jsval fnval;
JSBool ret = JS_GetUCProperty( g_ScriptingHost.GetContext(), g_ScriptingHost.GetGlobalObject(), ExternalFunction.c_str(), ExternalFunction.size(), &fnval );
debug_assert( ret );
JSFunction* fn = JS_ValueToFunction( g_ScriptingHost.GetContext(), fnval );
if( !fn )
{
LOG( ERROR, LOG_CATEGORY, "CEntityTemplate::LoadXml: Function does not exist for event %hs in file %s. Load failed.", EventName.c_str(), filename.c_str() );
break;
}
m_EventHandlers[eventID].SetFunction( fn );
}
else
m_EventHandlers[eventID].Compile( CStrW( filename ) + L"::" + EventName + L" (" + CStrW( Child.GetLineNumber() ) + L")", Code );
HasProperty( EventName )->m_Inherited = false;
break;
}
}
}
else if( ChildName == el_SoundGroups )
{
// Read every child element's value into m_SoundGroupTable with its tag as the key
XMBElementList children = Child.GetChildNodes();
for(int j = 0; j < children.Count; ++j)
{
XMBElement child = children.Item(j);
CStr8 name = toCamelCase( XeroFile.GetElementString( child.GetNodeName() ) );
CStr8 value = child.GetText();
m_SoundGroupTable[name] = value;
}
}
else
{
XMLLoadProperty( XeroFile, Child, CStrW() );
}
}
if( m_player == 0 )
{
m_unmodified = this;
}
else
{
m_unmodified = g_EntityTemplateCollection.GetTemplate( m_Tag, 0 );
}
return true;
}
void CEntityTemplate::XMLLoadProperty( const CXeromyces& XeroFile, const XMBElement& Source, const CStrW& BasePropertyName )
{
// Add a property, put the node text into it.
CStrW PropertyName = BasePropertyName + CStrW( toCamelCase( XeroFile.GetElementString( Source.GetNodeName() ) ) );
IJSComplexProperty* Existing = HasProperty( PropertyName );
if( Existing )
{
if( !Existing->m_Intrinsic )
LOG( WARNING, LOG_CATEGORY, "CEntityTemplate::XMLAddProperty: %ls already defined for %ls. Property trees will be merged.", PropertyName.c_str(), m_Tag.c_str() );
Existing->Set( this, JSParseString( Source.GetText() ) );
//Existing->m_Inherited = false;
}
else
{
if( !Source.GetText().length() )
{
// Arbitrarily say that if a node has no other value, define it to be 'true'.
// Appears to be the most convenient thing to do in most circumstances.
AddProperty( PropertyName, JSVAL_TRUE );
}
else
{
AddProperty( PropertyName, Source.GetText() );
}
}
PropertyName += CStrW( L"." );
// Retrieve any attributes it has and add them as subproperties.
XMBAttributeList AttributeSet = Source.GetAttributes();
for( unsigned int AttributeID = 0; AttributeID < (unsigned int)AttributeSet.Count; AttributeID++ )
{
XMBAttribute Attribute = AttributeSet.Item( AttributeID );
CStrW AttributeName = PropertyName + CStr8( toCamelCase( XeroFile.GetAttributeString( Attribute.Name ) ) );
Existing = HasProperty( AttributeName );
if( Existing )
{
Existing->Set( this, JSParseString( Attribute.Value ) );
Existing->m_Inherited = false;
}
else
AddProperty( AttributeName, Attribute.Value );
}
// Retrieve any child nodes the property has and, similarly, add them as subproperties.
XMBElementList NodeSet = Source.GetChildNodes();
for( unsigned int NodeID = 0; NodeID < (unsigned int)NodeSet.Count; NodeID++ )
{
XMBElement Node = NodeSet.Item( NodeID );
XMLLoadProperty( XeroFile, Node, PropertyName );
}
}
/*
Scripting Interface
*/
// Scripting initialization
void CEntityTemplate::ScriptingInit()
{
AddMethod<CStr, &CEntityTemplate::ToString>( "toString", 0 );
AddClassProperty( L"traits.id.classes", static_cast<GetFn>(&CEntityTemplate::GetClassSet), static_cast<SetFn>(&CEntityTemplate::SetClassSet) );
AddClassProperty( L"actions.move.speed", &CEntityTemplate::m_speed );
AddClassProperty( L"actions.move.turningRadius", &CEntityTemplate::m_turningRadius );
AddClassProperty( L"actions.move.run.speed", &CEntityTemplate::m_runSpeed );
AddClassProperty( L"actions.move.run.rangeMin", &CEntityTemplate::m_runMinRange );
AddClassProperty( L"actions.move.run.range", &CEntityTemplate::m_runMaxRange );
AddClassProperty( L"actions.move.run.regenRate", &CEntityTemplate::m_runRegenRate );
AddClassProperty( L"actions.move.run.decayRate", &CEntityTemplate::m_runDecayRate );
AddClassProperty( L"actions.move.passThroughAllies", &CEntityTemplate::m_passThroughAllies );
AddClassProperty( L"actor", &CEntityTemplate::m_actorName );
AddClassProperty( L"traits.health.max", &CEntityTemplate::m_healthMax );
AddClassProperty( L"traits.health.barHeight", &CEntityTemplate::m_healthBarHeight );
AddClassProperty( L"traits.health.barSize", &CEntityTemplate::m_healthBarSize );
AddClassProperty( L"traits.health.barWidth", &CEntityTemplate::m_healthBarWidth );
AddClassProperty( L"traits.health.borderHeight", &CEntityTemplate::m_healthBorderHeight);
AddClassProperty( L"traits.health.borderWidth", &CEntityTemplate::m_healthBorderWidth );
AddClassProperty( L"traits.health.borderName", &CEntityTemplate::m_healthBorderName );
AddClassProperty( L"traits.health.regenRate", &CEntityTemplate::m_healthRegenRate );
AddClassProperty( L"traits.health.regenStart", &CEntityTemplate::m_healthRegenStart );
AddClassProperty( L"traits.health.decayRate", &CEntityTemplate::m_healthDecayRate );
AddClassProperty( L"traits.stamina.max", &CEntityTemplate::m_staminaMax );
AddClassProperty( L"traits.stamina.barHeight", &CEntityTemplate::m_staminaBarHeight );
AddClassProperty( L"traits.stamina.barSize", &CEntityTemplate::m_staminaBarSize );
AddClassProperty( L"traits.stamina.barWidth", &CEntityTemplate::m_staminaBarWidth );
AddClassProperty( L"traits.stamina.borderHeight", &CEntityTemplate::m_staminaBorderHeight);
AddClassProperty( L"traits.stamina.borderWidth", &CEntityTemplate::m_staminaBorderWidth );
AddClassProperty( L"traits.stamina.borderName", &CEntityTemplate::m_staminaBorderName );
AddClassProperty( L"traits.rally.name", &CEntityTemplate::m_rallyName );
AddClassProperty( L"traits.rally.width", &CEntityTemplate::m_rallyWidth );
AddClassProperty( L"traits.rally.height", &CEntityTemplate::m_rallyHeight );
AddClassProperty( L"traits.flankPenalty.sectors", &CEntityTemplate::m_sectorDivs );
AddClassProperty( L"traits.pitch.sectors", &CEntityTemplate::m_pitchDivs );
AddClassProperty( L"traits.pitch.value", &CEntityTemplate::m_pitchValue );
AddClassProperty( L"traits.rank.width", &CEntityTemplate::m_rankWidth );
AddClassProperty( L"traits.rank.height", &CEntityTemplate::m_rankHeight );
AddClassProperty( L"traits.rank.name", &CEntityTemplate::m_rankName );
AddClassProperty( L"traits.ai.stance.curr", &CEntityTemplate::m_stanceName );
AddClassProperty( L"traits.miniMap.type", &CEntityTemplate::m_minimapType );
AddClassProperty( L"traits.miniMap.red", &CEntityTemplate::m_minimapR );
AddClassProperty( L"traits.miniMap.green", &CEntityTemplate::m_minimapG );
AddClassProperty( L"traits.miniMap.blue", &CEntityTemplate::m_minimapB );
AddClassProperty( L"traits.anchor.type", &CEntityTemplate::m_anchorType );
AddClassProperty( L"traits.anchor.conformX", &CEntityTemplate::m_anchorConformX );
AddClassProperty( L"traits.anchor.conformZ", &CEntityTemplate::m_anchorConformZ );
AddClassProperty( L"traits.vision.los", &CEntityTemplate::m_los );
AddClassProperty( L"traits.vision.permanent", &CEntityTemplate::m_visionPermanent );
AddClassProperty( L"traits.display.bars.enabled", &CEntityTemplate::m_barsEnabled );
AddClassProperty( L"traits.display.bars.offset", &CEntityTemplate::m_barOffset );
AddClassProperty( L"traits.display.bars.height", &CEntityTemplate::m_barHeight );
AddClassProperty( L"traits.display.bars.width", &CEntityTemplate::m_barWidth );
AddClassProperty( L"traits.display.bars.border", &CEntityTemplate::m_barBorder );
AddClassProperty( L"traits.display.bars.borderSize", &CEntityTemplate::m_barBorderSize );
AddClassProperty( L"traits.isTerritoryCentre", &CEntityTemplate::m_isTerritoryCentre );
AddClassProperty( L"traits.creation.foundation", &CEntityTemplate::m_foundation );
AddClassProperty( L"traits.creation.socket", &CEntityTemplate::m_socket );
AddClassProperty( L"traits.creation.territoryRestriction", &CEntityTemplate::m_territoryRestriction );
AddClassProperty( L"traits.creation.buildingLimitCategory", &CEntityTemplate::m_buildingLimitCategory );
CJSComplex<CEntityTemplate>::ScriptingInit( "EntityTemplate" );
}
// Script-bound functions
JSObject* CEntityTemplate::GetScriptExecContext( IEventTarget* target )
{
return( target->GetScriptExecContext( target ) );
}
CStr CEntityTemplate::ToString( JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv) )
{
if( m_player == 0 )
return "[object EntityTemplate: " + CStr(m_Tag) + " base]";
else
return "[object EntityTemplate: " + CStr(m_Tag) + " for player " + CStr(m_player->GetPlayerID()) + "]";
}