jedioutcast/code/icarus/Sequencer.cpp
2013-04-04 09:52:42 -05:00

2384 lines
48 KiB
C++

// Script Command Sequencer
//
// -- jweier
// this include must remain at the top of every Icarus CPP file
#include "icarus.h"
#include "g_headers.h"
#include "assert.h"
// Sequencer
CSequencer::CSequencer( void )
{
m_numCommands = 0;
m_curStream = NULL;
m_curSequence = NULL;
m_elseValid = 0;
m_elseOwner = NULL;
m_curGroup = NULL;
}
CSequencer::~CSequencer( void )
{
Free(); //Safe even if already freed
}
/*
========================
Create
Static creation function
========================
*/
CSequencer *CSequencer::Create ( void )
{
CSequencer *sequencer = new CSequencer;
return sequencer;
}
/*
========================
Init
Initializes the sequencer
========================
*/
int CSequencer::Init( int ownerID, interface_export_t *ie, CTaskManager *taskManager, ICARUS_Instance *iICARUS )
{
m_ownerID = ownerID;
m_owner = iICARUS;
m_taskManager = taskManager;
m_ie = ie;
return SEQ_OK;
}
/*
========================
Free
Releases all resources and re-inits the sequencer
========================
*/
int CSequencer::Free( void )
{
sequence_l::iterator sli;
//Flush the sequences
for ( sli = m_sequences.begin(); sli != m_sequences.end(); sli++ )
{
m_owner->DeleteSequence( (*sli) );
}
m_sequences.clear();
m_sequenceMap.clear();
m_taskSequences.clear();
//Clean up any other info
m_numCommands = 0;
m_curSequence = NULL;
return SEQ_OK;
}
/*
-------------------------
Flush
-------------------------
*/
int CSequencer::Flush( CSequence *owner )
{
if ( owner == NULL )
return SEQ_FAILED;
Recall();
sequence_l::iterator sli;
//Flush the sequences
for ( sli = m_sequences.begin(); sli != m_sequences.end(); )
{
if ( ( (*sli) == owner ) || ( owner->HasChild( (*sli) ) ) || ( (*sli)->HasFlag( SQ_PENDING ) ) || ( (*sli)->HasFlag( SQ_TASK ) ) )
{
sli++;
continue;
}
//Remove it from the map
m_sequenceMap.erase( (*sli)->GetID() );
//Delete it, and remove all references
RemoveSequence( (*sli), false, false );
m_owner->DeleteSequence( (*sli) );
//Delete from the sequence list and move on
sli = m_sequences.erase( sli );
}
//Make sure this owner knows it's now the root sequence
owner->SetParent( NULL );
owner->SetReturn( NULL );
return SEQ_OK;
}
/*
========================
AddStream
Creates a stream for parsing
========================
*/
bstream_t *CSequencer::AddStream( void )
{
bstream_t *stream;
stream = new bstream_t; //deleted in Route()
stream->stream = new CBlockStream; //deleted in Route()
stream->last = m_curStream;
return stream;
}
/*
========================
DeleteStream
Deletes parsing stream
========================
*/
void CSequencer::DeleteStream( bstream_t *bstream )
{
bstream->stream->Free();
delete bstream->stream;
delete bstream;
bstream = NULL;
}
/*
-------------------------
AddTaskSequence
-------------------------
*/
void CSequencer::AddTaskSequence( CSequence *sequence, CTaskGroup *group )
{
m_taskSequences[ group ] = sequence;
}
/*
-------------------------
GetTaskSequence
-------------------------
*/
CSequence *CSequencer::GetTaskSequence( CTaskGroup *group )
{
taskSequence_m::iterator tsi;
tsi = m_taskSequences.find( group );
if ( tsi == m_taskSequences.end() )
return NULL;
return (*tsi).second;
}
/*
========================
AddSequence
Creates and adds a sequence to the sequencer
========================
*/
CSequence *CSequencer::AddSequence( void )
{
CSequence *sequence = m_owner->GetSequence();
assert( sequence );
if ( sequence == NULL )
return NULL;
//The rest is handled internally to the class
m_sequenceMap[ sequence->GetID() ] = sequence;
//Add it to the list
m_sequences.insert( m_sequences.end(), sequence );
//FIXME: Temp fix
sequence->SetFlag( SQ_PENDING );
return sequence;
}
CSequence *CSequencer::AddSequence( CSequence *parent, CSequence *returnSeq, int flags )
{
CSequence *sequence = m_owner->GetSequence();
assert( sequence );
if ( sequence == NULL )
return NULL;
//The rest is handled internally to the class
m_sequenceMap[ sequence->GetID() ] = sequence;
//Add it to the list
m_sequences.insert( m_sequences.end(), sequence );
sequence->SetFlags( flags );
sequence->SetParent( parent );
sequence->SetReturn( returnSeq );
return sequence;
}
/*
========================
GetSequence
Retrieves a sequence by its ID
========================
*/
CSequence *CSequencer::GetSequence( int id )
{
sequenceID_m::iterator mi;
mi = m_sequenceMap.find( id );
if ( mi == m_sequenceMap.end() )
return NULL;
return (*mi).second;
}
/*
-------------------------
Interrupt
-------------------------
*/
void CSequencer::Interrupt( void )
{
CBlock *command = m_taskManager->GetCurrentTask();
if ( command == NULL )
return;
//Save it
PushCommand( command, PUSH_BACK );
}
/*
========================
Run
Runs a script
========================
*/
int CSequencer::Run( char *buffer, long size )
{
bstream_t *blockStream;
Recall();
//Create a new stream
blockStream = AddStream();
//Open the stream as an IBI stream
if (!blockStream->stream->Open( buffer, size ))
{
m_ie->I_DPrintf( WL_ERROR, "invalid stream" );
return SEQ_FAILED;
}
CSequence *sequence = AddSequence( NULL, m_curSequence, SQ_COMMON );
// Interpret the command blocks and route them properly
if ( S_FAILED( Route( sequence, blockStream )) )
{
//Error code is set inside of Route()
return SEQ_FAILED;
}
return SEQ_OK;
}
/*
========================
ParseRun
Parses a user triggered run command
========================
*/
int CSequencer::ParseRun( CBlock *block )
{
CSequence *new_sequence;
bstream_t *new_stream;
char *buffer;
char newname[ MAX_STRING_SIZE ];
int buffer_size;
//Get the name and format it
StripExtension( (char*) block->GetMemberData( 0 ), (char *) newname );
//Get the file from the game engine
buffer_size = m_ie->I_LoadFile( newname, (void **) &buffer );
if ( buffer_size <= 0 )
{
m_ie->I_DPrintf( WL_ERROR, "'%s' : could not open file\n", (char*) block->GetMemberData( 0 ));
return SEQ_FAILED;
}
//Create a new stream for this file
new_stream = AddStream();
//Begin streaming the file
if (!new_stream->stream->Open( buffer, buffer_size ))
{
m_ie->I_DPrintf( WL_ERROR, "invalid stream" );
return SEQ_FAILED;
}
//Create a new sequence
new_sequence = AddSequence( m_curSequence, m_curSequence, ( SQ_RUN | SQ_PENDING ) );
m_curSequence->AddChild( new_sequence );
// Interpret the command blocks and route them properly
if ( S_FAILED( Route( new_sequence, new_stream )) )
{
//Error code is set inside of Route()
return SEQ_FAILED;
}
m_curSequence = m_curSequence->GetReturn();
assert( m_curSequence );
block->Write( TK_FLOAT, (float) new_sequence->GetID() );
PushCommand( block, PUSH_FRONT );
return SEQ_OK;
}
/*
========================
ParseIf
Parses an if statement
========================
*/
int CSequencer::ParseIf( CBlock *block, bstream_t *bstream )
{
CSequence *sequence;
//Create the container sequence
sequence = AddSequence( m_curSequence, m_curSequence, SQ_CONDITIONAL );
assert( sequence );
if ( sequence == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "ParseIf: failed to allocate container sequence" );
return SEQ_FAILED;
}
m_curSequence->AddChild( sequence );
//Add a unique conditional identifier to the block for reference later
block->Write( TK_FLOAT, (float) sequence->GetID() );
//Push this onto the stack to mark the conditional entrance
PushCommand( block, PUSH_FRONT );
//Recursively obtain the conditional body
Route( sequence, bstream );
m_elseValid = 2;
m_elseOwner = block;
return SEQ_OK;
}
/*
========================
ParseElse
Parses an else statement
========================
*/
int CSequencer::ParseElse( CBlock *block, bstream_t *bstream )
{
CSequence *sequence;
//Create the container sequence
sequence = AddSequence( m_curSequence, m_curSequence, SQ_CONDITIONAL );
assert( sequence );
if ( sequence == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "ParseIf: failed to allocate container sequence" );
return SEQ_FAILED;
}
m_curSequence->AddChild( sequence );
//Add a unique conditional identifier to the block for reference later
//TODO: Emit warning
if ( m_elseOwner == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "Invalid 'else' found!\n" );
return SEQ_FAILED;
}
m_elseOwner->Write( TK_FLOAT, (float) sequence->GetID() );
m_elseOwner->SetFlag( BF_ELSE );
//The else is not retained
delete block;
block = NULL;
//Recursively obtain the conditional body
Route( sequence, bstream );
m_elseValid = 0;
m_elseOwner = NULL;
return SEQ_OK;
}
/*
========================
ParseLoop
Parses a loop command
========================
*/
int CSequencer::ParseLoop( CBlock *block, bstream_t *bstream )
{
CSequence *sequence;
CBlockMember *bm;
float min, max;
int rIter;
int memberNum = 0;
//Set the parent
sequence = AddSequence( m_curSequence, m_curSequence, ( SQ_LOOP | SQ_RETAIN ) );
assert( sequence );
if ( sequence == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "ParseLoop : failed to allocate container sequence" );
return SEQ_FAILED;
}
m_curSequence->AddChild( sequence );
//Set the number of iterations of this sequence
bm = block->GetMember( memberNum++ );
if ( bm->GetID() == ID_RANDOM )
{
//Parse out the random number
min = *(float *) block->GetMemberData( memberNum++ );
max = *(float *) block->GetMemberData( memberNum++ );
rIter = (int) m_ie->I_Random( min, max );
sequence->SetIterations( rIter );
}
else
{
sequence->SetIterations ( (int) (*(float *) bm->GetData()) );
}
//Add a unique loop identifier to the block for reference later
block->Write( TK_FLOAT, (float) sequence->GetID() );
//Push this onto the stack to mark the loop entrance
PushCommand( block, PUSH_FRONT );
//Recursively obtain the loop
Route( sequence, bstream );
return SEQ_OK;
}
/*
========================
AddAffect
Adds a sequence that is saved until the affect is called by the parent
========================
*/
int CSequencer::AddAffect( bstream_t *bstream, int retain, int *id )
{
CSequence *sequence = AddSequence();
bstream_t new_stream;
sequence->SetFlag( SQ_AFFECT | SQ_PENDING );
if ( retain )
sequence->SetFlag( SQ_RETAIN );
//This will be replaced once it's actually used, but this will restore the route state properly
sequence->SetReturn( m_curSequence );
//We need this as a temp holder
new_stream.last = m_curStream;
new_stream.stream = bstream->stream;
if S_FAILED( Route( sequence, &new_stream ) )
{
return SEQ_FAILED;
}
*id = sequence->GetID();
sequence->SetReturn( NULL );
return SEQ_OK;
}
/*
========================
ParseAffect
Parses an affect command
========================
*/
int CSequencer::ParseAffect( CBlock *block, bstream_t *bstream )
{
CSequencer *stream_sequencer = NULL;
char *entname = NULL;
int ret;
gentity_t *ent = 0;
entname = (char*) block->GetMemberData( 0 );
ent = m_ie->I_GetEntityByName( entname );
if( !ent ) // if there wasn't a valid entname in the affect, we need to check if it's a get command
{
//try to parse a 'get' command that is embeded in this 'affect'
int id;
char *p1 = NULL;
char *name = 0;
CBlockMember *bm = NULL;
//
// Get the first parameter (this should be the get)
//
bm = block->GetMember( 0 );
id = bm->GetID();
switch ( id )
{
// these 3 cases probably aren't necessary
case TK_STRING:
case TK_IDENTIFIER:
case TK_CHAR:
p1 = (char *) bm->GetData();
break;
case ID_GET:
{
int type;
//get( TYPE, NAME )
type = (int) (*(float *) block->GetMemberData( 1 ));
name = (char *) block->GetMemberData( 2 );
switch ( type ) // what type are they attempting to get
{
case TK_STRING:
//only string is acceptable for affect, store result in p1
if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false)
{
return false;
}
break;
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _1" );
return false;
break;
}
break;
}
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _2" );
return false;
break;
}//end id switch
if(p1)
{
ent = m_ie->I_GetEntityByName( p1 );
}
if(!ent)
{ // a valid entity name was not returned from the get command
m_ie->I_DPrintf( WL_WARNING, "'%s' : invalid affect() target\n");
}
} // end if(!ent)
if( ent )
{
stream_sequencer = ent->sequencer;
}
if (stream_sequencer == NULL)
{
m_ie->I_DPrintf( WL_WARNING, "'%s' : invalid affect() target\n", entname );
//Fast-forward out of this affect block onto the next valid code
CSequence *backSeq, *trashSeq = m_owner->GetSequence();
backSeq = m_curSequence;
Route( trashSeq, bstream );
RemoveSequence( trashSeq, true, true );
m_curSequence = backSeq;
m_owner->DeleteSequence( trashSeq );
return SEQ_OK;
}
if S_FAILED ( stream_sequencer->AddAffect( bstream, (int) m_curSequence->HasFlag( SQ_RETAIN ), &ret ) )
return SEQ_FAILED;
//Hold onto the id for later use
//FIXME: If the target sequence is freed, what then? (!suspect!)
block->Write( TK_FLOAT, (float) ret );
PushCommand( block, PUSH_FRONT );
/*
//Don't actually do these right now, we're just pre-processing (parsing) the affect
if( ent )
{ // ents need to update upon being affected
ent->taskManager->Update();
}
*/
return SEQ_OK;
}
/*
-------------------------
ParseTask
-------------------------
*/
int CSequencer::ParseTask( CBlock *block, bstream_t *bstream )
{
CSequence *sequence;
CTaskGroup *group;
const char *taskName;
//Setup the container sequence
sequence = AddSequence( m_curSequence, m_curSequence, SQ_TASK | SQ_RETAIN );
m_curSequence->AddChild( sequence );
//Get the name of this task for reference later
taskName = (const char *) block->GetMemberData( 0 );
//Get a new task group from the task manager
group = m_taskManager->AddTaskGroup( taskName );
if ( group == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "error : unable to allocate a new task group" );
return SEQ_FAILED;
}
//The current group is set to this group, all subsequent commands (until a block end) will fall into this task group
group->SetParent( m_curGroup );
m_curGroup = group;
//Keep an association between this task and the container sequence
AddTaskSequence( sequence, group );
//PushCommand( block, PUSH_FRONT );
delete block;
block = NULL;
//Recursively obtain the loop
Route( sequence, bstream );
return SEQ_OK;
}
/*
========================
Route
Properly handles and routes commands to the sequencer
========================
*/
//FIXME: Re-entering this code will produce unpredictable results if a script has already been routed and is running currently
//FIXME: A sequencer cannot properly affect itself
int CSequencer::Route( CSequence *sequence, bstream_t *bstream )
{
CBlockStream *stream;
CBlock *block;
//Take the stream as the current stream
m_curStream = bstream;
stream = bstream->stream;
m_curSequence = sequence;
//Obtain all blocks
while ( stream->BlockAvailable() )
{
block = new CBlock; //deleted in Free()
stream->ReadBlock( block );
//TEMP: HACK!
if ( m_elseValid )
m_elseValid--;
switch( block->GetBlockID() )
{
//Marks the end of a blocked section
case ID_BLOCK_END:
//Save this as a pre-process marker
PushCommand( block, PUSH_FRONT );
if ( m_curSequence->HasFlag( SQ_RUN ) || m_curSequence->HasFlag( SQ_AFFECT ) )
{
//Go back to the last stream
m_curStream = bstream->last;
}
if ( m_curSequence->HasFlag( SQ_TASK ) )
{
//Go back to the last stream
m_curStream = bstream->last;
m_curGroup = m_curGroup->GetParent();
}
m_curSequence = m_curSequence->GetReturn();
return SEQ_OK;
break;
//Affect pre-processor
case ID_AFFECT:
if S_FAILED( ParseAffect( block, bstream ) )
return SEQ_FAILED;
break;
//Run pre-processor
case ID_RUN:
if S_FAILED( ParseRun( block ) )
return SEQ_FAILED;
break;
//Loop pre-processor
case ID_LOOP:
if S_FAILED( ParseLoop( block, bstream ) )
return SEQ_FAILED;
break;
//Conditional pre-processor
case ID_IF:
if S_FAILED( ParseIf( block, bstream ) )
return SEQ_FAILED;
break;
case ID_ELSE:
//TODO: Emit warning
if ( m_elseValid == 0 )
{
m_ie->I_DPrintf( WL_ERROR, "Invalid 'else' found!\n" );
return SEQ_FAILED;
}
if S_FAILED( ParseElse( block, bstream ) )
return SEQ_FAILED;
break;
case ID_TASK:
if S_FAILED( ParseTask( block, bstream ) )
return SEQ_FAILED;
break;
//FIXME: For now this is to catch problems, but can ultimately be removed
case ID_WAIT:
case ID_PRINT:
case ID_SOUND:
case ID_MOVE:
case ID_ROTATE:
case ID_SET:
case ID_USE:
case ID_REMOVE:
case ID_KILL:
case ID_FLUSH:
case ID_CAMERA:
case ID_DO:
case ID_DECLARE:
case ID_FREE:
case ID_SIGNAL:
case ID_WAITSIGNAL:
case ID_PLAY:
//Commands go directly into the sequence without pre-process
PushCommand( block, PUSH_FRONT );
break;
//Error
default:
m_ie->I_DPrintf( WL_ERROR, "'%d' : invalid block ID", block->GetBlockID() );
return SEQ_FAILED;
break;
}
}
//Check for a run sequence, it must be marked
if ( m_curSequence->HasFlag( SQ_RUN ) )
{
block = new CBlock;
block->Create( ID_BLOCK_END );
PushCommand( block, PUSH_FRONT ); //mark the end of the run
/*
//Free the stream
m_curStream = bstream->last;
DeleteStream( bstream );
*/
return SEQ_OK;
}
//Check to start the communication
if ( ( bstream->last == NULL ) && ( m_numCommands > 0 ) )
{
//Everything is routed, so get it all rolling
Prime( m_taskManager, PopCommand( POP_BACK ) );
}
m_curStream = bstream->last;
//Free the stream
DeleteStream( bstream );
return SEQ_OK;
}
/*
========================
CheckRun
Checks for run command pre-processing
========================
*/
//Directly changes the parameter to avoid excess push/pop
void CSequencer::CheckRun( CBlock **command )
{
CBlock *block = *command;
if ( block == NULL )
return;
//Check for a run command
if ( block->GetBlockID() == ID_RUN )
{
int id = (int) (*(float *) block->GetMemberData( 1 ));
m_ie->I_DPrintf( WL_DEBUG, "%4d run( \"%s\" ); [%d]", m_ownerID, (char *) block->GetMemberData(0), m_ie->I_GetTime() );
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = GetSequence( id );
//TODO: Emit warning
assert( m_curSequence );
if ( m_curSequence == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find 'run' sequence!\n" );
*command = NULL;
return;
}
if ( m_curSequence->GetNumCommands() > 0 )
{
*command = PopCommand( POP_BACK );
Prep( command ); //Account for any other pre-processes
return;
}
return;
}
//Check for the end of a run
if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_RUN ) ) )
{
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = ReturnSequence( m_curSequence );
if ( m_curSequence && m_curSequence->GetNumCommands() > 0 )
{
*command = PopCommand( POP_BACK );
Prep( command ); //Account for any other pre-processes
return;
}
//FIXME: Check this...
}
}
/*
-------------------------
EvaluateConditional
-------------------------
*/
//FIXME: This function will be written better later once the functionality of the ideas here are tested
int CSequencer::EvaluateConditional( CBlock *block )
{
CBlockMember *bm;
char tempString1[128], tempString2[128];
vector_t vec;
int id, i, oper, memberNum = 0;
char *p1 = NULL, *p2 = NULL;
int t1, t2;
//
// Get the first parameter
//
bm = block->GetMember( memberNum++ );
id = bm->GetID();
t1 = id;
switch ( id )
{
case TK_FLOAT:
sprintf( (char *) tempString1, "%.3f", *(float *) bm->GetData() );
p1 = (char *) tempString1;
break;
case TK_VECTOR:
tempString1[0] = NULL;
for ( i = 0; i < 3; i++ )
{
bm = block->GetMember( memberNum++ );
vec[i] = *(float *) bm->GetData();
}
sprintf( (char *) tempString1, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] );
p1 = (char *) tempString1;
break;
case TK_STRING:
case TK_IDENTIFIER:
case TK_CHAR:
p1 = (char *) bm->GetData();
break;
case ID_GET:
{
int type;
char *name;
//get( TYPE, NAME )
type = (int) (*(float *) block->GetMemberData( memberNum++ ));
name = (char *) block->GetMemberData( memberNum++ );
//Get the type returned and hold onto it
t1 = type;
switch ( type )
{
case TK_FLOAT:
{
float fVal;
if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false)
return false;
sprintf( (char *) tempString1, "%.3f", fVal );
p1 = (char *) tempString1;
}
break;
case TK_INT:
{
float fVal;
if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false)
return false;
sprintf( (char *) tempString1, "%d", (int) fVal );
p1 = (char *) tempString1;
}
break;
case TK_STRING:
if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false)
return false;
break;
case TK_VECTOR:
{
vector_t vVal;
if ( m_ie->I_GetVector( m_ownerID, type, name, vVal ) == false)
return false;
sprintf( (char *) tempString1, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] );
p1 = (char *) tempString1;
}
break;
}
break;
}
case ID_RANDOM:
{
float min, max;
//FIXME: This will not account for nested random() statements
min = *(float *) block->GetMemberData( memberNum++ );
max = *(float *) block->GetMemberData( memberNum++ );
//A float value is returned from the function
t1 = TK_FLOAT;
sprintf( (char *) tempString1, "%.3f", m_ie->I_Random( min, max ) );
p1 = (char *) tempString1;
}
break;
case ID_TAG:
{
char *name;
float type;
name = (char *) block->GetMemberData( memberNum++ );
type = *(float *) block->GetMemberData( memberNum++ );
t1 = TK_VECTOR;
//TODO: Emit warning
if ( m_ie->I_GetTag( m_ownerID, name, (int) type, vec ) == false)
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find tag \"%s\"!\n", name );
return false;
}
sprintf( (char *) tempString1, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] );
p1 = (char *) tempString1;
break;
}
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on conditional" );
return false;
break;
}
//
// Get the comparison operator
//
bm = block->GetMember( memberNum++ );
id = bm->GetID();
switch ( id )
{
case TK_EQUALS:
case TK_GREATER_THAN:
case TK_LESS_THAN:
case TK_NOT:
oper = id;
break;
default:
m_ie->I_DPrintf( WL_ERROR, "Invalid operator type found on conditional!\n" );
return false; //FIXME: Emit warning
break;
}
//
// Get the second parameter
//
bm = block->GetMember( memberNum++ );
id = bm->GetID();
t2 = id;
switch ( id )
{
case TK_FLOAT:
sprintf( (char *) tempString2, "%.3f", *(float *) bm->GetData() );
p2 = (char *) tempString2;
break;
case TK_VECTOR:
tempString2[0] = NULL;
for ( i = 0; i < 3; i++ )
{
bm = block->GetMember( memberNum++ );
vec[i] = *(float *) bm->GetData();
}
sprintf( (char *) tempString2, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] );
p2 = (char *) tempString2;
break;
case TK_STRING:
case TK_IDENTIFIER:
case TK_CHAR:
p2 = (char *) bm->GetData();
break;
case ID_GET:
{
int type;
char *name;
//get( TYPE, NAME )
type = (int) (*(float *) block->GetMemberData( memberNum++ ));
name = (char *) block->GetMemberData( memberNum++ );
//Get the type returned and hold onto it
t2 = type;
switch ( type )
{
case TK_FLOAT:
{
float fVal;
if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false)
return false;
sprintf( (char *) tempString2, "%.3f", fVal );
p2 = (char *) tempString2;
}
break;
case TK_INT:
{
float fVal;
if ( m_ie->I_GetFloat( m_ownerID, type, name, &fVal ) == false)
return false;
sprintf( (char *) tempString2, "%d", (int) fVal );
p2 = (char *) tempString2;
}
break;
case TK_STRING:
if ( m_ie->I_GetString( m_ownerID, type, name, &p2 ) == false)
return false;
break;
case TK_VECTOR:
{
vector_t vVal;
if ( m_ie->I_GetVector( m_ownerID, type, name, vVal ) == false)
return false;
sprintf( (char *) tempString2, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] );
p2 = (char *) tempString2;
}
break;
}
break;
}
case ID_RANDOM:
{
float min, max;
//FIXME: This will not account for nested random() statements
min = *(float *) block->GetMemberData( memberNum++ );
max = *(float *) block->GetMemberData( memberNum++ );
//A float value is returned from the function
t2 = TK_FLOAT;
sprintf( (char *) tempString2, "%.3f", m_ie->I_Random( min, max ) );
p2 = (char *) tempString2;
}
break;
case ID_TAG:
{
char *name;
float type;
name = (char *) block->GetMemberData( memberNum++ );
type = *(float *) block->GetMemberData( memberNum++ );
t2 = TK_VECTOR;
//TODO: Emit warning
if ( m_ie->I_GetTag( m_ownerID, name, (int) type, vec ) == false)
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find tag \"%s\"!\n", name );
return false;
}
sprintf( (char *) tempString2, "%.3f %.3f %.3f", vec[0], vec[1], vec[2] );
p2 = (char *) tempString2;
break;
}
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on conditional" );
return false;
break;
}
return m_ie->I_Evaluate( t1, p1, t2, p2, oper );
}
/*
========================
CheckIf
Checks for if statement pre-processing
========================
*/
void CSequencer::CheckIf( CBlock **command )
{
CBlock *block = *command;
int successID, failureID;
CSequence *successSeq, *failureSeq;
if ( block == NULL )
return;
if ( block->GetBlockID() == ID_IF )
{
int ret = EvaluateConditional( block );
if ( ret /*TRUE*/ )
{
if ( block->HasFlag( BF_ELSE ) )
{
successID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 2 ));
}
else
{
successID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 1 ));
}
successSeq = GetSequence( successID );
//TODO: Emit warning
assert( successSeq );
if ( successSeq == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find conditional success sequence!\n" );
*command = NULL;
return;
}
//Only save the conditional statement if the calling sequence is retained
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = successSeq;
//Recursively work out any other pre-processors
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
if ( ( ret == false ) && ( block->HasFlag( BF_ELSE ) ) )
{
failureID = (int) (*(float *) block->GetMemberData( block->GetNumMembers() - 1 ));
failureSeq = GetSequence( failureID );
//TODO: Emit warning
assert( failureSeq );
if ( failureSeq == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find conditional failure sequence!\n" );
*command = NULL;
return;
}
//Only save the conditional statement if the calling sequence is retained
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = failureSeq;
//Recursively work out any other pre-processors
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
//Only save the conditional statement if the calling sequence is retained
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
//Conditional failed, just move on to the next command
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_CONDITIONAL ) ) )
{
assert( m_curSequence->GetReturn() );
if ( m_curSequence->GetReturn() == NULL )
{
*command = NULL;
return;
}
//Check to retain it
if ( m_curSequence->GetParent()->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
//Back out of the conditional and resume the previous sequence
m_curSequence = ReturnSequence( m_curSequence );
//This can safely happen
if ( m_curSequence == NULL )
{
*command = NULL;
return;
}
*command = PopCommand( POP_BACK );
Prep( command );
}
}
/*
========================
CheckLoop
Checks for loop command pre-processing
========================
*/
void CSequencer::CheckLoop( CBlock **command )
{
CBlockMember *bm;
CBlock *block = *command;
float min, max;
int iterations;
int loopID;
int memberNum = 0;
if ( block == NULL )
return;
//Check for a loop
if ( block->GetBlockID() == ID_LOOP )
{
//Get the loop ID
bm = block->GetMember( memberNum++ );
if ( bm->GetID() == ID_RANDOM )
{
//Parse out the random number
min = *(float *) block->GetMemberData( memberNum++ );
max = *(float *) block->GetMemberData( memberNum++ );
iterations = (int) m_ie->I_Random( min, max );
}
else
{
iterations = (int) (*(float *) bm->GetData());
}
loopID = (int) (*(float *) block->GetMemberData( memberNum++ ));
CSequence *loop = GetSequence( loopID );
//TODO: Emit warning
assert( loop );
if ( loop == NULL )
{
m_ie->I_DPrintf( WL_ERROR, "Unable to find 'loop' sequence!\n" );
*command = NULL;
return;
}
assert( loop->GetParent() );
if ( loop->GetParent() == NULL )
{
*command = NULL;
return;
}
//Restore the count if it has been lost
loop->SetIterations( iterations );
//Only save the loop command if the calling sequence is retained
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = loop;
//Recursively work out any other pre-processors
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
//Check for the end of the loop
if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_LOOP ) ) )
{
//We don't want to decrement -1
if ( m_curSequence->GetIterations() > 0 )
m_curSequence->SetIterations( m_curSequence->GetIterations()-1 ); //Nice, eh?
//Either there's another iteration, or it's infinite
if ( m_curSequence->GetIterations() != 0 )
{
//Another iteration is going to happen, so this will need to be considered again
PushCommand( block, PUSH_FRONT );
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
else
{
assert( m_curSequence->GetReturn() );
if ( m_curSequence->GetReturn() == NULL )
{
*command = NULL;
return;
}
//Check to retain it
if ( m_curSequence->GetParent()->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
//Back out of the loop and resume the previous sequence
m_curSequence = ReturnSequence( m_curSequence );
//This can safely happen
if ( m_curSequence == NULL )
{
*command = NULL;
return;
}
*command = PopCommand( POP_BACK );
Prep( command );
}
}
}
/*
========================
CheckFlush
Checks for flush command pre-processing
========================
*/
void CSequencer::CheckFlush( CBlock **command )
{
sequence_l::iterator sli;
CBlock *block = *command;
if ( block == NULL )
return;
if ( block->GetBlockID() == ID_FLUSH )
{
//Flush the sequence
Flush( m_curSequence );
//Check to retain it
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
}
/*
========================
CheckAffect
Checks for affect command pre-processing
========================
*/
void CSequencer::CheckAffect( CBlock **command )
{
CBlock *block = *command;
gentity_t *ent = NULL;
char *entname = NULL;
int memberNum = 0;
if ( block == NULL )
{
return;
}
if ( block->GetBlockID() == ID_AFFECT )
{
CSequencer *sequencer = NULL;
entname = (char*) block->GetMemberData( memberNum++ );
ent = m_ie->I_GetEntityByName( entname );
if( !ent ) // if there wasn't a valid entname in the affect, we need to check if it's a get command
{
//try to parse a 'get' command that is embeded in this 'affect'
int id;
char *p1 = NULL;
char *name = 0;
CBlockMember *bm = NULL;
//
// Get the first parameter (this should be the get)
//
bm = block->GetMember( 0 );
id = bm->GetID();
switch ( id )
{
// these 3 cases probably aren't necessary
case TK_STRING:
case TK_IDENTIFIER:
case TK_CHAR:
p1 = (char *) bm->GetData();
break;
case ID_GET:
{
int type;
//get( TYPE, NAME )
type = (int) (*(float *) block->GetMemberData( memberNum++ ));
name = (char *) block->GetMemberData( memberNum++ );
switch ( type ) // what type are they attempting to get
{
case TK_STRING:
//only string is acceptable for affect, store result in p1
if ( m_ie->I_GetString( m_ownerID, type, name, &p1 ) == false)
{
return;
}
break;
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _1" );
return;
break;
}
break;
}
default:
//FIXME: Make an enum id for the error...
m_ie->I_DPrintf( WL_ERROR, "Invalid parameter type on affect _2" );
return;
break;
}//end id switch
if(p1)
{
ent = m_ie->I_GetEntityByName( p1 );
}
if(!ent)
{ // a valid entity name was not returned from the get command
m_ie->I_DPrintf( WL_WARNING, "'%s' : invalid affect() target\n");
}
} // end if(!ent)
if( ent )
{
sequencer = ent->sequencer;
}
if(memberNum == 0)
{ //there was no get, increment manually before next step
memberNum++;
}
int type = (int) (*(float *) block->GetMemberData( memberNum ));
int id = (int) (*(float *) block->GetMemberData( memberNum+1 ));
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
//NOTENOTE: If this isn't found, continue on to the next command
if ( sequencer == NULL )
{
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
sequencer->Affect( id, type );
*command = PopCommand( POP_BACK );
Prep( command );
if( ent )
{ // ents need to update upon being affected
ent->taskManager->Update();
}
return;
}
if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_AFFECT ) ) )
{
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_curSequence = ReturnSequence( m_curSequence );
if ( m_curSequence == NULL )
{
*command = NULL;
return;
}
*command = PopCommand( POP_BACK );
Prep( command );
if( ent )
{ // ents need to update upon being affected
ent->taskManager->Update();
}
}
}
/*
-------------------------
CheckDo
-------------------------
*/
void CSequencer::CheckDo( CBlock **command )
{
CBlock *block = *command;
if ( block == NULL )
return;
if ( block->GetBlockID() == ID_DO )
{
//Get the sequence
const char *groupName = (const char *) block->GetMemberData( 0 );
CTaskGroup *group = m_taskManager->GetTaskGroup( groupName );
CSequence *sequence = GetTaskSequence( group );
//TODO: Emit warning
assert( group );
if ( group == NULL )
{
//TODO: Give name/number of entity trying to execute, too
m_ie->I_DPrintf( WL_ERROR, "ICARUS Unable to find task group \"%s\"!\n", groupName );
*command = NULL;
return;
}
//TODO: Emit warning
assert( sequence );
if ( sequence == NULL )
{
//TODO: Give name/number of entity trying to execute, too
m_ie->I_DPrintf( WL_ERROR, "ICARUS Unable to find task 'group' sequence!\n", groupName );
*command = NULL;
return;
}
//Only save the loop command if the calling sequence is retained
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
//Set this to our current sequence
sequence->SetReturn( m_curSequence );
m_curSequence = sequence;
group->SetParent( m_curGroup );
m_curGroup = group;
//Mark all the following commands as being in the task
m_taskManager->MarkTask( group->GetGUID(), TASK_START );
//Recursively work out any other pre-processors
*command = PopCommand( POP_BACK );
Prep( command );
return;
}
if ( ( block->GetBlockID() == ID_BLOCK_END ) && ( m_curSequence->HasFlag( SQ_TASK ) ) )
{
if ( m_curSequence->HasFlag( SQ_RETAIN ) )
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
*command = NULL;
}
m_taskManager->MarkTask( m_curGroup->GetGUID(), TASK_END );
m_curGroup = m_curGroup->GetParent();
CSequence *returnSeq = ReturnSequence( m_curSequence );
m_curSequence->SetReturn( NULL );
m_curSequence = returnSeq;
if ( m_curSequence == NULL )
{
*command = NULL;
return;
}
*command = PopCommand( POP_BACK );
Prep( command );
}
}
/*
========================
Prep
Handles internal sequencer maintenance
========================
*/
void CSequencer::Prep( CBlock **command )
{
//Check all pre-processes
CheckAffect( command );
CheckFlush( command );
CheckLoop( command );
CheckRun( command );
CheckIf( command );
CheckDo( command );
}
/*
========================
Prime
Starts communication between the task manager and this sequencer
========================
*/
int CSequencer::Prime( CTaskManager *taskManager, CBlock *command )
{
Prep( &command );
if ( command )
{
taskManager->SetCommand( command, PUSH_BACK );
}
return SEQ_OK;
}
/*
========================
Callback
Handles a completed task and returns a new task to be completed
========================
*/
int CSequencer::Callback( CTaskManager *taskManager, CBlock *block, int returnCode )
{
CBlock *command;
if (returnCode == TASK_RETURN_COMPLETE)
{
//There are no more pending commands
if ( m_curSequence == NULL )
return SEQ_OK;
//Check to retain the command
if ( m_curSequence->HasFlag( SQ_RETAIN ) ) //This isn't true for affect sequences...?
{
PushCommand( block, PUSH_FRONT );
}
else
{
delete block;
block = NULL;
}
//Check for pending commands
if ( m_curSequence->GetNumCommands() <= 0 )
{
if ( m_curSequence->GetReturn() == NULL)
return SEQ_OK;
m_curSequence = m_curSequence->GetReturn();
}
command = PopCommand( POP_BACK );
Prep( &command );
if ( command )
taskManager->SetCommand( command, PUSH_FRONT );
return SEQ_OK;
}
//FIXME: This could be more descriptive
m_ie->I_DPrintf( WL_ERROR, "command could not be called back\n" );
assert(0);
return SEQ_FAILED;
}
/*
-------------------------
Recall
-------------------------
*/
int CSequencer::Recall( void )
{
CBlock *block = NULL;
while ( ( block = m_taskManager->RecallTask() ) != NULL )
{
PushCommand( block, PUSH_BACK );
}
return true;
}
/*
-------------------------
Affect
-------------------------
*/
int CSequencer::Affect( int id, int type )
{
CSequence *sequence = GetSequence( id );
if ( sequence == NULL )
{
return SEQ_FAILED;
}
switch ( type )
{
case TYPE_FLUSH:
Recall();
//Get rid of all old code
Flush( sequence );
sequence->RemoveFlag( SQ_PENDING, true );
m_curSequence = sequence;
Prime( m_taskManager, PopCommand( POP_BACK ) );
break;
case TYPE_INSERT:
Recall();
sequence->SetReturn( m_curSequence );
sequence->RemoveFlag( SQ_PENDING, true );
m_curSequence = sequence;
Prime( m_taskManager, PopCommand( POP_BACK ) );
break;
default:
m_ie->I_DPrintf( WL_ERROR, "unknown affect type found" );
break;
}
return SEQ_OK;
}
/*
========================
PushCommand
Pushes a commands onto the current sequence
========================
*/
int CSequencer::PushCommand( CBlock *command, int flag )
{
//Make sure everything is ok
assert( m_curSequence );
if ( m_curSequence == NULL )
return SEQ_FAILED;
m_curSequence->PushCommand( command, flag );
m_numCommands++;
//Invalid flag
return SEQ_OK;
}
/*
========================
PopCommand
Pops a command off the current sequence
========================
*/
CBlock *CSequencer::PopCommand( int flag )
{
//Make sure everything is ok
assert( m_curSequence );
if ( m_curSequence == NULL )
return NULL;
CBlock *block = m_curSequence->PopCommand( flag );
if ( block != NULL )
m_numCommands--;
return block;
}
/*
========================
StripExtension
Filename ultility. Probably get rid of this if I decided to use CStrings...
========================
*/
void CSequencer::StripExtension( const char *in, char *out )
{
int i = strlen(in) + 1;
while ( (in[i] != '.') && (i >= 0) )
i--;
if ( i < 0 )
{
strcpy(out, in);
return;
}
strncpy(out, in, i);
}
/*
-------------------------
RemoveSequence
-------------------------
*/
//NOTENOTE: This only removes references to the sequence, IT DOES NOT FREE THE ALLOCATED MEMORY! You've be warned! =)
int CSequencer::RemoveSequence( CSequence *sequence, bool eraseSequence, bool removeChildren )
{
CSequence *temp;
if ( eraseSequence )
{
//Remove it from the list
m_sequenceMap.erase( sequence->GetID() );
m_sequences.remove( sequence );
}
int numChildren = sequence->GetNumChildren();
//Add all the children
for ( int i = 0; i < numChildren; i++ )
{
temp = sequence->GetChild( i );
//TODO: Emit warning
assert( temp );
if ( temp == NULL )
{
m_ie->I_DPrintf( WL_WARNING, "Unable to find child sequence on RemoveSequence call!\n" );
continue;
}
//Remove the references to this sequence
temp->SetParent( NULL );
temp->SetReturn( NULL );
if ( removeChildren )
RemoveSequence( temp, eraseSequence, removeChildren );
}
return SEQ_OK;
}
/*
-------------------------
ReturnSequence
-------------------------
*/
inline CSequence *CSequencer::ReturnSequence( CSequence *sequence )
{
while ( sequence->GetReturn() )
{
assert(sequence != sequence->GetReturn() );
if ( sequence == sequence->GetReturn() )
return NULL;
sequence = sequence->GetReturn();
if ( sequence->GetNumCommands() > 0 )
return sequence;
}
return NULL;
}
//Save / Load
/*
-------------------------
Save
-------------------------
*/
int CSequencer::Save( void )
{
sequence_l::iterator si;
taskSequence_m::iterator ti;
int numSequences = 0, id, numTasks;
//Get the number of sequences to save out
numSequences = m_sequences.size();
//Save out the owner sequence
m_ie->I_WriteSaveData( 'SQRE', &m_ownerID, sizeof( m_ownerID ) );
//Write out the number of sequences we need to read
m_ie->I_WriteSaveData( 'SQR#', &numSequences, sizeof( numSequences ) );
//Second pass, save out all sequences, in order
STL_ITERATE( si, m_sequences )
{
id = (*si)->GetID();
m_ie->I_WriteSaveData( 'SQRI', &id, sizeof( id ) );
}
//Save out the taskManager
m_taskManager->Save();
//Save out the task sequences mapping the name to the GUIDs
numTasks = m_taskSequences.size();
m_ie->I_WriteSaveData( 'SQT#', &numTasks, sizeof ( numTasks ) );
STL_ITERATE( ti, m_taskSequences )
{
//Save the task group's ID
id = ((*ti).first)->GetGUID();
m_ie->I_WriteSaveData( 'STID', &id, sizeof( id ) );
//Save the sequence's ID
id = ((*ti).second)->GetID();
m_ie->I_WriteSaveData( 'SSID', &id, sizeof( id ) );
}
int curGroupID = ( m_curGroup == NULL ) ? -1 : m_curGroup->GetGUID();
m_ie->I_WriteSaveData( 'SQCT', &curGroupID, sizeof ( m_numCommands ) );
//Output the number of commands
m_ie->I_WriteSaveData( 'SQ#C', &m_numCommands, sizeof ( m_numCommands ) ); //FIXME: This can be reconstructed
//Output the ID of the current sequence
id = ( m_curSequence != NULL ) ? m_curSequence->GetID() : -1;
m_ie->I_WriteSaveData( 'SQCS', &id, sizeof ( id ) );
return true;
}
/*
-------------------------
Load
-------------------------
*/
int CSequencer::Load( void )
{
//Get the owner of this sequencer
m_ie->I_ReadSaveData( 'SQRE', &m_ownerID, sizeof( m_ownerID ) );
//Link the entity back to the sequencer
m_ie->I_LinkEntity( m_ownerID, this, m_taskManager );
CTaskGroup *taskGroup;
CSequence *seq;
int numSequences, seqID, taskID, numTasks;
//Get the number of sequences to read
m_ie->I_ReadSaveData( 'SQR#', &numSequences, sizeof( numSequences ) );
//Read in all the sequences
for ( int i = 0; i < numSequences; i++ )
{
m_ie->I_ReadSaveData( 'SQRI', &seqID, sizeof( seqID ) );
seq = m_owner->GetSequence( seqID );
assert( seq );
STL_INSERT( m_sequences, seq );
m_sequenceMap[ seqID ] = seq;
}
//Setup the task manager
m_taskManager->Init( this );
//Load the task manager
m_taskManager->Load();
//Get the number of tasks in the map
m_ie->I_ReadSaveData( 'SQT#', &numTasks, sizeof( numTasks ) );
//Read in, and reassociate the tasks to the sequences
for ( i = 0; i < numTasks; i++ )
{
//Read in the task's ID
m_ie->I_ReadSaveData( 'STID', &taskID, sizeof( taskID ) );
//Read in the sequence's ID
m_ie->I_ReadSaveData( 'SSID', &seqID, sizeof( seqID ) );
taskGroup = m_taskManager->GetTaskGroup( taskID );
assert( taskGroup );
seq = m_owner->GetSequence( seqID );
assert( seq );
//Associate the values
m_taskSequences[ taskGroup ] = seq;
}
int curGroupID;
//Get the current task group
m_ie->I_ReadSaveData( 'SQCT', &curGroupID, sizeof( curGroupID ) );
m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskManager->GetTaskGroup( curGroupID );
//Get the number of commands
m_ie->I_ReadSaveData( 'SQ#C', &m_numCommands, sizeof( m_numCommands ) );
//Get the current sequence
m_ie->I_ReadSaveData( 'SQCS', &seqID, sizeof( seqID ) );
m_curSequence = ( seqID != -1 ) ? m_owner->GetSequence( seqID ) : NULL;
return true;
}