jedioutcast/code/game/g_ICARUS.cpp
2013-04-04 13:24:26 -05:00

648 lines
15 KiB
C++

// ICARUS Utility functions
// leave this line at the top for all g_xxxx.cpp files...
#include "g_headers.h"
#include "g_local.h"
#include "Q3_Interface.h"
#include "g_roff.h"
#include "g_icarus.h"
ICARUS_Instance *iICARUS;
bufferlist_t ICARUS_BufferList;
entlist_t ICARUS_EntList;
extern unsigned Com_BlockChecksum (const void *buffer, int length);
extern void Q3_DebugPrint( int level, const char *format, ... );
int ICARUS_entFilter = -1;
extern stringID_table_t setTable[];
/*
=============
ICARUS_GetScript
gets the named script from the cache or disk if not already loaded
=============
*/
int ICARUS_GetScript( const char *name, char **buf )
{
bufferlist_t::iterator ei;
//Make sure the caller is valid
//Attempt to retrieve a precached script
ei = ICARUS_BufferList.find( (char *) name );
//Not found, check the disk
if ( ei == ICARUS_BufferList.end() )
{
if ( ICARUS_RegisterScript( name ) == false )
return 0;
//Script is now inserted, retrieve it and pass through
ei = ICARUS_BufferList.find( (char *) name );
if ( ei == ICARUS_BufferList.end() )
{
//NOTENOTE: This is an internal error in STL if this happens...
assert(0);
return 0;
}
}
*buf = (*ei).second->buffer;
return (*ei).second->length;
}
/*
=============
ICARUS_RunScript
Runs the script by the given name
=============
*/
int ICARUS_RunScript( gentity_t *ent, const char *name )
{
char *buf;
int len;
//Make sure the caller is valid
if ( ent->sequencer == NULL )
{
//Com_Printf( "%s : entity is not a valid script user\n", ent->classname );
return false;
}
len = ICARUS_GetScript (name, &buf );
if (len == 0)
{
return false;
}
//Attempt to run the script
if S_FAILED(ent->sequencer->Run( buf, len ))
return false;
if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == ent->s.number ) )
{
Q3_DebugPrint( WL_VERBOSE, "%d Script %s executed by %s %s\n", level.time, (char *) name, ent->classname, ent->targetname );
}
return true;
}
/*
=================
ICARUS_Init
Allocates a new ICARUS instance
=================
*/
void ICARUS_Init( void )
{
//Link all interface functions
Interface_Init( &interface_export );
//Create the ICARUS instance for this session
iICARUS = ICARUS_Instance::Create( &interface_export );
if ( iICARUS == NULL )
{
Com_Error( ERR_DROP, "Unable to initialize ICARUS instance\n" );
return;
}
}
/*
=================
ICARUS_Shutdown
Frees up ICARUS resources from all entities
=================
*/
void ICARUS_Shutdown( void )
{
bufferlist_t::iterator ei;
gentity_t *ent = &g_entities[0];;
//Release all ICARUS resources from the entities
for ( int i = 0; i < globals.num_entities; i++, ent++ )
{
if ( !ent->inuse )
continue;
ICARUS_FreeEnt( ent );
}
//Clear out all precached scripts
for ( ei = ICARUS_BufferList.begin(); ei != ICARUS_BufferList.end(); ei++ )
{
gi.Free( (*ei).second->buffer );
delete (*ei).second;
}
ICARUS_BufferList.clear();
//Clear the name map
ICARUS_EntList.clear();
//Free this instance
if ( iICARUS )
{
iICARUS->Delete();
iICARUS = NULL;
}
}
/*
==============
ICARUS_FreeEnt
Frees all ICARUS resources on an entity
WARNING!!! DO NOT DO THIS WHILE RUNNING A SCRIPT, ICARUS WILL CRASH!!!
FIXME: shouldn't ICARUS handle this internally?
==============
*/
void ICARUS_FreeEnt( gentity_t *ent )
{
assert( iICARUS );
//Make sure the ent is valid
if ( ent->sequencer == NULL )
return;
//Remove them from the ICARUSE_EntList list so that when their g_entity index is reused, ICARUS doesn't try to affect the new (incorrect) ent.
if VALIDSTRING( ent->script_targetname )
{
char temp[1024];
strncpy( (char *) temp, ent->script_targetname, 1023 );
temp[ 1023 ] = 0;
entlist_t::iterator it = ICARUS_EntList.find( strupr(temp) );
if (it != ICARUS_EntList.end())
{
ICARUS_EntList.erase(it);
}
}
//Delete the sequencer and the task manager
iICARUS->DeleteSequencer( ent->sequencer );
//Clean up the pointers
ent->sequencer = NULL;
ent->taskManager = NULL;
}
/*
==============
ICARUS_ValidEnt
Determines whether or not an entity needs ICARUS information
==============
*/
bool ICARUS_ValidEnt( gentity_t *ent )
{
int i;
//Targeted by a script
if VALIDSTRING( ent->script_targetname )
return true;
//Potentially able to call a script
for ( i = 0; i < NUM_BSETS; i++ )
{
if VALIDSTRING( ent->behaviorSet[i] )
{
//Com_Printf( "WARNING: Entity %d (%s) has behaviorSet but no script_targetname -- using targetname\n", ent->s.number, ent->targetname );
ent->script_targetname = ent->targetname;
return true;
}
}
return false;
}
/*
==============
ICARUS_AssociateEnt
Associate the entity's id and name so that it can be referenced later
==============
*/
void ICARUS_AssociateEnt( gentity_t *ent )
{
char temp[1024];
if ( VALIDSTRING( ent->script_targetname ) == false )
return;
strncpy( (char *) temp, ent->script_targetname, 1023 );
temp[ 1023 ] = 0;
ICARUS_EntList[ strupr( (char *) temp ) ] = ent->s.number;
}
/*
==============
ICARUS_RegisterScript
Loads and caches a script
==============
*/
bool ICARUS_RegisterScript( const char *name, bool bCalledDuringInterrogate /* = false */ )
{
bufferlist_t::iterator ei;
pscript_t *pscript;
char newname[MAX_FILENAME_LENGTH];
char *buffer = NULL; // lose compiler warning about uninitialised vars
long length;
//Make sure this isn't already cached
ei = ICARUS_BufferList.find( (char *) name );
// note special return condition here, if doing interrogate and we already have this file then we MUST return
// false (which stops the interrogator proceeding), this not only saves some time, but stops a potential
// script recursion bug which could lock the program in an infinite loop... Return TRUE for normal though!
//
if ( ei != ICARUS_BufferList.end() )
return (bCalledDuringInterrogate)?false:true;
sprintf((char *) newname, "%s%s", name, IBI_EXT );
// small update here, if called during interrogate, don't let gi.FS_ReadFile() complain because it can't
// find stuff like BS_RUN_AND_SHOOT as scriptname... During FINALBUILD the message won't appear anyway, hence
// the ifndef, this just cuts down on internal error reports while testing release mode...
//
qboolean qbIgnoreFileRead = qfalse;
//
// NOTENOTE: For the moment I've taken this back out, to avoid doubling the number of fopen()'s per file.
#if 0//#ifndef FINAL_BUILD
if (bCalledDuringInterrogate)
{
fileHandle_t file;
gi.FS_FOpenFile( newname, &file, FS_READ );
if ( file == NULL )
{
qbIgnoreFileRead = qtrue; // warn disk code further down not to try FS_ReadFile()
}
else
{
gi.FS_FCloseFile( file );
}
}
#endif
length = qbIgnoreFileRead ? -1 : gi.FS_ReadFile( newname, (void **) &buffer );
if ( length <= 0 )
{
// File not found, but keep quiet during interrogate stage, because of stuff like BS_RUN_AND_SHOOT as scriptname
//
if (!bCalledDuringInterrogate)
{
Com_Printf(S_COLOR_RED"Could not open file '%s'\n", newname );
}
return false;
}
pscript = new pscript_t;
pscript->buffer = (char *) gi.Malloc(length, TAG_ICARUS, qfalse);
memcpy (pscript->buffer, buffer, length);
pscript->length = length;
gi.FS_FreeFile( buffer );
ICARUS_BufferList[ name ] = pscript;
return true;
}
/*
-------------------------
ICARUS_InterrogateScript
-------------------------
*/
// at this point the filename should have had the "scripts" (Q3_SCRIPT_DIR) added to it (but not the IBI extension)
//
void ICARUS_InterrogateScript( const char *filename )
{
CBlockStream stream;
CBlockMember *blockMember;
CBlock block;
if (!Q_stricmp(filename,"NULL") || !Q_stricmp(filename,"default"))
return;
//////////////////////////////////
//
// ensure "scripts" (Q3_SCRIPT_DIR), which will be missing if this was called recursively...
//
char sFilename[MAX_FILENAME_LENGTH]; // should really be MAX_QPATH (and 64 bytes instead of 1024), but this fits the rest of the code
if (!Q_stricmpn(filename,Q3_SCRIPT_DIR,strlen(Q3_SCRIPT_DIR)))
{
Q_strncpyz(sFilename,filename,sizeof(sFilename));
}
else
{
Q_strncpyz(sFilename,va("%s/%s",Q3_SCRIPT_DIR,filename),sizeof(sFilename));
}
//
//////////////////////////////////
//Attempt to register this script
if ( ICARUS_RegisterScript( sFilename, true ) == false ) // true = bCalledDuringInterrogate
return;
char *buf;
long len;
//Attempt to retrieve the new script data
if ( ( len = ICARUS_GetScript ( sFilename, &buf ) ) == 0 )
return;
//Open the stream
if ( stream.Open( buf, len ) == qfalse )
return;
const char *sVal1, *sVal2;
char temp[1024];
int setID;
//Now iterate through all blocks of the script, searching for keywords
while ( stream.BlockAvailable() )
{
//Get a block
if ( stream.ReadBlock( &block ) == qfalse )
return;
//Determine what type of block this is
switch( block.GetBlockID() )
{
case ID_CAMERA: // to cache ROFF files
{
float f = *(float *) block.GetMemberData( 0 );
if (f == TYPE_PATH)
{
sVal1 = (const char *) block.GetMemberData( 1 );
G_LoadRoff(sVal1);
}
}
break;
case ID_PLAY: // to cache ROFF files
sVal1 = (const char *) block.GetMemberData( 0 );
if (!Q_stricmp(sVal1,"PLAY_ROFF"))
{
sVal1 = (const char *) block.GetMemberData( 1 );
G_LoadRoff(sVal1);
}
break;
//Run commands
case ID_RUN:
sVal1 = (const char *) block.GetMemberData( 0 );
COM_StripExtension( sVal1, (char *) temp );
ICARUS_InterrogateScript( (const char *) &temp );
break;
case ID_SOUND:
sVal1 = (const char *) block.GetMemberData( 1 ); //0 is channel, 1 is filename
G_SoundIndex(sVal1);
break;
case ID_SET:
blockMember = block.GetMember( 0 );
//NOTENOTE: This will not catch special case get() inlines! (There's not really a good way to do that)
//Make sure we're testing against strings
if ( blockMember->GetID() == TK_STRING )
{
sVal1 = (const char *) block.GetMemberData( 0 );
sVal2 = (const char *) block.GetMemberData( 1 );
//Get the id for this set identifier
setID = GetIDForString( setTable, sVal1 );
//Check against valid types
switch ( setID )
{
case SET_SPAWNSCRIPT:
case SET_USESCRIPT:
case SET_AWAKESCRIPT:
case SET_ANGERSCRIPT:
case SET_ATTACKSCRIPT:
case SET_VICTORYSCRIPT:
case SET_LOSTENEMYSCRIPT:
case SET_PAINSCRIPT:
case SET_FLEESCRIPT:
case SET_DEATHSCRIPT:
case SET_DELAYEDSCRIPT:
case SET_BLOCKEDSCRIPT:
case SET_FFIRESCRIPT:
case SET_FFDEATHSCRIPT:
case SET_MINDTRICKSCRIPT:
case SET_CINEMATIC_SKIPSCRIPT:
//Recursively obtain all embedded scripts
ICARUS_InterrogateScript( sVal2 );
break;
case SET_LOOPSOUND: //like ID_SOUND, but set's looping
G_SoundIndex(sVal2);
break;
case SET_VIDEO_PLAY: //in game cinematic
extern cvar_t *com_buildScript;
if (com_buildScript->integer)
{
fileHandle_t file;
char name[MAX_OSPATH];
if (strstr(sVal2, "/") == NULL && strstr(sVal2, "\\") == NULL) {
Com_sprintf (name, sizeof(name), "video/%s", sVal2);
} else {
Com_sprintf (name, sizeof(name), "%s", sVal2);
}
COM_StripExtension(name,name);
COM_DefaultExtension(name,sizeof(name),".roq");
gi.FS_FOpenFile( name, &file, FS_READ ); // trigger the file copy
if (file)
{
gi.FS_FCloseFile( file );
}
}
break;
case SET_ADDRHANDBOLT_MODEL:
case SET_ADDLHANDBOLT_MODEL:
{
gi.G2API_PrecacheGhoul2Model( sVal2 );
}
break;
default:
break;
}
}
break;
default:
break;
}
//Clean out the block for the next pass
block.Free();
}
//All done
stream.Free();
}
/*
==============
ICARUS_PrecacheEnt
Precache all scripts being used by the entity
==============
*/
void ICARUS_PrecacheEnt( gentity_t *ent )
{
extern stringID_table_t BSTable[];
char newname[MAX_FILENAME_LENGTH];
int i;
for ( i = 0; i < NUM_BSETS; i++ )
{
if ( ent->behaviorSet[i] == NULL )
continue;
if ( GetIDForString( BSTable, ent->behaviorSet[i] ) == -1 )
{//not a behavior set
sprintf((char *) newname, "%s/%s", Q3_SCRIPT_DIR, ent->behaviorSet[i] );
//Precache this, and all internally referenced scripts
ICARUS_InterrogateScript( newname );
}
}
}
/*
==============
ICARUS_InitEnt
Allocates a sequencer and task manager only if an entity is a potential script user
==============
*/
void Q3_TaskIDClear( int *taskID );
void ICARUS_InitEnt( gentity_t *ent )
{
//Make sure this is a fresh ent
assert( iICARUS );
assert( ent->taskManager == NULL );
assert( ent->sequencer == NULL );
if ( ent->sequencer != NULL )
return;
if ( ent->taskManager != NULL )
return;
//Create the sequencer and setup the task manager
ent->sequencer = iICARUS->GetSequencer( ent->s.number );
ent->taskManager = ent->sequencer->GetTaskManager();
//Initialize all taskIDs to -1
memset( &ent->taskID, -1, sizeof( ent->taskID ) );
//Add this entity to a map of valid associated ents for quick retrieval later
ICARUS_AssociateEnt( ent );
//Precache all the entity's scripts
ICARUS_PrecacheEnt( ent );
}
/*
-------------------------
ICARUS_LinkEntity
-------------------------
*/
int ICARUS_LinkEntity( int entID, CSequencer *sequencer, CTaskManager *taskManager )
{
gentity_t *ent = &g_entities[ entID ];
if ( ent == NULL )
return false;
ent->sequencer = sequencer;
ent->taskManager = taskManager;
ICARUS_AssociateEnt( ent );
return true;
}
/*
-------------------------
Svcmd_ICARUS_f
-------------------------
*/
void Svcmd_ICARUS_f( void )
{
char *cmd = gi.argv( 1 );
if ( Q_stricmp( cmd, "log" ) == 0 )
{
g_ICARUSDebug->integer = WL_DEBUG;
if ( VALIDSTRING( gi.argv( 2 ) ) )
{
gentity_t *ent = G_Find( NULL, FOFS( script_targetname ), gi.argv(2) );
if ( ent == NULL )
{
Com_Printf( "Entity \"%s\" not found!\n", gi.argv(2) );
return;
}
//Start logging
Com_Printf("Logging ICARUS info for entity %s\n", gi.argv(2) );
ICARUS_entFilter = ( ent->s.number == ICARUS_entFilter ) ? -1 : ent->s.number;
return;
}
Com_Printf("Logging ICARUS info for all entities\n");
return;
}
}