jedi-academy/codemp/icarus/GameInterface.cpp
2013-04-23 15:21:39 +10:00

733 lines
17 KiB
C++

//Anything above this #include will be ignored by the compiler
#include "../qcommon/exe_headers.h"
// ICARUS Utility functions
//rww - mangled to work in server exe setting.
//#include "Q3_Interface.h"
//#include "g_roff.h"
#include "../game/g_public.h"
#include "../server/server.h"
#include "interface.h"
#include "GameInterface.h"
#include "../qcommon/RoffSystem.h"
#include "Q3_Interface.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;
/*
=============
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( sharedEntity_t *ent, const char *name )
{
char *buf;
int len;
//Make sure the caller is valid
if ( gSequencers[ent->s.number] == NULL )
{
//Com_Printf( "%s : entity is not a valid script user\n", ent->classname );
return false;
}
#ifdef _HACK_FOR_TESTING_ONLY_1
char namex[1024];
char *blah = strstr(name, "stu/");
int r = blah - name;
if (blah)
{
int i = 0;
while (i < r)
{
namex[i] = name[i];
i++;
}
namex[i] = 0;
strcat(namex, "ignorethisfolder/");
i = strlen(namex);
while (name[r] != '/')
{
r++;
}
r++;
while (name[r])
{
namex[i] = name[r];
r++;
i++;
}
namex[i] = 0;
}
else
{
strcpy(namex, name);
}
len = ICARUS_GetScript (namex, &buf);
#else
len = ICARUS_GetScript (name, &buf);
#endif
if (len == 0)
{
return false;
}
//Attempt to run the script
if S_FAILED(gSequencers[ent->s.number]->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", svs.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;
sharedEntity_t *ent = SV_GentityNum(0);
//Release all ICARUS resources from the entities
for ( int i = 0; i < /*globals.num_entities*/MAX_GENTITIES; i++ )
{
ent = SV_GentityNum(i);
if (gSequencers[i])
{
if (ent->s.number >= MAX_GENTITIES ||
ent->s.number < 0)
{
ent->s.number = i;
assert(0);
}
ICARUS_FreeEnt( ent );
}
}
//Clear out all precached scripts
for ( ei = ICARUS_BufferList.begin(); ei != ICARUS_BufferList.end(); ei++ )
{
//gi.Free( (*ei).second->buffer );
ICARUS_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( sharedEntity_t *ent )
{
assert( iICARUS );
if (ent->s.number >= MAX_GENTITIES ||
ent->s.number < 0)
{
assert(0);
return;
}
//Make sure the ent is valid
if ( gSequencers[ent->s.number] == 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( Q_strupr(temp) );
if (it != ICARUS_EntList.end())
{
ICARUS_EntList.erase(it);
}
}
//Delete the sequencer and the task manager
iICARUS->DeleteSequencer( gSequencers[ent->s.number] );
//Clean up the pointers
gSequencers[ent->s.number] = NULL;
gTaskManagers[ent->s.number] = NULL;
}
/*
==============
ICARUS_ValidEnt
Determines whether or not an entity needs ICARUS information
==============
*/
bool ICARUS_ValidEnt( sharedEntity_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;
//rww - You CANNOT do things like this now. We're switching memory around to be able to read this memory from vm land,
//and while this allows us to read it on our "fake" entity here, we can't modify pointers like this. We can however do
//something completely hackish such as the following.
assert(ent->s.number >= 0 && ent->s.number < MAX_GENTITIES);
sharedEntity_t *trueEntity = SV_GentityNum(ent->s.number);
//This works because we're modifying the actual shared game vm data and turning one pointer into another.
//While these pointers both look like garbage to us in here, they are not.
trueEntity->script_targetname = trueEntity->targetname;
return true;
}
}
return false;
}
/*
==============
ICARUS_AssociateEnt
Associate the entity's id and name so that it can be referenced later
==============
*/
void ICARUS_AssociateEnt( sharedEntity_t *ent )
{
char temp[1024];
if ( VALIDSTRING( ent->script_targetname ) == false )
return;
strncpy( (char *) temp, ent->script_targetname, 1023 );
temp[ 1023 ] = 0;
ICARUS_EntList[ Q_strupr( (char *) temp ) ] = ent->s.number;
}
/*
==============
ICARUS_RegisterScript
Loads and caches a script
==============
*/
bool ICARUS_RegisterScript( const char *name, qboolean 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 : 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 *) ICARUS_Malloc(length);//gi.Malloc(length, TAG_ICARUS, qfalse);
memcpy (pscript->buffer, buffer, length);
pscript->length = length;
FS_FreeFile( buffer );
ICARUS_BufferList[ name ] = pscript;
return true;
}
void ICARUS_SoundPrecache(const char *filename)
{
T_G_ICARUS_SOUNDINDEX *sharedMem = (T_G_ICARUS_SOUNDINDEX *)sv.mSharedMemory;
strcpy(sharedMem->filename, filename);
VM_Call(gvm, GAME_ICARUS_SOUNDINDEX);
}
int ICARUS_GetIDForString( const char *string )
{
T_G_ICARUS_GETSETIDFORSTRING *sharedMem = (T_G_ICARUS_GETSETIDFORSTRING *)sv.mSharedMemory;
strcpy(sharedMem->string, string);
return VM_Call(gvm, GAME_ICARUS_GETSETIDFORSTRING);
}
/*
-------------------------
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, qtrue ) == 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 );
//we can do this I guess since the roff is loaded on the server.
theROFFSystem.Cache((char *)sVal1, qfalse);
}
}
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 );
//we can do this I guess since the roff is loaded on the server.
theROFFSystem.Cache((char *)sVal1, qfalse);
}
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:
//We can't just call over to S_RegisterSound or whatever because this is on the server.
sVal1 = (const char *) block.GetMemberData( 1 ); //0 is channel, 1 is filename
ICARUS_SoundPrecache(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 = ICARUS_GetIDForString( 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
ICARUS_SoundPrecache(sVal2);
break;
case SET_VIDEO_PLAY: //in game cinematic
//do nothing for MP.
break;
case SET_ADDRHANDBOLT_MODEL:
case SET_ADDLHANDBOLT_MODEL:
//do nothing for MP
break;
default:
break;
}
}
break;
default:
break;
}
//Clean out the block for the next pass
block.Free();
}
//All done
stream.Free();
}
#ifdef _XBOX // We borrow the one in NPC_stats.c
extern stringID_table_t BSTable[];
#else
stringID_table_t BSTable[] =
{
ENUM2STRING(BS_DEFAULT),//# default behavior for that NPC
ENUM2STRING(BS_ADVANCE_FIGHT),//# Advance to captureGoal and shoot enemies if you can
ENUM2STRING(BS_SLEEP),//# Play awake script when startled by sound
ENUM2STRING(BS_FOLLOW_LEADER),//# Follow your leader and shoot any enemies you come across
ENUM2STRING(BS_JUMP),//# Face navgoal and jump to it.
ENUM2STRING(BS_SEARCH),//# Using current waypoint as a base), search the immediate branches of waypoints for enemies
ENUM2STRING(BS_WANDER),//# Wander down random waypoint paths
ENUM2STRING(BS_NOCLIP),//# Moves through walls), etc.
ENUM2STRING(BS_REMOVE),//# Waits for player to leave PVS then removes itself
ENUM2STRING(BS_CINEMATIC),//# Does nothing but face it's angles and move to a goal if it has one
//the rest are internal only
"", -1,
};
#endif
/*
==============
ICARUS_PrecacheEnt
Precache all scripts being used by the entity
==============
*/
void ICARUS_PrecacheEnt( sharedEntity_t *ent )
{
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( sharedEntity_t *ent )
{
//Make sure this is a fresh ent
assert( iICARUS );
assert( gTaskManagers[ent->s.number] == NULL );
assert( gSequencers[ent->s.number] == NULL );
if ( gSequencers[ent->s.number] != NULL )
return;
if ( gTaskManagers[ent->s.number] != NULL )
return;
//Create the sequencer and setup the task manager
gSequencers[ent->s.number] = iICARUS->GetSequencer( ent->s.number );
gTaskManagers[ent->s.number] = gSequencers[ent->s.number]->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 )
{
sharedEntity_t *ent = SV_GentityNum(entID);
if ( ent == NULL )
return false;
gSequencers[ent->s.number] = sequencer;
gTaskManagers[ent->s.number] = taskManager;
ICARUS_AssociateEnt( ent );
return true;
}
/*
-------------------------
Svcmd_ICARUS_f
-------------------------
*/
void Svcmd_ICARUS_f( void )
{
//rwwFIXMEFIXME: Do something with this for debugging purposes at some point.
/*
char *cmd = Cmd_Argv( 1 );
if ( Q_stricmp( cmd, "log" ) == 0 )
{
//g_ICARUSDebug->integer = WL_DEBUG;
if ( VALIDSTRING( Cmd_Argv( 2 ) ) )
{
sharedEntity_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;
}
*/
return;
}