2384 lines
48 KiB
C++
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;
|
|
}
|