//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "isaverestore.h" #include "ai_behavior.h" #include "scripted.h" #include "env_debughistory.h" #include "saverestore_utlvector.h" #include "checksum_crc.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" bool g_bBehaviorHost_PreventBaseClassGatherConditions; //----------------------------------------------------------------------------- BEGIN_SIMPLE_DATADESC( AIChannelScheduleState_t ) DEFINE_FIELD( bActive, FIELD_BOOLEAN ), // pSchedule // idealSchedule // failSchedule DEFINE_FIELD( iCurTask, FIELD_INTEGER ), DEFINE_FIELD( fTaskStatus, FIELD_INTEGER ), DEFINE_FIELD( timeStarted, FIELD_TIME ), DEFINE_FIELD( timeCurTaskStarted, FIELD_TIME ), DEFINE_FIELD( taskFailureCode, FIELD_INTEGER ), DEFINE_FIELD( bScheduleWasInterrupted, FIELD_BOOLEAN ), END_DATADESC() //----------------------------------------------------------------------------- // CAI_BehaviorBase //----------------------------------------------------------------------------- BEGIN_DATADESC_NO_BASE( CAI_BehaviorBase ) DEFINE_UTLVECTOR( m_ScheduleChannels, FIELD_EMBEDDED ), END_DATADESC() //------------------------------------- CAI_ClassScheduleIdSpace *CAI_BehaviorBase::GetClassScheduleIdSpace() { return GetOuter()->GetClassScheduleIdSpace(); } //----------------------------------------------------------------------------- // Purpose: Expose classmap of CAI_BehaviorBase that gets statically initialized // in time for use regardless of compiler whimsy. This also exposes // g_pBehaviorClasses, which allows us to inspect the classmap // in a debug session //----------------------------------------------------------------------------- static CGenericClassmap< CAI_BehaviorBase > *g_pBehaviorClasses = NULL; CGenericClassmap< CAI_BehaviorBase > *CAI_BehaviorBase::GetBehaviorClasses() { if( !g_pBehaviorClasses ) { g_pBehaviorClasses = GetBehaviorClassesInternal(); } return g_pBehaviorClasses; } CGenericClassmap< CAI_BehaviorBase > *CAI_BehaviorBase::GetBehaviorClassesInternal() { static CGenericClassmap< CAI_BehaviorBase > s_BehaviorClasses; return &s_BehaviorClasses; } //----------------------------------------------------------------------------- // Purpose: Draw any text overlays (override in subclass to add additional text) // Input : Previous text offset from the top // Output : Current text offset from the top //----------------------------------------------------------------------------- int CAI_BehaviorBase::DrawDebugTextOverlays( int text_offset ) { char tempstr[ 512 ]; if ( GetOuter()->m_debugOverlays & OVERLAY_TEXT_BIT ) { Q_snprintf( tempstr, sizeof( tempstr ), "Behv: %s, ", GetName() ); GetOuter()->EntityText( text_offset, tempstr, 0 ); text_offset++; for ( int i = 0; i < m_ScheduleChannels.Count(); i++ ) { AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[i]; if ( pScheduleState->bActive && pScheduleState->pSchedule ) { const char *pName = NULL; pName = pScheduleState->pSchedule->GetName(); if ( !pName ) { pName = "Unknown"; } Q_snprintf(tempstr,sizeof(tempstr)," Schd [%d]: %s, ", i, pName ); GetOuter()->EntityText(text_offset,tempstr,0); text_offset++; const Task_t *pTask = GetCurTask( i ); if ( pTask ) { Q_snprintf(tempstr,sizeof(tempstr)," Task [%d]: %s (#%d), ", i, GetSchedulingSymbols()->TaskIdToSymbol( GetClassScheduleIdSpace()->TaskLocalToGlobal( pTask->iTask ) ), pScheduleState->iCurTask ); } else { Q_snprintf(tempstr,sizeof(tempstr)," Task [%d]: None",i); } GetOuter()->EntityText(text_offset,tempstr,0); text_offset++; } } } return text_offset; } //------------------------------------- void CAI_BehaviorBase::GatherConditions() { Assert( m_pBackBridge != NULL ); m_pBackBridge->BehaviorBridge_GatherConditions(); } //------------------------------------- void CAI_BehaviorBase::OnStartSchedule( int scheduleType ) { } //------------------------------------- int CAI_BehaviorBase::SelectSchedule() { Assert( m_pBackBridge != NULL ); return m_pBackBridge->BehaviorBridge_SelectSchedule(); } //------------------------------------- int CAI_BehaviorBase::SelectFailSchedule( int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) { m_fOverrode = false; return SCHED_NONE; } //------------------------------------- void CAI_BehaviorBase::StartTask( const Task_t *pTask ) { m_fOverrode = false; } //------------------------------------- void CAI_BehaviorBase::RunTask( const Task_t *pTask ) { m_fOverrode = false; } //------------------------------------- int CAI_BehaviorBase::TranslateSchedule( int scheduleType ) { Assert( m_pBackBridge != NULL ); return m_pBackBridge->BehaviorBridge_TranslateSchedule( scheduleType ); } //------------------------------------- CAI_Schedule *CAI_BehaviorBase::GetNewSchedule( int channel ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return NULL; } int scheduleType; // // Schedule selection code here overrides all leaf schedule selection. // scheduleType = SelectSchedule( channel ); CAI_Schedule *pSchedule = GetSchedule( scheduleType ); if( pSchedule != NULL ) m_ScheduleChannels[channel].idealSchedule = pSchedule->GetId(); return pSchedule; } //------------------------------------- CAI_Schedule *CAI_BehaviorBase::GetFailSchedule( AIChannelScheduleState_t *pScheduleState ) { int prevSchedule; int failedTask; if ( pScheduleState->pSchedule ) prevSchedule = GetClassScheduleIdSpace()->ScheduleGlobalToLocal( pScheduleState->pSchedule->GetId() ); else prevSchedule = SCHED_NONE; const Task_t *pTask = GetTask( pScheduleState ); if ( pTask ) failedTask = pTask->iTask; else failedTask = TASK_INVALID; Assert( AI_IdIsLocal( prevSchedule ) ); Assert( AI_IdIsLocal( failedTask ) ); int scheduleType = SelectFailSchedule( prevSchedule, failedTask, pScheduleState->taskFailureCode ); return GetSchedule( scheduleType ); } //------------------------------------- CAI_Schedule *CAI_BehaviorBase::GetSchedule(int schedule) { if (!GetClassScheduleIdSpace()->IsGlobalBaseSet()) { Warning("ERROR: %s missing schedule!\n", GetSchedulingErrorName()); return g_AI_SchedulesManager.GetScheduleFromID(SCHED_IDLE_STAND); } if( schedule == SCHED_NONE ) return NULL; if ( AI_IdIsLocal( schedule ) ) { schedule = GetClassScheduleIdSpace()->ScheduleLocalToGlobal(schedule); } if ( schedule == -1 ) return NULL; return g_AI_SchedulesManager.GetScheduleFromID( schedule ); } //------------------------------------- const Task_t *CAI_BehaviorBase::GetTask( AIChannelScheduleState_t *pScheduleState ) { int iScheduleIndex = pScheduleState->iCurTask; if ( !pScheduleState->pSchedule || iScheduleIndex < 0 || iScheduleIndex >= pScheduleState->pSchedule->NumTasks() ) // iScheduleIndex is not within valid range for the NPC's current schedule. return NULL; return &pScheduleState->pSchedule->GetTaskList()[ iScheduleIndex ]; } //------------------------------------- bool CAI_BehaviorBase::IsCurSchedule( int schedule, bool fIdeal ) { if ( AI_IdIsLocal( schedule ) ) schedule = GetClassScheduleIdSpace()->ScheduleLocalToGlobal(schedule); return GetOuter()->IsCurSchedule( schedule, fIdeal ); } //------------------------------------- const char *CAI_BehaviorBase::GetSchedulingErrorName() { return "CAI_Behavior"; } //------------------------------------- void CAI_BehaviorBase::StartChannel( int channel ) { if ( channel < 0 ) { AssertMsg( 0, "Bad schedule channel" ); return; } m_ScheduleChannels.EnsureCount( channel + 1 ); m_ScheduleChannels[channel].bActive = true; } //------------------------------------- void CAI_BehaviorBase::StopChannel( int channel ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return; } m_ScheduleChannels[channel].bActive = false; } //------------------------------------- void CAI_BehaviorBase::MaintainChannelSchedules() { for ( int i = 0; i < m_ScheduleChannels.Count(); i++ ) { MaintainSchedule( i ); } } //------------------------------------- #define MAX_TASKS_RUN 10 void CAI_BehaviorBase::MaintainSchedule( int channel ) { CAI_Schedule *pNewSchedule; bool runTask = true; AssertMsg( m_ScheduleChannels.IsValidIndex( channel ), "Bad schedule channel" ); AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; if ( !pScheduleState->bActive ) { return; } int i; bool bStopProcessing = false; for ( i = 0; i < MAX_TASKS_RUN && !bStopProcessing; i++ ) { if ( pScheduleState->pSchedule != NULL && pScheduleState->fTaskStatus == TASKSTATUS_COMPLETE ) { // Schedule is valid, so advance to the next task if the current is complete. pScheduleState->fTaskStatus = TASKSTATUS_NEW; pScheduleState->iCurTask++; } // validate existing schedule if ( !IsScheduleValid( pScheduleState ) ) { // Notify the NPC that his schedule is changing pScheduleState->bScheduleWasInterrupted = true; OnScheduleChange( channel ); // if ( !m_bConditionsGathered ) // { // // occurs if a schedule is exhausted within one think // GatherConditions(); // } if ( pScheduleState->taskFailureCode != NO_TASK_FAILURE ) { // Get a fail schedule if the previous schedule failed during execution and // the NPC is still in its ideal state. Otherwise, the NPC would immediately // select the same schedule again and fail again. if ( GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT ) { DevMsg( GetOuter(), AIMF_IGNORE_SELECTED, " Behavior %s channel %d (failed)\n", GetName(), channel ); } ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Behavior %s channel %d (failed)\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel ) ); pNewSchedule = GetFailSchedule( pScheduleState ); pScheduleState->idealSchedule = pNewSchedule->GetId(); DevWarning( 2, "(%s) Schedule (%s) Failed at %d!\n", STRING( GetOuter()->GetEntityName() ), pScheduleState->pSchedule ? pScheduleState->pSchedule->GetName() : "GetCurSchedule() == NULL", pScheduleState->iCurTask ); SetSchedule( channel, pNewSchedule ); } else { pNewSchedule = GetNewSchedule( channel ); SetSchedule( channel, pNewSchedule ); } } if ( !pScheduleState->pSchedule ) { pNewSchedule = GetNewSchedule( channel ); if (pNewSchedule) { SetSchedule( channel, pNewSchedule ); } } if ( !pScheduleState->pSchedule || pScheduleState->pSchedule->NumTasks() == 0 ) { return; } if ( pScheduleState->fTaskStatus == TASKSTATUS_NEW ) { if ( pScheduleState->iCurTask == 0 ) { int globalId = pScheduleState->pSchedule->GetId(); int localId = GetClassScheduleIdSpace()->ScheduleGlobalToLocal( globalId ); OnStartSchedule( channel, (localId != -1)? localId : globalId ); } const Task_t *pTask = GetCurTask( channel ); Assert( pTask != NULL ); if ( GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT ) { DevMsg( GetOuter(), AIMF_IGNORE_SELECTED, " Behavior %s channel %d Task: %s\n", GetName(), channel, GetSchedulingSymbols()->TaskIdToSymbol( GetClassScheduleIdSpace()->TaskLocalToGlobal( pTask->iTask ) ) ); } ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Behavior %s channel %d Task: %s\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel, GetSchedulingSymbols()->TaskIdToSymbol( GetClassScheduleIdSpace()->TaskLocalToGlobal( pTask->iTask ) ) ) ); pScheduleState->fTaskStatus = TASKSTATUS_RUN_MOVE_AND_TASK; pScheduleState->taskFailureCode = NO_TASK_FAILURE; pScheduleState->timeCurTaskStarted = gpGlobals->curtime; StartTask( channel, pTask ); } if ( !TaskIsComplete( channel ) && pScheduleState->fTaskStatus != TASKSTATUS_NEW ) { if ( pScheduleState->fTaskStatus != TASKSTATUS_COMPLETE && pScheduleState->fTaskStatus != TASKSTATUS_RUN_MOVE && pScheduleState->taskFailureCode == NO_TASK_FAILURE && runTask ) { const Task_t *pTask = GetCurTask( channel ); Assert( pTask != NULL ); RunTask( channel, pTask ); if ( !TaskIsComplete( channel ) ) { bStopProcessing = true; } } else { bStopProcessing = true; } } } } //------------------------------------- bool CAI_BehaviorBase::IsScheduleValid( AIChannelScheduleState_t *pScheduleState ) { CAI_Schedule *pSchedule = pScheduleState->pSchedule; if ( !pSchedule || pSchedule->NumTasks() == 0 ) { return false; } if ( pScheduleState->iCurTask == pSchedule->NumTasks() ) { return false; } if ( pScheduleState->taskFailureCode != NO_TASK_FAILURE ) { return false; } //Start out with the base schedule's set interrupt conditions CAI_ScheduleBits testBits; pSchedule->GetInterruptMask( &testBits ); testBits.And( GetOuter()->AccessConditionBits(), &testBits ); if (!testBits.IsAllClear()) { // If in developer mode save the interrupt text for debug output if (developer.GetInt()) { // Find the first non-zero bit for (int i=0;iConditionName( AI_RemapToGlobal( i ) ); if (!pszInterruptText) { pszInterruptText = "(UNKNOWN CONDITION)"; } if (GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT) { DevMsg(GetOuter(), AIMF_IGNORE_SELECTED, " Behavior %s channel %d Break condition -> %s\n", GetName(), channel, pszInterruptText ); } ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Behavior %s channel %d Break condition -> %s\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel, pszInterruptText ) ); break; } } } return false; } return true; } //------------------------------------- void CAI_BehaviorBase::SetSchedule( int channel, CAI_Schedule *pNewSchedule ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return; } //Assert( pNewSchedule != NULL ); AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; pScheduleState->timeCurTaskStarted = pScheduleState->timeStarted = gpGlobals->curtime; pScheduleState->bScheduleWasInterrupted = false; pScheduleState->pSchedule = pNewSchedule ; pScheduleState->iCurTask = 0; pScheduleState->fTaskStatus = TASKSTATUS_NEW; pScheduleState->failSchedule = SCHED_NONE; // this is very useful code if you can isolate a test case in a level with a single NPC. It will notify // you of every schedule selection the NPC makes. if( pNewSchedule ) { if (GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT) { DevMsg(GetOuter(), AIMF_IGNORE_SELECTED, "Behavior %s channel %d Schedule: %s (time: %.2f)\n", GetName(), channel, pNewSchedule->GetName(), gpGlobals->curtime ); } ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Behavior %s channel %d Schedule: %s (time: %.2f)\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel, pNewSchedule->GetName(), gpGlobals->curtime ) ); } } //------------------------------------- bool CAI_BehaviorBase::SetSchedule( int channel, int localScheduleID ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return false; } int translatedSchedule = TranslateSchedule( channel, localScheduleID ); CAI_Schedule *pNewSchedule = GetSchedule( translatedSchedule ); if ( pNewSchedule ) { AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; if ( AI_IdIsLocal( localScheduleID ) ) { pScheduleState->idealSchedule = GetClassScheduleIdSpace()->ScheduleLocalToGlobal( localScheduleID ); } else { pScheduleState->idealSchedule = localScheduleID; } SetSchedule( channel, pNewSchedule ); return true; } return false; } //------------------------------------- void CAI_BehaviorBase::ClearSchedule( int channel, const char *szReason ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return; } if (szReason && GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT) { DevMsg( GetOuter(), AIMF_IGNORE_SELECTED, " Behavior %s channel %d Schedule cleared: %s\n", GetName(), channel, szReason ); } if ( szReason ) { ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs( "%s(%d): Behavior %s channel %d Schedule cleared: %s\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel, szReason ) ); } AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; pScheduleState->timeCurTaskStarted = pScheduleState->timeStarted = 0; pScheduleState->bScheduleWasInterrupted = true; pScheduleState->fTaskStatus = TASKSTATUS_NEW; pScheduleState->idealSchedule = AI_RemapToGlobal( SCHED_NONE ); pScheduleState->pSchedule = NULL; pScheduleState->iCurTask = 0; } //------------------------------------- CAI_Schedule *CAI_BehaviorBase::GetCurSchedule( int channel ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return NULL; } return m_ScheduleChannels[channel].pSchedule; } //------------------------------------- bool CAI_BehaviorBase::IsCurSchedule( int channel, int schedId, bool fIdeal ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return false; } AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; if ( !pScheduleState->pSchedule ) return ( schedId == SCHED_NONE || schedId == AI_RemapToGlobal(SCHED_NONE) ); schedId = ( AI_IdIsLocal( schedId ) ) ? GetClassScheduleIdSpace()->ScheduleLocalToGlobal(schedId) : schedId; if ( fIdeal ) return ( schedId == pScheduleState->idealSchedule ); return ( pScheduleState->pSchedule->GetId() == schedId ); } //------------------------------------- void CAI_BehaviorBase::OnScheduleChange( int channel ) { } //------------------------------------- void CAI_BehaviorBase::OnStartSchedule( int channel, int scheduleType ) { } //------------------------------------- int CAI_BehaviorBase::SelectSchedule( int channel ) { return SCHED_NONE; } //------------------------------------- int CAI_BehaviorBase::SelectFailSchedule( int channel, int failedSchedule, int failedTask, AI_TaskFailureCode_t taskFailCode ) { return SCHED_NONE; } //------------------------------------- void CAI_BehaviorBase::TaskComplete( int channel, bool fIgnoreSetFailedCondition ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return; } if ( fIgnoreSetFailedCondition || m_ScheduleChannels[channel].taskFailureCode == NO_TASK_FAILURE ) { m_ScheduleChannels[channel].taskFailureCode = NO_TASK_FAILURE; m_ScheduleChannels[channel].fTaskStatus = TASKSTATUS_COMPLETE; } } //------------------------------------- void CAI_BehaviorBase::TaskFail( int channel, AI_TaskFailureCode_t code ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return; } AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; if ( GetOuter()->m_debugOverlays & OVERLAY_TASK_TEXT_BIT) { DevMsg( GetOuter(), AIMF_IGNORE_SELECTED, " Behavior %s channel %d: TaskFail -> %s\n", GetName(), channel, TaskFailureToString( code ) ); } ADD_DEBUG_HISTORY( HISTORY_AI_DECISIONS, UTIL_VarArgs("%s(%d): Behavior %s channel %d: TaskFail -> %s\n", GetOuter()->GetDebugName(), GetOuter()->entindex(), GetName(), channel, TaskFailureToString( code ) ) ); pScheduleState->taskFailureCode = code; } //------------------------------------- const Task_t *CAI_BehaviorBase::GetCurTask( int channel ) { if ( !m_ScheduleChannels.IsValidIndex( channel ) ) { AssertMsg( 0, "Bad schedule channel" ); return NULL; } AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[channel]; int iScheduleIndex = pScheduleState->iCurTask; if ( !pScheduleState->pSchedule || iScheduleIndex < 0 || iScheduleIndex >= pScheduleState->pSchedule->NumTasks() ) // iScheduleIndex is not within valid range for the NPC's current schedule. return NULL; return &pScheduleState->pSchedule->GetTaskList()[ iScheduleIndex ]; } //------------------------------------- void CAI_BehaviorBase::StartTask( int channel, const Task_t *pTask ) { Assert( 0 ); } //------------------------------------- void CAI_BehaviorBase::RunTask( int channel, const Task_t *pTask ) { Assert( 0 ); } //------------------------------------- float CAI_BehaviorBase::GetJumpGravity() const { Assert( m_pBackBridge != NULL ); return m_pBackBridge->BehaviorBridge_GetJumpGravity(); } //------------------------------------- bool CAI_BehaviorBase::IsJumpLegal( const Vector &startPos, const Vector &apex, const Vector &endPos, float maxUp, float maxDown, float maxDist ) const { Assert( m_pBackBridge != NULL ); return m_pBackBridge->BehaviorBridge_IsJumpLegal( startPos, apex, endPos, maxUp, maxDown, maxDist ); } //------------------------------------- bool CAI_BehaviorBase::MovementCost( int moveType, const Vector &vecStart, const Vector &vecEnd, float *pCost ) { Assert( m_pBackBridge != NULL ); return m_pBackBridge->BehaviorBridge_MovementCost( moveType, vecStart, vecEnd, pCost ); } //------------------------------------- bool CAI_BehaviorBase::NotifyChangeBehaviorStatus( bool fCanFinishSchedule ) { bool fInterrupt = GetOuter()->OnBehaviorChangeStatus( this, fCanFinishSchedule ); if ( !GetOuter()->IsInterruptable()) return false; if ( fInterrupt ) { if ( GetOuter()->m_hCine ) { if( GetOuter()->m_hCine->PlayedSequence() ) { DevWarning( "NPC: %s canceled running script %s due to behavior change\n", GetOuter()->GetDebugName(), GetOuter()->m_hCine->GetDebugName() ); } else { DevWarning( "NPC: %s canceled script %s without playing, due to behavior change\n", GetOuter()->GetDebugName(), GetOuter()->m_hCine->GetDebugName() ); } GetOuter()->m_hCine->CancelScript(); } //!!!HACKHACK // this is dirty, but it forces NPC to pick a new schedule next time through. GetOuter()->ClearSchedule( "Changed behavior status" ); } return fInterrupt; } //------------------------------------- int CAI_BehaviorBase::Save( ISave &save ) { if ( save.WriteAll( this, GetDataDescMap() ) ) { SaveChannels( save ); return 1; } return 0; } //------------------------------------- int CAI_BehaviorBase::Restore( IRestore &restore ) { if ( restore.ReadAll( this, GetDataDescMap() ) ) { RestoreChannels( restore ); return 1; } return 0; } //------------------------------------- //----------------------------------------------------------------------------- const short AI_BEHAVIOR_CHANNEL_SAVE_HEADER_VERSION = 1; struct AIBehaviorChannelSaveHeader_t { AIBehaviorChannelSaveHeader_t() : flags(0), scheduleCrc(0) { szSchedule[0] = 0; szIdealSchedule[0] = 0; szFailSchedule[0] = 0; } unsigned flags; char szSchedule[128]; CRC32_t scheduleCrc; char szIdealSchedule[128]; char szFailSchedule[128]; DECLARE_SIMPLE_DATADESC(); }; //------------------------------------- BEGIN_SIMPLE_DATADESC( AIBehaviorChannelSaveHeader_t ) DEFINE_FIELD( flags, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( szSchedule, FIELD_CHARACTER ), DEFINE_FIELD( scheduleCrc, FIELD_INTEGER ), DEFINE_AUTO_ARRAY( szIdealSchedule, FIELD_CHARACTER ), DEFINE_AUTO_ARRAY( szFailSchedule, FIELD_CHARACTER ), END_DATADESC() void CAI_BehaviorBase::SaveChannels( ISave &save ) { AIBehaviorChannelSaveHeader_t saveHeader; if ( m_ScheduleChannels.Count() ) { save.StartBlock(); int version = AI_BEHAVIOR_CHANNEL_SAVE_HEADER_VERSION; save.WriteInt( &version ); for ( int i = 0; i < m_ScheduleChannels.Count(); i++ ) { AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[i]; if ( pScheduleState->pSchedule ) { const char *pszSchedule = pScheduleState->pSchedule->GetName(); Assert( Q_strlen( pszSchedule ) < sizeof( saveHeader.szSchedule ) - 1 ); Q_strncpy( saveHeader.szSchedule, pszSchedule, sizeof( saveHeader.szSchedule ) ); CRC32_Init( &saveHeader.scheduleCrc ); CRC32_ProcessBuffer( &saveHeader.scheduleCrc, (void *)pScheduleState->pSchedule->GetTaskList(), pScheduleState->pSchedule->NumTasks() * sizeof(Task_t) ); CRC32_Final( &saveHeader.scheduleCrc ); } else { saveHeader.szSchedule[0] = 0; saveHeader.scheduleCrc = 0; } const int SCHED_NONE_GLOBAL = AI_RemapToGlobal( SCHED_NONE ); int idealSchedule = ( pScheduleState->idealSchedule == SCHED_NONE ) ? SCHED_NONE_GLOBAL : pScheduleState->idealSchedule; Assert( !AI_IdIsLocal( idealSchedule ) ); if ( idealSchedule != -1 && idealSchedule != SCHED_NONE_GLOBAL ) { CAI_Schedule *pIdealSchedule = GetSchedule( pScheduleState->idealSchedule ); if ( pIdealSchedule ) { const char *pszIdealSchedule = pIdealSchedule->GetName(); Assert( Q_strlen( pszIdealSchedule ) < sizeof( saveHeader.szIdealSchedule ) - 1 ); Q_strncpy( saveHeader.szIdealSchedule, pszIdealSchedule, sizeof( saveHeader.szIdealSchedule ) ); } } int failSchedule = ( pScheduleState->failSchedule == SCHED_NONE ) ? SCHED_NONE_GLOBAL : pScheduleState->failSchedule; Assert( !AI_IdIsLocal( failSchedule ) ); if ( failSchedule != -1 && failSchedule != SCHED_NONE_GLOBAL ) { CAI_Schedule *pFailSchedule = GetSchedule( pScheduleState->failSchedule ); if ( pFailSchedule ) { const char *pszFailSchedule = pFailSchedule->GetName(); Assert( Q_strlen( pszFailSchedule ) < sizeof( saveHeader.szFailSchedule ) - 1 ); Q_strncpy( saveHeader.szFailSchedule, pszFailSchedule, sizeof( saveHeader.szFailSchedule ) ); } } save.WriteAll( &saveHeader ); } save.EndBlock(); } } //------------------------------------- void CAI_BehaviorBase::RestoreChannels( IRestore &restore ) { if ( m_ScheduleChannels.Count() ) { restore.StartBlock(); int version; restore.ReadInt( &version ); AIBehaviorChannelSaveHeader_t saveHeader; for ( int i = 0; i < m_ScheduleChannels.Count(); i++ ) { restore.ReadAll( &saveHeader ); AIChannelScheduleState_t *pScheduleState = &m_ScheduleChannels[i]; // Do schedule fix-up if ( saveHeader.szIdealSchedule[0] ) { CAI_Schedule *pIdealSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szIdealSchedule ); pScheduleState->idealSchedule = ( pIdealSchedule ) ? pIdealSchedule->GetId() : SCHED_NONE; } if ( saveHeader.szFailSchedule[0] ) { CAI_Schedule *pFailSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szFailSchedule ); pScheduleState->failSchedule = ( pFailSchedule ) ? pFailSchedule->GetId() : SCHED_NONE; } bool bDiscardScheduleState = ( saveHeader.szSchedule[0] == 0 ); if ( pScheduleState->taskFailureCode >= NUM_FAIL_CODES ) pScheduleState->taskFailureCode = FAIL_NO_TARGET; // must have been a string, gotta punt if ( !bDiscardScheduleState ) { pScheduleState->pSchedule = g_AI_SchedulesManager.GetScheduleByName( saveHeader.szSchedule ); if ( pScheduleState->pSchedule ) { CRC32_t scheduleCrc; CRC32_Init( &scheduleCrc ); CRC32_ProcessBuffer( &scheduleCrc, (void *)pScheduleState->pSchedule->GetTaskList(), pScheduleState->pSchedule->NumTasks() * sizeof(Task_t) ); CRC32_Final( &scheduleCrc ); if ( scheduleCrc != saveHeader.scheduleCrc ) { pScheduleState->pSchedule = NULL; } } } if ( !pScheduleState->pSchedule ) bDiscardScheduleState = true; if ( bDiscardScheduleState ) { ClearSchedule( i, "Restoring NPC" ); } } restore.EndBlock(); } } //------------------------------------- #define BEHAVIOR_SAVE_BLOCKNAME "AI_Behaviors" #define BEHAVIOR_SAVE_VERSION 2 void CAI_BehaviorBase::SaveBehaviors(ISave &save, CAI_BehaviorBase *pCurrentBehavior, CAI_BehaviorBase **ppBehavior, int nBehaviors, bool bTestIfNPCSave ) { save.StartBlock( BEHAVIOR_SAVE_BLOCKNAME ); short temp = BEHAVIOR_SAVE_VERSION; save.WriteShort( &temp ); temp = (short)nBehaviors; save.WriteShort( &temp ); for ( int i = 0; i < nBehaviors; i++ ) { if ( ( !bTestIfNPCSave || ppBehavior[i]->ShouldNPCSave() ) ) { bool bHasDatadesc = ( strcmp( ppBehavior[i]->GetDataDescMap()->dataClassName, CAI_BehaviorBase::m_DataMap.dataClassName ) != 0 ); if ( bHasDatadesc ) { save.StartBlock(); save.WriteString( ppBehavior[i]->GetDataDescMap()->dataClassName ); bool bIsCurrent = ( pCurrentBehavior == ppBehavior[i] ); save.WriteBool( &bIsCurrent ); ppBehavior[i]->Save( save ); save.EndBlock(); } else { DevMsg( "Note: behavior \"%s\" lacks a datadesc and probably won't save/restore correctly\n", ppBehavior[i]->GetName() ); // You need at least an empty datadesc to allow for correct binding on load } } } save.EndBlock(); } //------------------------------------- int CAI_BehaviorBase::RestoreBehaviors(IRestore &restore, CAI_BehaviorBase **ppBehavior, int nBehaviors, bool bTestIfNPCSave ) { int iCurrent = -1; char szBlockName[SIZE_BLOCK_NAME_BUF]; restore.StartBlock( szBlockName ); if ( strcmp( szBlockName, BEHAVIOR_SAVE_BLOCKNAME ) == 0 ) { short version; restore.ReadShort( &version ); if ( version == BEHAVIOR_SAVE_VERSION ) { short nToRestore; char szClassNameCurrent[256]; restore.ReadShort( &nToRestore ); for ( int i = 0; i < nToRestore; i++ ) { restore.StartBlock(); restore.ReadString( szClassNameCurrent, sizeof( szClassNameCurrent ), 0 ); bool bIsCurrent; restore.ReadBool( &bIsCurrent ); for ( int j = 0; j < nBehaviors; j++ ) { if ( ( !bTestIfNPCSave || ppBehavior[j]->ShouldNPCSave() ) && strcmp( ppBehavior[j]->GetDataDescMap()->dataClassName, szClassNameCurrent ) == 0 ) { if ( bIsCurrent ) iCurrent = j; ppBehavior[j]->Restore( restore ); } } restore.EndBlock(); } } } restore.EndBlock(); return iCurrent; } //-----------------------------------------------------------------------------