/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ // Script Command Sequencer // // -- jweier // this include must remain at the top of every Icarus CPP file #include "StdAfx.h" #include "IcarusImplementation.h" #include "blockstream.h" #include "sequence.h" #include "taskmanager.h" #include "sequencer.h" #define S_FAILED(a) (a!=SEQ_OK) #define STL_ITERATE( a, b ) for ( a = b.begin(); a != b.end(); ++a ) #define STL_INSERT( a, b ) a.insert( a.end(), b ); // Save/Load restructuring. // Date: 10/29/02 // By: Aurelio Reis // Purpose: In an effort to reduce file size and overhead, it was decided that // using a chunks for EVERYTHING is vastly inefficient and wasteful. Thus, the saving // is now primary focused on saving large chunks with *expected* data read from there. // Sequencer CSequencer::CSequencer( void ) { static int uniqueID = 1; m_id = uniqueID++; m_numCommands = 0; m_curStream = NULL; m_curSequence = NULL; m_elseValid = 0; m_elseOwner = NULL; m_curGroup = NULL; } CSequencer::~CSequencer( void ) { } /* ======================== Create Static creation function ======================== */ CSequencer *CSequencer::Create ( void ) { CSequencer *sequencer = new CSequencer; return sequencer; } /* ======================== Init Initializes the sequencer ======================== */ int CSequencer::Init( int ownerID, CTaskManager *taskManager ) { m_ownerID = ownerID; m_taskManager = taskManager; return SEQ_OK; } /* ======================== Free Releases all resources and re-inits the sequencer ======================== */ void CSequencer::Free( CIcarus* icarus ) { //Flush the sequences /* sequenceID_m::iterator iterSeq = NULL; for ( iterSeq = m_sequenceMap.begin(); iterSeq != m_sequenceMap.end(); iterSeq++ ) { icarus->DeleteSequence( (*iterSeq).second ); } m_sequenceMap.clear();*/ // OLD STUFF! sequence_l::iterator sli; for ( sli = m_sequences.begin(); sli != m_sequences.end(); ++sli ) { icarus->DeleteSequence( (*sli) ); } m_sequences.clear(); m_taskSequences.clear(); //Clean up any other info m_numCommands = 0; m_curSequence = NULL; bstream_t *streamToDel; while(!m_streamsCreated.empty()) { streamToDel = m_streamsCreated.back(); DeleteStream(streamToDel); } delete this; } /* ------------------------- Flush ------------------------- */ int CSequencer::Flush( CSequence *owner, CIcarus* icarus ) { if ( owner == NULL ) return SEQ_FAILED; Recall(icarus); //Flush the sequences /* sequenceID_m::iterator iterSeq = NULL; for ( iterSeq = m_sequenceMap.begin(); iterSeq != m_sequenceMap.end(); ) { if ( ( (*iterSeq).second == owner ) || ( owner->HasChild( (*iterSeq).second ) ) || ( (*iterSeq).second->HasFlag( CSequence::SQ_PENDING ) ) || ( (*iterSeq).second->HasFlag( CSequence::SQ_TASK ) ) ) { iterSeq++; continue; } //Delete it, and remove all references RemoveSequence( (*iterSeq).second, icarus ); icarus->DeleteSequence( (*iterSeq).second ); //Remove it from the map //Delete from the sequence list and move on iterSeq = m_sequenceMap.erase( iterSeq ); }*/ // OLD STUFF! //Flush the sequences sequence_l::iterator sli; for ( sli = m_sequences.begin(); sli != m_sequences.end(); ) { if ( ( (*sli) == owner ) || ( owner->HasChild( (*sli) ) ) || ( (*sli)->HasFlag( CSequence::SQ_PENDING ) ) || ( (*sli)->HasFlag( CSequence::SQ_TASK ) ) ) { ++sli; continue; } //Remove it from the map //m_sequenceMap.erase( (*sli)->GetID() ); //Delete it, and remove all references RemoveSequence( (*sli), icarus ); icarus->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; m_streamsCreated.push_back(stream); return stream; } /* ======================== DeleteStream Deletes parsing stream ======================== */ void CSequencer::DeleteStream( bstream_t *bstream ) { std::vector::iterator finder = std::find(m_streamsCreated.begin(), m_streamsCreated.end(), bstream); if(finder != m_streamsCreated.end()) { m_streamsCreated.erase(finder); } 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( CIcarus* icarus ) { CSequence *sequence = (CSequence*)icarus->GetSequence(); assert( sequence ); if ( sequence == NULL ) return NULL; //The rest is handled internally to the class //m_sequenceMap[ sequence->GetID() ] = sequence; // OLD STUFF! //Add it to the list m_sequences.insert( m_sequences.end(), sequence ); //FIXME: Temp fix sequence->SetFlag( CSequence::SQ_PENDING ); return sequence; } CSequence *CSequencer::AddSequence( CSequence *parent, CSequence *returnSeq, int flags, CIcarus* icarus ) { CSequence *sequence = (CSequence*)icarus->GetSequence(); assert( sequence ); if ( sequence == NULL ) return NULL; //The rest is handled internally to the class // m_sequenceMap[ sequence->GetID() ] = sequence; // OLD STUFF! //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;*/ sequence_l::iterator iterSeq; STL_ITERATE( iterSeq, m_sequences ) { if ( (*iterSeq)->GetID() == id ) return (*iterSeq); } return NULL; } /* ------------------------- Interrupt ------------------------- */ void CSequencer::Interrupt( void ) { CBlock *command = m_taskManager->GetCurrentTask(); if ( command == NULL ) return; //Save it PushCommand( command, CSequence::PUSH_BACK ); } /* ======================== Run Runs a script ======================== */ int CSequencer::Run( char *buffer, long size, CIcarus* icarus ) { bstream_t *blockStream; IGameInterface* game = icarus->GetGame(); Recall(icarus); //Create a new stream blockStream = AddStream(); //Open the stream as an IBI stream if (!blockStream->stream->Open( buffer, size )) { game->DebugPrint(IGameInterface::WL_ERROR, "invalid stream" ); return SEQ_FAILED; } CSequence *sequence = AddSequence( NULL, m_curSequence, CSequence::SQ_COMMON, icarus ); // Interpret the command blocks and route them properly if ( S_FAILED( Route( sequence, blockStream, icarus )) ) { //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 , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CSequence *new_sequence; bstream_t *new_stream; char *buffer; char newname[ CIcarus::MAX_STRING_SIZE ]; int buffer_size; //Get the name and format it COM_StripExtension( (char*) block->GetMemberData( 0 ), (char *) newname, sizeof(newname) ); //Get the file from the game engine buffer_size = game->LoadFile( newname, (void **) &buffer ); if ( buffer_size <= 0 ) { game->DebugPrint(IGameInterface::WL_ERROR, "'%s' : could not open file\n", (char*) block->GetMemberData( 0 )); block->Free(icarus); delete block; block = NULL; 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 )) { game->DebugPrint(IGameInterface::WL_ERROR, "invalid stream" ); block->Free(icarus); delete block; block = NULL; return SEQ_FAILED; } //Create a new sequence new_sequence = AddSequence( m_curSequence, m_curSequence, ( CSequence::SQ_RUN | CSequence::SQ_PENDING ), icarus ); m_curSequence->AddChild( new_sequence ); // Interpret the command blocks and route them properly if ( S_FAILED( Route( new_sequence, new_stream, icarus )) ) { //Error code is set inside of Route() block->Free(icarus); delete block; block = NULL; return SEQ_FAILED; } m_curSequence = m_curSequence->GetReturn(); assert( m_curSequence ); block->Write( CIcarus::TK_FLOAT, (float) new_sequence->GetID() , icarus); PushCommand( block, CSequence::PUSH_FRONT ); return SEQ_OK; } /* ======================== ParseIf Parses an if statement ======================== */ int CSequencer::ParseIf( CBlock *block, bstream_t *bstream , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CSequence *sequence; //Create the container sequence sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_CONDITIONAL, icarus); assert( sequence ); if ( sequence == NULL ) { game->DebugPrint(IGameInterface::WL_ERROR, "ParseIf: failed to allocate container sequence" ); block->Free(icarus); delete block; block = NULL; return SEQ_FAILED; } m_curSequence->AddChild( sequence ); //Add a unique conditional identifier to the block for reference later block->Write( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); //Push this onto the stack to mark the conditional entrance PushCommand( block, CSequence::PUSH_FRONT ); //Recursively obtain the conditional body Route( sequence, bstream, icarus ); m_elseValid = 2; m_elseOwner = block; return SEQ_OK; } /* ======================== ParseElse Parses an else statement ======================== */ int CSequencer::ParseElse( CBlock *block, bstream_t *bstream , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); //The else is not retained block->Free(icarus); delete block; block = NULL; CSequence *sequence; //Create the container sequence sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_CONDITIONAL, icarus ); assert( sequence ); if ( sequence == NULL ) { game->DebugPrint(IGameInterface::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 ) { game->DebugPrint(IGameInterface::WL_ERROR, "Invalid 'else' found!\n" ); return SEQ_FAILED; } m_elseOwner->Write( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); m_elseOwner->SetFlag( BF_ELSE ); //Recursively obtain the conditional body Route( sequence, bstream, icarus ); m_elseValid = 0; m_elseOwner = NULL; return SEQ_OK; } /* ======================== ParseLoop Parses a loop command ======================== */ int CSequencer::ParseLoop( CBlock *block, bstream_t *bstream , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CSequence *sequence; CBlockMember *bm; float min, max; int rIter; int memberNum = 0; //Set the parent sequence = AddSequence( m_curSequence, m_curSequence, ( CSequence::SQ_LOOP | CSequence::SQ_RETAIN ), icarus ); assert( sequence ); if ( sequence == NULL ) { game->DebugPrint(IGameInterface::WL_ERROR, "ParseLoop : failed to allocate container sequence" ); block->Free(icarus); delete block; block = NULL; return SEQ_FAILED; } m_curSequence->AddChild( sequence ); //Set the number of iterations of this sequence bm = block->GetMember( memberNum++ ); if ( bm->GetID() == CIcarus::ID_RANDOM ) { //Parse out the random number min = *(float *) block->GetMemberData( memberNum++ ); max = *(float *) block->GetMemberData( memberNum++ ); rIter = (int) game->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( CIcarus::TK_FLOAT, (float) sequence->GetID(), icarus ); //Push this onto the stack to mark the loop entrance PushCommand( block, CSequence::PUSH_FRONT ); //Recursively obtain the loop Route( sequence, bstream , icarus); 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, CIcarus* icarus ) { CSequence *sequence = AddSequence(icarus); bstream_t new_stream; sequence->SetFlag( CSequence::SQ_AFFECT | CSequence::SQ_PENDING ); if ( retain ) sequence->SetFlag( CSequence::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 , icarus) ) { 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, CIcarus* icarus ) { IGameInterface* game = icarus->GetGame(); CSequencer *stream_sequencer = NULL; char *entname = NULL; int ret; int ent = -1; entname = (char*) block->GetMemberData( 0 ); ent = game->GetByName( entname ); if( ent < 0 ) // 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 CIcarus::TK_STRING: case CIcarus::TK_IDENTIFIER: case CIcarus::TK_CHAR: p1 = (char *) bm->GetData(); break; case CIcarus::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 CIcarus::TK_STRING: //only string is acceptable for affect, store result in p1 if ( game->GetString( m_ownerID, name, &p1 ) == false) { block->Free(icarus); delete block; block = NULL; return false; } break; default: //FIXME: Make an enum id for the error... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _1" ); block->Free(icarus); delete block; block = NULL; return false; break; } break; } default: //FIXME: Make an enum id for the error... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _2" ); block->Free(icarus); delete block; block = NULL; return false; break; }//end id switch if(p1) { ent = game->GetByName( p1 ); } if(ent < 0) { // a valid entity name was not returned from the get command game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n"); } } // end if(!ent) if( ent >= 0 ) { int sequencerID = game->CreateIcarus(ent); stream_sequencer = icarus->FindSequencer(sequencerID); } if (stream_sequencer == NULL) { game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n", entname ); //Fast-forward out of this affect block onto the next valid code CSequence *backSeq = m_curSequence; CSequence *trashSeq = (CSequence*)icarus->GetSequence(); Route( trashSeq, bstream , icarus); Recall(icarus); DestroySequence( trashSeq, icarus ); m_curSequence = backSeq; block->Free(icarus); delete block; block = NULL; return SEQ_OK; } if S_FAILED ( stream_sequencer->AddAffect( bstream, (int) m_curSequence->HasFlag( CSequence::SQ_RETAIN ), &ret, icarus) ) { block->Free(icarus); delete block; block = NULL; return SEQ_FAILED; } //Hold onto the id for later use //FIXME: If the target sequence is freed, what then? (!suspect!) block->Write( CIcarus::TK_FLOAT, (float) ret, icarus ); PushCommand( block, CSequence::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 , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CSequence *sequence; CTaskGroup *group; const char *taskName; //Setup the container sequence sequence = AddSequence( m_curSequence, m_curSequence, CSequence::SQ_TASK | CSequence::SQ_RETAIN, icarus); 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, icarus ); if ( group == NULL ) { game->DebugPrint(IGameInterface::WL_ERROR, "error : unable to allocate a new task group" ); block->Free(icarus); delete block; block = NULL; 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 ); block->Free(icarus); delete block; block = NULL; //Recursively obtain the loop Route( sequence, bstream, icarus ); 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 , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); 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 , icarus); //TEMP: HACK! if ( m_elseValid ) m_elseValid--; switch( block->GetBlockID() ) { //Marks the end of a blocked section case CIcarus::ID_BLOCK_END: //Save this as a pre-process marker PushCommand( block, CSequence::PUSH_FRONT ); if ( m_curSequence->HasFlag( CSequence::SQ_RUN ) || m_curSequence->HasFlag( CSequence::SQ_AFFECT ) ) { //Go back to the last stream m_curStream = bstream->last; } if ( m_curSequence->HasFlag( CSequence::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 CIcarus::ID_AFFECT: if S_FAILED( ParseAffect( block, bstream, icarus ) ) return SEQ_FAILED; break; //Run pre-processor case CIcarus::ID_RUN: if S_FAILED( ParseRun( block, icarus ) ) return SEQ_FAILED; break; //Loop pre-processor case CIcarus::ID_LOOP: if S_FAILED( ParseLoop( block, bstream, icarus ) ) return SEQ_FAILED; break; //Conditional pre-processor case CIcarus::ID_IF: if S_FAILED( ParseIf( block, bstream, icarus ) ) return SEQ_FAILED; break; case CIcarus::ID_ELSE: //TODO: Emit warning if ( m_elseValid == 0 ) { game->DebugPrint(IGameInterface::WL_ERROR, "Invalid 'else' found!\n" ); return SEQ_FAILED; } if S_FAILED( ParseElse( block, bstream, icarus ) ) return SEQ_FAILED; break; case CIcarus::ID_TASK: if S_FAILED( ParseTask( block, bstream, icarus ) ) return SEQ_FAILED; break; //FIXME: For now this is to catch problems, but can ultimately be removed case CIcarus::ID_WAIT: case CIcarus::ID_PRINT: case CIcarus::ID_SOUND: case CIcarus::ID_MOVE: case CIcarus::ID_ROTATE: case CIcarus::ID_SET: case CIcarus::ID_USE: case CIcarus::ID_REMOVE: case CIcarus::ID_KILL: case CIcarus::ID_FLUSH: case CIcarus::ID_CAMERA: case CIcarus::ID_DO: case CIcarus::ID_DECLARE: case CIcarus::ID_FREE: case CIcarus::ID_SIGNAL: case CIcarus::ID_WAITSIGNAL: case CIcarus::ID_PLAY: //Commands go directly into the sequence without pre-process PushCommand( block, CSequence::PUSH_FRONT ); break; //Error default: game->DebugPrint(IGameInterface::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( CSequence::SQ_RUN ) ) { block = new CBlock; block->Create( CIcarus::ID_BLOCK_END ); PushCommand( block, CSequence::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( CSequence::POP_BACK ), icarus ); } 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 , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CBlock *block = *command; if ( block == NULL ) return; //Check for a run command if ( block->GetBlockID() == CIcarus::ID_RUN ) { int id = (int) (*(float *) block->GetMemberData( 1 )); game->DebugPrint(IGameInterface::WL_DEBUG, "%4d run( \"%s\" ); [%d]", m_ownerID, (char *) block->GetMemberData(0), game->GetTime() ); if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = GetSequence( id ); //TODO: Emit warning assert( m_curSequence ); if ( m_curSequence == NULL ) { game->DebugPrint(IGameInterface::WL_ERROR, "Unable to find 'run' sequence!\n" ); *command = NULL; return; } if ( m_curSequence->GetNumCommands() > 0 ) { *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); //Account for any other pre-processes return; } return; } //Check for the end of a run if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_RUN ) ) ) { if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = ReturnSequence( m_curSequence ); if ( m_curSequence && m_curSequence->GetNumCommands() > 0 ) { *command = PopCommand( CSequence::POP_BACK ); Prep( command, icarus ); //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 , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CBlockMember *bm; char tempString1[128], tempString2[128]; vec3_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 CIcarus::TK_FLOAT: sprintf( (char *) tempString1, "%.3f", *(float *) bm->GetData() ); p1 = (char *) tempString1; break; case CIcarus::TK_VECTOR: tempString1[0] = '\0'; 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 CIcarus::TK_STRING: case CIcarus::TK_IDENTIFIER: case CIcarus::TK_CHAR: p1 = (char *) bm->GetData(); break; case CIcarus::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 CIcarus::TK_FLOAT: { float fVal; if ( game->GetFloat( m_ownerID, name, &fVal ) == false) return false; sprintf( (char *) tempString1, "%.3f", fVal ); p1 = (char *) tempString1; } break; case CIcarus::TK_INT: { float fVal; if ( game->GetFloat( m_ownerID, name, &fVal ) == false) return false; sprintf( (char *) tempString1, "%d", (int) fVal ); p1 = (char *) tempString1; } break; case CIcarus::TK_STRING: if ( game->GetString( m_ownerID, name, &p1 ) == false) return false; break; case CIcarus::TK_VECTOR: { vec3_t vVal; if ( game->GetVector( m_ownerID, name, vVal ) == false) return false; sprintf( (char *) tempString1, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); p1 = (char *) tempString1; } break; } break; } case CIcarus::ID_RANDOM: { float min, max; //FIXME: This will not account for nested Q_flrand(0.0f, 1.0f) statements min = *(float *) block->GetMemberData( memberNum++ ); max = *(float *) block->GetMemberData( memberNum++ ); //A float value is returned from the function t1 = CIcarus::TK_FLOAT; sprintf( (char *) tempString1, "%.3f", game->Random( min, max ) ); p1 = (char *) tempString1; } break; case CIcarus::ID_TAG: { char *name; float type; name = (char *) block->GetMemberData( memberNum++ ); type = *(float *) block->GetMemberData( memberNum++ ); t1 = CIcarus::TK_VECTOR; //TODO: Emit warning if ( game->GetTag( m_ownerID, name, (int) type, vec ) == false) { game->DebugPrint(IGameInterface::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... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on conditional" ); return false; break; } // // Get the comparison operator // bm = block->GetMember( memberNum++ ); id = bm->GetID(); switch ( id ) { case CIcarus::TK_EQUALS: case CIcarus::TK_GREATER_THAN: case CIcarus::TK_LESS_THAN: case CIcarus::TK_NOT: oper = id; break; default: game->DebugPrint(IGameInterface::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 CIcarus::TK_FLOAT: sprintf( (char *) tempString2, "%.3f", *(float *) bm->GetData() ); p2 = (char *) tempString2; break; case CIcarus::TK_VECTOR: tempString2[0] = '\0'; 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 CIcarus::TK_STRING: case CIcarus::TK_IDENTIFIER: case CIcarus::TK_CHAR: p2 = (char *) bm->GetData(); break; case CIcarus::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 CIcarus::TK_FLOAT: { float fVal; if ( game->GetFloat( m_ownerID, name, &fVal ) == false) return false; sprintf( (char *) tempString2, "%.3f", fVal ); p2 = (char *) tempString2; } break; case CIcarus::TK_INT: { float fVal; if ( game->GetFloat( m_ownerID, name, &fVal ) == false) return false; sprintf( (char *) tempString2, "%d", (int) fVal ); p2 = (char *) tempString2; } break; case CIcarus::TK_STRING: if ( game->GetString( m_ownerID, name, &p2 ) == false) return false; break; case CIcarus::TK_VECTOR: { vec3_t vVal; if ( game->GetVector( m_ownerID, name, vVal ) == false) return false; sprintf( (char *) tempString2, "%.3f %.3f %.3f", vVal[0], vVal[1], vVal[2] ); p2 = (char *) tempString2; } break; } break; } case CIcarus::ID_RANDOM: { float min, max; //FIXME: This will not account for nested Q_flrand(0.0f, 1.0f) statements min = *(float *) block->GetMemberData( memberNum++ ); max = *(float *) block->GetMemberData( memberNum++ ); //A float value is returned from the function t2 = CIcarus::TK_FLOAT; sprintf( (char *) tempString2, "%.3f", game->Random( min, max ) ); p2 = (char *) tempString2; } break; case CIcarus::ID_TAG: { char *name; float type; name = (char *) block->GetMemberData( memberNum++ ); type = *(float *) block->GetMemberData( memberNum++ ); t2 = CIcarus::TK_VECTOR; //TODO: Emit warning if ( game->GetTag( m_ownerID, name, (int) type, vec ) == false) { game->DebugPrint(IGameInterface::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... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on conditional" ); return false; break; } return game->Evaluate( t1, p1, t2, p2, oper ); } /* ======================== CheckIf Checks for if statement pre-processing ======================== */ void CSequencer::CheckIf( CBlock **command , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CBlock *block = *command; int successID, failureID; CSequence *successSeq, *failureSeq; if ( block == NULL ) return; if ( block->GetBlockID() == CIcarus::ID_IF ) { int ret = EvaluateConditional( block, icarus ); 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 ) { game->DebugPrint(IGameInterface::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( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = successSeq; //Recursively work out any other pre-processors *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); 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 ) { game->DebugPrint(IGameInterface::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( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = failureSeq; //Recursively work out any other pre-processors *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } //Only save the conditional statement if the calling sequence is retained if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } //Conditional failed, just move on to the next command *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_CONDITIONAL ) ) ) { assert( m_curSequence->GetReturn() ); if ( m_curSequence->GetReturn() == NULL ) { *command = NULL; return; } //Check to retain it if ( m_curSequence->GetParent()->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); 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( CSequence::POP_BACK ); Prep( command , icarus); } } /* ======================== CheckLoop Checks for loop command pre-processing ======================== */ void CSequencer::CheckLoop( CBlock **command , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); 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() == CIcarus::ID_LOOP ) { //Get the loop ID bm = block->GetMember( memberNum++ ); if ( bm->GetID() == CIcarus::ID_RANDOM ) { //Parse out the random number min = *(float *) block->GetMemberData( memberNum++ ); max = *(float *) block->GetMemberData( memberNum++ ); iterations = (int) game->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 ) { game->DebugPrint(IGameInterface::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( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = loop; //Recursively work out any other pre-processors *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } //Check for the end of the loop if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::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, CSequence::PUSH_FRONT ); *command = PopCommand( CSequence::POP_BACK ); Prep( command, icarus ); return; } else { assert( m_curSequence->GetReturn() ); if ( m_curSequence->GetReturn() == NULL ) { *command = NULL; return; } //Check to retain it if ( m_curSequence->GetParent()->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); 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( CSequence::POP_BACK ); Prep( command, icarus); } } } /* ======================== CheckFlush Checks for flush command pre-processing ======================== */ void CSequencer::CheckFlush( CBlock **command, CIcarus* icarus) { CBlock *block = *command; if ( block == NULL ) return; if ( block->GetBlockID() == CIcarus::ID_FLUSH ) { //Flush the sequence Flush( m_curSequence, icarus ); //Check to retain it if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } } /* ======================== CheckAffect Checks for affect command pre-processing ======================== */ void CSequencer::CheckAffect( CBlock **command , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CBlock *block = *command; int ent = -1; char *entname = NULL; int memberNum = 0; if ( block == NULL ) { return; } if ( block->GetBlockID() == CIcarus::ID_AFFECT ) { CSequencer *sequencer = NULL; entname = (char*) block->GetMemberData( memberNum++ ); ent = game->GetByName( entname ); if( ent < 0 ) // 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 CIcarus::TK_STRING: case CIcarus::TK_IDENTIFIER: case CIcarus::TK_CHAR: p1 = (char *) bm->GetData(); break; case CIcarus::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 CIcarus::TK_STRING: //only string is acceptable for affect, store result in p1 if ( game->GetString( m_ownerID, name, &p1 ) == false) { return; } break; default: //FIXME: Make an enum id for the error... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _1" ); return; break; } break; } default: //FIXME: Make an enum id for the error... game->DebugPrint(IGameInterface::WL_ERROR, "Invalid parameter type on affect _2" ); return; break; }//end id switch if(p1) { ent = game->GetByName( p1 ); } if(ent < 0) { // a valid entity name was not returned from the get command game->DebugPrint(IGameInterface::WL_WARNING, "'%s' : invalid affect() target\n"); } } // end if(!ent) if( ent >= 0) { int sequencerID = game->CreateIcarus(ent); sequencer = icarus->FindSequencer(sequencerID); } 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( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } //NOTENOTE: If this isn't found, continue on to the next command if ( sequencer == NULL ) { *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } sequencer->Affect( id, type , icarus); *command = PopCommand( CSequence::POP_BACK ); Prep( command, icarus ); if( ent >= 0 ) { // ents need to update upon being affected int sequencerID = game->CreateIcarus(ent); CSequencer* entsequencer = icarus->FindSequencer(sequencerID); CTaskManager* taskmanager = entsequencer->GetTaskManager(); if(taskmanager) { taskmanager->Update(icarus); } } return; } if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_AFFECT ) ) ) { if ( m_curSequence->HasFlag(CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_curSequence = ReturnSequence( m_curSequence ); if ( m_curSequence == NULL ) { *command = NULL; return; } *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); if( ent >= 0) { // ents need to update upon being affected int sequencerID = game->CreateIcarus(ent); CSequencer* entsequencer = icarus->FindSequencer(sequencerID); CTaskManager* taskmanager = entsequencer->GetTaskManager(); if(taskmanager) { taskmanager->Update(icarus); } } } } /* ------------------------- CheckDo ------------------------- */ void CSequencer::CheckDo( CBlock **command , CIcarus* icarus) { IGameInterface* game = icarus->GetGame(); CBlock *block = *command; if ( block == NULL ) return; if ( block->GetBlockID() == CIcarus::ID_DO ) { //Get the sequence const char *groupName = (const char *) block->GetMemberData( 0 ); CTaskGroup *group = m_taskManager->GetTaskGroup( groupName, icarus ); CSequence *sequence = GetTaskSequence( group ); //TODO: Emit warning assert( group ); if ( group == NULL ) { //TODO: Give name/number of entity trying to execute, too game->DebugPrint(IGameInterface::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 game->DebugPrint(IGameInterface::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( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); 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, icarus ); //Recursively work out any other pre-processors *command = PopCommand( CSequence::POP_BACK ); Prep( command , icarus); return; } if ( ( block->GetBlockID() == CIcarus::ID_BLOCK_END ) && ( m_curSequence->HasFlag( CSequence::SQ_TASK ) ) ) { if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); delete block; block = NULL; *command = NULL; } m_taskManager->MarkTask( m_curGroup->GetGUID(), TASK_END, icarus ); 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( CSequence::POP_BACK ); Prep( command , icarus); } } /* ======================== Prep Handles internal sequencer maintenance ======================== */ void CSequencer::Prep( CBlock **command , CIcarus* icarus) { //Check all pre-processes CheckAffect( command , icarus); CheckFlush( command , icarus); CheckLoop( command , icarus); CheckRun( command , icarus); CheckIf( command , icarus); CheckDo( command , icarus); } /* ======================== Prime Starts communication between the task manager and this sequencer ======================== */ int CSequencer::Prime( CTaskManager *taskManager, CBlock *command , CIcarus* icarus) { Prep( &command , icarus); if ( command ) { taskManager->SetCommand( command, CSequence::PUSH_BACK, icarus ); } 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, CIcarus* icarus ) { IGameInterface* game = icarus->GetGame(); CBlock *command; if (returnCode == TASK_RETURN_COMPLETE) { //There are no more pending commands if ( m_curSequence == NULL ) { block->Free(icarus); delete block; block = NULL; return SEQ_OK; } //Check to retain the command if ( m_curSequence->HasFlag( CSequence::SQ_RETAIN ) ) //This isn't true for affect sequences...? { PushCommand( block, CSequence::PUSH_FRONT ); } else { block->Free(icarus); 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( CSequence::POP_BACK ); Prep( &command , icarus); if ( command ) taskManager->SetCommand( command, CSequence::PUSH_FRONT, icarus ); return SEQ_OK; } //FIXME: This could be more descriptive game->DebugPrint(IGameInterface::WL_ERROR, "command could not be called back\n" ); assert(0); return SEQ_FAILED; } /* ------------------------- Recall ------------------------- */ int CSequencer::Recall( CIcarus* icarus ) { CBlock *block = NULL; while ( ( block = m_taskManager->RecallTask() ) != NULL ) { if (m_curSequence) { PushCommand( block, CSequence::PUSH_BACK ); } else { block->Free(icarus); delete block; block = NULL; } } return true; } /* ------------------------- Affect ------------------------- */ int CSequencer::Affect( int id, int type, CIcarus* icarus ) { IGameInterface* game = icarus->GetGame(); CSequence *sequence = GetSequence( id ); if ( sequence == NULL ) { return SEQ_FAILED; } switch ( type ) { case CIcarus::TYPE_FLUSH: //Get rid of all old code Flush( sequence, icarus ); sequence->RemoveFlag( CSequence::SQ_PENDING, true ); m_curSequence = sequence; Prime( m_taskManager, PopCommand( CSequence::POP_BACK ), icarus ); break; case CIcarus::TYPE_INSERT: Recall(icarus); sequence->SetReturn( m_curSequence ); sequence->RemoveFlag( CSequence::SQ_PENDING, true ); m_curSequence = sequence; Prime( m_taskManager, PopCommand( CSequence::POP_BACK ), icarus ); break; default: game->DebugPrint(IGameInterface::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 if ( m_curSequence == NULL ) return NULL; CBlock *block = m_curSequence->PopCommand( flag ); if ( block != NULL ) m_numCommands--; return block; } /* ------------------------- 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, CIcarus* icarus ) { IGameInterface* game = icarus->GetGame(); CSequence *temp; int numChildren = sequence->GetNumChildren(); //Add all the children for ( int i = 0; i < numChildren; i++ ) { temp = sequence->GetChildByIndex( i ); //TODO: Emit warning assert( temp ); if ( temp == NULL ) { game->DebugPrint(IGameInterface::WL_WARNING, "Unable to find child sequence on RemoveSequence call!\n" ); continue; } //Remove the references to this sequence temp->SetParent( NULL ); temp->SetReturn( NULL ); } return SEQ_OK; } int CSequencer::DestroySequence( CSequence *sequence, CIcarus* icarus ) { if ( !sequence || !icarus ) return SEQ_FAILED; //m_sequenceMap.erase( sequence->GetID() ); m_sequences.remove( sequence ); taskSequence_m::iterator tsi; for ( tsi = m_taskSequences.begin(); tsi != m_taskSequences.end(); ) { if((*tsi).second == sequence) { m_taskSequences.erase(tsi++); } else { ++tsi; } } // Remove this guy from his parents list. CSequence* parent = sequence->GetParent(); if ( parent ) { parent->RemoveChild( sequence ); parent = NULL; } int curChild = sequence->GetNumChildren(); while( curChild ) { // Stop if we're about to go negative (invalid index!). if ( curChild > 0 ) { DestroySequence( sequence->GetChildByIndex( --curChild ), icarus); } else break; } icarus->DeleteSequence( sequence ); 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() { taskSequence_m::iterator ti; int numSequences = 0, id, numTasks; // Data saved here. // Owner Sequence. // Number of Sequences. // Sequences (data). // Taskmanager. // Number of Task Sequences. // Task Sequences (data): // -Task group ID. // -Sequence ID. // Group ID. // Number of Commands. // ID of current Sequence. CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); //Get the number of sequences to save out numSequences = /*m_sequenceMap.size();*/ m_sequences.size(); //Save out the owner sequence pIcarus->BufferWrite( &m_ownerID, sizeof( m_ownerID ) ); //Write out the number of sequences we need to read pIcarus->BufferWrite( &numSequences, sizeof( numSequences ) ); //Second pass, save out all sequences, in order sequence_l::iterator iterSeq = m_sequences.end(); STL_ITERATE( iterSeq, m_sequences ) { id = (*iterSeq)->GetID(); pIcarus->BufferWrite( &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(); pIcarus->BufferWrite( &numTasks, sizeof( numTasks ) ); STL_ITERATE( ti, m_taskSequences ) { //Save the task group's ID id = ((*ti).first)->GetGUID(); pIcarus->BufferWrite( &id, sizeof( id ) ); //Save the sequence's ID id = ((*ti).second)->GetID(); pIcarus->BufferWrite( &id, sizeof( id ) ); } int curGroupID = ( m_curGroup == NULL ) ? -1 : m_curGroup->GetGUID(); // Right the group ID. pIcarus->BufferWrite( &curGroupID, sizeof( curGroupID ) ); //Output the number of commands pIcarus->BufferWrite( &m_numCommands, sizeof( m_numCommands ) ); //Output the ID of the current sequence id = ( m_curSequence != NULL ) ? m_curSequence->GetID() : -1; pIcarus->BufferWrite( &id, sizeof( id ) ); return true; } /* ------------------------- Load ------------------------- */ int CSequencer::Load( CIcarus* icarus, IGameInterface* game ) { // Data expected/loaded here. // Owner Sequence. // Number of Sequences. // Sequences (data). // Taskmanager. // Number of Task Sequences. // Task Sequences (data): // -Task group ID. // -Sequence ID. // Group ID. // Number of Commands. // ID of current Sequence. CIcarus *pIcarus = (CIcarus *)IIcarusInterface::GetIcarus(); //Get the owner of this sequencer pIcarus->BufferRead( &m_ownerID, sizeof( m_ownerID ) ); //Link the entity back to the sequencer game->LinkGame( m_ownerID, m_id ); CTaskGroup *taskGroup; CSequence *seq; int numSequences, seqID, taskID, numTasks; //Get the number of sequences to read pIcarus->BufferRead( &numSequences, sizeof( numSequences ) ); //Read in all the sequences for ( int i = 0; i < numSequences; i++ ) { pIcarus->BufferRead( &seqID, sizeof( seqID ) ); seq = (CSequence*)icarus->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(icarus); //Get the number of tasks in the map pIcarus->BufferRead( &numTasks, sizeof( numTasks ) ); //Read in, and reassociate the tasks to the sequences for ( int i = 0; i < numTasks; i++ ) { //Read in the task's ID pIcarus->BufferRead( &taskID, sizeof( taskID ) ); //Read in the sequence's ID pIcarus->BufferRead( &seqID, sizeof( seqID ) ); taskGroup = m_taskManager->GetTaskGroup( taskID , icarus); assert( taskGroup ); seq = icarus->GetSequence( seqID ); assert( seq ); //Associate the values m_taskSequences[ taskGroup ] = seq; } int curGroupID; //Get the current task group pIcarus->BufferRead( &curGroupID, sizeof( curGroupID ) ); m_curGroup = ( curGroupID == -1 ) ? NULL : m_taskManager->GetTaskGroup( curGroupID , icarus); //Get the number of commands pIcarus->BufferRead( &m_numCommands, sizeof( m_numCommands ) ); //Get the current sequence pIcarus->BufferRead( &seqID, sizeof( seqID ) ); m_curSequence = ( seqID != -1 ) ? (CSequence*)icarus->GetSequence( seqID ) : NULL; return true; }