Files
DoomRTX/neo/doom3/game/gamesys/State.cpp
T
2026-05-18 21:35:07 -07:00

497 lines
10 KiB
C++

#include "precompiled.h"
#include "../Game_local.h"
const int HISTORY_COUNT = 50;
/*
=====================
stateParms_t::Wait
=====================
*/
void stateParms_t::Wait( float seconds )
{
time = gameLocal.time + SEC2MS( seconds );
}
/*
=====================
stateParms_t::Save
=====================
*/
void stateParms_t::Save( idSaveGame* saveFile ) const
{
saveFile->WriteInt( blendFrames );
saveFile->WriteInt( time );
saveFile->WriteInt( stage );
}
/*
=====================
stateParms_t::Restore
=====================
*/
void stateParms_t::Restore( idRestoreGame* saveFile )
{
saveFile->ReadInt( blendFrames );
saveFile->ReadInt( time );
saveFile->ReadInt( stage );
}
/*
=====================
stateCall_t::Save
=====================
*/
void stateCall_t::Save( idSaveGame* saveFile ) const
{
saveFile->WriteString( state );
// TOSAVE: idLinkList<stateCall_t> node;
saveFile->WriteInt( flags );
saveFile->WriteInt( delay );
parms.Save( saveFile );
}
/*
=====================
stateCall_t::Save
=====================
*/
void stateCall_t::Restore( idRestoreGame* saveFile, const idClass* owner )
{
saveFile->ReadString( state );
saveFile->ReadInt( flags );
saveFile->ReadInt( delay );
parms.Restore( saveFile );
}
/*
=====================
rvStateThread::rvStateThread
=====================
*/
rvStateThread::rvStateThread( void )
{
owner = NULL;
insertAfter = NULL;
lastResult = SRESULT_DONE;
states.Clear( );
interrupted.Clear( );
memset( &fl, 0, sizeof( fl ) );
}
/*
=====================
rvStateThread::~rvStateThread
=====================
*/
rvStateThread::~rvStateThread( void )
{
Clear( true );
}
/*
=====================
rvStateThread::SetOwner
=====================
*/
void rvStateThread::SetOwner( idClass* _owner )
{
owner = _owner;
}
/*
=====================
rvStateThread::Post
=====================
*/
stateResult_t rvStateThread::PostState( const char* name, int blendFrames, int delay, int flags )
{
stateCall_t* call;
call = new stateCall_t;
call->state = name;
call->delay = delay;
call->flags = flags;
call->parms.blendFrames = blendFrames;
call->parms.time = -1;
call->parms.stage = 0;
call->parms.substage = 0;
call->parms.param1 = 0;
call->parms.param2 = 0;
call->parms.subparam1 = 0;
call->parms.subparam2 = 0;
call->node.SetOwner( call );
if( fl.executing && insertAfter )
{
call->node.InsertAfter( insertAfter->node );
}
else
{
call->node.AddToEnd( states );
}
insertAfter = call;
return SRESULT_OK;
}
/*
=====================
rvStateThread::Set
=====================
*/
stateResult_t rvStateThread::SetState( const char* name, int blendFrames, int delay, int flags )
{
Clear( );
return PostState( name, blendFrames, delay, flags );
}
/*
=====================
rvStateThread::InterruptState
=====================
*/
stateResult_t rvStateThread::InterruptState( const char* name, int blendFrames, int delay, int flags )
{
stateCall_t* call;
// Move all states to the front of the interrupted list in the same order
for( call = states.Prev(); call; call = states.Prev() )
{
call->node.Remove( );
call->node.AddToFront( interrupted );
}
// Nothing to insert after anymore
insertAfter = NULL;
fl.stateInterrupted = true;
// Post the state now
return PostState( name, blendFrames, delay, flags );
}
/*
=====================
rvStateThread::CurrentStateIs
=====================
*/
bool rvStateThread::CurrentStateIs( const char* name ) const
{
return ( !IsIdle() ) ? name == GetState()->state : false;
}
/*
=====================
rvStateThread::Clear
=====================
*/
void rvStateThread::Clear( bool ignoreStateCalls )
{
stateCall_t* call;
// Clear all states from the main state list
for( call = states.Next(); call != NULL; call = states.Next() )
{
if( !ignoreStateCalls && ( call->flags & ( SFLAG_ONCLEAR | SFLAG_ONCLEARONLY ) ) )
{
//owner->ProcessState ( call->state, call->parms );
//owner->SetStateParms(call->parms);
owner->Invoke( call->state, &call->parms );
}
call->node.Remove();
delete call;
}
// Clear all interrupted states
for( call = interrupted.Next(); call != NULL; call = interrupted.Next() )
{
if( !ignoreStateCalls && ( call->flags & ( SFLAG_ONCLEAR | SFLAG_ONCLEARONLY ) ) )
{
//owner->ProcessState ( call->state, call->parms );
// owner->SetStateParms(call->parms);
owner->Invoke( call->state, &call->parms );
}
call->node.Remove();
delete call;
}
insertAfter = NULL;
fl.stateCleared = true;
states.Clear( );
interrupted.Clear( );
}
/*
=====================
rvStateThread::Execute
=====================
*/
stateResult_t rvStateThread::Execute( void )
{
stateCall_t* call = NULL;
int count;
const char* stateName;
int stateStage;
const char* historyState[HISTORY_COUNT];
int historyStage[HISTORY_COUNT];
int historyStart;
int historyEnd;
// If our main state loop is empty copy over any states in the interrupted state
if( !states.Next( ) )
{
for( call = interrupted.Next(); call; call = interrupted.Next() )
{
call->node.Remove( );
call->node.AddToEnd( states );
}
assert( !interrupted.Next( ) );
}
// State thread is idle if there are no states
if( !states.Next() )
{
return SRESULT_IDLE;
}
fl.executing = true;
// Run through the states until there are no more or one of them tells us to wait
count = 0;
historyStart = 0;
historyEnd = 0;
for( call = states.Next(); call && count < HISTORY_COUNT; call = states.Next(), ++count )
{
insertAfter = call;
fl.stateCleared = false;
fl.stateInterrupted = false;
// If this state is only called when being cleared then just skip it
if( call->flags & SFLAG_ONCLEARONLY )
{
call->node.Remove( );
delete call;
continue;
}
// If the call has a delay on it the time will be set to negative initially and then
// converted to game time.
if( call->parms.time <= 0 )
{
call->parms.time = gameLocal.time;
}
// Check for delayed states
if( gameLocal.time < call->parms.time + call->delay )
{
fl.executing = false;
return SRESULT_WAIT;
}
// Debugging
if( lastResult != SRESULT_WAIT )
{
// Keep a history of the called states so we can dump them on an overflow
historyState[historyEnd] = call->state;
historyStage[historyEnd] = call->parms.stage;
historyEnd = ( historyEnd + 1 ) % HISTORY_COUNT;
if( historyEnd == historyStart )
{
historyStart = ( historyEnd + 1 ) % HISTORY_COUNT;
}
}
// Cache name and stage for error messages
stateName = call->state;
stateStage = call->parms.stage;
if( g_debugState.GetBool() )
{
if( call->parms.stage )
{
gameLocal.Printf( "%s: %s (%d)\n", ((idEntity *)owner)->GetClassname(), call->state.c_str(), call->parms.stage );
}
else
{
gameLocal.Printf( "%s: %s\n", ((idEntity*)owner)->GetClassname(), call->state.c_str() );
}
}
// Actually call the state function
lastResult = ( stateResult_t )owner->Invoke( call->state, &call->parms );
switch( lastResult )
{
case SRESULT_DONE_FRAME:
fl.executing = false;
return SRESULT_DONE;
case SRESULT_WAIT:
fl.executing = false;
return SRESULT_WAIT;
case SRESULT_ERROR:
gameLocal.Error( "rvStateThread: error reported by state '%s (%d)'", stateName, stateStage );
fl.executing = false;
return SRESULT_ERROR;
}
if (lastResult == SRESULT_DONE) {
owner->StateThreadChanged();
}
// Dont remove the node if it was interrupted or cleared in the last process
if( !fl.stateCleared && !fl.stateInterrupted )
{
if( lastResult >= SRESULT_SETDELAY )
{
call->delay = lastResult - SRESULT_SETDELAY;
call->parms.time = gameLocal.GetTime();
continue;
}
else if( lastResult >= SRESULT_SETSTAGE )
{
call->parms.stage = lastResult - SRESULT_SETSTAGE;
continue;
}
// Done with state so remove it from list
call->node.Remove( );
delete call;
}
// Finished the last state but wait a frame for next one
if( lastResult == SRESULT_DONE_WAIT )
{
fl.executing = false;
return SRESULT_WAIT;
}
}
// Runaway state loop?
if( count >= HISTORY_COUNT )
{
idFile* file;
fileSystem->RemoveFile( "statedump.txt" );
file = fileSystem->OpenFileWrite( "statedump.txt" );
for( ; historyStart != historyEnd; historyStart = ( historyStart + 1 ) % HISTORY_COUNT )
{
if( historyStage[historyStart] )
{
gameLocal.Printf( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] );
}
else
{
gameLocal.Printf( "rvStateThread: %s\n", historyState[historyStart] );
}
if( file )
{
if( historyStage[historyStart] )
{
file->Printf( "rvStateThread: %s (%d)\n", historyState[historyStart], historyStage[historyStart] );
}
else
{
file->Printf( "rvStateThread: %s\n", historyState[historyStart] );
}
}
}
if( file )
{
fileSystem->CloseFile( file );
}
gameLocal.Error( "rvStateThread: run away state loop '%s'", name.c_str() );
}
insertAfter = NULL;
fl.executing = false;
// Move interrupted states back into the main state list when the main state list is empty
if( !states.Next() && interrupted.Next( ) )
{
return Execute( );
}
return lastResult;
}
/*
=====================
rvStateThread::Save
=====================
*/
void rvStateThread::Save( idSaveGame* saveFile ) const
{
saveFile->WriteString( name.c_str() );
// No need to save owner, its setup in restore
saveFile->WriteInt( lastResult );
saveFile->Write( &fl, sizeof( fl ) );
saveFile->WriteInt( states.Num() );
for( idLinkList<stateCall_t>* node = states.NextNode(); node; node = node->NextNode() )
{
node->Owner()->Save( saveFile );
}
saveFile->WriteInt( interrupted.Num() );
for( idLinkList<stateCall_t>* node = interrupted.NextNode(); node; node = node->NextNode() )
{
node->Owner()->Save( saveFile );
}
// TOSAVE: stateCall_t* insertAfter;
// TOSAVE: stateResult_t lastResult;
}
/*
=====================
rvStateThread::Restore
=====================
*/
void rvStateThread::Restore( idRestoreGame* saveFile, idClass* owner )
{
int numStates;
stateCall_t* call = NULL;
saveFile->ReadString( name );
this->owner = owner;
saveFile->ReadInt( ( int& )lastResult );
saveFile->Read( &fl, sizeof( fl ) );
saveFile->ReadInt( numStates );
for( ; numStates > 0; numStates-- )
{
call = new stateCall_t;
assert( call );
call->Restore( saveFile, owner );
call->node.SetOwner( call );
call->node.AddToEnd( states );
}
saveFile->ReadInt( numStates );
for( ; numStates > 0; numStates-- )
{
call = new stateCall_t;
assert( call );
call->Restore( saveFile, owner );
call->node.SetOwner( call );
call->node.AddToEnd( interrupted );
}
}