mirror of
https://github.com/DrBeef/JKXR.git
synced 2024-11-29 23:42:38 +00:00
4597b03873
Opens in Android Studio but haven't even tried to build it yet (it won't.. I know that much!)
4129 lines
97 KiB
C++
4129 lines
97 KiB
C++
/*
|
|
===========================================================================
|
|
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 <http://www.gnu.org/licenses/>.
|
|
===========================================================================
|
|
*/
|
|
|
|
//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"
|
|
#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 && fileIndex<MAX_ANIM_FILES);
|
|
|
|
const char *psAnimFileInternalName = (iRealGLAIndex == -1 ? NULL : gi.G2API_GetAnimFileInternalNameIndex( iRealGLAIndex ));
|
|
bool bIsFrameSkipped = (psAnimFileInternalName && strlen(psAnimFileInternalName)>5 && !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; fileIndex++)
|
|
{
|
|
if (Q_stricmp(level.knownAnimFileSets[fileIndex].filename, skeletonName)==0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Ok, We Don't Already Have One, So Time To Create A New One And Initialize It
|
|
//------------------------------------------------------------------------------
|
|
if (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; i<MAX_ANIMATIONS; i++)
|
|
{
|
|
animations[i].firstFrame = 0;
|
|
animations[i].numFrames = 0;
|
|
animations[i].loopFrames = -1;
|
|
animations[i].frameLerp = 100;
|
|
// animations[i].initialLerp = 100;
|
|
animations[i].glaIndex = 0;
|
|
}
|
|
|
|
// Initialize Animation Event Array
|
|
//----------------------------------
|
|
for(i=0; i<MAX_ANIM_EVENTS; i++)
|
|
{
|
|
torsoAnimEvents[i].eventType = AEV_NONE; //Type of event
|
|
legsAnimEvents[i].eventType = AEV_NONE;
|
|
torsoAnimEvents[i].keyFrame = (unsigned short)-1; //65535 should never be a valid frame... :)
|
|
legsAnimEvents[i].keyFrame = (unsigned short)-1; //Frame to play event on
|
|
torsoAnimEvents[i].stringData = NULL; //we allow storage of one string, temporarily (in case we have to look up an index later,
|
|
legsAnimEvents[i].stringData = NULL; //then make sure to set stringData to NULL so we only do the look-up once)
|
|
torsoAnimEvents[i].modelOnly = 0;
|
|
legsAnimEvents[i].modelOnly = 0;
|
|
torsoAnimEvents[i].glaIndex = 0;
|
|
legsAnimEvents[i].glaIndex = 0;
|
|
|
|
|
|
for (j=0; j<AED_ARRAY_SIZE; j++) //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc.
|
|
{
|
|
torsoAnimEvents[i].eventData[j] = -1;
|
|
legsAnimEvents[i].eventData[j] = -1;
|
|
}
|
|
}
|
|
|
|
// Get The Cinematic GLA Name
|
|
//----------------------------
|
|
if (Q_stricmp(skeletonName, "_humanoid")==0)
|
|
{
|
|
const char* mapName = strrchr( level.mapname, '/' );
|
|
if (mapName)
|
|
{
|
|
mapName++;
|
|
}
|
|
else
|
|
{
|
|
mapName = level.mapname;
|
|
}
|
|
char skeletonMapName[MAX_QPATH];
|
|
Com_sprintf(skeletonMapName, MAX_QPATH, "_humanoid_%s", mapName);
|
|
const int normalGLAIndex = gi.G2API_PrecacheGhoul2Model("models/players/_humanoid/_humanoid.gla");//double check this always comes first!
|
|
|
|
// Make Sure To Precache The GLAs (both regular and cinematic), And Remember Their Indicies
|
|
//------------------------------------------------------------------------------------------
|
|
G_ParseAnimationFile(0, skeletonName, fileIndex);
|
|
G_ParseAnimationEvtFile(0, skeletonName, fileIndex, normalGLAIndex, false/*flag for model specific*/);
|
|
|
|
const int cineGLAIndex = gi.G2API_PrecacheGhoul2Model( va("models/players/%s/%s.gla", skeletonMapName, skeletonMapName));
|
|
if (cineGLAIndex)
|
|
{
|
|
assert(cineGLAIndex == normalGLAIndex+1);
|
|
if (cineGLAIndex != normalGLAIndex+1)
|
|
{
|
|
Com_Error(ERR_DROP,"Cinematic GLA was not loaded after the normal GLA. Cannot continue safely.");
|
|
}
|
|
G_ParseAnimationFile(1, skeletonMapName, fileIndex);
|
|
G_ParseAnimationEvtFile(1, skeletonMapName, fileIndex, cineGLAIndex, false/*flag for model specific*/);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// non-humanoid...
|
|
//
|
|
// Make Sure To Precache The GLAs (both regular and cinematic), And Remember Their Indicies
|
|
//------------------------------------------------------------------------------------------
|
|
G_ParseAnimationFile(0, skeletonName, fileIndex);
|
|
G_ParseAnimationEvtFile(0, skeletonName, fileIndex);
|
|
}
|
|
}
|
|
|
|
// Tack Any Additional Per Model Events
|
|
//--------------------------------------
|
|
if (modelName)
|
|
{
|
|
// Quick Search To See If We've Already Loaded This Model
|
|
//--------------------------------------------------------
|
|
int curModel=0;
|
|
hstring curModelName(modelName);
|
|
while (curModel<MAX_MODELS_PER_LEVEL && !modelsAlreadyDone[curModel].empty())
|
|
{
|
|
if (modelsAlreadyDone[curModel]==curModelName)
|
|
{
|
|
return fileIndex;
|
|
}
|
|
curModel++;
|
|
}
|
|
|
|
if (curModel == MAX_MODELS_PER_LEVEL)
|
|
{
|
|
Com_Error(ERR_DROP, "About to overflow modelsAlreadyDone, increase MAX_MODELS_PER_LEVEL\n");
|
|
}
|
|
|
|
// Nope, Ok, Record The Model As Found And Parse It's Event File
|
|
//---------------------------------------------------------------
|
|
modelsAlreadyDone[curModel]=curModelName;
|
|
|
|
// Only Do The Event File If The Model Is Not The Same As The Skeleton
|
|
//---------------------------------------------------------------------
|
|
if (Q_stricmp(skeletonName, modelName)!=0)
|
|
{
|
|
const int iGLAIndexToCheckForSkip = (Q_stricmp(skeletonName, "_humanoid")?-1:gi.G2API_PrecacheGhoul2Model("models/players/_humanoid/_humanoid.gla")); // ;-)
|
|
|
|
G_ParseAnimationEvtFile(0, modelName, fileIndex, iGLAIndexToCheckForSkip, true);
|
|
}
|
|
}
|
|
|
|
return fileIndex;
|
|
}
|
|
|
|
extern cvar_t *g_char_model;
|
|
void G_LoadAnimFileSet( gentity_t *ent, const char *pModelName )
|
|
{
|
|
//load its animation config
|
|
char animName[MAX_QPATH];
|
|
char *GLAName, *modelName;
|
|
char *slash = NULL;
|
|
char *strippedName;
|
|
|
|
if ( ent->playerModel == -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 <vehiclename>'!!! 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<hstring, 15> 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;
|
|
}
|
|
}
|
|
}
|