/*
===========================================================================
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 .
===========================================================================
*/
//NPC_stats.cpp
#include "b_local.h"
#include "b_public.h"
#include "anims.h"
#include "wp_saber.h"
#include "g_vehicles.h"
#include "../cgame/cg_local.h"
extern cvar_t *g_TeamBeefDirectorsCut;
#if !defined(RUFL_HSTRING_INC)
#include "../Rufl/hstring.h"
#endif
#include "../Ratl/string_vs.h"
#include "../Rufl/hstring.h"
#include "../Ratl/vector_vs.h"
extern void WP_RemoveSaber( gentity_t *ent, int saberNum );
extern qboolean NPCsPrecached;
extern vec3_t playerMins;
extern vec3_t playerMaxs;
extern stringID_table_t WPTable[];
#define MAX_MODELS_PER_LEVEL 60
hstring modelsAlreadyDone[MAX_MODELS_PER_LEVEL];
stringID_table_t animEventTypeTable[] =
{
ENUM2STRING(AEV_SOUND), //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay
ENUM2STRING(AEV_FOOTSTEP), //# animID AEV_FOOTSTEP framenum footstepType
ENUM2STRING(AEV_EFFECT), //# animID AEV_EFFECT framenum effectpath boltName
ENUM2STRING(AEV_FIRE), //# animID AEV_FIRE framenum altfire chancetofire
ENUM2STRING(AEV_MOVE), //# animID AEV_MOVE framenum forwardpush rightpush uppush
ENUM2STRING(AEV_SOUNDCHAN), //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay
ENUM2STRING(AEV_SABER_SWING), //# animID AEV_SABER_SWING framenum CHANNEL randomlow randomhi chancetoplay
ENUM2STRING(AEV_SABER_SPIN), //# animID AEV_SABER_SPIN framenum CHANNEL chancetoplay
//must be terminated
{ NULL,-1 }
};
stringID_table_t footstepTypeTable[] =
{
ENUM2STRING(FOOTSTEP_R),
ENUM2STRING(FOOTSTEP_L),
ENUM2STRING(FOOTSTEP_HEAVY_R),
ENUM2STRING(FOOTSTEP_HEAVY_L),
//must be terminated
{ NULL,-1 }
};
stringID_table_t FPTable[] =
{
ENUM2STRING(FP_HEAL),
ENUM2STRING(FP_LEVITATION),
ENUM2STRING(FP_SPEED),
ENUM2STRING(FP_PUSH),
ENUM2STRING(FP_PULL),
ENUM2STRING(FP_TELEPATHY),
ENUM2STRING(FP_GRIP),
ENUM2STRING(FP_LIGHTNING),
ENUM2STRING(FP_SABERTHROW),
ENUM2STRING(FP_SABER_DEFENSE),
ENUM2STRING(FP_SABER_OFFENSE),
//new Jedi Academy powers
ENUM2STRING(FP_RAGE),
ENUM2STRING(FP_PROTECT),
ENUM2STRING(FP_ABSORB),
ENUM2STRING(FP_DRAIN),
ENUM2STRING(FP_SEE),
{ "", -1 }
};
stringID_table_t TeamTable[] =
{
{ "free", TEAM_FREE }, // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc
ENUM2STRING(TEAM_FREE), // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc
{ "player", TEAM_PLAYER },
ENUM2STRING(TEAM_PLAYER),
{ "enemy", TEAM_ENEMY },
ENUM2STRING(TEAM_ENEMY),
{ "neutral", TEAM_NEUTRAL }, // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator
ENUM2STRING(TEAM_NEUTRAL), // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator
{ "", -1 }
};
// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h
stringID_table_t ClassTable[] =
{
ENUM2STRING(CLASS_NONE), // hopefully this will never be used by an npc), just covering all bases
ENUM2STRING(CLASS_ATST), // technically droid...
ENUM2STRING(CLASS_BARTENDER),
ENUM2STRING(CLASS_BESPIN_COP),
ENUM2STRING(CLASS_CLAW),
ENUM2STRING(CLASS_COMMANDO),
ENUM2STRING(CLASS_DESANN),
ENUM2STRING(CLASS_FISH),
ENUM2STRING(CLASS_FLIER2),
ENUM2STRING(CLASS_GALAK),
ENUM2STRING(CLASS_GLIDER),
ENUM2STRING(CLASS_GONK), // droid
ENUM2STRING(CLASS_GRAN),
ENUM2STRING(CLASS_HOWLER),
ENUM2STRING(CLASS_RANCOR),
ENUM2STRING(CLASS_SAND_CREATURE),
ENUM2STRING(CLASS_WAMPA),
ENUM2STRING(CLASS_IMPERIAL),
ENUM2STRING(CLASS_IMPWORKER),
ENUM2STRING(CLASS_INTERROGATOR), // droid
ENUM2STRING(CLASS_JAN),
ENUM2STRING(CLASS_JEDI),
ENUM2STRING(CLASS_KYLE),
ENUM2STRING(CLASS_LANDO),
ENUM2STRING(CLASS_LIZARD),
ENUM2STRING(CLASS_LUKE),
ENUM2STRING(CLASS_MARK1), // droid
ENUM2STRING(CLASS_MARK2), // droid
ENUM2STRING(CLASS_GALAKMECH), // droid
ENUM2STRING(CLASS_MINEMONSTER),
ENUM2STRING(CLASS_MONMOTHA),
ENUM2STRING(CLASS_MORGANKATARN),
ENUM2STRING(CLASS_MOUSE), // droid
ENUM2STRING(CLASS_MURJJ),
ENUM2STRING(CLASS_PRISONER),
ENUM2STRING(CLASS_PROBE), // droid
ENUM2STRING(CLASS_PROTOCOL), // droid
ENUM2STRING(CLASS_R2D2), // droid
ENUM2STRING(CLASS_R5D2), // droid
ENUM2STRING(CLASS_REBEL),
ENUM2STRING(CLASS_REBORN),
ENUM2STRING(CLASS_REELO),
ENUM2STRING(CLASS_REMOTE),
ENUM2STRING(CLASS_RODIAN),
ENUM2STRING(CLASS_SEEKER), // droid
ENUM2STRING(CLASS_SENTRY),
ENUM2STRING(CLASS_SHADOWTROOPER),
ENUM2STRING(CLASS_SABOTEUR),
ENUM2STRING(CLASS_STORMTROOPER),
ENUM2STRING(CLASS_SWAMP),
ENUM2STRING(CLASS_SWAMPTROOPER),
ENUM2STRING(CLASS_NOGHRI),
ENUM2STRING(CLASS_TAVION),
ENUM2STRING(CLASS_ALORA),
ENUM2STRING(CLASS_TRANDOSHAN),
ENUM2STRING(CLASS_UGNAUGHT),
ENUM2STRING(CLASS_JAWA),
ENUM2STRING(CLASS_WEEQUAY),
ENUM2STRING(CLASS_TUSKEN),
ENUM2STRING(CLASS_BOBAFETT),
ENUM2STRING(CLASS_ROCKETTROOPER),
ENUM2STRING(CLASS_SABER_DROID),
ENUM2STRING(CLASS_PLAYER),
ENUM2STRING(CLASS_ASSASSIN_DROID),
ENUM2STRING(CLASS_HAZARD_TROOPER),
ENUM2STRING(CLASS_VEHICLE),
{ "", -1 }
};
/*
NPC_ReactionTime
*/
//FIXME use grandom in here
int NPC_ReactionTime ( void )
{
return 200 * ( 6 - NPCInfo->stats.reactions );
}
//
// parse support routines
//
qboolean G_ParseLiteral( const char **data, const char *string )
{
const 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;
}
//
// NPC parameters file : ext_data/NPCs/*.npc*
//
#define MAX_NPC_DATA_SIZE 0x80000
char NPCParms[MAX_NPC_DATA_SIZE];
/*
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;
}
saber_colors_t TranslateSaberColor( const char *name )
{
if ( !Q_stricmp( name, "red" ) )
{
return SABER_RED;
}
if ( !Q_stricmp( name, "orange" ) )
{
return SABER_ORANGE;
}
if ( !Q_stricmp( name, "yellow" ) )
{
return SABER_YELLOW;
}
if ( !Q_stricmp( name, "green" ) )
{
return SABER_GREEN;
}
if ( !Q_stricmp( name, "blue" ) )
{
return SABER_BLUE;
}
if ( !Q_stricmp( name, "purple" ) )
{
return SABER_PURPLE;
}
if ( !Q_stricmp( name, "random" ) )
{
return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE )));
}
return SABER_BLUE;
}
/* 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 );
//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING
#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING
void SpewDebugStuffToFile(animation_t *bgGlobalAnimations)
{
char BGPAFtext[40000];
fileHandle_t f;
int i = 0;
gi.FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE);
if (!f)
{
return;
}
BGPAFtext[0] = 0;
while (i < MAX_ANIMATIONS)
{
strcat(BGPAFtext, va("%i %i\n", i, bgGlobalAnimations[i].frameLerp));
i++;
}
gi.FS_Write(BGPAFtext, strlen(BGPAFtext), f);
gi.FS_FCloseFile(f);
}
#endif
int CG_CheckAnimFrameForEventType( animevent_t *animEvents, int keyFrame, animEventType_t eventType, unsigned short modelIndex )
{
for ( int i = 0; i < MAX_ANIM_EVENTS; i++ )
{
if ( animEvents[i].keyFrame == keyFrame )
{//there is an animevent on this frame already
if ( animEvents[i].eventType == eventType )
{//and it is of the same type
if ( animEvents[i].modelOnly == modelIndex )
{//and it is for the same model
return i;
}
}
}
}
//nope
return -1;
}
/*
======================
ParseAnimationEvtBlock
======================
*/
static void ParseAnimationEvtBlock(int glaIndex, unsigned short modelIndex, const char* aeb_filename, animevent_t *animEvents, animation_t *animations, unsigned char &lastAnimEvent, const char **text_p, bool bIsFrameSkipped)
{
const char *token;
int num, n, animNum, keyFrame, lowestVal, highestVal, curAnimEvent = 0;
animEventType_t eventType;
char stringData[MAX_QPATH];
// get past starting bracket
while(1)
{
token = COM_Parse( text_p );
if ( !Q_stricmp( token, "{" ) )
{
break;
}
}
//NOTE: instead of a blind increment, increase the index
// this way if we have an event on an anim that already
// has an event of that type, it stomps it
// read information for each frame
while ( 1 )
{
// Get base frame of sequence
token = COM_Parse( text_p );
if ( !token || !token[0])
{
break;
}
if ( !Q_stricmp( token, "}" ) ) // At end of block
{
break;
}
//Compare to same table as animations used
// so we don't have to use actual numbers for animation first frames,
// just need offsets.
//This way when animation numbers change, this table won't have to be updated,
// at least not much.
animNum = GetIDForString(animTable, token);
if(animNum == -1)
{//Unrecognized ANIM ENUM name,
Com_Printf(S_COLOR_YELLOW"WARNING: Unknown ANIM %s in file %s\n", token, aeb_filename );
//skip this entry
SkipRestOfLine( text_p );
continue;
}
if ( animations[animNum].numFrames == 0 )
{//we don't use this anim
#ifndef FINAL_BUILD
Com_Printf(S_COLOR_YELLOW"WARNING: %s: anim %s not used by this model\n", aeb_filename, token);
#endif
//skip this entry
SkipRestOfLine( text_p );
continue;
}
token = COM_Parse( text_p );
eventType = (animEventType_t)GetIDForString(animEventTypeTable, token);
if ( eventType == AEV_NONE || eventType == (animEventType_t)-1 )
{//Unrecognized ANIM EVENT TYPE
Com_Printf(S_COLOR_RED"ERROR: Unknown EVENT %s in animEvent file %s\n", token, aeb_filename );
continue;
}
// Get offset to frame within sequence
token = COM_Parse( text_p );
if ( !token )
{
break;
}
keyFrame = atoi( token );
if ( bIsFrameSkipped &&
(animations[animNum].numFrames>2) // important, else frame 1 gets divided down and becomes frame 0. Carcass & Assimilate also work this way
)
{
keyFrame /= 2; // if we ever use any other value in frame-skipping we'll have to figure out some way of reading it, since it's not stored anywhere
}
if(keyFrame >= animations[animNum].numFrames)
{
Com_Printf(S_COLOR_YELLOW"WARNING: Event out of range on %s in %s\n", GetStringForID(animTable,animNum), aeb_filename );
assert(keyFrame < animations[animNum].numFrames);
keyFrame = animations[animNum].numFrames-1; //clamp it
}
//set our start frame
keyFrame += animations[animNum].firstFrame;
//see if this frame already has an event of this type on it, if so, overwrite it
curAnimEvent = CG_CheckAnimFrameForEventType( animEvents, keyFrame, eventType, modelIndex );
if ( curAnimEvent == -1 )
{//this anim frame doesn't already have an event of this type on it
curAnimEvent = lastAnimEvent;
}
//now that we know which event index we're going to plug the data into, start doing it
animEvents[curAnimEvent].eventType = eventType;
assert(keyFrame >= 0 && keyFrame < 65535); //
animEvents[curAnimEvent].keyFrame = keyFrame;
animEvents[curAnimEvent].glaIndex = glaIndex;
animEvents[curAnimEvent].modelOnly = modelIndex;
int tempVal;
//now read out the proper data based on the type
switch ( animEvents[curAnimEvent].eventType )
{
case AEV_SOUNDCHAN: //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay
token = COM_Parse( text_p );
if ( !token )
{
break;
}
if ( Q_stricmp( token, "CHAN_VOICE_ATTEN" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_ATTEN;
}
else if ( Q_stricmp( token, "CHAN_VOICE_GLOBAL" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_GLOBAL;
}
else if ( Q_stricmp( token, "CHAN_ANNOUNCER" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_ANNOUNCER;
}
else if ( Q_stricmp( token, "CHAN_BODY" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_BODY;
}
else if ( Q_stricmp( token, "CHAN_WEAPON" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_WEAPON;
}
else if ( Q_stricmp( token, "CHAN_VOICE" ) == 0 )
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE;
}
else
{
animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_AUTO;
}
//fall through to normal sound
case AEV_SOUND: //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay
//get soundstring
token = COM_Parse( text_p );
if ( !token )
{
break;
}
strcpy(stringData, token);
//get lowest value
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
lowestVal = atoi( token );
//get highest value
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
highestVal = atoi( token );
//Now precache all the sounds
//NOTE: If we can be assured sequential handles, we can store the first sound index and count
// unfortunately, if these sounds were previously registered, we cannot be guaranteed sequential indices. Thus an array
if(lowestVal && highestVal)
{
assert(highestVal - lowestVal < MAX_RANDOM_ANIM_SOUNDS);
for ( n = lowestVal, num = AED_SOUNDINDEX_START; n <= highestVal && num <= AED_SOUNDINDEX_END; n++, num++ )
{
animEvents[curAnimEvent].eventData[num] = G_SoundIndex( va( stringData, n ) );//cgi_S_RegisterSound
}
animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = num - 1;
}
else
{
animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = G_SoundIndex( stringData );//cgi_S_RegisterSound
#if 0 //#ifndef FINAL_BUILD (only meaningfull if using S_RegisterSound
if ( !animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] )
{//couldn't register it - file not found
Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (%s)!\n", stringData, *aeb_filename );
}
#endif
animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = 0;
}
//get probability
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY] = atoi( token );
//last part - cheat and check and see if it's a special overridable saber sound we know of...
if ( !Q_stricmpn( "sound/weapons/saber/saberhup", stringData, 28 ) )
{//a saber swing
animEvents[curAnimEvent].eventType = AEV_SABER_SWING;
animEvents[curAnimEvent].eventData[AED_SABER_SWING_SABERNUM] = 0;//since we don't know which one they meant if we're hacking this, always use first saber
animEvents[curAnimEvent].eventData[AED_SABER_SWING_PROBABILITY] = animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY];
if ( lowestVal < 4 )
{//fast swing
animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = SWING_FAST;
}
else if ( lowestVal < 7 )
{//medium swing
animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = SWING_MEDIUM;
}
else
{//strong swing
animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = SWING_STRONG;
}
}
else if ( !Q_stricmpn( "sound/weapons/saber/saberspin", stringData, 29 ) )
{//a saber spin
animEvents[curAnimEvent].eventType = AEV_SABER_SPIN;
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_SABERNUM] = 0;//since we don't know which one they meant if we're hacking this, always use first saber
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_PROBABILITY] = animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY];
if ( stringData[29] == 'o' )
{//saberspinoff
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 0;
}
else if ( stringData[29] == '1' )
{//saberspin1
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 2;
}
else if ( stringData[29] == '2' )
{//saberspin2
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 3;
}
else if ( stringData[29] == '3' )
{//saberspin3
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 4;
}
else if ( stringData[29] == '%' )
{//saberspin%d
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 5;
}
else
{//just plain saberspin
animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 1;
}
}
break;
case AEV_FOOTSTEP: //# animID AEV_FOOTSTEP framenum footstepType
//get footstep type
token = COM_Parse( text_p );
if ( !token )
{
break;
}
animEvents[curAnimEvent].eventData[AED_FOOTSTEP_TYPE] = GetIDForString(footstepTypeTable, token);
//get probability
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
animEvents[curAnimEvent].eventData[AED_FOOTSTEP_PROBABILITY] = atoi( token );
break;
case AEV_EFFECT: //# animID AEV_EFFECT framenum effectpath boltName
//get effect index
token = COM_Parse( text_p );
if ( !token )
{
break;
}
if ( token[0] && Q_stricmp( "special", token ) == 0 )
{//special hard-coded effects
//let cgame know it's not a real effect
animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = -1;
//get the name of it
token = COM_Parse( text_p );
animEvents[curAnimEvent].stringData = G_NewString( token );
}
else
{//regular effect
tempVal = G_EffectIndex(token);
assert(tempVal > -32767 && tempVal < 32767);
animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = tempVal;
//get bolt index
token = COM_Parse( text_p );
if ( !token )
{
break;
}
if ( Q_stricmp( "none", token ) != 0 && Q_stricmp( "NULL", token ) != 0 )
{//actually are specifying a bolt to use
animEvents[curAnimEvent].stringData = G_NewString( token );
}
}
//NOTE: this string will later be used to add a bolt and store the index, as below:
//animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData );
//get probability
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
animEvents[curAnimEvent].eventData[AED_EFFECT_PROBABILITY] = atoi( token );
break;
case AEV_FIRE: //# animID AEV_FIRE framenum altfire chancetofire
//get altfire
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
animEvents[curAnimEvent].eventData[AED_FIRE_ALT] = atoi( token );
//get probability
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
animEvents[curAnimEvent].eventData[AED_FIRE_PROBABILITY] = atoi( token );
break;
case AEV_MOVE: //# animID AEV_MOVE framenum forwardpush rightpush uppush
//get forward push
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
tempVal = atoi(token);
assert(tempVal > -32767 && tempVal < 32767);
animEvents[curAnimEvent].eventData[AED_MOVE_FWD] = tempVal;
//get right push
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
tempVal = atoi(token);
assert(tempVal > -32767 && tempVal < 32767);
animEvents[curAnimEvent].eventData[AED_MOVE_RT] = tempVal;
//get upwards push
token = COM_Parse( text_p );
if ( !token )
{//WARNING! BAD TABLE!
break;
}
tempVal = atoi(token);
assert(tempVal > -32767 && tempVal < 32767);
animEvents[curAnimEvent].eventData[AED_MOVE_UP] = tempVal;
break;
default: //unknown?
SkipRestOfLine( text_p );
continue;
break;
}
if ( curAnimEvent == lastAnimEvent )
{
lastAnimEvent++;
}
}
}
/*
======================
G_ParseAnimationEvtFile
Read a configuration file containing animation events
models/players/kyle/animevents.cfg, etc
This file's presence is not required
======================
*/
static
void G_ParseAnimationEvtFile(int glaIndex, const char* eventsDirectory, int fileIndex, int iRealGLAIndex = -1, bool modelSpecific = false)
{
int len;
const char* token;
char text[80000];
const char* text_p = text;
fileHandle_t f;
char eventsPath[MAX_QPATH];
int modelIndex = 0;
assert(fileIndex>=0 && fileIndex5 && !Q_stricmp(&psAnimFileInternalName[strlen(psAnimFileInternalName)-5],"_skip"));
// Open The File, Make Sure It Is Safe
//-------------------------------------
Com_sprintf(eventsPath, MAX_QPATH, "models/players/%s/animevents.cfg", eventsDirectory);
len = cgi_FS_FOpenFile(eventsPath, &f, FS_READ);
if ( len <= 0 )
{//no file
return;
}
if ( len >= (int)(sizeof( text ) - 1) )
{
cgi_FS_FCloseFile( f );
CG_Printf( "File %s too long\n", eventsPath );
return;
}
// Read It To The Buffer, Close The File
//---------------------------------------
cgi_FS_Read( text, len, f );
text[len] = 0;
cgi_FS_FCloseFile( f );
// Get The Pointers To The Anim Event Arrays
//-------------------------------------------
animFileSet_t& afileset = level.knownAnimFileSets[fileIndex];
animevent_t *legsAnimEvents = afileset.legsAnimEvents;
animevent_t *torsoAnimEvents = afileset.torsoAnimEvents;
animation_t *animations = afileset.animations;
if (modelSpecific)
{
hstring modelName(eventsDirectory);
modelIndex = modelName.handle();
}
// read information for batches of sounds (UPPER or LOWER)
COM_BeginParseSession();
while ( 1 )
{
// Get base frame of sequence
token = COM_Parse( &text_p );
if ( !token || !token[0] )
{
break;
}
//these stomp anything set in the include file (if it's an event of the same type on the same frame)!
if ( !Q_stricmp(token,"UPPEREVENTS") ) // A batch of upper events
{
ParseAnimationEvtBlock(glaIndex, modelIndex, eventsPath, torsoAnimEvents, animations, afileset.torsoAnimEventCount, &text_p, bIsFrameSkipped);
}
else if ( !Q_stricmp(token,"LOWEREVENTS") ) // A batch of lower events
{
ParseAnimationEvtBlock(glaIndex, modelIndex, eventsPath, legsAnimEvents, animations, afileset.legsAnimEventCount, &text_p, bIsFrameSkipped);
}
}
COM_EndParseSession();
}
/*
======================
G_ParseAnimationFile
Read a configuration file containing animation coutns and rates
models/players/visor/animation.cfg, etc
======================
*/
qboolean G_ParseAnimationFile(int glaIndex, const char *skeletonName, int fileIndex)
{
char text[80000];
int len = 0;
const char *token = 0;
float fps = 0;
const char* text_p = text;
int animNum = 0;
animation_t* animations = level.knownAnimFileSets[fileIndex].animations;
char skeletonPath[MAX_QPATH];
// Read In The File To The Text Buffer, Make Sure Everything Is Safe To Continue
//-------------------------------------------------------------------------------
Com_sprintf(skeletonPath, MAX_QPATH, "models/players/%s/%s.cfg", skeletonName, skeletonName);
len = gi.RE_GetAnimationCFG(skeletonPath, text, sizeof(text));
if ( len <= 0 )
{
Com_sprintf(skeletonPath, MAX_QPATH, "models/players/%s/animation.cfg", skeletonName);
len = gi.RE_GetAnimationCFG(skeletonPath, text, sizeof(text));
if ( len <= 0 )
{
return qfalse;
}
}
if ( len >= (int)(sizeof( text ) - 1) )
{
G_Error( "G_ParseAnimationFile: File %s too long\n (%d > %d)", skeletonName, len, sizeof( text ) - 1);
return qfalse;
}
// Read In Each Token
//--------------------
COM_BeginParseSession();
while(1)
{
token = COM_Parse( &text_p );
// If No Token, We've Reached The End Of The File
//------------------------------------------------
if ( !token || !token[0])
{
break;
}
// Get The Anim Number Converted From The First Token
//----------------------------------------------------
animNum = GetIDForString(animTable, token);
if(animNum == -1)
{
#ifndef FINAL_BUILD
if (strcmp(token,"ROOT"))
{
Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, skeletonPath);
}
#endif
//unrecognized animation so skip to end of line,
while (token[0])
{
token = COM_ParseExt( &text_p, qfalse ); //returns empty string when next token is EOL
}
continue;
}
// GLAIndex
//----------
animations[animNum].glaIndex = glaIndex; // Passed Into This Func
// First Frame
//-------------
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
assert(atoi(token) >= 0 && atoi(token) < 65536);
animations[animNum].firstFrame = atoi( token );
// Num Frames
//------------
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
assert(atoi(token) >= 0 && atoi(token) < 65536);
animations[animNum].numFrames = atoi( token );
// Loop Frames
//-------------
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
assert(atoi(token) >= -1 && atoi(token) < 128);
animations[animNum].loopFrames = atoi( token );
// FPS
//-----
token = COM_Parse( &text_p );
if ( !token )
{
break;
}
fps = atof( token );
if ( fps == 0 )
{
fps = 1;//Don't allow divide by zero error
}
// Calculate Frame Lerp
//----------------------
int lerp;
if ( fps < 0 )
{//backwards
lerp = floor(1000.0f / fps);
assert(lerp > -32767 && lerp < 32767);
animations[animNum].frameLerp = lerp;
assert(animations[animNum].frameLerp <= 1);
}
else
{
lerp = ceil(1000.0f / fps);
assert(lerp > -32767 && lerp < 32767);
animations[animNum].frameLerp = lerp;
assert(animations[animNum].frameLerp >= 1);
}
/* lerp = ceil(1000.0f / Q_fabs(fps));
assert(lerp > -32767 && lerp < 32767);
animations[animNum].initialLerp = lerp;
*/
}
COM_EndParseSession();
#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING
if (strstr(af_filename, "humanoid"))
{
SpewDebugStuffToFile(animations);
}
#endif
return qtrue;
}
////////////////////////////////////////////////////////////////////////
// G_ParseAnimFileSet
//
// This function is responsible for building the animation file and
// the animation event file.
//
////////////////////////////////////////////////////////////////////////
int G_ParseAnimFileSet(const char *skeletonName, const char *modelName=0)
{
int fileIndex=0;
// Try To Find An Existing Skeleton File For This Animation Set
//--------------------------------------------------------------
for (fileIndex=0; fileIndex=level.numKnownAnimFileSets)
{
if (level.numKnownAnimFileSets==MAX_ANIM_FILES)
{
G_Error( "G_ParseAnimFileSet: MAX_ANIM_FILES" );
return -1;
}
// Allocate A new File Set, And Get Some Shortcut Pointers
//---------------------------------------------------------
fileIndex = level.numKnownAnimFileSets;
level.numKnownAnimFileSets++;
strcpy(level.knownAnimFileSets[fileIndex].filename, skeletonName);
level.knownAnimFileSets[fileIndex].torsoAnimEventCount = 0;
level.knownAnimFileSets[fileIndex].legsAnimEventCount = 0;
animation_t* animations = level.knownAnimFileSets[fileIndex].animations;
animevent_t* legsAnimEvents = level.knownAnimFileSets[fileIndex].legsAnimEvents;
animevent_t* torsoAnimEvents = level.knownAnimFileSets[fileIndex].torsoAnimEvents;
int i, j;
// Initialize The Frames Information
//-----------------------------------
for(i=0; iplayerModel == -1 )
{
return;
}
if ( Q_stricmp( "player", pModelName ) == 0 )
{//model is actually stored on console
modelName = g_char_model->string;
}
else
{
modelName = (char *)pModelName;
}
//get the location of the animation.cfg
GLAName = gi.G2API_GetGLAName( &ent->ghoul2[ent->playerModel] );
//now load and parse the animation.cfg, animevents.cfg and set the animFileIndex
if ( !GLAName)
{
Com_Printf( S_COLOR_RED"Failed find animation file name models/players/%s\n", modelName );
strippedName="_humanoid"; //take a guess, maybe it's right?
}
else
{
Q_strncpyz( animName, GLAName, sizeof( animName ) );
slash = strrchr( animName, '/' );
if ( slash )
{
*slash = 0;
}
strippedName = COM_SkipPath( animName );
}
//now load and parse the animation.cfg, animevents.cfg and set the animFileIndex
ent->client->clientInfo.animFileIndex = G_ParseAnimFileSet(strippedName, modelName);
if (ent->client->clientInfo.animFileIndex<0)
{
Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/%s/animation.cfg\n", modelName );
#ifndef FINAL_BUILD
Com_Error(ERR_FATAL, "Failed to load animation file set models/players/%s/animation.cfg\n", modelName);
#endif
}
}
void NPC_PrecacheAnimationCFG( const char *NPC_type )
{
char filename[MAX_QPATH];
const char *token;
const char *value;
const char *p;
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 )
{
COM_EndParseSession( );
return;
}
if ( !Q_stricmp( token, NPC_type ) )
{
break;
}
SkipBracedSection( &p );
}
if ( !p )
{
COM_EndParseSession( );
return;
}
if ( G_ParseLiteral( &p, "{" ) )
{
COM_EndParseSession( );
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 );
COM_EndParseSession( );
return;
}
if ( !Q_stricmp( token, "}" ) )
{
break;
}
// legsmodel
if ( !Q_stricmp( token, "legsmodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
//must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt
Q_strncpyz( filename, value, sizeof( filename ) );
G_ParseAnimFileSet( filename );
COM_EndParseSession( );
return;
}
// playerModel
if ( !Q_stricmp( token, "playerModel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
char animName[MAX_QPATH];
char *GLAName;
char *slash = NULL;
char *strippedName;
int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) );
if ( handle > 0 )//FIXME: isn't 0 a valid handle?
{
GLAName = gi.G2API_GetAnimFileNameIndex( handle );
if ( GLAName )
{
Q_strncpyz( animName, GLAName, sizeof( animName ) );
slash = strrchr( animName, '/' );
if ( slash )
{
*slash = 0;
}
strippedName = COM_SkipPath( animName );
//must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt
Q_strncpyz( filename, value, sizeof( filename ) );
G_ParseAnimFileSet(strippedName, filename);
COM_EndParseSession( );
return;
}
}
}
}
COM_EndParseSession( );
}
extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type );
void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype )
{
int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype );
gitem_t *item;
for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ )
{
if ( (weapons & ( 1 << curWeap )) )
{
item = FindItemForWeapon( ((weapon_t)(curWeap)) ); //precache the weapon
CG_RegisterItemSounds( (item-bg_itemlist) );
CG_RegisterItemVisuals( (item-bg_itemlist) );
//precache the in-hand/in-world ghoul2 weapon model
char weaponModel[64];
strcpy (weaponModel, weaponData[curWeap].weaponMdl);
if (char *spot = strstr(weaponModel, ".md3") ) {
*spot = 0;
spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
if (!spot) {
strcat (weaponModel, "_w");
}
strcat (weaponModel, ".glm"); //and change to ghoul2
}
gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model
}
}
}
/*
void NPC_PrecacheByClassName ( char *NPCName )
This runs all the class specific precache functions
*/
extern void NPC_ShadowTrooper_Precache( void );
extern void NPC_Gonk_Precache( void );
extern void NPC_Mouse_Precache( void );
extern void NPC_Seeker_Precache( void );
extern void NPC_Remote_Precache( void );
extern void NPC_R2D2_Precache(void);
extern void NPC_R5D2_Precache(void);
extern void NPC_Probe_Precache(void);
extern void NPC_Interrogator_Precache(gentity_t *self);
extern void NPC_MineMonster_Precache( void );
extern void NPC_Howler_Precache( void );
extern void NPC_Rancor_Precache( void );
extern void NPC_MutantRancor_Precache( void );
extern void NPC_Wampa_Precache( void );
extern void NPC_ATST_Precache(void);
extern void NPC_Sentry_Precache(void);
extern void NPC_Mark1_Precache(void);
extern void NPC_Mark2_Precache(void);
extern void NPC_Protocol_Precache( void );
extern void Boba_Precache( void );
extern void RT_Precache( void );
extern void SandCreature_Precache( void );
extern void NPC_TavionScepter_Precache( void );
extern void NPC_TavionSithSword_Precache( void );
extern void NPC_Rosh_Dark_Precache( void );
extern void NPC_Tusken_Precache( void );
extern void NPC_Saboteur_Precache( void );
extern void NPC_CultistDestroyer_Precache( void );
void NPC_Jawa_Precache( void )
{
for ( int i = 1; i < 7; i++ )
{
G_SoundIndex( va( "sound/chars/jawa/misc/chatter%d.wav", i ) );
}
G_SoundIndex( "sound/chars/jawa/misc/ooh-tee-nee.wav" );
}
void NPC_PrecacheByClassName( const char* type )
{
if (!type || !type[0])
{
return;
}
if ( !Q_stricmp( "gonk", type))
{
NPC_Gonk_Precache();
}
else if ( !Q_stricmp( "mouse", type))
{
NPC_Mouse_Precache();
}
else if ( !Q_stricmpn( "r2d2", type, 4))
{
NPC_R2D2_Precache();
}
else if ( !Q_stricmp( "atst", type))
{
NPC_ATST_Precache();
}
else if ( !Q_stricmpn( "r5d2", type, 4))
{
NPC_R5D2_Precache();
}
else if ( !Q_stricmp( "mark1", type))
{
NPC_Mark1_Precache();
}
else if ( !Q_stricmp( "mark2", type))
{
NPC_Mark2_Precache();
}
else if ( !Q_stricmp( "interrogator", type))
{
NPC_Interrogator_Precache(NULL);
}
else if ( !Q_stricmp( "probe", type))
{
NPC_Probe_Precache();
}
else if ( !Q_stricmp( "seeker", type))
{
NPC_Seeker_Precache();
}
else if ( !Q_stricmpn( "remote", type, 6))
{
NPC_Remote_Precache();
}
else if ( !Q_stricmpn( "shadowtrooper", type, 13 ) )
{
NPC_ShadowTrooper_Precache();
}
else if ( !Q_stricmp( "minemonster", type ))
{
NPC_MineMonster_Precache();
}
else if ( !Q_stricmp( "howler", type ))
{
NPC_Howler_Precache();
}
else if ( !Q_stricmp( "rancor", type ))
{
NPC_Rancor_Precache();
}
else if ( !Q_stricmp( "mutant_rancor", type ))
{
NPC_Rancor_Precache();
NPC_MutantRancor_Precache();
}
else if ( !Q_stricmp( "wampa", type ))
{
NPC_Wampa_Precache();
}
else if ( !Q_stricmp( "sand_creature", type ))
{
SandCreature_Precache();
}
else if ( !Q_stricmp( "sentry", type ))
{
NPC_Sentry_Precache();
}
else if ( !Q_stricmp( "protocol", type ))
{
NPC_Protocol_Precache();
}
else if ( !Q_stricmp( "boba_fett", type ))
{
Boba_Precache();
}
else if ( !Q_stricmp( "rockettrooper2", type ))
{
RT_Precache();
}
else if ( !Q_stricmp( "rockettrooper2Officer", type ))
{
RT_Precache();
}
else if ( !Q_stricmp( "tavion_scepter", type ))
{
NPC_TavionScepter_Precache();
}
else if ( !Q_stricmp( "tavion_sith_sword", type ))
{
NPC_TavionSithSword_Precache();
}
else if ( !Q_stricmp( "rosh_dark", type ) )
{
NPC_Rosh_Dark_Precache();
}
else if ( !Q_stricmpn( "tusken", type, 6 ) )
{
NPC_Tusken_Precache();
}
else if ( !Q_stricmpn( "saboteur", type, 8 ) )
{
NPC_Saboteur_Precache();
}
else if ( !Q_stricmp( "cultist_destroyer", type ) )
{
NPC_CultistDestroyer_Precache();
}
else if ( !Q_stricmpn( "jawa", type, 4 ) )
{
NPC_Jawa_Precache();
}
}
/*
void NPC_Precache ( char *NPCName )
Precaches NPC skins, tgas and md3s.
*/
void CG_NPC_Precache ( gentity_t *spawner )
{
clientInfo_t ci={};
renderInfo_t ri={};
team_t playerTeam = TEAM_FREE;
const char *token;
const char *value;
const char *p;
char *patch;
char sound[MAX_QPATH];
qboolean md3Model = qfalse;
char playerModel[MAX_QPATH] = { 0 };
char customSkin[MAX_QPATH];
if ( !Q_stricmp( "random", spawner->NPC_type ) )
{//sorry, can't precache a random just yet
return;
}
strcpy(customSkin,"default");
p = NPCParms;
COM_BeginParseSession();
// look for the right NPC
while ( p )
{
token = COM_ParseExt( &p, qtrue );
if ( token[0] == 0 )
{
COM_EndParseSession( );
return;
}
if ( !Q_stricmp( token, spawner->NPC_type ) )
{
break;
}
SkipBracedSection( &p );
}
if ( !p )
{
COM_EndParseSession( );
return;
}
if ( G_ParseLiteral( &p, "{" ) )
{
COM_EndParseSession( );
return;
}
// parse the NPC info block
while ( 1 )
{
COM_EndParseSession(); // if still in session (or using continue;)
COM_BeginParseSession();
token = COM_ParseExt( &p, qtrue );
if ( !token[0] )
{
gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type );
COM_EndParseSession( );
return;
}
if ( !Q_stricmp( token, "}" ) )
{
break;
}
// headmodel
if ( !Q_stricmp( token, "headmodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if(!Q_stricmp("none", value))
{
}
else
{
Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName));
}
md3Model = qtrue;
continue;
}
// torsomodel
if ( !Q_stricmp( token, "torsomodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if(!Q_stricmp("none", value))
{
}
else
{
Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName));
}
md3Model = qtrue;
continue;
}
// legsmodel
if ( !Q_stricmp( token, "legsmodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName));
md3Model = qtrue;
continue;
}
// playerModel
if ( !Q_stricmp( token, "playerModel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( playerModel, value, sizeof(playerModel));
md3Model = qfalse;
continue;
}
// customSkin
if ( !Q_stricmp( token, "customSkin" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( customSkin, value, sizeof(customSkin));
continue;
}
// playerTeam
if ( !Q_stricmp( token, "playerTeam" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
playerTeam = (team_t)GetIDForString( TeamTable, token );
continue;
}
// snd
if ( !Q_stricmp( token, "snd" ) ) {
if ( COM_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 ( COM_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 ( COM_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;
}
// sndjedi
if ( !Q_stricmp( token, "sndjedi" ) ) {
if ( COM_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.customJediSoundDir = G_NewString( sound );
}
continue;
}
//cache weapons
if ( !Q_stricmp( token, "weapon" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
int weap = GetIDForString( WPTable, value );
if ( weap >= WP_NONE && weap < WP_NUM_WEAPONS )
{
if ( weap > WP_NONE )
{
RegisterItem( FindItemForWeapon( (weapon_t)(weap) ) ); //precache the weapon
}
}
continue;
}
//cache sabers
//saber name
if ( !Q_stricmp( token, "saber" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
char *saberName = G_NewString( value );
saberInfo_t saber;
WP_SaberParseParms( saberName, &saber );
if ( saber.model && saber.model[0] )
{
G_ModelIndex( saber.model );
}
if ( saber.skin && saber.skin[0] )
{
gi.RE_RegisterSkin( saber.skin );
G_SkinIndex( saber.skin );
}
if ( saber.g2MarksShader[0] )
{
cgi_R_RegisterShader( saber.g2MarksShader );
}
if ( saber.g2MarksShader2[0] )
{
cgi_R_RegisterShader( saber.g2MarksShader2 );
}
if ( saber.g2WeaponMarkShader[0] )
{
cgi_R_RegisterShader( saber.g2WeaponMarkShader );
}
if ( saber.g2WeaponMarkShader2[0] )
{
cgi_R_RegisterShader( saber.g2WeaponMarkShader2 );
}
continue;
}
//second saber name
if ( !Q_stricmp( token, "saber2" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
char *saberName = G_NewString( value );
saberInfo_t saber;
WP_SaberParseParms( saberName, &saber );
if ( saber.model && saber.model[0] )
{
G_ModelIndex( saber.model );
}
if ( saber.skin && saber.skin[0] )
{
gi.RE_RegisterSkin( saber.skin );
G_SkinIndex( saber.skin );
}
continue;
}
}
COM_EndParseSession( );
if ( md3Model )
{
CG_RegisterClientRenderInfo( &ci, &ri );
}
else
{
char skinName[MAX_QPATH];
//precache ghoul2 model
gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", playerModel ) );
//precache skin
if (strchr(customSkin, '|'))
{//three part skin
Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/|%s", playerModel, customSkin );
}
else
{//standard skin
Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", playerModel, customSkin );
}
// lets see if it's out there
gi.RE_RegisterSkin( skinName );
}
//precache this NPC's possible weapons
NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type );
// Anything else special about them
NPC_PrecacheByClassName( spawner->NPC_type );
CG_RegisterNPCCustomSounds( &ci );
//CG_RegisterNPCEffects( playerTeam );
//FIXME: Look for a "sounds" directory and precache death, pain, alert sounds
}
void NPC_BuildRandom( gentity_t *NPC )
{
}
extern void G_MatchPlayerWeapon( gentity_t *ent );
extern void G_InitPlayerFromCvars( gentity_t *ent );
extern void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn );
qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC )
{
const char *token;
const char *value;
const char *p;
int n;
float f;
char *patch;
char sound[MAX_QPATH];
char playerModel[MAX_QPATH];
char customSkin[MAX_QPATH];
clientInfo_t *ci = &NPC->client->clientInfo;
renderInfo_t *ri = &NPC->client->renderInfo;
gNPCstats_t *stats = NULL;
qboolean md3Model = qtrue;
char surfOff[1024]={0};
char surfOn[1024]={0};
qboolean parsingPlayer = qfalse;
strcpy(customSkin,"default");
if ( !NPCName || !NPCName[0])
{
NPCName = "Player";
}
if ( !NPC->s.number && NPC->client != NULL )
{//player, only want certain data
parsingPlayer = qtrue;
}
if ( NPC->NPC )
{
stats = &NPC->NPC->stats;
/*
NPC->NPC->allWeaponOrder[0] = WP_BRYAR_PISTOL;
NPC->NPC->allWeaponOrder[1] = WP_SABER;
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;
*/
// fill in defaults
stats->sex = SEX_MALE;
stats->aggression = 3;
stats->aim = 3;
stats->earshot = 1024;
stats->evasion = 3;
stats->hfov = 90;
stats->intelligence = 3;
stats->move = 3;
stats->reactions = 3;
stats->vfov = 60;
stats->vigilance = 0.1f;
stats->visrange = 1024;
if (g_entities[ENTITYNUM_WORLD].max_health && stats->visrange>g_entities[ENTITYNUM_WORLD].max_health)
{
stats->visrange = g_entities[ENTITYNUM_WORLD].max_health;
}
stats->health = 0;
stats->yawSpeed = 90;
stats->walkSpeed = 90;
stats->runSpeed = 300;
stats->acceleration = 15;//Increase/descrease speed this much per frame (20fps)
}
else
{
stats = NULL;
}
Q_strncpyz( ci->name, NPCName, sizeof( ci->name ) );
NPC->playerModel = -1;
//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);
ri->headModelName[0] = 0;
ri->torsoModelName[0]= 0;
ri->legsModelName[0] =0;
ri->headYawRangeLeft = 80;
ri->headYawRangeRight = 80;
ri->headPitchRangeUp = 45;
ri->headPitchRangeDown = 45;
ri->torsoYawRangeLeft = 60;
ri->torsoYawRangeRight = 60;
ri->torsoPitchRangeUp = 30;
ri->torsoPitchRangeDown = 50;
VectorCopy(playerMins, NPC->mins);
VectorCopy(playerMaxs, NPC->maxs);
NPC->client->crouchheight = CROUCH_MAXS_2;
NPC->client->standheight = DEFAULT_MAXS_2;
NPC->client->moveType = MT_RUNJUMP;
NPC->client->dismemberProbHead = 100;
NPC->client->dismemberProbArms = 100;
NPC->client->dismemberProbHands = 100;
NPC->client->dismemberProbWaist = 100;
NPC->client->dismemberProbLegs = 100;
NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = 1.0f;
ri->customRGBA[0] = ri->customRGBA[1] = ri->customRGBA[2] = ri->customRGBA[3] = 0xFFu;
if ( !Q_stricmp( "random", NPCName ) )
{//Randomly assemble an NPC
NPC_BuildRandom( NPC );
}
else
{
p = NPCParms;
COM_BeginParseSession();
#ifdef _WIN32
#pragma region(NPC Stats)
#endif
// look for the right NPC
while ( p )
{
token = COM_ParseExt( &p, qtrue );
if ( token[0] == 0 )
{
COM_EndParseSession( );
return qfalse;
}
if ( !Q_stricmp( token, NPCName ) )
{
break;
}
SkipBracedSection( &p );
}
if ( !p )
{
COM_EndParseSession( );
return qfalse;
}
if ( G_ParseLiteral( &p, "{" ) )
{
COM_EndParseSession( );
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 );
COM_EndParseSession( );
return qfalse;
}
if ( !Q_stricmp( token, "}" ) )
{
break;
}
//===MODEL PROPERTIES===========================================================
// custom color
if ( !Q_stricmp( token, "customRGBA" ) )
{
// eezstreet TODO: Put these into functions, damn it! They're too big!
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if ( !Q_stricmp( value, "random") )
{
ri->customRGBA[0]=Q_irand(0,255);
ri->customRGBA[1]=Q_irand(0,255);
ri->customRGBA[2]=Q_irand(0,255);
ri->customRGBA[3]=255;
}
else if ( !Q_stricmp( value, "random1") )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,5))
{
default:
case 0:
ri->customRGBA[0]=127;
ri->customRGBA[1]=153;
ri->customRGBA[2]=255;
break;
case 1:
ri->customRGBA[0]=177;
ri->customRGBA[1]=29;
ri->customRGBA[2]=13;
break;
case 2:
ri->customRGBA[0]=47;
ri->customRGBA[1]=90;
ri->customRGBA[2]=40;
break;
case 3:
ri->customRGBA[0]=181;
ri->customRGBA[1]=207;
ri->customRGBA[2]=255;
break;
case 4:
ri->customRGBA[0]=138;
ri->customRGBA[1]=83;
ri->customRGBA[2]=0;
break;
case 5:
ri->customRGBA[0]=254;
ri->customRGBA[1]=199;
ri->customRGBA[2]=14;
break;
}
}
else if ( !Q_stricmp( value, "jedi_hf" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,7))
{
default:
case 0://red1
ri->customRGBA[0]=165;
ri->customRGBA[1]=48;
ri->customRGBA[2]=21;
break;
case 1://yellow1
ri->customRGBA[0]=254;
ri->customRGBA[1]=230;
ri->customRGBA[2]=132;
break;
case 2://bluegray
ri->customRGBA[0]=181;
ri->customRGBA[1]=207;
ri->customRGBA[2]=255;
break;
case 3://pink
ri->customRGBA[0]=233;
ri->customRGBA[1]=183;
ri->customRGBA[2]=208;
break;
case 4://lt blue
ri->customRGBA[0]=161;
ri->customRGBA[1]=226;
ri->customRGBA[2]=240;
break;
case 5://blue
ri->customRGBA[0]=101;
ri->customRGBA[1]=159;
ri->customRGBA[2]=255;
break;
case 6://orange
ri->customRGBA[0]=255;
ri->customRGBA[1]=157;
ri->customRGBA[2]=114;
break;
case 7://violet
ri->customRGBA[0]=216;
ri->customRGBA[1]=160;
ri->customRGBA[2]=255;
break;
}
}
else if ( !Q_stricmp( value, "jedi_hm" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,7))
{
default:
case 0://yellow
ri->customRGBA[0]=252;
ri->customRGBA[1]=243;
ri->customRGBA[2]=180;
break;
case 1://blue
ri->customRGBA[0]=69;
ri->customRGBA[1]=109;
ri->customRGBA[2]=255;
break;
case 2://gold
ri->customRGBA[0]=254;
ri->customRGBA[1]=197;
ri->customRGBA[2]=73;
break;
case 3://orange
ri->customRGBA[0]=178;
ri->customRGBA[1]=78;
ri->customRGBA[2]=18;
break;
case 4://bluegreen
ri->customRGBA[0]=112;
ri->customRGBA[1]=153;
ri->customRGBA[2]=161;
break;
case 5://blue2
ri->customRGBA[0]=123;
ri->customRGBA[1]=182;
ri->customRGBA[2]=255;
break;
case 6://green2
ri->customRGBA[0]=0;
ri->customRGBA[1]=88;
ri->customRGBA[2]=105;
break;
case 7://violet
ri->customRGBA[0]=138;
ri->customRGBA[1]=0;
ri->customRGBA[2]=0;
break;
}
}
else if ( !Q_stricmp( value, "jedi_kdm" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,8))
{
default:
case 0://blue
ri->customRGBA[0]=85;
ri->customRGBA[1]=120;
ri->customRGBA[2]=255;
break;
case 1://violet
ri->customRGBA[0]=173;
ri->customRGBA[1]=142;
ri->customRGBA[2]=219;
break;
case 2://brown1
ri->customRGBA[0]=254;
ri->customRGBA[1]=197;
ri->customRGBA[2]=73;
break;
case 3://orange
ri->customRGBA[0]=138;
ri->customRGBA[1]=83;
ri->customRGBA[2]=0;
break;
case 4://gold
ri->customRGBA[0]=254;
ri->customRGBA[1]=199;
ri->customRGBA[2]=14;
break;
case 5://blue2
ri->customRGBA[0]=68;
ri->customRGBA[1]=194;
ri->customRGBA[2]=217;
break;
case 6://red1
ri->customRGBA[0]=170;
ri->customRGBA[1]=3;
ri->customRGBA[2]=30;
break;
case 7://yellow1
ri->customRGBA[0]=225;
ri->customRGBA[1]=226;
ri->customRGBA[2]=144;
break;
case 8://violet2
ri->customRGBA[0]=167;
ri->customRGBA[1]=202;
ri->customRGBA[2]=255;
break;
}
}
else if ( !Q_stricmp( value, "jedi_rm" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,8))
{
default:
case 0://blue
ri->customRGBA[0]=127;
ri->customRGBA[1]=153;
ri->customRGBA[2]=255;
break;
case 1://green1
ri->customRGBA[0]=208;
ri->customRGBA[1]=249;
ri->customRGBA[2]=85;
break;
case 2://blue2
ri->customRGBA[0]=181;
ri->customRGBA[1]=207;
ri->customRGBA[2]=255;
break;
case 3://gold
ri->customRGBA[0]=138;
ri->customRGBA[1]=83;
ri->customRGBA[2]=0;
break;
case 4://gold
ri->customRGBA[0]=224;
ri->customRGBA[1]=171;
ri->customRGBA[2]=44;
break;
case 5://green2
ri->customRGBA[0]=49;
ri->customRGBA[1]=155;
ri->customRGBA[2]=131;
break;
case 6://red1
ri->customRGBA[0]=163;
ri->customRGBA[1]=79;
ri->customRGBA[2]=17;
break;
case 7://violet2
ri->customRGBA[0]=148;
ri->customRGBA[1]=104;
ri->customRGBA[2]=228;
break;
case 8://green3
ri->customRGBA[0]=138;
ri->customRGBA[1]=136;
ri->customRGBA[2]=0;
break;
}
}
else if ( !Q_stricmp( value, "jedi_tf" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,5))
{
default:
case 0://green1
ri->customRGBA[0]=255;
ri->customRGBA[1]=235;
ri->customRGBA[2]=100;
break;
case 1://blue1
ri->customRGBA[0]=62;
ri->customRGBA[1]=155;
ri->customRGBA[2]=255;
break;
case 2://red1
ri->customRGBA[0]=255;
ri->customRGBA[1]=110;
ri->customRGBA[2]=120;
break;
case 3://purple
ri->customRGBA[0]=180;
ri->customRGBA[1]=150;
ri->customRGBA[2]=255;
break;
case 4://flesh
ri->customRGBA[0]=255;
ri->customRGBA[1]=200;
ri->customRGBA[2]=212;
break;
case 5://base
ri->customRGBA[0]=255;
ri->customRGBA[1]=255;
ri->customRGBA[2]=255;
break;
}
}
else if ( !Q_stricmp( value, "jedi_zf" ) )
{
ri->customRGBA[3]=255;
switch (Q_irand(0,7))
{
default:
case 0://red1
ri->customRGBA[0]=204;
ri->customRGBA[1]=19;
ri->customRGBA[2]=21;
break;
case 1://orange1
ri->customRGBA[0]=255;
ri->customRGBA[1]=107;
ri->customRGBA[2]=40;
break;
case 2://pink1
ri->customRGBA[0]=255;
ri->customRGBA[1]=148;
ri->customRGBA[2]=155;
break;
case 3://gold
ri->customRGBA[0]=255;
ri->customRGBA[1]=164;
ri->customRGBA[2]=59;
break;
case 4://violet1
ri->customRGBA[0]=216;
ri->customRGBA[1]=160;
ri->customRGBA[2]=255;
break;
case 5://blue1
ri->customRGBA[0]=101;
ri->customRGBA[1]=159;
ri->customRGBA[2]=255;
break;
case 6://blue2
ri->customRGBA[0]=161;
ri->customRGBA[1]=226;
ri->customRGBA[2]=240;
break;
case 7://blue3
ri->customRGBA[0]=37;
ri->customRGBA[1]=155;
ri->customRGBA[2]=181;
break;
}
}
else
{
ri->customRGBA[0]=atoi(value);
if ( COM_ParseInt( &p, &n ) )
{
continue;
}
ri->customRGBA[1]=n;
if ( COM_ParseInt( &p, &n ) )
{
continue;
}
ri->customRGBA[2]=n;
if ( COM_ParseInt( &p, &n ) )
{
continue;
}
ri->customRGBA[3]=n;
}
continue;
}
// headmodel
if ( !Q_stricmp( token, "headmodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if(!Q_stricmp("none", value))
{
ri->headModelName[0] = '\0';
//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));
}
continue;
}
// torsomodel
if ( !Q_stricmp( token, "torsomodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if(!Q_stricmp("none", value))
{
ri->torsoModelName[0] = '\0';
//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));
}
continue;
}
// legsmodel
if ( !Q_stricmp( token, "legsmodel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName));
//Need to do this here to get the right index
ci->animFileIndex = G_ParseAnimFileSet(ri->legsModelName);
continue;
}
// playerModel
if ( !Q_stricmp( token, "playerModel" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( playerModel, value, sizeof(playerModel));
md3Model = qfalse;
continue;
}
// customSkin
if ( !Q_stricmp( token, "customSkin" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
Q_strncpyz( customSkin, value, sizeof(customSkin));
continue;
}
// surfOff
if ( !Q_stricmp( token, "surfOff" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if ( surfOff[0] )
{
Q_strcat( surfOff, sizeof(surfOff), "," );
Q_strcat( surfOff, sizeof(surfOff), value );
}
else
{
Q_strncpyz( surfOff, value, sizeof(surfOff));
}
continue;
}
// surfOn
if ( !Q_stricmp( token, "surfOn" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if ( surfOn[0] )
{
Q_strcat( surfOn, sizeof(surfOn), "," );
Q_strcat( surfOn, sizeof(surfOn), value );
}
else
{
Q_strncpyz( surfOn, value, sizeof(surfOn));
}
continue;
}
//headYawRangeLeft
if ( !Q_stricmp( token, "headYawRangeLeft" ) )
{
if ( COM_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 ( COM_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 ( COM_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 ( COM_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 ( COM_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 ( COM_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 ( COM_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 ( COM_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 ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if (n != 100)
{
NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = n/100.0f;
}
continue;
}
//X scale
if ( !Q_stricmp( token, "scaleX" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if (n != 100)
{
NPC->s.modelScale[0] = n/100.0f;
}
continue;
}
//Y scale
if ( !Q_stricmp( token, "scaleY" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if (n != 100)
{
NPC->s.modelScale[1] = n/100.0f;
}
continue;
}
//Z scale
if ( !Q_stricmp( token, "scaleZ" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if (n != 100)
{
NPC->s.modelScale[2] = n/100.0f;
}
continue;
}
//===AI STATS=====================================================================
if ( !parsingPlayer )
{
// aggression
if ( !Q_stricmp( token, "aggression" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 5 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->aggression = n;
}
continue;
}
// aim
if ( !Q_stricmp( token, "aim" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 5 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->aim = n;
}
continue;
}
// earshot
if ( !Q_stricmp( token, "earshot" ) ) {
if ( COM_ParseFloat( &p, &f ) ) {
SkipRestOfLine( &p );
continue;
}
if ( f < 0.0f )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->earshot = f;
}
continue;
}
// evasion
if ( !Q_stricmp( token, "evasion" ) )
{
if ( COM_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;
}
if ( NPC->NPC )
{
stats->evasion = n;
}
continue;
}
// hfov
if ( !Q_stricmp( token, "hfov" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 180 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->hfov = n;// / 2; //FIXME: Why was this being done?!
}
continue;
}
// intelligence
if ( !Q_stricmp( token, "intelligence" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 5 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->intelligence = n;
}
continue;
}
// move
if ( !Q_stricmp( token, "move" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 5 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->move = n;
}
continue;
}
// reactions
if ( !Q_stricmp( token, "reactions" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 1 || n > 5 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->reactions = n;
}
continue;
}
// shootDistance
if ( !Q_stricmp( token, "shootDistance" ) ) {
if ( COM_ParseFloat( &p, &f ) ) {
SkipRestOfLine( &p );
continue;
}
if ( f < 0.0f )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->shootDistance = f;
}
continue;
}
// vfov
if ( !Q_stricmp( token, "vfov" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 2 || n > 360 ) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->vfov = n / 2;
}
continue;
}
// vigilance
if ( !Q_stricmp( token, "vigilance" ) ) {
if ( COM_ParseFloat( &p, &f ) ) {
SkipRestOfLine( &p );
continue;
}
if ( f < 0.0f )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->vigilance = f;
}
continue;
}
// visrange
if ( !Q_stricmp( token, "visrange" ) ) {
if ( COM_ParseFloat( &p, &f ) ) {
SkipRestOfLine( &p );
continue;
}
if ( f < 0.0f )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->visrange = f;
if (g_entities[ENTITYNUM_WORLD].max_health && stats->visrange>g_entities[ENTITYNUM_WORLD].max_health)
{
stats->visrange = g_entities[ENTITYNUM_WORLD].max_health;
}
}
continue;
}
// race
// if ( !Q_stricmp( token, "race" ) )
// {
// if ( COM_ParseString( &p, &value ) )
// {
// continue;
// }
// NPC->client->race = TranslateRaceName(value);
// continue;
// }
// rank
if ( !Q_stricmp( token, "rank" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if ( NPC->NPC )
{
NPC->NPC->rank = TranslateRankName(value);
}
continue;
}
}
// health
if ( !Q_stricmp( token, "health" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->health = n;
}
else if ( parsingPlayer )
{
NPC->client->ps.stats[STAT_MAX_HEALTH] = NPC->client->pers.maxHealth = NPC->max_health = n;
}
continue;
}
// fullName
if ( !Q_stricmp( token, "fullName" ) )
{
#ifndef FINAL_BUILD
gi.Printf( S_COLOR_YELLOW"WARNING: fullname ignored in NPC '%s'\n", NPCName );
#endif
if ( COM_ParseString( &p, &value ) )
{
continue;
}
continue;
}
// playerTeam
if ( !Q_stricmp( token, "playerTeam" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->playerTeam = (team_t)GetIDForString( TeamTable, value );
continue;
}
// enemyTeam
if ( !Q_stricmp( token, "enemyTeam" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->enemyTeam = (team_t)GetIDForString( TeamTable, value );
continue;
}
// class
if ( !Q_stricmp( token, "class" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->NPC_class = (class_t)GetIDForString( ClassTable, value );
// No md3's for vehicles.
if ( NPC->client->NPC_class == CLASS_VEHICLE )
{
if ( !NPC->m_pVehicle )
{//you didn't spawn this guy right!
Com_Printf ( S_COLOR_RED "ERROR: Tried to spawn a vehicle NPC (%s) without using NPC_Vehicle or 'NPC spawn vehicle '!!! Bad, bad, bad! Shame on you!\n", NPCName );
COM_EndParseSession();
return qfalse;
}
md3Model = qfalse;
}
continue;
}
// dismemberment probability for head
if ( !Q_stricmp( token, "dismemberProbHead" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
NPC->client->dismemberProbHead = n;
}
continue;
}
// dismemberment probability for arms
if ( !Q_stricmp( token, "dismemberProbArms" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
NPC->client->dismemberProbArms = n;
}
continue;
}
// dismemberment probability for hands
if ( !Q_stricmp( token, "dismemberProbHands" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
NPC->client->dismemberProbHands = n;
}
continue;
}
// dismemberment probability for waist
if ( !Q_stricmp( token, "dismemberProbWaist" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
NPC->client->dismemberProbWaist = n;
}
continue;
}
// dismemberment probability for legs
if ( !Q_stricmp( token, "dismemberProbLegs" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
NPC->client->dismemberProbLegs = n;
}
continue;
}
//===MOVEMENT STATS============================================================
if ( !Q_stricmp( token, "width" ) )
{
if ( COM_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 ( COM_ParseInt( &p, &n ) )
{
continue;
}
if ( NPC->client->NPC_class == CLASS_VEHICLE
&& NPC->m_pVehicle
&& NPC->m_pVehicle->m_pVehicleInfo
&& NPC->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
{//a flying vehicle's origin must be centered in bbox
NPC->maxs[2] = NPC->client->standheight = (n/2.0f);
NPC->mins[2] = -NPC->maxs[2];
NPC->s.origin[2] += (DEFAULT_MINS_2-NPC->mins[2])+0.125f;
VectorCopy(NPC->s.origin, NPC->client->ps.origin);
VectorCopy(NPC->s.origin, NPC->currentOrigin);
G_SetOrigin( NPC, NPC->s.origin );
gi.linkentity(NPC);
}
else
{
NPC->mins[2] = DEFAULT_MINS_2;//Cannot change
NPC->maxs[2] = NPC->client->standheight = n + DEFAULT_MINS_2;
}
NPC->s.radius = n;
continue;
}
if ( !Q_stricmp( token, "crouchheight" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
continue;
}
NPC->client->crouchheight = n + DEFAULT_MINS_2;
continue;
}
if ( !parsingPlayer )
{
if ( !Q_stricmp( token, "movetype" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->moveType = (movetype_t)MoveTypeNameToEnum(value);
continue;
}
// yawSpeed
if ( !Q_stricmp( token, "yawSpeed" ) ) {
if ( COM_ParseInt( &p, &n ) ) {
SkipRestOfLine( &p );
continue;
}
if ( n <= 0) {
gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->yawSpeed = ((float)(n));
}
continue;
}
// walkSpeed
if ( !Q_stricmp( token, "walkSpeed" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->walkSpeed = n;
}
continue;
}
//runSpeed
if ( !Q_stricmp( token, "runSpeed" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->runSpeed = n;
}
continue;
}
//acceleration
if ( !Q_stricmp( token, "acceleration" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( n < 0 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
continue;
}
if ( NPC->NPC )
{
stats->acceleration = n;
}
continue;
}
//sex
if ( !Q_stricmp( token, "sex" ) )
{
if ( COM_ParseString( &p, &value ) )
{
SkipRestOfLine( &p );
continue;
}
if ( value[0] == 'm' )
{//male
stats->sex = SEX_MALE;
}
else if ( value[0] == 'n' )
{//neutral
stats->sex = SEX_NEUTRAL;
}
else if ( value[0] == 'a' )
{//asexual?
stats->sex = SEX_NEUTRAL;
}
else if ( value[0] == 'f' )
{//female
stats->sex = SEX_FEMALE;
}
else if ( value[0] == 's' )
{//shemale?
stats->sex = SEX_SHEMALE;
}
else if ( value[0] == 'h' )
{//hermaphrodite?
stats->sex = SEX_SHEMALE;
}
else if ( value[0] == 't' )
{//transsexual/transvestite?
stats->sex = SEX_SHEMALE;
}
continue;
}
//===MISC===============================================================================
// default behavior
if ( !Q_stricmp( token, "behavior" ) )
{
if ( COM_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;
}
if ( NPC->NPC )
{
NPC->NPC->defaultBehavior = (bState_t)(n);
}
continue;
}
}
// snd
if ( !Q_stricmp( token, "snd" ) )
{
if ( COM_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 ( COM_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 ( COM_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;
}
// sndjedi
if ( !Q_stricmp( token, "sndjedi" ) )
{
if ( COM_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->customJediSoundDir = G_NewString( sound );
}
continue;
}
//New NPC/jedi stats:
//starting weapon
if ( !Q_stricmp( token, "weapon" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
//FIXME: need to precache the weapon, too? (in above func)
int weap = GetIDForString( WPTable, value );
if ( weap >= WP_NONE && weap < WP_NUM_WEAPONS )
{
NPC->client->ps.weapon = weap;
NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << weap );
if ( weap > WP_NONE )
{
RegisterItem( FindItemForWeapon( (weapon_t)(weap) ) ); //precache the weapon
NPC->client->ps.ammo[weaponData[weap].ammoIndex] = ammoData[weaponData[weap].ammoIndex].max;
}
}
continue;
}
if ( !parsingPlayer )
{
//altFire
if ( !Q_stricmp( token, "altFire" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
if ( NPC->NPC )
{
if ( n != 0 )
{
NPC->NPC->scriptFlags |= SCF_ALT_FIRE;
}
}
continue;
}
//Other unique behaviors/numbers that are currently hardcoded?
}
//force powers
int fp = GetIDForString( FPTable, token );
if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
//FIXME: need to precache the fx, too? (in above func)
//cap
if ( n > 5 )
{
n = 5;
}
else if ( n < 0 )
{
n = 0;
}
if ( n )
{//set
NPC->client->ps.forcePowersKnown |= ( 1 << fp );
}
else
{//clear
NPC->client->ps.forcePowersKnown &= ~( 1 << fp );
}
NPC->client->ps.forcePowerLevel[fp] = n;
continue;
}
//max force power
if ( !Q_stricmp( token, "forcePowerMax" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
NPC->client->ps.forcePowerMax = n;
continue;
}
//force regen rate - default is 100ms
if ( !Q_stricmp( token, "forceRegenRate" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
NPC->client->ps.forcePowerRegenRate = n;
continue;
}
//force regen amount - default is 1 (points per second)
if ( !Q_stricmp( token, "forceRegenAmount" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
NPC->client->ps.forcePowerRegenAmount = n;
continue;
}
//have a sabers.cfg and just name your saber in your NPCs.cfg/ICARUS script
//saber name
if ( !Q_stricmp( token, "saber" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
char *saberName = G_NewString( value );
WP_SaberParseParms( saberName, &NPC->client->ps.saber[0] );
//if it requires a specific style, make sure we know how to use it
if ( NPC->client->ps.saber[0].stylesLearned )
{
NPC->client->ps.saberStylesKnown |= NPC->client->ps.saber[0].stylesLearned;
}
if ( NPC->client->ps.saber[0].singleBladeStyle )
{
NPC->client->ps.saberStylesKnown |= NPC->client->ps.saber[0].singleBladeStyle;
}
continue;
}
//second saber name
if ( !Q_stricmp( token, "saber2" ) )
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
if ( !(NPC->client->ps.saber[0].saberFlags&SFL_TWO_HANDED) )
{//can't use a second saber if first one is a two-handed saber...?
char *saberName = G_NewString( value );
WP_SaberParseParms( saberName, &NPC->client->ps.saber[1] );
if ( NPC->client->ps.saber[1].stylesLearned )
{
NPC->client->ps.saberStylesKnown |= NPC->client->ps.saber[1].stylesLearned;
}
if ( NPC->client->ps.saber[1].singleBladeStyle )
{
NPC->client->ps.saberStylesKnown |= NPC->client->ps.saber[1].singleBladeStyle;
}
if ( (NPC->client->ps.saber[1].saberFlags&SFL_TWO_HANDED) )
{//tsk tsk, can't use a twoHanded saber as second saber
WP_RemoveSaber( NPC, 1 );
}
else
{
NPC->client->ps.dualSabers = qtrue;
}
}
continue;
}
// saberColor
if ( !Q_stricmpn( token, "saberColor", 10) )
{
if ( !NPC->client )
{
continue;
}
if (strlen(token)==10)
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
saber_colors_t color = TranslateSaberColor( value );
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[0].blade[n].color = color;
}
}
else if (strlen(token)==11)
{
int index = atoi(&token[10])-1;
if (index > 7 || index < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->ps.saber[0].blade[index].color = TranslateSaberColor( value );
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, NPCName );
}
continue;
}
if ( !Q_stricmpn( token, "saber2Color", 11 ) )
{
if ( !NPC->client )
{
continue;
}
if (strlen(token)==11)
{
if ( COM_ParseString( &p, &value ) )
{
continue;
}
saber_colors_t color = TranslateSaberColor( value );
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[1].blade[n].color = color;
}
}
else if (strlen(token)==12)
{
n = atoi(&token[11])-1;
if (n > 7 || n < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Color '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseString( &p, &value ) )
{
continue;
}
NPC->client->ps.saber[1].blade[n].color = TranslateSaberColor( value );
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Color '%s' in %s\n", token, NPCName );
}
continue;
}
//saber length
if ( !Q_stricmpn( token, "saberLength", 11) )
{
if (strlen(token)==11)
{
n = -1;
}
else if (strlen(token)==12)
{
n = atoi(&token[11])-1;
if (n > 7 || n < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, NPCName );
continue;
}
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseFloat( &p, &f ) )
{
SkipRestOfLine( &p );
continue;
}
//cap
if ( f < 4.0f )
{
f = 4.0f;
}
if (n == -1)//do them all
{
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[0].blade[n].lengthMax = f;
}
}
else //just one
{
NPC->client->ps.saber[0].blade[n].lengthMax = f;
}
continue;
}
if ( !Q_stricmpn( token, "saber2Length", 12) )
{
if (strlen(token)==12)
{
n = -1;
}
else if (strlen(token)==13)
{
n = atoi(&token[12])-1;
if (n > 7 || n < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Length '%s' in %s\n", token, NPCName );
continue;
}
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Length '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseFloat( &p, &f ) )
{
SkipRestOfLine( &p );
continue;
}
//cap
if ( f < 4.0f )
{
f = 4.0f;
}
if (n == -1)//do them all
{
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[1].blade[n].lengthMax = f;
}
}
else //just one
{
NPC->client->ps.saber[1].blade[n].lengthMax = f;
}
continue;
}
//saber radius
if ( !Q_stricmpn( token, "saberRadius", 11 ) )
{
if (strlen(token)==11)
{
n = -1;
}
else if (strlen(token)==12)
{
n = atoi(&token[11])-1;
if (n > 7 || n < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, NPCName );
continue;
}
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseFloat( &p, &f ) )
{
SkipRestOfLine( &p );
continue;
}
//cap
if ( f < 0.25f )
{
f = 0.25f;
}
if (n==-1)
{//NOTE: this fills in the rest of the blades with the same length by default
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[0].blade[n].radius = f;
}
}
else
{
NPC->client->ps.saber[0].blade[n].radius = f;
}
continue;
}
if ( !Q_stricmpn( token, "saber2Radius", 12 ) )
{
if (strlen(token)==12)
{
n = -1;
}
else if (strlen(token)==13)
{
n = atoi(&token[12])-1;
if (n > 7 || n < 1 )
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Radius '%s' in %s\n", token, NPCName );
continue;
}
}
else
{
gi.Printf( S_COLOR_YELLOW"WARNING: bad saber2Radius '%s' in %s\n", token, NPCName );
continue;
}
if ( COM_ParseFloat( &p, &f ) )
{
SkipRestOfLine( &p );
continue;
}
//cap
if ( f < 0.25f )
{
f = 0.25f;
}
if (n==-1)
{//NOTE: this fills in the rest of the blades with the same length by default
for ( n = 0; n < MAX_BLADES; n++ )
{
NPC->client->ps.saber[1].blade[n].radius = f;
}
}
else
{
NPC->client->ps.saber[1].blade[n].radius = f;
}
continue;
}
//ADD:
//saber sounds (on, off, loop)
//loop sound (like Vader's breathing or droid bleeps, etc.)
//starting saber style
if ( !Q_stricmp( token, "saberStyle" ) )
{
if ( COM_ParseInt( &p, &n ) )
{
SkipRestOfLine( &p );
continue;
}
//cap
if ( n > SS_STAFF )
{
n = SS_STAFF;
}
else if ( n < SS_FAST )
{
n = SS_FAST;
}
if ( n )
{//set
NPC->client->ps.saberStylesKnown |= ( 1 << n );
}
else
{//clear
NPC->client->ps.saberStylesKnown &= ~( 1 << n );
}
NPC->client->ps.saberAnimLevel = n;
if ( parsingPlayer )
{
cg.saberAnimLevelPending = n;
}
continue;
}
if ( !parsingPlayer )
{
gi.Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName );
}
SkipRestOfLine( &p );
}
#ifdef _WIN32
#pragma endregion
#endif
COM_EndParseSession( );
}
ci->infoValid = qfalse;
/*
Ghoul2 Insert Start
*/
if ( !md3Model )
{
NPC->weaponModel[0] = -1;
if ( Q_stricmp( "player", playerModel ) == 0 )
{//set the model from the console cvars
G_InitPlayerFromCvars( NPC );
//now set the weapon, etc.
G_MatchPlayerWeapon( NPC );
//NPC->NPC->aiFlags |= NPCAI_MATCHPLAYERWEAPON;//FIXME: may not always want this
}
else
{//do a normal model load
// If this is a vehicle, set the model name from the vehicle type array.
if ( NPC->client->NPC_class == CLASS_VEHICLE )
{
int iVehIndex = BG_VehicleGetIndex( NPC->NPC_type );
strcpy(customSkin, "default"); // Ignore any custom skin that may have come from the NPC File
Q_strncpyz( playerModel, g_vehicleInfo[iVehIndex].model, sizeof(playerModel));
if ( g_vehicleInfo[iVehIndex].skin && g_vehicleInfo[iVehIndex].skin[0] )
{
bool forceSkin = false;
// Iterate Over All Possible Skins
//---------------------------------
ratl::vector_vs skinarray;
ratl::string_vs<256> skins(g_vehicleInfo[iVehIndex].skin);
for (ratl::string_vs<256>::tokenizer i = skins.begin("|"); i!=skins.end(); i++)
{
if (NPC->soundSet && NPC->soundSet[0] && Q_stricmp(*i, NPC->soundSet)==0)
{
forceSkin = true;
}
skinarray.push_back(*i);
}
// Soundset Is The Designer Set Way To Supply A Skin
//---------------------------------------------------
if (forceSkin)
{
Q_strncpyz( customSkin, NPC->soundSet, sizeof(customSkin));
}
// Otherwise Choose A Random Skin
//--------------------------------
else
{
if (NPC->soundSet && NPC->soundSet[0])
{
gi.Printf(S_COLOR_RED"WARNING: Unable to use skin (%s)", NPC->soundSet);
}
Q_strncpyz( customSkin, *skinarray[Q_irand(0, skinarray.size()-1)], sizeof(customSkin));
}
if (NPC->soundSet && gi.bIsFromZone(NPC->soundSet, TAG_G_ALLOC)) {
gi.Free(NPC->soundSet);
}
NPC->soundSet = 0; // clear the pointer
}
}
G_SetG2PlayerModel( NPC, playerModel, customSkin, surfOff, surfOn );
}
}
/*
Ghoul2 Insert End
*/
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, fileCnt, i;
char *buffer, *holdChar, *marker;
char npcExtensionListBuf[2048]; // The list of file names read in
//gi.Printf( "Parsing ext_data/npcs/*.npc definitions\n" );
//set where to store the first one
totallen = 0;
marker = NPCParms;
marker[0] = '\0';
//now load in the .npc definitions
fileCnt = gi.FS_GetFileList("ext_data/npcs", ".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/npcs/%s", holdChar), (void **) &buffer );
if ( len == -1 )
{
gi.Printf( "NPC_LoadParms: error reading file %s\n", holdChar );
}
else
{
if ( totallen && *(marker-1) == '}' )
{//don't let previous file end on a } because that must be a stand-alone token
strcat( marker, " " );
totallen++;
marker++;
}
len = COM_Compress( buffer );
if ( totallen + len >= MAX_NPC_DATA_SIZE ) {
G_Error( "NPC_LoadParms: ran out of space before reading %s\n(you must make the .npc files smaller)", holdChar );
}
strcat( marker, buffer );
gi.FS_FreeFile( buffer );
totallen += len;
marker += len;
}
}
}