//----------------------------------------------------------------------------- // Copyright (c) 2013 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "io/stream.h" #include "string/stringUnit.h" #include "memory/frameAllocator.h" #include "component/behaviors/behaviorComponent.h" #include "component/behaviors/behaviorTemplate.h" #ifndef _ASSET_FIELD_TYPES_H_ #include "assets/assetFieldTypes.h" #endif #ifndef _TAML_CUSTOM_H_ #include "persistence/taml/tamlCustom.h" #endif #ifndef _TAML_H_ #include "persistence/taml/taml.h" #endif // Script bindings. #include "behaviorComponent_ScriptBinding.h" //----------------------------------------------------------------------------- static StringTableEntry behaviorIdFieldName = StringTable->insert( "Id" ); static StringTableEntry behaviorNodeName = StringTable->insert( "Behaviors" ); static StringTableEntry behaviorConnectionTypeName = StringTable->insert( "Connection" ); static StringTableEntry behaviorTemplateAssetName = StringTable->insert( "Asset" ); //----------------------------------------------------------------------------- BehaviorComponent::BehaviorComponent() : mMasterBehaviorId( 1 ), mpBehaviorFieldNames( NULL ) { SIMSET_SET_ASSOCIATION( mBehaviors ); } //----------------------------------------------------------------------------- bool BehaviorComponent::onAdd() { if( !Parent::onAdd() ) return false; return true; } //----------------------------------------------------------------------------- void BehaviorComponent::onRemove() { // Remove all behaviors and notify. clearBehaviors(); // Call parent. Parent::onRemove(); } //----------------------------------------------------------------------------- void BehaviorComponent::onDeleteNotify( SimObject *object ) { // Cast to a behavior instance. BehaviorInstance* pInstance = dynamic_cast( object ); // Ignore if not appropriate. if ( pInstance == NULL ) return; // Is the behavior instance owned by this component? if ( pInstance->getBehaviorOwner() == this ) { // Yes, so remove. removeBehavior( pInstance, false ); } // Destroy any input connections to the instance. destroyBehaviorInputConnections( pInstance ); } //----------------------------------------------------------------------------- void BehaviorComponent::copyTo(SimObject* obj) { // Call parent. Parent::copyTo(obj); // Fetch object. BehaviorComponent* pObject = dynamic_cast(obj); // Sanity! AssertFatal(pObject != NULL, "BehaviorComponent::copyTo() - Object is not the correct type."); // Clear behaviors. pObject->clearBehaviors(); // Behaviors U32 behaviorCount = getBehaviorCount(); // Finish if no behaviors. if ( behaviorCount == 0 ) return; // Initialize a clone map. typedef HashMap typeBehaviorCloneHash; typeBehaviorCloneHash behaviorInstanceCloneMap; // Iterate behaviors. for ( U32 index = 0; index < behaviorCount; ++index ) { // Clone the behavior instance. BehaviorInstance* pFromInstance = getBehavior(index); BehaviorTemplate* pFromTemplate = pFromInstance->getTemplate(); BehaviorInstance* pToInstance = pFromTemplate->createInstance(); // Assign dynamic fields from behavior instance. pToInstance->assignDynamicFieldsFrom( pFromInstance ); // Add the behavior instance. pObject->addBehavior(pToInstance); // Add to the clone map. behaviorInstanceCloneMap.insert( pFromInstance, pToInstance ); } // Iterate instance connections. for( typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.begin(); instanceItr != mBehaviorConnections.end(); ++instanceItr ) { // Fetch output name connection(s). typeOutputNameConnectionHash* pOutputNameConnection = instanceItr->value; // Iterate output name connections. for( typeOutputNameConnectionHash::iterator outputItr = pOutputNameConnection->begin(); outputItr != pOutputNameConnection->end(); ++outputItr ) { // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Iterate input connections. for( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Fetch connection. BehaviorPortConnection* pConnection = connectionItr; // Find behavior instance mappings. typeBehaviorCloneHash::iterator toOutputItr = behaviorInstanceCloneMap.find( pConnection->mOutputInstance ); typeBehaviorCloneHash::iterator toInputItr = behaviorInstanceCloneMap.find( pConnection->mInputInstance ); // Sanity! AssertFatal( toOutputItr != behaviorInstanceCloneMap.end(), "Failed to find output behavior instance mapping during copy." ); AssertFatal( toInputItr != behaviorInstanceCloneMap.end(), "Failed to find input behavior instance mapping during copy." ); // Fetch behavior instance mappings. BehaviorInstance* pToInstanceOutput = toOutputItr->value; BehaviorInstance* pToInstanceInput = toInputItr->value; // Make cloned connection. pObject->connect( pToInstanceOutput, pToInstanceInput, pConnection->mOutputName, pConnection->mInputName ); } } } } //----------------------------------------------------------------------------- bool BehaviorComponent::addBehavior( BehaviorInstance* bi ) { if( bi == NULL || !bi->isProperlyAdded() ) return false; // Store behavior. mBehaviors.pushObject( bi ); // Notify if the behavior instance is destroyed. deleteNotify( bi ); // Set the behavior owner. bi->setBehaviorOwner( this ); // Allocate a behavior Id. bi->setBehaviorId( mMasterBehaviorId++ ); if( bi->isMethod("onBehaviorAdd") ) Con::executef( bi , 1, "onBehaviorAdd" ); return true; } //----------------------------------------------------------------------------- bool BehaviorComponent::removeBehavior( BehaviorInstance *bi, bool deleteBehavior ) { for( SimSet::iterator itr = mBehaviors.begin(); itr != mBehaviors.end(); ++itr ) { if( *itr == bi ) { mBehaviors.removeObject( *itr ); // Perform callback if allowed. if( bi->isProperlyAdded() && bi->isMethod("onBehaviorRemove") ) Con::executef( bi , 1, "onBehaviorRemove" ); // Destroy any output connections. destroyBehaviorOutputConnections( bi ); if ( deleteBehavior && bi->isProperlyAdded() ) { bi->deleteObject(); } else { bi->setBehaviorOwner( NULL ); bi->setBehaviorId( 0 ); // Remove delete notification. clearNotify( bi ); } return true; } } return false; } //----------------------------------------------------------------------------- void BehaviorComponent::clearBehaviors() { while( mBehaviors.size() > 0 ) { BehaviorInstance *bi = dynamic_cast( mBehaviors.first() ); removeBehavior( bi ); } } //----------------------------------------------------------------------------- BehaviorInstance *BehaviorComponent::getBehavior( StringTableEntry behaviorTemplateName ) { for( SimSet::iterator itr = mBehaviors.begin(); itr != mBehaviors.end(); ++itr ) { // Fetch behavior. BehaviorInstance* pBehaviorInstance = dynamic_cast(*itr); if ( !pBehaviorInstance || pBehaviorInstance->getTemplateName() != behaviorTemplateName ) continue; return pBehaviorInstance; } return NULL; } //----------------------------------------------------------------------------- bool BehaviorComponent::reOrder( BehaviorInstance *obj, U32 desiredIndex /* = 0 */ ) { if( desiredIndex > (U32)mBehaviors.size() ) return false; SimObject *target = mBehaviors.at( desiredIndex ); return mBehaviors.reOrder( obj, target ); } //----------------------------------------------------------------------------- void BehaviorComponent::destroyBehaviorOutputConnections( BehaviorInstance* pOutputBehavior ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not destroy output behavior connection for behavior '%s' as the output behavior is not owned by this component.", pOutputBehavior->getTemplateName() ); return; } // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Finish if there are no outbound connections for this output behavior. if ( instanceItr == mBehaviorConnections.end() ) return; // Fetch output name hash. typeOutputNameConnectionHash* pOutputNameHash = instanceItr->value; // Iterate all outputs. for ( typeOutputNameConnectionHash::iterator outputItr = pOutputNameHash->begin(); outputItr != pOutputNameHash->end(); ++outputItr ) { // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Destroy port connections. delete pPortConnections; } // Destroy outputs. delete pOutputNameHash; // Remove connection. mBehaviorConnections.erase( pOutputBehavior->getId() ); } //----------------------------------------------------------------------------- void BehaviorComponent::destroyBehaviorInputConnections( BehaviorInstance* pInputBehavior ) { // Sanity! AssertFatal( pInputBehavior != NULL, "Input behavior cannot be NULL." ); // Iterate connections. for ( typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.begin(); instanceItr != mBehaviorConnections.end(); ++instanceItr ) { // Fetch output name hash. typeOutputNameConnectionHash* pOutputNameHash = instanceItr->value; // Iterate all outputs. for ( typeOutputNameConnectionHash::iterator outputItr = pOutputNameHash->begin(); outputItr != pOutputNameHash->end(); ++outputItr ) { // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; bool connectionFound; do { // Flag connection as 'not found' initially. connectionFound = false; // Look for an existing connection to the specified input instance. for ( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Is this the input behavior? if ( connectionItr->mInputInstance == pInputBehavior ) { // Yes, so destroy it. pPortConnections->erase_fast( connectionItr ); // Flag connection as 'found'. connectionFound = true; break; } } } while (connectionFound); } } } //----------------------------------------------------------------------------- BehaviorInstance* BehaviorComponent::getBehaviorByInstanceId( const U32 behaviorId ) { for( SimSet::iterator instanceItr = mBehaviors.begin(); instanceItr != mBehaviors.end(); ++instanceItr ) { // Fetch behavior instance. BehaviorInstance* pInstance = static_cast( *instanceItr ); // Return instance if it has the same behavior Id. if ( pInstance->getBehaviorId() == behaviorId ) return pInstance; } // Not found. return NULL; } //----------------------------------------------------------------------------- bool BehaviorComponent::connect( BehaviorInstance* pOutputBehavior, BehaviorInstance* pInputBehavior, StringTableEntry pOutputName, StringTableEntry pInputName ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pInputBehavior != NULL, "Input behavior cannot be NULL." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); AssertFatal( pInputName != NULL, "Input name cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not connect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the output behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Is the input behavior owned by this behavior component? if ( pInputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not connect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the input behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Does the output behavior have the specified output? if ( !pOutputBehavior->getTemplate()->hasBehaviorOutput( pOutputName ) ) { // No, so warn. Con::warnf( "Could not connect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the output behavior does not have such an output.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Does the input behavior have the specified input? if ( !pInputBehavior->getTemplate()->hasBehaviorInput( pInputName ) ) { // No, so warn. Con::warnf( "Could not connect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the input behavior does not have such an input.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Are there currently any outbound connections for this output instance? if ( instanceItr == mBehaviorConnections.end() ) { // No, so create an entry for this instance. typeOutputNameConnectionHash* pOutputHash = new typeOutputNameConnectionHash(); // Insert new output hash. instanceItr = mBehaviorConnections.insert( pOutputBehavior->getId(), pOutputHash ); } // Fetch output name hash. typeOutputNameConnectionHash* pOutputNameHash = instanceItr->value; // Find instance output connection. typeOutputNameConnectionHash::iterator outputItr = pOutputNameHash->find( pOutputName ); // Are there currently any outbound connections for this specific output? if ( outputItr == pOutputNameHash->end() ) { // No, so create an entry for this output. typePortConnectionVector* pPortConnections = new typePortConnectionVector(); // Insert new port connections. outputItr = pOutputNameHash->insert( pOutputName, pPortConnections ); } // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Look for an identical connection. for ( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Is this an identical connection? if ( connectionItr->mInputInstance == pInputBehavior && connectionItr->mInputName == pInputName ) { // Yes, so warn. Con::warnf( "Could not connect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the connection already exists.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } } // Populate port connection. BehaviorPortConnection portConnection( pOutputBehavior, pInputBehavior, pOutputName, pInputName ); // Add port connection. pPortConnections->push_back( portConnection ); // Notify if the input behavior instance is destroyed. deleteNotify( pInputBehavior ); return true; } //----------------------------------------------------------------------------- bool BehaviorComponent::disconnect( BehaviorInstance* pOutputBehavior, BehaviorInstance* pInputBehavior, StringTableEntry pOutputName, StringTableEntry pInputName ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pInputBehavior != NULL, "Input behavior cannot be NULL." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); AssertFatal( pInputName != NULL, "Input name cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' from input '%s' on behavior '%s' as the output behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Is the input behavior owned by this behavior component? if ( pInputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' from input '%s' on behavior '%s' as the input behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Does the output behavior have the specified output? if ( !pOutputBehavior->getTemplate()->hasBehaviorOutput( pOutputName ) ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the output behavior does not have such an output.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Does the input behavior have the specified input? if ( !pInputBehavior->getTemplate()->hasBehaviorInput( pInputName ) ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' to input '%s' on behavior '%s' as the input behavior does not have such an input.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Are there currently any outbound connections for this output instance? if ( instanceItr == mBehaviorConnections.end() ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' from input '%s' on behavior '%s' as the behavior does not have any connections.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Fetch output name hash. typeOutputNameConnectionHash* pOutputNameHash = instanceItr->value; // Find instance output connection. typeOutputNameConnectionHash::iterator outputItr = pOutputNameHash->find( pOutputName ); // Are there currently any outbound connections for this specific output? if ( outputItr == pOutputNameHash->end() ) { // No, so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' from input '%s' on behavior '%s' as the specified output does not have any connections.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Look for an existing connection to the specified input. for ( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Is this the requested disconnection? if ( connectionItr->mInputInstance == pInputBehavior && connectionItr->mInputName == pInputName ) { // Yes, so remove connection. pPortConnections->erase_fast( connectionItr ); return true; } } // Not found so warn. Con::warnf( "Could not disconnect output '%s' on behavior '%s' from input '%s' on behavior '%s' as the connection does not exist.", pOutputName, pOutputBehavior->getTemplateName(), pInputName, pInputBehavior->getTemplateName() ); return false; } //----------------------------------------------------------------------------- bool BehaviorComponent::raise( BehaviorInstance* pOutputBehavior, StringTableEntry pOutputName ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pOutputBehavior->isProperlyAdded(), "Output behavior is not registered." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not raise output '%s' on behavior '%s' as the output behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName() ); return false; } // Does the behavior have the specified output? if ( !pOutputBehavior->getTemplate()->hasBehaviorOutput( pOutputName ) ) { // No, so warn. Con::warnf( "Could not raise output '%s' on behavior '%s' as the behavior does not have such an output.", pOutputName, pOutputBehavior->getTemplateName() ); return false; } // Execute a callback for the output. // NOTE: This callback should not delete behaviors otherwise strange things can happen! Con::executef( this, 2, pOutputName, pOutputBehavior->getIdString() ); // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Finish if there are no outbound connections for this output behavior. if ( instanceItr == mBehaviorConnections.end() ) return true; // Find instance output connection. typeOutputNameConnectionHash::iterator outputItr = instanceItr->value->find( pOutputName ); // Finish if there are no outbound connections for this output. if ( outputItr == instanceItr->value->end() ) return true; // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Process output connection(s). for ( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Fetch input behavior. BehaviorInstance* pInputBehavior = connectionItr->mInputInstance; // Fetch input name. StringTableEntry pInputName = connectionItr->mInputName; #ifdef TORQUE_DEBUG // Sanity! AssertFatal( pInputBehavior->isProperlyAdded(), "Input behavior is not registered." ); // Does the behavior have the specified input? if ( !pInputBehavior->getTemplate()->hasBehaviorInput( pInputName ) ) { // No, so warn. Con::warnf( "Could not raise input '%s' on behavior '%s' as the behavior does not have such an input.", pInputName, pInputBehavior->getTemplateName() ); return false; } #endif // Execute a callback for the input. // NOTE: This callback should not delete behaviors otherwise strange things can happen! Con::executef( pInputBehavior, 3, pInputName, pOutputBehavior->getIdString(), pOutputName ); } return true; } //----------------------------------------------------------------------------- U32 BehaviorComponent::getBehaviorConnectionCount( BehaviorInstance* pOutputBehavior, StringTableEntry pOutputName ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not get behavior connection count on output '%s' on behavior '%s' as the output behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName() ); return 0; } // Does the behavior have the specified output? if ( !pOutputBehavior->getTemplate()->hasBehaviorOutput( pOutputName ) ) { // No, so warn. Con::warnf( "Could not get behavior connection count for output '%s' on behavior '%s' as the behavior does not have such an output.", pOutputName, pOutputBehavior->getTemplateName() ); return 0; } // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Finish if there are no outbound connections for this output behavior. if ( instanceItr == mBehaviorConnections.end() ) return 0; // Find instance output connection. typeOutputNameConnectionHash::iterator outputItr = instanceItr->value->find( pOutputName ); // Finish if there are no outbound connections for this output. if ( outputItr == instanceItr->value->end() ) return 0; // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Return number of connections. return pPortConnections->size(); } //----------------------------------------------------------------------------- const BehaviorComponent::BehaviorPortConnection* BehaviorComponent::getBehaviorConnection( BehaviorInstance* pOutputBehavior, StringTableEntry pOutputName, const U32 connectionIndex ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); // Fetch behavior connection count. const U32 behaviorConnectionCount = getBehaviorConnectionCount( pOutputBehavior, pOutputName ); // Finish if there are no connections. if ( behaviorConnectionCount == 0 ) return NULL; // Is the connection index valid? if ( connectionIndex >= behaviorConnectionCount ) { // No, so warn. Con::warnf( "Could not get behavior the behavior connection index '%d' on output '%s' on behavior '%s' as the output behavior only has '%d' connections", connectionIndex, pOutputName, pOutputBehavior->getTemplateName(), behaviorConnectionCount ); return NULL; } // Fetch behavior connections. const typePortConnectionVector* pConnections = getBehaviorConnections( pOutputBehavior, pOutputName ); // Fetch behavior connection. const BehaviorComponent::BehaviorPortConnection* pBehaviorConnection = &((*pConnections)[connectionIndex]); // Return behavior connection. return pBehaviorConnection; } //----------------------------------------------------------------------------- const BehaviorComponent::typePortConnectionVector* BehaviorComponent::getBehaviorConnections( BehaviorInstance* pOutputBehavior, StringTableEntry pOutputName ) { // Sanity! AssertFatal( pOutputBehavior != NULL, "Output behavior cannot be NULL." ); AssertFatal( pOutputName != NULL, "Output name cannot be NULL." ); // Is the output behavior owned by this behavior component? if ( pOutputBehavior->getBehaviorOwner() != this ) { // No, so warn. Con::warnf( "Could not get behavior connections on output '%s' on behavior '%s' as the output behavior is not owned by this component.", pOutputName, pOutputBehavior->getTemplateName() ); return NULL; } // Does the behavior have the specified output? if ( !pOutputBehavior->getTemplate()->hasBehaviorOutput( pOutputName ) ) { // No, so warn. Con::warnf( "Could not get behavior connections for output '%s' on behavior '%s' as the behavior does not have such an output.", pOutputName, pOutputBehavior->getTemplateName() ); return NULL; } // Find behavior instance connections. typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.find( pOutputBehavior->getId() ); // Finish if there are no outbound connections for this output behavior. if ( instanceItr == mBehaviorConnections.end() ) return NULL; // Find instance output connection. typeOutputNameConnectionHash::iterator outputItr = instanceItr->value->find( pOutputName ); // Finish if there are no outbound connections for this output. if ( outputItr == instanceItr->value->end() ) return NULL; // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Return number of connections. return pPortConnections; } //----------------------------------------------------------------------------- void BehaviorComponent::write( Stream &stream, U32 tabStop, U32 flags /* = 0 */ ) { // Export selected only? if( ( flags & SelectedOnly ) && !isSelected() ) { return; } if( mBehaviors.size() == 0 ) { Parent::write( stream, tabStop, flags ); return; } // The work we want to perform here is in the Taml callback. onTamlPreWrite(); // Write object. Parent::write( stream, tabStop, flags ); // The work we want to perform here is in the Taml callback. onTamlPostWrite(); } //----------------------------------------------------------------------------- bool BehaviorComponent::handlesConsoleMethod( const char *fname, S32 *routingId ) { // CodeReview [6/25/2007 justind] // If we're deleting the BehaviorComponent, don't forward the call to the // behaviors, the parent onRemove will handle freeing them // This should really be handled better, and is in the Parent implementation // but behaviors are a special case because they always want to be called BEFORE // the parent to act. if( dStricmp( fname, "delete" ) == 0 ) return Parent::handlesConsoleMethod( fname, routingId ); for( SimSet::iterator nItr = mBehaviors.begin(); nItr != mBehaviors.end(); nItr++ ) { SimObject *pComponent = dynamic_cast(*nItr); if( pComponent != NULL && pComponent->isMethod( fname ) ) { *routingId = -2; // -2 denotes method on component return true; } } // Let parent handle it return Parent::handlesConsoleMethod( fname, routingId ); } //----------------------------------------------------------------------------- // Needed to be able to directly call execute on a Namespace::Entry extern ExprEvalState gEvalState; const char *BehaviorComponent::callOnBehaviors( U32 argc, const char *argv[] ) { if( mBehaviors.empty() ) return Parent::callOnBehaviors( argc, argv ); // Copy the arguments to avoid weird clobbery situations. FrameTemp argPtrs (argc); U32 strdupWatermark = FrameAllocator::getWaterMark(); for( U32 i = 0; i < argc; i++ ) { argPtrs[i] = reinterpret_cast( FrameAllocator::alloc( dStrlen( argv[i] ) + 1 ) ); dStrcpy( argPtrs[i], argv[i] ); } // Walk backwards through the list just as with components const char* result = ""; bool handled = false; for( SimSet::iterator i = (mBehaviors.end()-1); i >= mBehaviors.begin(); i-- ) { BehaviorInstance *pBehavior = dynamic_cast( *i ); AssertFatal( pBehavior, "BehaviorComponent::callOnBehaviors - Bad behavior instance in list." ); AssertFatal( pBehavior->getId() > 0, "Invalid id for behavior component" ); // Use the BehaviorInstance's namespace Namespace *pNamespace = pBehavior->getNamespace(); if(!pNamespace) continue; // Lookup the Callback Namespace entry and then splice callback const char *cbName = StringTable->insert(argv[0]); Namespace::Entry *pNSEntry = pNamespace->lookup(cbName); if( pNSEntry ) { // Set %this to our BehaviorInstance's Object ID argPtrs[1] = const_cast( pBehavior->getIdString() ); // Change the Current Console object, execute, restore Object SimObject *save = gEvalState.thisObject; gEvalState.thisObject = pBehavior; result = pNSEntry->execute(argc, const_cast( ~argPtrs ), &gEvalState); gEvalState.thisObject = save; handled = true; break; } } // If this wasn't handled by a behavior above then pass along to the parent DynamicConsoleMethodComponent // to deal with it. If the parent cannot handle the message it will return an error string. if (!handled) { result = Parent::callOnBehaviors( argc, argv ); } // Clean up. FrameAllocator::setWaterMark( strdupWatermark ); return result; } //----------------------------------------------------------------------------- const char *BehaviorComponent::_callMethod( U32 argc, const char *argv[], bool callThis /* = true */ ) { if( mBehaviors.empty() ) return Parent::_callMethod( argc, argv, callThis ); // Copy the arguments to avoid weird clobbery situations. FrameTemp argPtrs (argc); U32 strdupWatermark = FrameAllocator::getWaterMark(); for( U32 i = 0; i < argc; i++ ) { argPtrs[i] = reinterpret_cast( FrameAllocator::alloc( dStrlen( argv[i] ) + 1 ) ); dStrcpy( argPtrs[i], argv[i] ); } for( SimSet::iterator i = mBehaviors.begin(); i != mBehaviors.end(); i++ ) { BehaviorInstance *pBehavior = dynamic_cast( *i ); AssertFatal( pBehavior, "BehaviorComponent::_callMethod - Bad behavior instance in list." ); AssertFatal( pBehavior->getId() > 0, "Invalid id for behavior component" ); // Use the BehaviorInstance's namespace Namespace *pNamespace = pBehavior->getNamespace(); if(!pNamespace) continue; // Lookup the Callback Namespace entry and then splice callback const char *cbName = StringTable->insert(argv[0]); Namespace::Entry *pNSEntry = pNamespace->lookup(cbName); if( pNSEntry ) { // Set %this to our BehaviorInstance's Object ID argPtrs[1] = const_cast( pBehavior->getIdString() ); // Change the Current Console object, execute, restore Object SimObject *save = gEvalState.thisObject; gEvalState.thisObject = pBehavior; pNSEntry->execute(argc, const_cast( ~argPtrs ), &gEvalState); gEvalState.thisObject = save; } } // Pass this up to the parent since a BehaviorComponent is still a DynamicConsoleMethodComponent // it needs to be able to contain other components and behave properly const char* fnRet = Parent::_callMethod( argc, argv, callThis ); // Clean up. FrameAllocator::setWaterMark( strdupWatermark ); return fnRet; } //----------------------------------------------------------------------------- void BehaviorComponent::onTamlCustomWrite( TamlCustomNodes& customNodes ) { // Call parent. Parent::onTamlCustomWrite( customNodes ); // Fetch behavior count. const U32 behaviorCount = (U32)mBehaviors.size(); // Finish if no behaviors. if( behaviorCount == 0 ) return; // Fetch behavior template asset field type. StringTableEntry behaviorTemplateAssetFieldType = StringTable->insert( behaviorTemplateAssetName ); // Add custom behaviors node. TamlCustomNode* pCustomBehaviorNode = customNodes.addNode( behaviorNodeName ); // Iterate behaviors. for( SimSet::iterator behaviorItr = mBehaviors.begin(); behaviorItr != mBehaviors.end(); ++behaviorItr ) { // Fetch behavior. BehaviorInstance* pBehaviorInstance = dynamic_cast( *behaviorItr ); // Fetch template. BehaviorTemplate* pBehaviorTemplate = pBehaviorInstance->getTemplate(); // Add behavior node. TamlCustomNode* pBehaviorNode = pCustomBehaviorNode->addNode( pBehaviorInstance->getTemplateName() ); // Add behavior Id field. pBehaviorNode->addField( behaviorIdFieldName, pBehaviorInstance->getBehaviorId() ); // Fetch field count, const U32 behaviorFieldCount = pBehaviorTemplate->getBehaviorFieldCount(); // Write out the fields which the behavior template knows about. for( U32 fieldIndex = 0; fieldIndex < behaviorFieldCount; ++fieldIndex ) { // Fetch field. BehaviorTemplate::BehaviorField* pBehaviorField = pBehaviorTemplate->getBehaviorField( fieldIndex ); // Set default field type. S32 fieldType = -1; // Is this an asset field type? if ( pBehaviorField != NULL && pBehaviorField->mType == behaviorTemplateAssetFieldType ) { // Yes, so update field type. fieldType = TypeAssetId; } // Fetch field value. const char* pFieldValue = pBehaviorInstance->getPrefixedDynamicDataField( pBehaviorField->mName, NULL, fieldType ); // Add behavior field. pBehaviorNode->addField( pBehaviorField->mName, pFieldValue ); } } // Fetch behavior connection count. const U32 behaviorConnectionCount = (U32)mBehaviorConnections.size(); // Finish if no behavior connections. if ( behaviorConnectionCount == 0 ) return; // Iterate instance connections. for( typeInstanceConnectionHash::iterator instanceItr = mBehaviorConnections.begin(); instanceItr != mBehaviorConnections.end(); ++instanceItr ) { // Fetch output name connection(s). typeOutputNameConnectionHash* pOutputNameConnection = instanceItr->value; // Iterate output name connections. for( typeOutputNameConnectionHash::iterator outputItr = pOutputNameConnection->begin(); outputItr != pOutputNameConnection->end(); ++outputItr ) { // Fetch port connection(s). typePortConnectionVector* pPortConnections = outputItr->value; // Iterate input connections. for( typePortConnectionVector::iterator connectionItr = pPortConnections->begin(); connectionItr != pPortConnections->end(); ++connectionItr ) { // Fetch connection. BehaviorPortConnection* pConnection = connectionItr; // Add connectionnode. TamlCustomNode* pConnectionNode = pCustomBehaviorNode->addNode( behaviorConnectionTypeName ); // Add behavior field. pConnectionNode->addField( pConnection->mOutputName, pConnection->mOutputInstance->getBehaviorId() ); pConnectionNode->addField( pConnection->mInputName, pConnection->mInputInstance->getBehaviorId() ); } } } } //----------------------------------------------------------------------------- void BehaviorComponent::onTamlCustomRead( const TamlCustomNodes& customNodes ) { // Call parent. Parent::onTamlCustomRead( customNodes ); // Find custom behaviors node. const TamlCustomNode* pCustomBehaviorNode = customNodes.findNode( behaviorNodeName ); // Do we have the property? if ( pCustomBehaviorNode != NULL ) { // Yes, so reset maximum behavior Id. S32 maximumBehaviorId = 0; // Fetch behavior Id field name. StringTableEntry behaviorFieldIdName = StringTable->insert( behaviorIdFieldName ); // Fetch behavior template asset field type. StringTableEntry behaviorTemplateAssetFieldType = StringTable->insert( behaviorTemplateAssetName ); // Fetch children behavior nodes. const TamlCustomNodeVector& behaviorNodes = pCustomBehaviorNode->getChildren(); // Iterate behavior nodes. for( TamlCustomNodeVector::const_iterator behaviorNodeItr = behaviorNodes.begin(); behaviorNodeItr != behaviorNodes.end(); ++behaviorNodeItr ) { // Fetch behavior node. TamlCustomNode* pBehaviorNode = *behaviorNodeItr; if ( pBehaviorNode->getNodeName() == behaviorConnectionTypeName ) { // Fetch field nodes. const TamlCustomFieldVector& connectionFieldNodes = pBehaviorNode->getFields(); // Are there two properties? if ( connectionFieldNodes.size() != 2 ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Encountered a behavior connection with more than two connection fields." ); continue; } // Fetch property field #1. TamlCustomField* pPropertyField1 = *connectionFieldNodes.begin(); TamlCustomField* pPropertyField2 = *(connectionFieldNodes.begin()+1); // Fetch behavior instances #1. BehaviorInstance* pBehaviorInstance1 = getBehaviorByInstanceId( dAtoi( pPropertyField1->getFieldValue() ) ); // Did we find the behavior? if ( pBehaviorInstance1 == NULL ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Could not find a behavior instance Id '%s=%s'.", pPropertyField1->getFieldName(), pPropertyField1->getFieldValue() ); continue; } // Fetch behavior instances #2. BehaviorInstance* pBehaviorInstance2 = getBehaviorByInstanceId( dAtoi( pPropertyField2->getFieldValue() ) ); // Did we find the behavior? if ( pBehaviorInstance2 == NULL ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Could not find a behavior instance Id '%s=%s'.", pPropertyField2->getFieldName(), pPropertyField2->getFieldValue() ); continue; } // Fetch behavior fields. StringTableEntry behaviorFieldName1 = pPropertyField1->getFieldName(); StringTableEntry behaviorFieldName2 = pPropertyField2->getFieldName(); BehaviorInstance* pOutputInstance; BehaviorInstance* pInputInstance; StringTableEntry outputName; StringTableEntry inputName; // Is the output on behavior instance #1? if ( pBehaviorInstance1->getTemplate()->hasBehaviorOutput( behaviorFieldName1 ) ) { // Yes, so has behavior instance #2 got the input? if ( !pBehaviorInstance2->getTemplate()->hasBehaviorInput( behaviorFieldName2 ) ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Could not find a behavior input '%s' on behavior '%s'.", behaviorFieldName2, pBehaviorInstance2->getTemplateName() ); continue; } // Assign output/input appropriately. pOutputInstance = pBehaviorInstance1; pInputInstance = pBehaviorInstance2; outputName = behaviorFieldName1; inputName = behaviorFieldName2; } // Is the output on behavior instance #2? else if ( pBehaviorInstance2->getTemplate()->hasBehaviorOutput( behaviorFieldName2 ) ) { // Yes, so has behavior instance #1 got the input? if ( !pBehaviorInstance1->getTemplate()->hasBehaviorInput( behaviorFieldName1 ) ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Could not find a behavior input '%s' on behavior '%s'.", behaviorFieldName1, pBehaviorInstance1->getTemplateName() ); continue; } // Assign output/input appropriately. pOutputInstance = pBehaviorInstance2; pInputInstance = pBehaviorInstance1; outputName = behaviorFieldName2; inputName = behaviorFieldName1; } else { // Warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Invalid output/input on behavior connection '%s=%s' & '%s=%s''.", behaviorFieldName1, pBehaviorInstance1->getTemplateName(), behaviorFieldName2, pBehaviorInstance2->getTemplateName() ); continue; } // Can we connect? if ( !connect( pOutputInstance, pInputInstance, outputName, inputName ) ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Failed to connect behaviors '%s=%s' & '%s=%s''.", behaviorFieldName1, pBehaviorInstance1->getTemplateName(), behaviorFieldName2, pBehaviorInstance2->getTemplateName() ); continue; } } else { // Fetch template. BehaviorTemplate* pTemplate = dynamic_cast( Sim::findObject( pBehaviorNode->getNodeName() ) ); // Find template? if( pTemplate == NULL ) { // No, so warn appropriately. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Missing Behavior '%s'", pBehaviorNode->getNodeName() ); if( isMethod( "onBehaviorMissing" ) ) Con::executef( this, 2, "onBehaviorMissing", pBehaviorNode->getNodeName() ); // Skip it. continue; } // Create an instance of the template. BehaviorInstance* pBehaviorInstance = pTemplate->createInstance(); // Did we create a behavior instance? if ( pBehaviorInstance == NULL ) { // No, so warn appropriately. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Found behavior could not create an instance '%s'", pBehaviorNode->getNodeName() ); if( isMethod( "onBehaviorMissing" ) ) Con::executef( this, 2, "onBehaviorMissing", pBehaviorNode->getNodeName() ); // Skip it. continue; } S32 behaviorId = 0; // Fetch field nodes. const TamlCustomFieldVector& fields = pBehaviorNode->getFields(); // Iterate fields. for ( TamlCustomFieldVector::const_iterator nodeFieldItr = fields.begin(); nodeFieldItr != fields.end(); ++nodeFieldItr ) { // Fetch field. TamlCustomField* pField = *nodeFieldItr; // Fetch field name. const char* pFieldName = pField->getFieldName(); // Fetch field value. const char* pFieldValue = pField->getFieldValue(); // Is this the behavior field Id name? if ( pFieldName == behaviorFieldIdName ) { // Yes, so assign it. behaviorId = dAtoi( pFieldValue ); // Is the behavior Id valid? if ( behaviorId < 1 ) { // No, so warn. Con::warnf( "BehaviorComponent::onTamlCustomRead() - Encountered an invalid behavior Id of '%d' on behavior '%s'.", behaviorId, pBehaviorNode->getNodeName() ); } // Update maximum behavior Id found. if ( behaviorId > maximumBehaviorId ) maximumBehaviorId = behaviorId; /// Skip it. continue; } // Fetch behavior field. BehaviorTemplate::BehaviorField* pBehaviorField = pTemplate->getBehaviorField( pFieldName ); // Set default field type. S32 fieldType = -1; // Is this an asset field type? if ( pBehaviorField != NULL && pBehaviorField->mType == behaviorTemplateAssetFieldType ) { // Yes, so update field type. fieldType = TypeAssetId; } // Set field. pBehaviorInstance->setPrefixedDynamicDataField( pField->getFieldName(), NULL, pField->getFieldValue(), fieldType ); } // Add behavior. addBehavior( pBehaviorInstance ); // Override the automatically allocated behavior Id if the Id field is already defined in the TAML file. // NOTE: This must be done after adding the behavior. if (behaviorId != 0) { pBehaviorInstance->setBehaviorId( behaviorId ); } } } // Set master as next behavior id. mMasterBehaviorId = (U32)maximumBehaviorId+1; } } //----------------------------------------------------------------------------- static void WriteCustomTamlSchema( const AbstractClassRep* pClassRep, TiXmlElement* pParentElement ) { // Sanity! AssertFatal( pClassRep != NULL, "BehaviorComponent::WriteCustomTamlSchema() - ClassRep cannot be NULL." ); AssertFatal( pParentElement != NULL, "BehaviorComponent::WriteCustomTamlSchema() - Parent Element cannot be NULL." ); // Write an unrestricted custom Taml schema. Taml::WriteUnrestrictedCustomTamlSchema( behaviorNodeName, pClassRep, pParentElement ); } //----------------------------------------------------------------------------- IMPLEMENT_CONOBJECT_SCHEMA( BehaviorComponent, WriteCustomTamlSchema );