1798 lines
38 KiB
C++
1798 lines
38 KiB
C++
|
//NPC_stats.cpp
|
||
|
#include "b_local.h"
|
||
|
#include "b_public.h"
|
||
|
#include "boltOns.h"
|
||
|
#include "anims.h"
|
||
|
|
||
|
extern qboolean NPCsPrecached;
|
||
|
extern vec3_t playerMins;
|
||
|
extern vec3_t playerMaxs;
|
||
|
|
||
|
int numKnownAnimFileSets = 0;
|
||
|
|
||
|
char *TeamNames[TEAM_NUM_TEAMS] =
|
||
|
{
|
||
|
"",
|
||
|
"starfleet",
|
||
|
"borg",
|
||
|
"parasite",
|
||
|
"scavengers",
|
||
|
"klingon",
|
||
|
"malon",
|
||
|
"hirogen",
|
||
|
"imperial",
|
||
|
"stasis",
|
||
|
"species8472",
|
||
|
"dreadnought",
|
||
|
"forge",
|
||
|
"disguise",
|
||
|
"player (not valid)"
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
NPC_ReactionTime
|
||
|
*/
|
||
|
//FIXME use grandom in here
|
||
|
int NPC_ReactionTime ( void )
|
||
|
{
|
||
|
return 200 * ( 6 - NPCInfo->stats.reactions );
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// parse support routines
|
||
|
//
|
||
|
|
||
|
qboolean G_ParseLiteral( char **data, const char *string )
|
||
|
{
|
||
|
char *token;
|
||
|
|
||
|
token = COM_ParseExt( data, qtrue );
|
||
|
if ( token[0] == 0 )
|
||
|
{
|
||
|
gi.Printf( "unexpected EOF\n" );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
if ( Q_stricmp( token, string ) )
|
||
|
{
|
||
|
gi.Printf( "required string '%s' missing\n", string );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
qboolean G_ParseString( char **data, char **s )
|
||
|
{
|
||
|
*s = COM_ParseExt( data, qfalse );
|
||
|
if ( s[0] == 0 )
|
||
|
{
|
||
|
gi.Printf( "unexpected EOF\n" );
|
||
|
return qtrue;
|
||
|
}
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
|
||
|
qboolean G_ParseInt( char **data, int *i )
|
||
|
{
|
||
|
char *token;
|
||
|
|
||
|
token = COM_ParseExt( data, qfalse );
|
||
|
if ( token[0] == 0 )
|
||
|
{
|
||
|
gi.Printf( "unexpected EOF\n" );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
*i = atoi( token );
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
qboolean G_ParseFloat( char **data, float *f )
|
||
|
{
|
||
|
char *token;
|
||
|
|
||
|
token = COM_ParseExt( data, qfalse );
|
||
|
if ( token[0] == 0 )
|
||
|
{
|
||
|
gi.Printf( "unexpected EOF\n" );
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
*f = atof( token );
|
||
|
return qfalse;
|
||
|
}
|
||
|
//
|
||
|
// NPC parameters file : scripts/NPCs.cfg
|
||
|
//
|
||
|
#define MAX_NPC_DATA_SIZE 0x10000
|
||
|
char NPCParms[MAX_NPC_DATA_SIZE];
|
||
|
|
||
|
team_t TranslateTeamName( const char *name )
|
||
|
{
|
||
|
for ( int n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ )
|
||
|
{
|
||
|
if ( Q_stricmp( TeamNames[n], name ) == 0 )
|
||
|
{
|
||
|
return ((team_t) n);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return TEAM_FREE;
|
||
|
}
|
||
|
|
||
|
static race_t TranslateRaceName( const char *name )
|
||
|
{
|
||
|
if ( !Q_stricmp( name, "human" ) )
|
||
|
{
|
||
|
return RACE_HUMAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "borg" ) )
|
||
|
{
|
||
|
return RACE_BORG;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "parasite" ) )
|
||
|
{
|
||
|
return RACE_PARASITE;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "klingon" ) )
|
||
|
{
|
||
|
return RACE_KLINGON;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "malon" ) )
|
||
|
{
|
||
|
return RACE_MALON;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "hirogen" ) )
|
||
|
{
|
||
|
return RACE_HIROGEN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "stasis" ) )
|
||
|
{
|
||
|
return RACE_STASIS;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "species8472" ) )
|
||
|
{
|
||
|
return RACE_8472;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "dreadnought" ) )
|
||
|
{
|
||
|
return RACE_BOT;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "harvester" ) || !Q_stricmp( name, "biohulk" ) )
|
||
|
{
|
||
|
return RACE_HARVESTER;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "reaver" ) )
|
||
|
{
|
||
|
return RACE_REAVER;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "avatar" ) )
|
||
|
{
|
||
|
return RACE_AVATAR;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "vulcan" ) )
|
||
|
{
|
||
|
return RACE_VULCAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "betazoid" ) )
|
||
|
{
|
||
|
return RACE_BETAZOID;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "bolian" ) )
|
||
|
{
|
||
|
return RACE_BOLIAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "talaxian" ) )
|
||
|
{
|
||
|
return RACE_TALAXIAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "bajoran" ) )
|
||
|
{
|
||
|
return RACE_BAJORAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "hologram" ) )
|
||
|
{
|
||
|
return RACE_HOLOGRAM;
|
||
|
}
|
||
|
|
||
|
return RACE_NONE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
static rank_t TranslateRankName( const char *name )
|
||
|
|
||
|
Should be used to determine pip bolt-ons
|
||
|
*/
|
||
|
static rank_t TranslateRankName( const char *name )
|
||
|
{
|
||
|
if ( !Q_stricmp( name, "civilian" ) )
|
||
|
{
|
||
|
return RANK_CIVILIAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "crewman" ) )
|
||
|
{
|
||
|
return RANK_CREWMAN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "ensign" ) )
|
||
|
{
|
||
|
return RANK_ENSIGN;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "ltjg" ) )
|
||
|
{
|
||
|
return RANK_LT_JG;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "lt" ) )
|
||
|
{
|
||
|
return RANK_LT;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "ltcomm" ) )
|
||
|
{
|
||
|
return RANK_LT_COMM;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "commander" ) )
|
||
|
{
|
||
|
return RANK_COMMANDER;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( name, "captain" ) )
|
||
|
{
|
||
|
return RANK_CAPTAIN;
|
||
|
}
|
||
|
|
||
|
return RANK_CIVILIAN;
|
||
|
}
|
||
|
|
||
|
/* static int MethodNameToNumber( const char *name ) {
|
||
|
if ( !Q_stricmp( name, "EXPONENTIAL" ) ) {
|
||
|
return METHOD_EXPONENTIAL;
|
||
|
}
|
||
|
if ( !Q_stricmp( name, "LINEAR" ) ) {
|
||
|
return METHOD_LINEAR;
|
||
|
}
|
||
|
if ( !Q_stricmp( name, "LOGRITHMIC" ) ) {
|
||
|
return METHOD_LOGRITHMIC;
|
||
|
}
|
||
|
if ( !Q_stricmp( name, "ALWAYS" ) ) {
|
||
|
return METHOD_ALWAYS;
|
||
|
}
|
||
|
if ( !Q_stricmp( name, "NEVER" ) ) {
|
||
|
return METHOD_NEVER;
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
static int ItemNameToNumber( const char *name, int itemType ) {
|
||
|
// int n;
|
||
|
|
||
|
for ( n = 0; n < bg_numItems; n++ ) {
|
||
|
if ( bg_itemlist[n].type != itemType ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) {
|
||
|
return bg_itemlist[n].tag;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
static int MoveTypeNameToEnum( const char *name )
|
||
|
{
|
||
|
if(!Q_stricmp("runjump", name))
|
||
|
{
|
||
|
return MT_RUNJUMP;
|
||
|
}
|
||
|
else if(!Q_stricmp("walk", name))
|
||
|
{
|
||
|
return MT_WALK;
|
||
|
}
|
||
|
else if(!Q_stricmp("flyswim", name))
|
||
|
{
|
||
|
return MT_FLYSWIM;
|
||
|
}
|
||
|
else if(!Q_stricmp("static", name))
|
||
|
{
|
||
|
return MT_STATIC;
|
||
|
}
|
||
|
|
||
|
return MT_STATIC;
|
||
|
}
|
||
|
|
||
|
extern void CG_RegisterClientRenderInfo(clientInfo_t *ci, renderInfo_t *ri);
|
||
|
extern void CG_RegisterClientModels (int entityNum);
|
||
|
extern void CG_RegisterNPCCustomSounds( clientInfo_t *ci );
|
||
|
extern void CG_RegisterNPCEffects( team_t team );
|
||
|
extern void CG_ParseAnimationSndFile( const char *filename, int animFileIndex );
|
||
|
|
||
|
/*
|
||
|
======================
|
||
|
CG_ParseAnimationFile
|
||
|
|
||
|
Read a configuration file containing animation coutns and rates
|
||
|
models/players/visor/animation.cfg, etc
|
||
|
|
||
|
======================
|
||
|
*/
|
||
|
qboolean G_ParseAnimationFile( const char *filename )
|
||
|
{
|
||
|
char *text_p;
|
||
|
int len;
|
||
|
int i;
|
||
|
char *token;
|
||
|
float fps;
|
||
|
int skip;
|
||
|
char text[20000];
|
||
|
fileHandle_t f;
|
||
|
int animNum;
|
||
|
animation_t *animations = knownAnimFileSets[numKnownAnimFileSets].animations;
|
||
|
|
||
|
// load the file
|
||
|
len = gi.FS_FOpenFile( filename, &f, FS_READ );
|
||
|
if ( len <= 0 )
|
||
|
{
|
||
|
return qfalse;
|
||
|
}
|
||
|
if ( len >= sizeof( text ) - 1 )
|
||
|
{
|
||
|
gi.Printf( "File %s too long\n", filename );
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
gi.FS_Read( text, len, f );
|
||
|
text[len] = 0;
|
||
|
gi.FS_FCloseFile( f );
|
||
|
|
||
|
// parse the text
|
||
|
text_p = text;
|
||
|
skip = 0; // quiet the compiler warning
|
||
|
|
||
|
//FIXME: have some way of playing anims backwards... negative numFrames?
|
||
|
|
||
|
//initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100
|
||
|
for(i = 0; i < MAX_ANIMATIONS; i++)
|
||
|
{
|
||
|
animations[i].firstFrame = 0;
|
||
|
animations[i].numFrames = 0;
|
||
|
animations[i].loopFrames = -1;
|
||
|
animations[i].frameLerp = 100;
|
||
|
animations[i].initialLerp = 100;
|
||
|
}
|
||
|
|
||
|
// read information for each frame
|
||
|
while(1)
|
||
|
{
|
||
|
token = COM_Parse( &text_p );
|
||
|
|
||
|
if ( !token || !token[0])
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
animNum = GetIDForString(animTable, token);
|
||
|
if(animNum == -1)
|
||
|
{
|
||
|
Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
token = COM_Parse( &text_p );
|
||
|
if ( !token )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
animations[animNum].firstFrame = atoi( token );
|
||
|
|
||
|
token = COM_Parse( &text_p );
|
||
|
if ( !token )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
animations[animNum].numFrames = atoi( token );
|
||
|
|
||
|
token = COM_Parse( &text_p );
|
||
|
if ( !token )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
animations[animNum].loopFrames = atoi( token );
|
||
|
|
||
|
token = COM_Parse( &text_p );
|
||
|
if ( !token )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
fps = atof( token );
|
||
|
if ( fps == 0 )
|
||
|
{
|
||
|
fps = 1;//Don't allow divide by zero error
|
||
|
}
|
||
|
if ( fps < 0 )
|
||
|
{//backwards
|
||
|
animations[animNum].frameLerp = floor(1000.0f / fps);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
animations[animNum].frameLerp = ceil(1000.0f / fps);
|
||
|
}
|
||
|
|
||
|
animations[animNum].initialLerp = ceil(1000.0f / fabs(fps));
|
||
|
}
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
qboolean G_ParseAnimFileSet( const char *filename, int *animFileIndex, qboolean cacheSounds )
|
||
|
{
|
||
|
char afilename[MAX_QPATH];
|
||
|
char strippedName[MAX_QPATH];
|
||
|
int i;
|
||
|
char *slash;
|
||
|
|
||
|
Q_strncpyz( strippedName, filename, sizeof(strippedName), qtrue);
|
||
|
slash = strchr( strippedName, '/' );
|
||
|
if ( slash )
|
||
|
{
|
||
|
// truncate modelName to find just the dir not the extension
|
||
|
*slash = 0;
|
||
|
}
|
||
|
|
||
|
//if this anims file was loaded before, don't parse it again, just point to the correct table of info
|
||
|
for ( i = 0; i < numKnownAnimFileSets; i++ )
|
||
|
{
|
||
|
if ( Q_stricmp(knownAnimFileSets[i].filename, strippedName ) == 0 )
|
||
|
{
|
||
|
*animFileIndex = i;
|
||
|
if ( cacheSounds )
|
||
|
{//we want to cache sounds do it NOW
|
||
|
CG_ParseAnimationSndFile( strippedName, *animFileIndex );
|
||
|
}
|
||
|
return qtrue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( numKnownAnimFileSets == MAX_ANIM_FILES )
|
||
|
{//TOO MANY!
|
||
|
G_Error( "G_ParseAnimFileSet: MAX_ANIM_FILES" );
|
||
|
}
|
||
|
|
||
|
//Okay, time to parse in a new one
|
||
|
Q_strncpyz( knownAnimFileSets[numKnownAnimFileSets].filename, strippedName, sizeof( knownAnimFileSets[numKnownAnimFileSets].filename ) );
|
||
|
|
||
|
// Load and parse animations.cfg file
|
||
|
Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/animation.cfg", strippedName );
|
||
|
if ( !G_ParseAnimationFile( afilename ) )
|
||
|
{
|
||
|
*animFileIndex = -1;
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
//set index and increment
|
||
|
*animFileIndex = numKnownAnimFileSets++;
|
||
|
|
||
|
if ( cacheSounds )
|
||
|
{//we want to cache sounds do it NOW
|
||
|
CG_ParseAnimationSndFile( strippedName, *animFileIndex );
|
||
|
}
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
void NPC_PrecacheAnimationCFG( const char *NPC_type )
|
||
|
{
|
||
|
char *token;
|
||
|
char *value;
|
||
|
char *p;
|
||
|
int junk;
|
||
|
|
||
|
if ( !Q_stricmp( "random", NPC_type ) )
|
||
|
{//sorry, can't precache a random just yet
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
p = NPCParms;
|
||
|
COM_BeginParseSession();
|
||
|
|
||
|
// look for the right NPC
|
||
|
while ( p )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( token[0] == 0 )
|
||
|
return;
|
||
|
|
||
|
if ( !Q_stricmp( token, NPC_type ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SkipBracedSection( &p );
|
||
|
}
|
||
|
|
||
|
if ( !p )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( G_ParseLiteral( &p, "{" ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// parse the NPC info block
|
||
|
while ( 1 )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "}" ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// legsmodel
|
||
|
if ( !Q_stricmp( token, "legsmodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
G_ParseAnimFileSet( value, &junk, qfalse );
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
void NPC_Precache ( char *NPCName )
|
||
|
|
||
|
Precaches NPC skins, tgas and md3s.
|
||
|
|
||
|
*/
|
||
|
void NPC_Precache ( gentity_t *spawner )
|
||
|
{
|
||
|
clientInfo_t ci={0};
|
||
|
renderInfo_t ri={0};
|
||
|
team_t playerTeam = TEAM_FREE;
|
||
|
char *token;
|
||
|
char *value;
|
||
|
char *p;
|
||
|
char *patch;
|
||
|
char sound[MAX_QPATH];
|
||
|
|
||
|
if ( !Q_stricmp( "random", spawner->NPC_type ) )
|
||
|
{//sorry, can't precache a random just yet
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
p = NPCParms;
|
||
|
COM_BeginParseSession();
|
||
|
|
||
|
// look for the right NPC
|
||
|
while ( p )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( token[0] == 0 )
|
||
|
return;
|
||
|
|
||
|
if ( !Q_stricmp( token, spawner->NPC_type ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SkipBracedSection( &p );
|
||
|
}
|
||
|
|
||
|
if ( !p )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( G_ParseLiteral( &p, "{" ) )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// parse the NPC info block
|
||
|
while ( 1 )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "}" ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// headmodel
|
||
|
if ( !Q_stricmp( token, "headmodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(!Q_stricmp("none", value))
|
||
|
{
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName), qtrue);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// torsomodel
|
||
|
if ( !Q_stricmp( token, "torsomodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(!Q_stricmp("none", value))
|
||
|
{
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName), qtrue);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// legsmodel
|
||
|
if ( !Q_stricmp( token, "legsmodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName), qtrue);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// playerTeam
|
||
|
if ( !Q_stricmp( token, "playerTeam" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
playerTeam = TranslateTeamName(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// snd
|
||
|
if ( !Q_stricmp( token, "snd" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(spawner->svFlags&SVF_NO_BASIC_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci.customBasicSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndcombat
|
||
|
if ( !Q_stricmp( token, "sndcombat" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(spawner->svFlags&SVF_NO_COMBAT_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci.customCombatSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndextra
|
||
|
if ( !Q_stricmp( token, "sndextra" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci.customExtraSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndscav
|
||
|
if ( !Q_stricmp( token, "sndscav" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(spawner->svFlags&SVF_NO_SCAV_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci.customScavSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CG_RegisterClientRenderInfo( &ci, &ri );
|
||
|
CG_RegisterNPCCustomSounds( &ci );
|
||
|
CG_RegisterNPCEffects( playerTeam );
|
||
|
//FIXME: precache the beam-in/exit sound and effect if not silentspawn
|
||
|
//FIXME: Look for a "sounds" directory and precache death, pain, alert sounds
|
||
|
}
|
||
|
|
||
|
void NPC_BuildRandom( gentity_t *NPC )
|
||
|
{
|
||
|
int sex, color, head;
|
||
|
|
||
|
sex = Q_irand(0, 2);
|
||
|
color = Q_irand(0, 2);
|
||
|
switch( sex )
|
||
|
{
|
||
|
case 0://female
|
||
|
head = Q_irand(0, 2);
|
||
|
switch( head )
|
||
|
{
|
||
|
default:
|
||
|
case 0:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 1:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 2:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
color = Q_irand(3, 5);//torso needs to be afam
|
||
|
break;
|
||
|
}
|
||
|
switch( color )
|
||
|
{
|
||
|
default:
|
||
|
case 0:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 1:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 2:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 3:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 4:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 5:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
}
|
||
|
Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName), qtrue );
|
||
|
break;
|
||
|
default:
|
||
|
case 1://male
|
||
|
case 2://male
|
||
|
head = Q_irand(0, 4);
|
||
|
switch( head )
|
||
|
{
|
||
|
default:
|
||
|
case 0:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 1:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 2:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 3:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
case 4:
|
||
|
Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName), qtrue );
|
||
|
break;
|
||
|
}
|
||
|
switch( color )
|
||
|
{
|
||
|
default:
|
||
|
case 0:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 1:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
case 2:
|
||
|
Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
|
||
|
break;
|
||
|
//NOTE: 3 - 5 should be red, gold & blue, afram hands
|
||
|
}
|
||
|
Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName), qtrue );
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
NPC->client->renderInfo.scaleXYZ[0] = NPC->client->renderInfo.scaleXYZ[1] = NPC->client->renderInfo.scaleXYZ[2] = Q_irand(87, 102);
|
||
|
NPC->client->race = RACE_HUMAN;
|
||
|
NPC->NPC->rank = RANK_CREWMAN;
|
||
|
NPC->client->playerTeam = TEAM_STARFLEET;
|
||
|
NPC->client->clientInfo.customBasicSoundDir = "munro";
|
||
|
}
|
||
|
|
||
|
qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC )
|
||
|
{
|
||
|
char *token;
|
||
|
char *value;
|
||
|
char *p;
|
||
|
int n;
|
||
|
float f;
|
||
|
char *patch;
|
||
|
char sound[MAX_QPATH];
|
||
|
clientInfo_t *ci = &NPC->client->clientInfo;
|
||
|
renderInfo_t *ri = &NPC->client->renderInfo;
|
||
|
gNPCstats_t *stats = &NPC->NPC->stats;
|
||
|
|
||
|
if ( !NPCName || !NPCName[0])
|
||
|
{
|
||
|
NPCName = "Player";
|
||
|
}
|
||
|
|
||
|
Q_strncpyz( ci->name, NPCName, sizeof( ci->name ) );
|
||
|
|
||
|
// fill in defaults
|
||
|
stats->aggression = 3;
|
||
|
stats->aim = 3;
|
||
|
stats->earshot = 1024;
|
||
|
stats->evasion = 3;
|
||
|
stats->hfov = 75;
|
||
|
stats->intelligence = 3;
|
||
|
stats->move = 3;
|
||
|
stats->reactions = 3;
|
||
|
stats->vfov = 45;
|
||
|
stats->vigilance = 0.1f;
|
||
|
stats->visrange = 1024;
|
||
|
|
||
|
stats->moveType = MT_RUNJUMP;
|
||
|
stats->yawSpeed = 90;
|
||
|
stats->walkSpeed = 90;
|
||
|
stats->runSpeed = 300;
|
||
|
stats->acceleration = 15;//Increase/descrease speed this much per frame (20fps)
|
||
|
|
||
|
ri->scaleXYZ[0] = ri->scaleXYZ[1] = ri->scaleXYZ[2] = 100;
|
||
|
|
||
|
//Set defaults
|
||
|
//FIXME: should probably put default torso and head models, but what about enemies
|
||
|
//that don't have any- like Stasis?
|
||
|
Q_strncpyz( ri->headModelName, DEFAULT_HEADMODEL, sizeof(ri->headModelName), qtrue);
|
||
|
Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName), qtrue);
|
||
|
Q_strncpyz( ri->legsModelName, DEFAULT_LEGSMODEL, sizeof(ri->legsModelName), qtrue);
|
||
|
|
||
|
//FIXME: should we have one for weapon too?
|
||
|
|
||
|
/*
|
||
|
ri->headYawRangeLeft = 50;
|
||
|
ri->headYawRangeRight = 50;
|
||
|
ri->headPitchRangeUp = 40;
|
||
|
ri->headPitchRangeDown = 50;
|
||
|
ri->torsoYawRangeLeft = 60;
|
||
|
ri->torsoYawRangeRight = 60;
|
||
|
ri->torsoPitchRangeUp = 30;
|
||
|
ri->torsoPitchRangeDown = 70;
|
||
|
*/
|
||
|
|
||
|
ri->headYawRangeLeft = 80;
|
||
|
ri->headYawRangeRight = 80;
|
||
|
ri->headPitchRangeUp = 45;
|
||
|
ri->headPitchRangeDown = 45;
|
||
|
ri->torsoYawRangeLeft = 90;
|
||
|
ri->torsoYawRangeRight = 90;
|
||
|
ri->torsoPitchRangeUp = 30;
|
||
|
ri->torsoPitchRangeDown = 50;
|
||
|
|
||
|
/*
|
||
|
NPC->NPC->allWeaponOrder[0] = WP_COMPRESSION_RIFLE;
|
||
|
NPC->NPC->allWeaponOrder[1] = WP_PHASER;
|
||
|
NPC->NPC->allWeaponOrder[2] = WP_IMOD;
|
||
|
NPC->NPC->allWeaponOrder[3] = WP_SCAVENGER_RIFLE;
|
||
|
NPC->NPC->allWeaponOrder[4] = WP_TRICORDER;
|
||
|
NPC->NPC->allWeaponOrder[6] = WP_NONE;
|
||
|
NPC->NPC->allWeaponOrder[6] = WP_NONE;
|
||
|
NPC->NPC->allWeaponOrder[7] = WP_NONE;
|
||
|
*/
|
||
|
|
||
|
VectorCopy(playerMins, NPC->mins);
|
||
|
VectorCopy(playerMaxs, NPC->maxs);
|
||
|
NPC->client->crouchheight = CROUCH_MAXS_2;
|
||
|
NPC->client->standheight = DEFAULT_MAXS_2;
|
||
|
|
||
|
//ci->customBasicSoundDir = "munro";
|
||
|
|
||
|
if ( !Q_stricmp( "random", NPCName ) )
|
||
|
{//Randomly assemble a starfleet guy
|
||
|
NPC_BuildRandom( NPC );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
p = NPCParms;
|
||
|
COM_BeginParseSession();
|
||
|
|
||
|
// look for the right NPC
|
||
|
while ( p )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( token[0] == 0 )
|
||
|
{
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, NPCName ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SkipBracedSection( &p );
|
||
|
}
|
||
|
if ( !p )
|
||
|
{
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
if ( G_ParseLiteral( &p, "{" ) )
|
||
|
{
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
// parse the NPC info block
|
||
|
while ( 1 )
|
||
|
{
|
||
|
token = COM_ParseExt( &p, qtrue );
|
||
|
if ( !token[0] )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName );
|
||
|
return qfalse;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "}" ) )
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
//===MODEL PROPERTIES===========================================================
|
||
|
// headmodel
|
||
|
if ( !Q_stricmp( token, "headmodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(!Q_stricmp("none", value))
|
||
|
{
|
||
|
ri->headModelName[0] = NULL;
|
||
|
//Zero the head clamp range so the torso & legs don't lag behind
|
||
|
ri->headYawRangeLeft =
|
||
|
ri->headYawRangeRight =
|
||
|
ri->headPitchRangeUp =
|
||
|
ri->headPitchRangeDown = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpyz( ri->headModelName, value, sizeof(ri->headModelName), qtrue);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// torsomodel
|
||
|
if ( !Q_stricmp( token, "torsomodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(!Q_stricmp("none", value))
|
||
|
{
|
||
|
ri->torsoModelName[0] = NULL;
|
||
|
//Zero the torso clamp range so the legs don't lag behind
|
||
|
ri->torsoYawRangeLeft =
|
||
|
ri->torsoYawRangeRight =
|
||
|
ri->torsoPitchRangeUp =
|
||
|
ri->torsoPitchRangeDown = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Q_strncpyz( ri->torsoModelName, value, sizeof(ri->torsoModelName), qtrue);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// legsmodel
|
||
|
if ( !Q_stricmp( token, "legsmodel" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName), qtrue);
|
||
|
//Need to do this here to get the right index
|
||
|
G_ParseAnimFileSet( value, &ci->animFileIndex, qfalse );
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//headYawRangeLeft
|
||
|
if ( !Q_stricmp( token, "headYawRangeLeft" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->headYawRangeLeft = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//headYawRangeRight
|
||
|
if ( !Q_stricmp( token, "headYawRangeRight" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->headYawRangeRight = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//headPitchRangeUp
|
||
|
if ( !Q_stricmp( token, "headPitchRangeUp" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->headPitchRangeUp = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//headPitchRangeDown
|
||
|
if ( !Q_stricmp( token, "headPitchRangeDown" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->headPitchRangeDown = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//torsoYawRangeLeft
|
||
|
if ( !Q_stricmp( token, "torsoYawRangeLeft" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->torsoYawRangeLeft = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//torsoYawRangeRight
|
||
|
if ( !Q_stricmp( token, "torsoYawRangeRight" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->torsoYawRangeRight = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//torsoPitchRangeUp
|
||
|
if ( !Q_stricmp( token, "torsoPitchRangeUp" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->torsoPitchRangeUp = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//torsoPitchRangeDown
|
||
|
if ( !Q_stricmp( token, "torsoPitchRangeDown" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->torsoPitchRangeDown = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Uniform XYZ scale
|
||
|
if ( !Q_stricmp( token, "scale" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->scaleXYZ[0] = ri->scaleXYZ[1] = ri->scaleXYZ[2] = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//X scale
|
||
|
if ( !Q_stricmp( token, "scaleX" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->scaleXYZ[0] = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//Y scale
|
||
|
if ( !Q_stricmp( token, "scaleY" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->scaleXYZ[1] = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//Z scale
|
||
|
if ( !Q_stricmp( token, "scaleZ" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
ri->scaleXYZ[2] = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "boltOn" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
G_AddBoltOn( NPC, value );
|
||
|
continue;
|
||
|
}
|
||
|
//===AI STATS=====================================================================
|
||
|
// aggression
|
||
|
if ( !Q_stricmp( token, "aggression" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->aggression = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// aim
|
||
|
if ( !Q_stricmp( token, "aim" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->aim = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// earshot
|
||
|
if ( !Q_stricmp( token, "earshot" ) ) {
|
||
|
if ( G_ParseFloat( &p, &f ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( f < 0.0f )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->earshot = f;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// evasion
|
||
|
if ( !Q_stricmp( token, "evasion" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->evasion = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// hfov
|
||
|
if ( !Q_stricmp( token, "hfov" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 30 || n > 180 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->hfov = n;// / 2; //FIXME: Why was this being done?!
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// intelligence
|
||
|
if ( !Q_stricmp( token, "intelligence" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->intelligence = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// move
|
||
|
if ( !Q_stricmp( token, "move" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->move = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// reactions
|
||
|
if ( !Q_stricmp( token, "reactions" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 1 || n > 5 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->reactions = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// shootDistance
|
||
|
if ( !Q_stricmp( token, "shootDistance" ) ) {
|
||
|
if ( G_ParseFloat( &p, &f ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( f < 0.0f )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->shootDistance = f;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// vfov
|
||
|
if ( !Q_stricmp( token, "vfov" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 30 || n > 180 ) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->vfov = n / 2;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// vigilance
|
||
|
if ( !Q_stricmp( token, "vigilance" ) ) {
|
||
|
if ( G_ParseFloat( &p, &f ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( f < 0.0f )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->vigilance = f;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// visrange
|
||
|
if ( !Q_stricmp( token, "visrange" ) ) {
|
||
|
if ( G_ParseFloat( &p, &f ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( f < 0.0f )
|
||
|
{
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->visrange = f;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// race
|
||
|
if ( !Q_stricmp( token, "race" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
NPC->client->race = TranslateRaceName(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// rank
|
||
|
if ( !Q_stricmp( token, "rank" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
NPC->NPC->rank = TranslateRankName(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// fullName
|
||
|
if ( !Q_stricmp( token, "fullName" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
NPC->fullName = G_NewString(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// playerTeam
|
||
|
if ( !Q_stricmp( token, "playerTeam" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
NPC->client->playerTeam = TranslateTeamName(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// enemyTeam
|
||
|
if ( !Q_stricmp( token, "enemyTeam" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
NPC->client->enemyTeam = TranslateTeamName(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//===MOVEMENT STATS============================================================
|
||
|
|
||
|
if ( !Q_stricmp( token, "width" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
NPC->mins[0] = NPC->mins[1] = -n;
|
||
|
NPC->maxs[0] = NPC->maxs[1] = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "height" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
NPC->mins[2] = DEFAULT_MINS_2;//Cannot change
|
||
|
NPC->maxs[2] = NPC->client->standheight = n + DEFAULT_MINS_2;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "crouchheight" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
NPC->client->crouchheight = n + DEFAULT_MINS_2;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ( !Q_stricmp( token, "movetype" ) )
|
||
|
{
|
||
|
if ( G_ParseString( &p, &value ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
stats->moveType = (movetype_t)MoveTypeNameToEnum(value);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// yawSpeed
|
||
|
if ( !Q_stricmp( token, "yawSpeed" ) ) {
|
||
|
if ( G_ParseInt( &p, &n ) ) {
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n <= 0) {
|
||
|
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->yawSpeed = ((float)(n));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// walkSpeed
|
||
|
if ( !Q_stricmp( token, "walkSpeed" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->walkSpeed = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//runSpeed
|
||
|
if ( !Q_stricmp( token, "runSpeed" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->runSpeed = n;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//acceleration
|
||
|
if ( !Q_stricmp( token, "acceleration" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < 0 )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
stats->acceleration = n;
|
||
|
continue;
|
||
|
}
|
||
|
//===MISC===============================================================================
|
||
|
// default behavior
|
||
|
if ( !Q_stricmp( token, "behavior" ) )
|
||
|
{
|
||
|
if ( G_ParseInt( &p, &n ) )
|
||
|
{
|
||
|
SkipRestOfLine( &p );
|
||
|
continue;
|
||
|
}
|
||
|
if ( n < BS_DEFAULT || n >= NUM_BSTATES )
|
||
|
{
|
||
|
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
|
||
|
continue;
|
||
|
}
|
||
|
NPC->NPC->defaultBehavior = (bState_t)(n);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// snd
|
||
|
if ( !Q_stricmp( token, "snd" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(NPC->svFlags&SVF_NO_BASIC_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci->customBasicSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndcombat
|
||
|
if ( !Q_stricmp( token, "sndcombat" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(NPC->svFlags&SVF_NO_COMBAT_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci->customCombatSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndextra
|
||
|
if ( !Q_stricmp( token, "sndextra" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci->customExtraSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// sndscav
|
||
|
if ( !Q_stricmp( token, "sndscav" ) ) {
|
||
|
if ( G_ParseString( &p, &value ) ) {
|
||
|
continue;
|
||
|
}
|
||
|
if ( !(NPC->svFlags&SVF_NO_SCAV_SOUNDS) )
|
||
|
{
|
||
|
//FIXME: store this in some sound field or parse in the soundTable like the animTable...
|
||
|
Q_strncpyz( sound, value, sizeof( sound ) );
|
||
|
patch = strstr( sound, "/" );
|
||
|
if ( patch )
|
||
|
{
|
||
|
*patch = 0;
|
||
|
}
|
||
|
ci->customScavSoundDir = G_NewString( sound );
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
gi.Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName );
|
||
|
SkipRestOfLine( &p );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ci->infoValid = qfalse;
|
||
|
|
||
|
if( NPCsPrecached )
|
||
|
{//Spawning in after initial precache, our models are precached, we just need to set our clientInfo
|
||
|
CG_RegisterClientModels( NPC->s.number );
|
||
|
CG_RegisterNPCCustomSounds( ci );
|
||
|
CG_RegisterNPCEffects( NPC->client->playerTeam );
|
||
|
}
|
||
|
|
||
|
return qtrue;
|
||
|
}
|
||
|
|
||
|
void NPC_LoadParms( void )
|
||
|
{
|
||
|
int len, totallen, npcExtFNLen, mainBlockLen, fileCnt, i;
|
||
|
const char filename[] = "ext_data/NPCs.cfg";
|
||
|
char *buffer, *holdChar, *marker;
|
||
|
char npcExtensionListBuf[2048]; // The list of file names read in
|
||
|
|
||
|
//First, load in the npcs.cfg
|
||
|
gi.Printf( "Parsing %s\n", filename );
|
||
|
len = gi.FS_ReadFile( filename, (void **) &buffer );
|
||
|
if ( len == -1 ) {
|
||
|
gi.Printf( "file not found\n" );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ( len >= MAX_NPC_DATA_SIZE ) {
|
||
|
G_Error( "ext_data/NPCs.cfg is too large" );
|
||
|
}
|
||
|
|
||
|
strncpy( NPCParms, buffer, sizeof( NPCParms ) - 1 );
|
||
|
gi.FS_FreeFile( buffer );
|
||
|
|
||
|
//remember where to store the next one
|
||
|
totallen = mainBlockLen = len;
|
||
|
marker = NPCParms+totallen;
|
||
|
|
||
|
//now load in the extra .npc extensions
|
||
|
fileCnt = gi.FS_GetFileList("ext_data", ".npc", npcExtensionListBuf, sizeof(npcExtensionListBuf) );
|
||
|
|
||
|
holdChar = npcExtensionListBuf;
|
||
|
for ( i = 0; i < fileCnt; i++, holdChar += npcExtFNLen + 1 )
|
||
|
{
|
||
|
npcExtFNLen = strlen( holdChar );
|
||
|
|
||
|
gi.Printf( "Parsing %s\n", holdChar );
|
||
|
|
||
|
len = gi.FS_ReadFile( va( "ext_data/%s", holdChar), (void **) &buffer );
|
||
|
|
||
|
if ( len == -1 )
|
||
|
{
|
||
|
gi.Printf( "error reading file\n" );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( totallen + len >= MAX_NPC_DATA_SIZE ) {
|
||
|
G_Error( "NPC extensions (*.npc) are too large" );
|
||
|
}
|
||
|
strcat( marker, buffer );
|
||
|
gi.FS_FreeFile( buffer );
|
||
|
|
||
|
totallen += len;
|
||
|
marker = NPCParms+totallen;
|
||
|
}
|
||
|
}
|
||
|
}
|