1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 //NPC_stats.cpp
24 
25 #include "g_headers.h"
26 
27 #include "b_local.h"
28 #include "b_public.h"
29 #include "anims.h"
30 
31 extern qboolean NPCsPrecached;
32 extern vec3_t playerMins;
33 extern vec3_t playerMaxs;
34 
35 char	*TeamNames[TEAM_NUM_TEAMS] =
36 {
37 	"",
38 	"player",
39 	"enemy",
40 	"neutral"
41 };
42 
43 // this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h
44 char	*ClassNames[CLASS_NUM_CLASSES] =
45 {
46 	"",				// class none
47 	"atst",
48 	"bartender",
49 	"bespin_cop",
50 	"claw",
51 	"commando",
52 	"desann",
53 	"fish",
54 	"flier2",
55 	"galak",
56 	"glider",
57 	"gonk",
58 	"gran",
59 	"howler",
60 	"imperial",
61 	"impworker",
62 	"interrogator",
63 	"jan",
64 	"jedi",
65 	"kyle",
66 	"lando",
67 	"lizard",
68 	"luke",
69 	"mark1",
70 	"mark2",
71 	"galak_mech",
72 	"minemonster",
73 	"monmotha",
74 	"morgankatarn",
75 	"mouse",
76 	"murjj",
77 	"prisoner",
78 	"probe",
79 	"protocol",
80 	"r2d2",
81 	"r5d2",
82 	"rebel",
83 	"reborn",
84 	"reelo",
85 	"remote",
86 	"rodian",
87 	"seeker",
88 	"sentry",
89 	"shadowtrooper",
90 	"stormtrooper",
91 	"swamp",
92 	"swamptrooper",
93 	"tavion",
94 	"trandoshan",
95 	"ugnaught",
96 	"weequay",
97 };
98 
99 
100 /*
101 NPC_ReactionTime
102 */
103 //FIXME use grandom in here
NPC_ReactionTime(void)104 int NPC_ReactionTime ( void )
105 {
106 	return 200 * ( 6 - NPCInfo->stats.reactions );
107 }
108 
109 //
110 // parse support routines
111 //
112 
G_ParseLiteral(const char ** data,const char * string)113 qboolean G_ParseLiteral( const char **data, const char *string )
114 {
115 	const char	*token;
116 
117 	token = COM_ParseExt( data, qtrue );
118 	if ( token[0] == 0 )
119 	{
120 		gi.Printf( "unexpected EOF\n" );
121 		return qtrue;
122 	}
123 
124 	if ( Q_stricmp( token, string ) )
125 	{
126 		gi.Printf( "required string '%s' missing\n", string );
127 		return qtrue;
128 	}
129 
130 	return qfalse;
131 }
132 
133 //
134 // NPC parameters file : scripts/NPCs.cfg
135 //
136 #define MAX_NPC_DATA_SIZE 0x40000
137 char	NPCParms[MAX_NPC_DATA_SIZE];
138 
TranslateTeamName(const char * name)139 team_t TranslateTeamName( const char *name )
140 {
141 	for ( int n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ )
142 	{
143 		if ( Q_stricmp( TeamNames[n], name ) == 0 )
144 		{
145 			return ((team_t) n);
146 		}
147 	}
148 
149 	return TEAM_FREE;
150 }
151 
152 
TranslateClassName(const char * name)153 class_t TranslateClassName( const char *name )
154 {
155 	for ( int n = (CLASS_NONE + 1); n < CLASS_NUM_CLASSES; n++ )
156 	{
157 		if ( Q_stricmp( ClassNames[n], name ) == 0 )
158 		{
159 			return ((class_t) n);
160 		}
161 	}
162 
163 	return CLASS_NONE;  // I hope this never happens, maybe print a warning
164 }
165 
166 /*
167 static rank_t TranslateRankName( const char *name )
168 
169   Should be used to determine pip bolt-ons
170 */
TranslateRankName(const char * name)171 static rank_t TranslateRankName( const char *name )
172 {
173 	if ( !Q_stricmp( name, "civilian" ) )
174 	{
175 		return RANK_CIVILIAN;
176 	}
177 
178 	if ( !Q_stricmp( name, "crewman" ) )
179 	{
180 		return RANK_CREWMAN;
181 	}
182 
183 	if ( !Q_stricmp( name, "ensign" ) )
184 	{
185 		return RANK_ENSIGN;
186 	}
187 
188 	if ( !Q_stricmp( name, "ltjg" ) )
189 	{
190 		return RANK_LT_JG;
191 	}
192 
193 	if ( !Q_stricmp( name, "lt" ) )
194 	{
195 		return RANK_LT;
196 	}
197 
198 	if ( !Q_stricmp( name, "ltcomm" ) )
199 	{
200 		return RANK_LT_COMM;
201 	}
202 
203 	if ( !Q_stricmp( name, "commander" ) )
204 	{
205 		return RANK_COMMANDER;
206 	}
207 
208 	if ( !Q_stricmp( name, "captain" ) )
209 	{
210 		return RANK_CAPTAIN;
211 	}
212 
213 	return RANK_CIVILIAN;
214 }
TranslateSaberColor(const char * name)215 static saber_colors_t TranslateSaberColor( const char *name )
216 {
217 	if ( !Q_stricmp( name, "red" ) )
218 	{
219 		return SABER_RED;
220 	}
221 	if ( !Q_stricmp( name, "orange" ) )
222 	{
223 		return SABER_ORANGE;
224 	}
225 	if ( !Q_stricmp( name, "yellow" ) )
226 	{
227 		return SABER_YELLOW;
228 	}
229 	if ( !Q_stricmp( name, "green" ) )
230 	{
231 		return SABER_GREEN;
232 	}
233 	if ( !Q_stricmp( name, "blue" ) )
234 	{
235 		return SABER_BLUE;
236 	}
237 	if ( !Q_stricmp( name, "purple" ) )
238 	{
239 		return SABER_PURPLE;
240 	}
241 	if ( !Q_stricmp( name, "random" ) )
242 	{
243 		return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE )));
244 	}
245 	return SABER_BLUE;
246 }
247 
248 /* static int MethodNameToNumber( const char *name ) {
249 	if ( !Q_stricmp( name, "EXPONENTIAL" ) ) {
250 		return METHOD_EXPONENTIAL;
251 	}
252 	if ( !Q_stricmp( name, "LINEAR" ) ) {
253 		return METHOD_LINEAR;
254 	}
255 	if ( !Q_stricmp( name, "LOGRITHMIC" ) ) {
256 		return METHOD_LOGRITHMIC;
257 	}
258 	if ( !Q_stricmp( name, "ALWAYS" ) ) {
259 		return METHOD_ALWAYS;
260 	}
261 	if ( !Q_stricmp( name, "NEVER" ) ) {
262 		return METHOD_NEVER;
263 	}
264 	return -1;
265 }
266 
267 static int ItemNameToNumber( const char *name, int itemType ) {
268 //	int		n;
269 
270 	for ( n = 0; n < bg_numItems; n++ ) {
271 		if ( bg_itemlist[n].type != itemType ) {
272 			continue;
273 		}
274 		if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) {
275 			return bg_itemlist[n].tag;
276 		}
277 	}
278 	return -1;
279 }
280 */
281 
MoveTypeNameToEnum(const char * name)282 static int MoveTypeNameToEnum( const char *name )
283 {
284 	if(!Q_stricmp("runjump", name))
285 	{
286 		return MT_RUNJUMP;
287 	}
288 	else if(!Q_stricmp("walk", name))
289 	{
290 		return MT_WALK;
291 	}
292 	else if(!Q_stricmp("flyswim", name))
293 	{
294 		return MT_FLYSWIM;
295 	}
296 	else if(!Q_stricmp("static", name))
297 	{
298 		return MT_STATIC;
299 	}
300 
301 	return MT_STATIC;
302 }
303 
304 extern void CG_RegisterClientRenderInfo(clientInfo_t *ci, renderInfo_t *ri);
305 extern void CG_RegisterClientModels (int entityNum);
306 extern void CG_RegisterNPCCustomSounds( clientInfo_t *ci );
307 extern void CG_RegisterNPCEffects( team_t team );
308 extern void CG_ParseAnimationSndFile( const char *filename, int animFileIndex );
309 
310 //#define CONVENIENT_ANIMATION_FILE_DEBUG_THING
311 
312 #ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING
SpewDebugStuffToFile(animation_t * bgGlobalAnimations)313 void SpewDebugStuffToFile(animation_t *bgGlobalAnimations)
314 {
315 	char BGPAFtext[40000];
316 	fileHandle_t f;
317 	int i = 0;
318 
319 	gi.FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE);
320 
321 	if (!f)
322 	{
323 		return;
324 	}
325 
326 	BGPAFtext[0] = 0;
327 
328 	while (i < MAX_ANIMATIONS)
329 	{
330 		strcat(BGPAFtext, va("%i %i\n", i, bgGlobalAnimations[i].frameLerp));
331 		i++;
332 	}
333 
334 	gi.FS_Write(BGPAFtext, strlen(BGPAFtext), f);
335 	gi.FS_FCloseFile(f);
336 }
337 #endif
338 
339 /*
340 ======================
341 CG_ParseAnimationFile
342 
343 Read a configuration file containing animation coutns and rates
344 models/players/visor/animation.cfg, etc
345 
346 ======================
347 */
G_ParseAnimationFile(const char * af_filename)348 qboolean G_ParseAnimationFile( const char *af_filename )
349 {
350 	const char		*text_p;
351 	int			len;
352 	int			i;
353 	const char		*token;
354 	float		fps;
355 	//int			skip;
356 	char		text[40000];
357 	int			animNum;
358 	animation_t	*animations = level.knownAnimFileSets[level.numKnownAnimFileSets].animations;
359 
360 	len = gi.RE_GetAnimationCFG(af_filename, NULL, 0);
361 	if (len <= 0)
362 	{
363 		return qfalse;
364 	}
365 	if ( len <= 0 )
366 	{
367 		return qfalse;
368 	}
369 	if ( len >= (int)sizeof(text) - 1 )
370 	{
371 		G_Error( "G_ParseAnimationFile: File %s too long\n (%d > %d)", af_filename, len, sizeof( text ) - 1);
372 		return qfalse;
373 	}
374 	len = gi.RE_GetAnimationCFG(af_filename, text, sizeof(text));
375 
376 	// parse the text
377 	text_p = text;
378 	//skip = 0;	// quiet the compiler warning
379 
380 	//FIXME: have some way of playing anims backwards... negative numFrames?
381 
382 	//initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100
383 	for(i = 0; i < MAX_ANIMATIONS; i++)
384 	{
385 		animations[i].firstFrame = 0;
386 		animations[i].numFrames = 0;
387 		animations[i].loopFrames = -1;
388 		animations[i].frameLerp = 100;
389 		animations[i].initialLerp = 100;
390 	}
391 
392 	// read information for each frame
393 	COM_BeginParseSession();
394 	while(1)
395 	{
396 		token = COM_Parse( &text_p );
397 
398 		if ( !token || !token[0])
399 		{
400 			break;
401 		}
402 
403 		animNum = GetIDForString(animTable, token);
404 		if(animNum == -1)
405 		{
406 //#ifndef FINAL_BUILD
407 #ifdef _DEBUG
408 			Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, af_filename);
409 #endif
410 			continue;
411 		}
412 
413 		token = COM_Parse( &text_p );
414 		if ( !token )
415 		{
416 			break;
417 		}
418 		animations[animNum].firstFrame = atoi( token );
419 
420 		token = COM_Parse( &text_p );
421 		if ( !token )
422 		{
423 			break;
424 		}
425 		animations[animNum].numFrames = atoi( token );
426 
427 		token = COM_Parse( &text_p );
428 		if ( !token )
429 		{
430 			break;
431 		}
432 		animations[animNum].loopFrames = atoi( token );
433 
434 		token = COM_Parse( &text_p );
435 		if ( !token )
436 		{
437 			break;
438 		}
439 		fps = atof( token );
440 		if ( fps == 0 )
441 		{
442 			fps = 1;//Don't allow divide by zero error
443 		}
444 		if ( fps < 0 )
445 		{//backwards
446 			animations[animNum].frameLerp = floor(1000.0f / fps);
447 		}
448 		else
449 		{
450 			animations[animNum].frameLerp = ceil(1000.0f / fps);
451 		}
452 
453 		animations[animNum].initialLerp = ceil(1000.0f / fabs(fps));
454 	}
455 	COM_EndParseSession();
456 
457 #ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING
458 	if (strstr(af_filename, "humanoid"))
459 	{
460 		SpewDebugStuffToFile(animations);
461 	}
462 #endif
463 
464 	return qtrue;
465 }
466 
G_ParseAnimFileSet(const char * filename,const char * animCFG,int * animFileIndex)467 qboolean G_ParseAnimFileSet( const char *filename, const char *animCFG, int *animFileIndex )
468 {
469 	char		afilename[MAX_QPATH];
470 	char		strippedName[MAX_QPATH];
471 	int			i;
472 	char		*slash;
473 
474 	Q_strncpyz(strippedName, filename, sizeof(strippedName));
475 	slash = strchr( strippedName, '/' );
476 	if ( slash )
477 	{
478 		// truncate modelName to find just the dir not the extension
479 		*slash = 0;
480 	}
481 
482 	//if this anims file was loaded before, don't parse it again, just point to the correct table of info
483 	for ( i = 0; i < level.numKnownAnimFileSets; i++ )
484 	{
485 		if ( Q_stricmp(level.knownAnimFileSets[i].filename, strippedName ) == 0 )
486 		{
487 			*animFileIndex = i;
488 			CG_ParseAnimationSndFile( strippedName, *animFileIndex );
489 			return qtrue;
490 		}
491 	}
492 
493 	if ( level.numKnownAnimFileSets == MAX_ANIM_FILES )
494 	{//TOO MANY!
495 		G_Error( "G_ParseAnimFileSet: MAX_ANIM_FILES" );
496 	}
497 
498 	//Okay, time to parse in a new one
499 	Q_strncpyz( level.knownAnimFileSets[level.numKnownAnimFileSets].filename, strippedName, sizeof( level.knownAnimFileSets[level.numKnownAnimFileSets].filename ) );
500 
501 	// Load and parse animations.cfg file
502 	Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/animation.cfg", animCFG );
503 	if ( !G_ParseAnimationFile( afilename ) )
504 	{
505 		*animFileIndex = -1;
506 		return qfalse;
507 	}
508 
509 	//set index and increment
510 	*animFileIndex = level.numKnownAnimFileSets++;
511 
512 	CG_ParseAnimationSndFile( strippedName, *animFileIndex );
513 
514 	return qtrue;
515 }
516 
G_LoadAnimFileSet(gentity_t * ent,const char * modelName)517 void G_LoadAnimFileSet( gentity_t *ent, const char *modelName )
518 {
519 //load its animation config
520 	char	animName[MAX_QPATH];
521 	char	*GLAName;
522 	char	*slash = NULL;
523 	char	*strippedName;
524 
525 	if ( ent->playerModel == -1 )
526 	{
527 		return;
528 	}
529 	//get the location of the animation.cfg
530 	GLAName = gi.G2API_GetGLAName( &ent->ghoul2[ent->playerModel] );
531 	//now load and parse the animation.cfg, animsounds.cfg and set the animFileIndex
532 	if ( !GLAName)
533 	{
534 		Com_Printf( S_COLOR_RED"Failed find animation file name models/players/%s/animation.cfg\n", modelName );
535 		strippedName="broken";
536 	}
537 	else
538 	{
539 		Q_strncpyz(animName, GLAName, sizeof( animName ));
540 		slash = strrchr( animName, '/' );
541 		if ( slash )
542 		{
543 			*slash = 0;
544 		}
545 		strippedName = COM_SkipPath( animName );
546 	}
547 
548 	//now load and parse the animation.cfg, animsounds.cfg and set the animFileIndex
549 	if ( !G_ParseAnimFileSet( modelName, strippedName, &ent->client->clientInfo.animFileIndex ) )
550 	{
551 		Com_Printf( S_COLOR_RED"Failed to load animation file set models/players/%s/animation.cfg\n", modelName );
552 	}
553 }
554 
555 
NPC_PrecacheAnimationCFG(const char * NPC_type)556 void NPC_PrecacheAnimationCFG( const char *NPC_type )
557 {
558 	char	filename[MAX_QPATH];
559 	const char	*token;
560 	const char	*value;
561 	const char	*p;
562 	int		junk;
563 
564 	if ( !Q_stricmp( "random", NPC_type ) )
565 	{//sorry, can't precache a random just yet
566 		return;
567 	}
568 
569 	p = NPCParms;
570 	COM_BeginParseSession();
571 
572 	// look for the right NPC
573 	while ( p )
574 	{
575 		token = COM_ParseExt( &p, qtrue );
576 		if ( token[0] == 0 )
577 		{
578 			COM_EndParseSession(  );
579 			return;
580 		}
581 
582 		if ( !Q_stricmp( token, NPC_type ) )
583 		{
584 			break;
585 		}
586 
587 		SkipBracedSection( &p );
588 	}
589 
590 	if ( !p )
591 	{
592 		COM_EndParseSession(  );
593 		return;
594 	}
595 
596 	if ( G_ParseLiteral( &p, "{" ) )
597 	{
598 		COM_EndParseSession(  );
599 		return;
600 	}
601 
602 	// parse the NPC info block
603 	while ( 1 )
604 	{
605 		token = COM_ParseExt( &p, qtrue );
606 		if ( !token[0] )
607 		{
608 			gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type );
609 			COM_EndParseSession(  );
610 			return;
611 		}
612 
613 		if ( !Q_stricmp( token, "}" ) )
614 		{
615 			break;
616 		}
617 
618 		// legsmodel
619 		if ( !Q_stricmp( token, "legsmodel" ) )
620 		{
621 			if ( COM_ParseString( &p, &value ) )
622 			{
623 				continue;
624 			}
625 			//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
626 			Q_strncpyz(filename, value, sizeof( filename ));
627 			G_ParseAnimFileSet( filename, filename, &junk );
628 			COM_EndParseSession(  );
629 			return;
630 		}
631 
632 		// playerModel
633 		if ( !Q_stricmp( token, "playerModel" ) )
634 		{
635 			if ( COM_ParseString( &p, &value ) )
636 			{
637 				continue;
638 			}
639 			char	animName[MAX_QPATH];
640 			char	*GLAName;
641 			char	*slash = NULL;
642 			char	*strippedName;
643 
644 			int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) );
645 			if ( handle > 0 )//FIXME: isn't 0 a valid handle?
646 			{
647 				GLAName = gi.G2API_GetAnimFileNameIndex( handle );
648 				if ( GLAName )
649 				{
650 					Q_strncpyz(animName, GLAName, sizeof( animName ));
651 					slash = strrchr( animName, '/' );
652 					if ( slash )
653 					{
654 						*slash = 0;
655 					}
656 					strippedName = COM_SkipPath( animName );
657 
658 					//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
659 					Q_strncpyz(filename, value, sizeof( filename ));
660 					G_ParseAnimFileSet( value, strippedName, &junk );//qfalse );
661 					COM_EndParseSession(  );
662 					//FIXME: still not precaching the animsounds.cfg?
663 					return;
664 				}
665 			}
666 		}
667 	}
668 	COM_EndParseSession(  );
669 }
670 
671 extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type );
NPC_PrecacheWeapons(team_t playerTeam,int spawnflags,char * NPCtype)672 void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype )
673 {
674 	int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype );
675 	gitem_t	*item;
676 	for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ )
677 	{
678 		if ( (weapons & ( 1 << curWeap )) )
679 		{
680 			item = FindItemForWeapon( ((weapon_t)(curWeap)) );	//precache the weapon
681 			CG_RegisterItemSounds( (item-bg_itemlist) );
682 			CG_RegisterItemVisuals( (item-bg_itemlist) );
683 			//precache the in-hand/in-world ghoul2 weapon model
684 
685 			char weaponModel[MAX_QPATH];
686 			Q_strncpyz(weaponModel, weaponData[curWeap].weaponMdl, sizeof(weaponModel));
687 			if (char *spot = strstr(weaponModel, ".md3") ) {
688 				*spot = 0;
689 				spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
690 				if (!spot) {
691 					strcat (weaponModel, "_w");
692 				}
693 				strcat (weaponModel, ".glm");	//and change to ghoul2
694 			}
695 			gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model
696 		}
697 	}
698 }
699 
700 /*
701 void NPC_Precache ( char *NPCName )
702 
703 Precaches NPC skins, tgas and md3s.
704 
705 */
NPC_Precache(gentity_t * spawner)706 void NPC_Precache ( gentity_t *spawner )
707 {
708 	clientInfo_t	ci={};
709 	renderInfo_t	ri={};
710 	team_t			playerTeam = TEAM_FREE;
711 	const char	*token;
712 	const char	*value;
713 	const char	*p;
714 	char	*patch;
715 	char	sound[MAX_QPATH];
716 	qboolean	md3Model = qfalse;
717 	char	playerModel[MAX_QPATH];
718 	char	customSkin[MAX_QPATH];
719 
720 	if ( !Q_stricmp( "random", spawner->NPC_type ) )
721 	{//sorry, can't precache a random just yet
722 		return;
723 	}
724 	Q_strncpyz(customSkin, "default", sizeof(customSkin));
725 
726 	p = NPCParms;
727 	COM_BeginParseSession();
728 
729 	// look for the right NPC
730 	while ( p )
731 	{
732 		token = COM_ParseExt( &p, qtrue );
733 		if ( token[0] == 0 )
734 		{
735 			COM_EndParseSession(  );
736 			return;
737 		}
738 
739 		if ( !Q_stricmp( token, spawner->NPC_type ) )
740 		{
741 			break;
742 		}
743 
744 		SkipBracedSection( &p );
745 	}
746 
747 	if ( !p )
748 	{
749 		COM_EndParseSession(  );
750 		return;
751 	}
752 
753 	if ( G_ParseLiteral( &p, "{" ) )
754 	{
755 		COM_EndParseSession(  );
756 		return;
757 	}
758 
759 	// parse the NPC info block
760 	while ( 1 )
761 	{
762 		COM_EndParseSession();	// if still in session (or using continue;)
763 		COM_BeginParseSession();
764 		token = COM_ParseExt( &p, qtrue );
765 		if ( !token[0] )
766 		{
767 			gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type );
768 			COM_EndParseSession(  );
769 			return;
770 		}
771 
772 		if ( !Q_stricmp( token, "}" ) )
773 		{
774 			break;
775 		}
776 
777 		// headmodel
778 		if ( !Q_stricmp( token, "headmodel" ) )
779 		{
780 			if ( COM_ParseString( &p, &value ) )
781 			{
782 				continue;
783 			}
784 
785 			if(!Q_stricmp("none", value))
786 			{
787 			}
788 			else
789 			{
790 				Q_strncpyz(ri.headModelName, value, sizeof(ri.headModelName));
791 			}
792 			md3Model = qtrue;
793 			continue;
794 		}
795 
796 		// torsomodel
797 		if ( !Q_stricmp( token, "torsomodel" ) )
798 		{
799 			if ( COM_ParseString( &p, &value ) )
800 			{
801 				continue;
802 			}
803 
804 			if(!Q_stricmp("none", value))
805 			{
806 			}
807 			else
808 			{
809 				Q_strncpyz(ri.torsoModelName, value, sizeof(ri.torsoModelName));
810 			}
811 			md3Model = qtrue;
812 			continue;
813 		}
814 
815 		// legsmodel
816 		if ( !Q_stricmp( token, "legsmodel" ) )
817 		{
818 			if ( COM_ParseString( &p, &value ) )
819 			{
820 				continue;
821 			}
822 			Q_strncpyz(ri.legsModelName, value, sizeof(ri.legsModelName));
823 			md3Model = qtrue;
824 			continue;
825 		}
826 
827 		// playerModel
828 		if ( !Q_stricmp( token, "playerModel" ) )
829 		{
830 			if ( COM_ParseString( &p, &value ) )
831 			{
832 				continue;
833 			}
834 			Q_strncpyz(playerModel, value, sizeof(playerModel));
835 			md3Model = qfalse;
836 			continue;
837 		}
838 
839 		// customSkin
840 		if ( !Q_stricmp( token, "customSkin" ) )
841 		{
842 			if ( COM_ParseString( &p, &value ) )
843 			{
844 				continue;
845 			}
846 			Q_strncpyz(customSkin, value, sizeof(customSkin));
847 			continue;
848 		}
849 
850 		// playerTeam
851 		if ( !Q_stricmp( token, "playerTeam" ) )
852 		{
853 			if ( COM_ParseString( &p, &value ) )
854 			{
855 				continue;
856 			}
857 			playerTeam = TranslateTeamName(value);
858 			continue;
859 		}
860 
861 
862 		// snd
863 		if ( !Q_stricmp( token, "snd" ) ) {
864 			if ( COM_ParseString( &p, &value ) ) {
865 				continue;
866 			}
867 			if ( !(spawner->svFlags&SVF_NO_BASIC_SOUNDS) )
868 			{
869 				//FIXME: store this in some sound field or parse in the soundTable like the animTable...
870 				Q_strncpyz( sound, value, sizeof( sound ) );
871 				patch = strstr( sound, "/" );
872 				if ( patch )
873 				{
874 					*patch = 0;
875 				}
876 				ci.customBasicSoundDir = G_NewString( sound );
877 			}
878 			continue;
879 		}
880 
881 		// sndcombat
882 		if ( !Q_stricmp( token, "sndcombat" ) ) {
883 			if ( COM_ParseString( &p, &value ) ) {
884 				continue;
885 			}
886 			if ( !(spawner->svFlags&SVF_NO_COMBAT_SOUNDS) )
887 			{
888 				//FIXME: store this in some sound field or parse in the soundTable like the animTable...
889 				Q_strncpyz( sound, value, sizeof( sound ) );
890 				patch = strstr( sound, "/" );
891 				if ( patch )
892 				{
893 					*patch = 0;
894 				}
895 				ci.customCombatSoundDir = G_NewString( sound );
896 			}
897 			continue;
898 		}
899 
900 		// sndextra
901 		if ( !Q_stricmp( token, "sndextra" ) ) {
902 			if ( COM_ParseString( &p, &value ) ) {
903 				continue;
904 			}
905 			if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) )
906 			{
907 				//FIXME: store this in some sound field or parse in the soundTable like the animTable...
908 				Q_strncpyz( sound, value, sizeof( sound ) );
909 				patch = strstr( sound, "/" );
910 				if ( patch )
911 				{
912 					*patch = 0;
913 				}
914 				ci.customExtraSoundDir = G_NewString( sound );
915 			}
916 			continue;
917 		}
918 
919 		// sndjedi
920 		if ( !Q_stricmp( token, "sndjedi" ) ) {
921 			if ( COM_ParseString( &p, &value ) ) {
922 				continue;
923 			}
924 			if ( !(spawner->svFlags&SVF_NO_EXTRA_SOUNDS) )
925 			{
926 				//FIXME: store this in some sound field or parse in the soundTable like the animTable...
927 				Q_strncpyz( sound, value, sizeof( sound ) );
928 				patch = strstr( sound, "/" );
929 				if ( patch )
930 				{
931 					*patch = 0;
932 				}
933 				ci.customJediSoundDir = G_NewString( sound );
934 			}
935 			continue;
936 		}
937 	}
938 
939 	COM_EndParseSession(  );
940 
941 	if ( md3Model )
942 	{
943 		CG_RegisterClientRenderInfo( &ci, &ri );
944 	}
945 	else
946 	{
947 		char	skinName[MAX_QPATH];
948 		//precache ghoul2 model
949 		gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", playerModel ) );
950 		//precache skin
951 		Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", playerModel, customSkin );
952 		// lets see if it's out there
953 		gi.RE_RegisterSkin( skinName );
954 	}
955 
956 	//precache this NPC's possible weapons
957 	NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type );
958 
959 	CG_RegisterNPCCustomSounds( &ci );
960 	CG_RegisterNPCEffects( playerTeam );
961 
962 	//FIXME: Look for a "sounds" directory and precache death, pain, alert sounds
963 }
964 
NPC_BuildRandom(gentity_t * NPC)965 void NPC_BuildRandom( gentity_t *NPC )
966 {
967 	int	sex, color, head;
968 
969 	sex = Q_irand(0, 2);
970 	color = Q_irand(0, 2);
971 	switch( sex )
972 	{
973 	case 0://female
974 		head = Q_irand(0, 2);
975 		switch( head )
976 		{
977 		default:
978 		case 0:
979 			Q_strncpyz(NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName));
980 			break;
981 		case 1:
982 			Q_strncpyz(NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName));
983 			break;
984 		case 2:
985 			Q_strncpyz(NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName));
986 			color = Q_irand(3, 5);//torso needs to be afam
987 			break;
988 		}
989 		switch( color )
990 		{
991 		default:
992 		case 0:
993 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName));
994 			break;
995 		case 1:
996 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName));
997 			break;
998 		case 2:
999 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName));
1000 			break;
1001 		case 3:
1002 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName));
1003 			break;
1004 		case 4:
1005 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName));
1006 			break;
1007 		case 5:
1008 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName));
1009 			break;
1010 		}
1011 		Q_strncpyz(NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName));
1012 		break;
1013 	default:
1014 	case 1://male
1015 	case 2://male
1016 		head = Q_irand(0, 4);
1017 		switch( head )
1018 		{
1019 		default:
1020 		case 0:
1021 			Q_strncpyz(NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName));
1022 			break;
1023 		case 1:
1024 			Q_strncpyz(NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName));
1025 			break;
1026 		case 2:
1027 			Q_strncpyz(NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName));
1028 			break;
1029 		case 3:
1030 			Q_strncpyz(NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName));
1031 			break;
1032 		case 4:
1033 			Q_strncpyz(NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName));
1034 			break;
1035 		}
1036 		switch( color )
1037 		{
1038 		default:
1039 		case 0:
1040 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName));
1041 			break;
1042 		case 1:
1043 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName));
1044 			break;
1045 		case 2:
1046 			Q_strncpyz(NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName));
1047 			break;
1048 			//NOTE: 3 - 5 should be red, gold & blue, afram hands
1049 		}
1050 		Q_strncpyz(NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName));
1051 		break;
1052 	}
1053 
1054 	NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = Q_irand(87, 102)/100.0f;
1055 	NPC->NPC->rank = RANK_CREWMAN;
1056 	NPC->client->playerTeam = TEAM_PLAYER;
1057 	NPC->client->clientInfo.customBasicSoundDir = "kyle";
1058 }
1059 
1060 extern void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn );
NPC_ParseParms(const char * NPCName,gentity_t * NPC)1061 qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC )
1062 {
1063 	const char	*token;
1064 	const char	*value;
1065 	const char	*p;
1066 	int		n;
1067 	float	f;
1068 	char	*patch;
1069 	char	sound[MAX_QPATH];
1070 	char	playerModel[MAX_QPATH];
1071 	char	customSkin[MAX_QPATH];
1072 	clientInfo_t	*ci = &NPC->client->clientInfo;
1073 	renderInfo_t	*ri = &NPC->client->renderInfo;
1074 	gNPCstats_t		*stats = NULL;
1075 	qboolean	md3Model = qtrue;
1076 	char	surfOff[1024];
1077 	char	surfOn[1024];
1078 
1079 	Q_strncpyz(customSkin, "default", sizeof(customSkin));
1080 	if ( !NPCName || !NPCName[0])
1081 	{
1082 		NPCName = "Player";
1083 	}
1084 
1085 	if ( NPC->NPC )
1086 	{
1087 		stats = &NPC->NPC->stats;
1088 /*
1089 	NPC->NPC->allWeaponOrder[0]	= WP_BRYAR_PISTOL;
1090 	NPC->NPC->allWeaponOrder[1]	= WP_SABER;
1091 	NPC->NPC->allWeaponOrder[2]	= WP_IMOD;
1092 	NPC->NPC->allWeaponOrder[3]	= WP_SCAVENGER_RIFLE;
1093 	NPC->NPC->allWeaponOrder[4]	= WP_TRICORDER;
1094 	NPC->NPC->allWeaponOrder[6]	= WP_NONE;
1095 	NPC->NPC->allWeaponOrder[6]	= WP_NONE;
1096 	NPC->NPC->allWeaponOrder[7]	= WP_NONE;
1097 */
1098 		// fill in defaults
1099 		stats->aggression	= 3;
1100 		stats->aim			= 3;
1101 		stats->earshot		= 1024;
1102 		stats->evasion		= 3;
1103 		stats->hfov			= 90;
1104 		stats->intelligence	= 3;
1105 		stats->move			= 3;
1106 		stats->reactions	= 3;
1107 		stats->vfov			= 60;
1108 		stats->vigilance	= 0.1f;
1109 		stats->visrange		= 1024;
1110 
1111 		stats->health		= 0;
1112 
1113 		stats->moveType		= MT_RUNJUMP;
1114 		stats->yawSpeed		= 90;
1115 		stats->walkSpeed	= 90;
1116 		stats->runSpeed		= 300;
1117 		stats->acceleration	= 15;//Increase/descrease speed this much per frame (20fps)
1118 	}
1119 	else
1120 	{
1121 		stats = NULL;
1122 	}
1123 
1124 	Q_strncpyz( ci->name, NPCName, sizeof( ci->name ) );
1125 
1126 	NPC->playerModel = -1;
1127 
1128 	//Set defaults
1129 	//FIXME: should probably put default torso and head models, but what about enemies
1130 	//that don't have any- like Stasis?
1131 	//Q_strncpyz( ri->headModelName,	DEFAULT_HEADMODEL,  sizeof(ri->headModelName));
1132 	//Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName));
1133 	//Q_strncpyz( ri->legsModelName,	DEFAULT_LEGSMODEL,  sizeof(ri->legsModelName));
1134 	memset( ri->headModelName, 0, sizeof( ri->headModelName ) );
1135 	memset( ri->torsoModelName, 0, sizeof( ri->torsoModelName ) );
1136 	memset( ri->legsModelName, 0, sizeof( ri->legsModelName ) );
1137 	//FIXME: should we have one for weapon too?
1138 	memset( (char *)surfOff, 0, sizeof(surfOff) );
1139 	memset( (char *)surfOn, 0, sizeof(surfOn) );
1140 
1141 	/*
1142 	ri->headYawRangeLeft = 50;
1143 	ri->headYawRangeRight = 50;
1144 	ri->headPitchRangeUp = 40;
1145 	ri->headPitchRangeDown = 50;
1146 	ri->torsoYawRangeLeft = 60;
1147 	ri->torsoYawRangeRight = 60;
1148 	ri->torsoPitchRangeUp = 30;
1149 	ri->torsoPitchRangeDown = 70;
1150 	*/
1151 
1152 	ri->headYawRangeLeft = 80;
1153 	ri->headYawRangeRight = 80;
1154 	ri->headPitchRangeUp = 45;
1155 	ri->headPitchRangeDown = 45;
1156 	ri->torsoYawRangeLeft = 60;
1157 	ri->torsoYawRangeRight = 60;
1158 	ri->torsoPitchRangeUp = 30;
1159 	ri->torsoPitchRangeDown = 50;
1160 
1161 	VectorCopy(playerMins, NPC->mins);
1162 	VectorCopy(playerMaxs, NPC->maxs);
1163 	NPC->client->crouchheight = CROUCH_MAXS_2;
1164 	NPC->client->standheight = DEFAULT_MAXS_2;
1165 
1166 	NPC->client->dismemberProbHead = 100;
1167 	NPC->client->dismemberProbArms = 100;
1168 	NPC->client->dismemberProbHands = 100;
1169 	NPC->client->dismemberProbWaist = 100;
1170 	NPC->client->dismemberProbLegs = 100;
1171 
1172 
1173 	if ( !Q_stricmp( "random", NPCName ) )
1174 	{//Randomly assemble a starfleet guy
1175 		NPC_BuildRandom( NPC );
1176 	}
1177 	else
1178 	{
1179 		p = NPCParms;
1180 		COM_BeginParseSession();
1181 
1182 		// look for the right NPC
1183 		while ( p )
1184 		{
1185 			token = COM_ParseExt( &p, qtrue );
1186 			if ( token[0] == 0 )
1187 			{
1188 				COM_EndParseSession(  );
1189 				return qfalse;
1190 			}
1191 
1192 			if ( !Q_stricmp( token, NPCName ) )
1193 			{
1194 				break;
1195 			}
1196 
1197 			SkipBracedSection( &p );
1198 		}
1199 		if ( !p )
1200 		{
1201 			COM_EndParseSession(  );
1202 			return qfalse;
1203 		}
1204 
1205 		if ( G_ParseLiteral( &p, "{" ) )
1206 		{
1207 			COM_EndParseSession(  );
1208 			return qfalse;
1209 		}
1210 
1211 		// parse the NPC info block
1212 		while ( 1 )
1213 		{
1214 			token = COM_ParseExt( &p, qtrue );
1215 			if ( !token[0] )
1216 			{
1217 				gi.Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName );
1218 				COM_EndParseSession(  );
1219 				return qfalse;
1220 			}
1221 
1222 			if ( !Q_stricmp( token, "}" ) )
1223 			{
1224 				break;
1225 			}
1226 	//===MODEL PROPERTIES===========================================================
1227 			// headmodel
1228 			if ( !Q_stricmp( token, "headmodel" ) )
1229 			{
1230 				if ( COM_ParseString( &p, &value ) )
1231 				{
1232 					continue;
1233 				}
1234 
1235 				if(!Q_stricmp("none", value))
1236 				{
1237 					ri->headModelName[0] = '\0';
1238 					//Zero the head clamp range so the torso & legs don't lag behind
1239 					ri->headYawRangeLeft =
1240 					ri->headYawRangeRight =
1241 					ri->headPitchRangeUp =
1242 					ri->headPitchRangeDown = 0;
1243 				}
1244 				else
1245 				{
1246 					Q_strncpyz( ri->headModelName, value, sizeof(ri->headModelName));
1247 				}
1248 				continue;
1249 			}
1250 
1251 			// torsomodel
1252 			if ( !Q_stricmp( token, "torsomodel" ) )
1253 			{
1254 				if ( COM_ParseString( &p, &value ) )
1255 				{
1256 					continue;
1257 				}
1258 
1259 				if(!Q_stricmp("none", value))
1260 				{
1261 					ri->torsoModelName[0] = '\0';
1262 					//Zero the torso clamp range so the legs don't lag behind
1263 					ri->torsoYawRangeLeft =
1264 					ri->torsoYawRangeRight =
1265 					ri->torsoPitchRangeUp =
1266 					ri->torsoPitchRangeDown = 0;
1267 				}
1268 				else
1269 				{
1270 					Q_strncpyz( ri->torsoModelName, value, sizeof(ri->torsoModelName));
1271 				}
1272 				continue;
1273 			}
1274 
1275 			// legsmodel
1276 			if ( !Q_stricmp( token, "legsmodel" ) )
1277 			{
1278 				if ( COM_ParseString( &p, &value ) )
1279 				{
1280 					continue;
1281 				}
1282 				Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName));
1283 				//Need to do this here to get the right index
1284 				G_ParseAnimFileSet( ri->legsModelName, ri->legsModelName, &ci->animFileIndex );
1285 				continue;
1286 			}
1287 
1288 			// playerModel
1289 			if ( !Q_stricmp( token, "playerModel" ) )
1290 			{
1291 				if ( COM_ParseString( &p, &value ) )
1292 				{
1293 					continue;
1294 				}
1295 				Q_strncpyz( playerModel, value, sizeof(playerModel));
1296 				md3Model = qfalse;
1297 				continue;
1298 			}
1299 
1300 			// customSkin
1301 			if ( !Q_stricmp( token, "customSkin" ) )
1302 			{
1303 				if ( COM_ParseString( &p, &value ) )
1304 				{
1305 					continue;
1306 				}
1307 				Q_strncpyz( customSkin, value, sizeof(customSkin));
1308 				continue;
1309 			}
1310 
1311 			// surfOff
1312 			if ( !Q_stricmp( token, "surfOff" ) )
1313 			{
1314 				if ( COM_ParseString( &p, &value ) )
1315 				{
1316 					continue;
1317 				}
1318 				if ( surfOff[0] )
1319 				{
1320 					Q_strcat( surfOff, sizeof( surfOff ), "," );
1321 					Q_strcat( surfOff, sizeof( surfOff ), value );
1322 				}
1323 				else
1324 				{
1325 					Q_strncpyz( surfOff, value, sizeof(surfOff));
1326 				}
1327 				continue;
1328 			}
1329 
1330 			// surfOn
1331 			if ( !Q_stricmp( token, "surfOn" ) )
1332 			{
1333 				if ( COM_ParseString( &p, &value ) )
1334 				{
1335 					continue;
1336 				}
1337 				if ( surfOn[0] )
1338 				{
1339 					Q_strcat( surfOn, sizeof( surfOn ), "," );
1340 					Q_strcat( surfOn, sizeof( surfOn ), value );
1341 				}
1342 				else
1343 				{
1344 					Q_strncpyz( surfOn, value, sizeof(surfOn));
1345 				}
1346 				continue;
1347 			}
1348 
1349 			//headYawRangeLeft
1350 			if ( !Q_stricmp( token, "headYawRangeLeft" ) )
1351 			{
1352 				if ( COM_ParseInt( &p, &n ) )
1353 				{
1354 					SkipRestOfLine( &p );
1355 					continue;
1356 				}
1357 				if ( n < 0 )
1358 				{
1359 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1360 					continue;
1361 				}
1362 				ri->headYawRangeLeft = n;
1363 				continue;
1364 			}
1365 
1366 			//headYawRangeRight
1367 			if ( !Q_stricmp( token, "headYawRangeRight" ) )
1368 			{
1369 				if ( COM_ParseInt( &p, &n ) )
1370 				{
1371 					SkipRestOfLine( &p );
1372 					continue;
1373 				}
1374 				if ( n < 0 )
1375 				{
1376 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1377 					continue;
1378 				}
1379 				ri->headYawRangeRight = n;
1380 				continue;
1381 			}
1382 
1383 			//headPitchRangeUp
1384 			if ( !Q_stricmp( token, "headPitchRangeUp" ) )
1385 			{
1386 				if ( COM_ParseInt( &p, &n ) )
1387 				{
1388 					SkipRestOfLine( &p );
1389 					continue;
1390 				}
1391 				if ( n < 0 )
1392 				{
1393 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1394 					continue;
1395 				}
1396 				ri->headPitchRangeUp = n;
1397 				continue;
1398 			}
1399 
1400 			//headPitchRangeDown
1401 			if ( !Q_stricmp( token, "headPitchRangeDown" ) )
1402 			{
1403 				if ( COM_ParseInt( &p, &n ) )
1404 				{
1405 					SkipRestOfLine( &p );
1406 					continue;
1407 				}
1408 				if ( n < 0 )
1409 				{
1410 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1411 					continue;
1412 				}
1413 				ri->headPitchRangeDown = n;
1414 				continue;
1415 			}
1416 
1417 			//torsoYawRangeLeft
1418 			if ( !Q_stricmp( token, "torsoYawRangeLeft" ) )
1419 			{
1420 				if ( COM_ParseInt( &p, &n ) )
1421 				{
1422 					SkipRestOfLine( &p );
1423 					continue;
1424 				}
1425 				if ( n < 0 )
1426 				{
1427 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1428 					continue;
1429 				}
1430 				ri->torsoYawRangeLeft = n;
1431 				continue;
1432 			}
1433 
1434 			//torsoYawRangeRight
1435 			if ( !Q_stricmp( token, "torsoYawRangeRight" ) )
1436 			{
1437 				if ( COM_ParseInt( &p, &n ) )
1438 				{
1439 					SkipRestOfLine( &p );
1440 					continue;
1441 				}
1442 				if ( n < 0 )
1443 				{
1444 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1445 					continue;
1446 				}
1447 				ri->torsoYawRangeRight = n;
1448 				continue;
1449 			}
1450 
1451 			//torsoPitchRangeUp
1452 			if ( !Q_stricmp( token, "torsoPitchRangeUp" ) )
1453 			{
1454 				if ( COM_ParseInt( &p, &n ) )
1455 				{
1456 					SkipRestOfLine( &p );
1457 					continue;
1458 				}
1459 				if ( n < 0 )
1460 				{
1461 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1462 					continue;
1463 				}
1464 				ri->torsoPitchRangeUp = n;
1465 				continue;
1466 			}
1467 
1468 			//torsoPitchRangeDown
1469 			if ( !Q_stricmp( token, "torsoPitchRangeDown" ) )
1470 			{
1471 				if ( COM_ParseInt( &p, &n ) )
1472 				{
1473 					SkipRestOfLine( &p );
1474 					continue;
1475 				}
1476 				if ( n < 0 )
1477 				{
1478 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1479 					continue;
1480 				}
1481 				ri->torsoPitchRangeDown = n;
1482 				continue;
1483 			}
1484 
1485 			// Uniform XYZ scale
1486 			if ( !Q_stricmp( token, "scale" ) )
1487 			{
1488 				if ( COM_ParseInt( &p, &n ) )
1489 				{
1490 					SkipRestOfLine( &p );
1491 					continue;
1492 				}
1493 				if ( n < 0 )
1494 				{
1495 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1496 					continue;
1497 				}
1498 				if (n != 100)
1499 				{
1500 					NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = n/100.0f;
1501 				}
1502 				continue;
1503 			}
1504 
1505 			//X scale
1506 			if ( !Q_stricmp( token, "scaleX" ) )
1507 			{
1508 				if ( COM_ParseInt( &p, &n ) )
1509 				{
1510 					SkipRestOfLine( &p );
1511 					continue;
1512 				}
1513 				if ( n < 0 )
1514 				{
1515 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1516 					continue;
1517 				}
1518 				if (n != 100)
1519 				{
1520 					NPC->s.modelScale[0] = n/100.0f;
1521 				}
1522 				continue;
1523 			}
1524 
1525 			//Y scale
1526 			if ( !Q_stricmp( token, "scaleY" ) )
1527 			{
1528 				if ( COM_ParseInt( &p, &n ) )
1529 				{
1530 					SkipRestOfLine( &p );
1531 					continue;
1532 				}
1533 				if ( n < 0 )
1534 				{
1535 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1536 					continue;
1537 				}
1538 				if (n != 100)
1539 				{
1540 					NPC->s.modelScale[1] = n/100.0f;
1541 				}
1542 				continue;
1543 			}
1544 
1545 			//Z scale
1546 			if ( !Q_stricmp( token, "scaleZ" ) )
1547 			{
1548 				if ( COM_ParseInt( &p, &n ) )
1549 				{
1550 					SkipRestOfLine( &p );
1551 					continue;
1552 				}
1553 				if ( n < 0 )
1554 				{
1555 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1556 					continue;
1557 				}
1558 				if (n != 100)
1559 				{
1560 					NPC->s.modelScale[2] = n/100.0f;
1561 				}
1562 				continue;
1563 			}
1564 
1565 	//===AI STATS=====================================================================
1566 			// aggression
1567 			if ( !Q_stricmp( token, "aggression" ) ) {
1568 				if ( COM_ParseInt( &p, &n ) ) {
1569 					SkipRestOfLine( &p );
1570 					continue;
1571 				}
1572 				if ( n < 1 || n > 5 ) {
1573 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1574 					continue;
1575 				}
1576 				if ( NPC->NPC )
1577 				{
1578 					stats->aggression = n;
1579 				}
1580 				continue;
1581 			}
1582 
1583 			// aim
1584 			if ( !Q_stricmp( token, "aim" ) ) {
1585 				if ( COM_ParseInt( &p, &n ) ) {
1586 					SkipRestOfLine( &p );
1587 					continue;
1588 				}
1589 				if ( n < 1 || n > 5 ) {
1590 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1591 					continue;
1592 				}
1593 				if ( NPC->NPC )
1594 				{
1595 					stats->aim = n;
1596 				}
1597 				continue;
1598 			}
1599 
1600 			// earshot
1601 			if ( !Q_stricmp( token, "earshot" ) ) {
1602 				if ( COM_ParseFloat( &p, &f ) ) {
1603 					SkipRestOfLine( &p );
1604 					continue;
1605 				}
1606 				if ( f < 0.0f )
1607 				{
1608 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1609 					continue;
1610 				}
1611 				if ( NPC->NPC )
1612 				{
1613 					stats->earshot = f;
1614 				}
1615 				continue;
1616 			}
1617 
1618 			// evasion
1619 			if ( !Q_stricmp( token, "evasion" ) )
1620 			{
1621 				if ( COM_ParseInt( &p, &n ) )
1622 				{
1623 					SkipRestOfLine( &p );
1624 					continue;
1625 				}
1626 				if ( n < 1 || n > 5 )
1627 				{
1628 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1629 					continue;
1630 				}
1631 				if ( NPC->NPC )
1632 				{
1633 					stats->evasion = n;
1634 				}
1635 				continue;
1636 			}
1637 
1638 			// hfov
1639 			if ( !Q_stricmp( token, "hfov" ) ) {
1640 				if ( COM_ParseInt( &p, &n ) ) {
1641 					SkipRestOfLine( &p );
1642 					continue;
1643 				}
1644 				if ( n < 30 || n > 180 ) {
1645 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1646 					continue;
1647 				}
1648 				if ( NPC->NPC )
1649 				{
1650 					stats->hfov = n;// / 2;	//FIXME: Why was this being done?!
1651 				}
1652 				continue;
1653 			}
1654 
1655 			// intelligence
1656 			if ( !Q_stricmp( token, "intelligence" ) ) {
1657 				if ( COM_ParseInt( &p, &n ) ) {
1658 					SkipRestOfLine( &p );
1659 					continue;
1660 				}
1661 				if ( n < 1 || n > 5 ) {
1662 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1663 					continue;
1664 				}
1665 				if ( NPC->NPC )
1666 				{
1667 					stats->intelligence = n;
1668 				}
1669 				continue;
1670 			}
1671 
1672 			// move
1673 			if ( !Q_stricmp( token, "move" ) ) {
1674 				if ( COM_ParseInt( &p, &n ) ) {
1675 					SkipRestOfLine( &p );
1676 					continue;
1677 				}
1678 				if ( n < 1 || n > 5 ) {
1679 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1680 					continue;
1681 				}
1682 				if ( NPC->NPC )
1683 				{
1684 					stats->move = n;
1685 				}
1686 				continue;
1687 			}
1688 
1689 			// reactions
1690 			if ( !Q_stricmp( token, "reactions" ) ) {
1691 				if ( COM_ParseInt( &p, &n ) ) {
1692 					SkipRestOfLine( &p );
1693 					continue;
1694 				}
1695 				if ( n < 1 || n > 5 ) {
1696 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1697 					continue;
1698 				}
1699 				if ( NPC->NPC )
1700 				{
1701 					stats->reactions = n;
1702 				}
1703 				continue;
1704 			}
1705 
1706 			// shootDistance
1707 			if ( !Q_stricmp( token, "saberColor" ) ) {
1708 				if ( COM_ParseString( &p, &value ) )
1709 				{
1710 					continue;
1711 				}
1712 				if ( NPC->client )
1713 				{
1714 					NPC->client->ps.saberColor = TranslateSaberColor( value );
1715 				}
1716 				continue;
1717 			}
1718 
1719 			// shootDistance
1720 			if ( !Q_stricmp( token, "shootDistance" ) ) {
1721 				if ( COM_ParseFloat( &p, &f ) ) {
1722 					SkipRestOfLine( &p );
1723 					continue;
1724 				}
1725 				if ( f < 0.0f )
1726 				{
1727 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1728 					continue;
1729 				}
1730 				if ( NPC->NPC )
1731 				{
1732 					stats->shootDistance = f;
1733 				}
1734 				continue;
1735 			}
1736 
1737 			// shootDistance
1738 			if ( !Q_stricmp( token, "health" ) )
1739 			{
1740 				if ( COM_ParseInt( &p, &n ) )
1741 				{
1742 					SkipRestOfLine( &p );
1743 					continue;
1744 				}
1745 				if ( n < 0 )
1746 				{
1747 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
1748 					continue;
1749 				}
1750 				if ( NPC->NPC )
1751 				{
1752 					stats->health = n;
1753 				}
1754 				continue;
1755 			}
1756 
1757 			// vfov
1758 			if ( !Q_stricmp( token, "vfov" ) ) {
1759 				if ( COM_ParseInt( &p, &n ) ) {
1760 					SkipRestOfLine( &p );
1761 					continue;
1762 				}
1763 				if ( n < 30 || n > 180 ) {
1764 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
1765 					continue;
1766 				}
1767 				if ( NPC->NPC )
1768 				{
1769 					stats->vfov = n / 2;
1770 				}
1771 				continue;
1772 			}
1773 
1774 			// vigilance
1775 			if ( !Q_stricmp( token, "vigilance" ) ) {
1776 				if ( COM_ParseFloat( &p, &f ) ) {
1777 					SkipRestOfLine( &p );
1778 					continue;
1779 				}
1780 				if ( f < 0.0f )
1781 				{
1782 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1783 					continue;
1784 				}
1785 				if ( NPC->NPC )
1786 				{
1787 					stats->vigilance = f;
1788 				}
1789 				continue;
1790 			}
1791 
1792 			// visrange
1793 			if ( !Q_stricmp( token, "visrange" ) ) {
1794 				if ( COM_ParseFloat( &p, &f ) ) {
1795 					SkipRestOfLine( &p );
1796 					continue;
1797 				}
1798 				if ( f < 0.0f )
1799 				{
1800 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1801 					continue;
1802 				}
1803 				if ( NPC->NPC )
1804 				{
1805 					stats->visrange = f;
1806 				}
1807 				continue;
1808 			}
1809 
1810 			// race
1811 	//		if ( !Q_stricmp( token, "race" ) )
1812 	//		{
1813 	//			if ( COM_ParseString( &p, &value ) )
1814 	//			{
1815 	//				continue;
1816 	//			}
1817 	//			NPC->client->race = TranslateRaceName(value);
1818 	//			continue;
1819 	//		}
1820 
1821 			// rank
1822 			if ( !Q_stricmp( token, "rank" ) )
1823 			{
1824 				if ( COM_ParseString( &p, &value ) )
1825 				{
1826 					continue;
1827 				}
1828 				if ( NPC->NPC )
1829 				{
1830 					NPC->NPC->rank = TranslateRankName(value);
1831 				}
1832 				continue;
1833 			}
1834 
1835 			// fullName
1836 			if ( !Q_stricmp( token, "fullName" ) )
1837 			{
1838 				if ( COM_ParseString( &p, &value ) )
1839 				{
1840 					continue;
1841 				}
1842 				NPC->fullName = G_NewString(value);
1843 				continue;
1844 			}
1845 
1846 			// playerTeam
1847 			if ( !Q_stricmp( token, "playerTeam" ) )
1848 			{
1849 				if ( COM_ParseString( &p, &value ) )
1850 				{
1851 					continue;
1852 				}
1853 				NPC->client->playerTeam = TranslateTeamName(value);
1854 				continue;
1855 			}
1856 
1857 			// enemyTeam
1858 			if ( !Q_stricmp( token, "enemyTeam" ) )
1859 			{
1860 				if ( COM_ParseString( &p, &value ) )
1861 				{
1862 					continue;
1863 				}
1864 				NPC->client->enemyTeam = TranslateTeamName(value);
1865 				continue;
1866 			}
1867 
1868 			// class
1869 			if ( !Q_stricmp( token, "class" ) )
1870 			{
1871 				if ( COM_ParseString( &p, &value ) )
1872 				{
1873 					continue;
1874 				}
1875 				NPC->client->NPC_class = TranslateClassName(value);
1876 				continue;
1877 			}
1878 
1879 			// dismemberment probability for head
1880 			if ( !Q_stricmp( token, "dismemberProbHead" ) ) {
1881 				if ( COM_ParseInt( &p, &n ) ) {
1882 					SkipRestOfLine( &p );
1883 					continue;
1884 				}
1885 				if ( n < 0 )
1886 				{
1887 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1888 					continue;
1889 				}
1890 				if ( NPC->NPC )
1891 				{
1892 					NPC->client->dismemberProbHead = n;
1893 				}
1894 				continue;
1895 			}
1896 
1897 			// dismemberment probability for arms
1898 			if ( !Q_stricmp( token, "dismemberProbArms" ) ) {
1899 				if ( COM_ParseInt( &p, &n ) ) {
1900 					SkipRestOfLine( &p );
1901 					continue;
1902 				}
1903 				if ( n < 0 )
1904 				{
1905 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1906 					continue;
1907 				}
1908 				if ( NPC->NPC )
1909 				{
1910 					NPC->client->dismemberProbArms = n;
1911 				}
1912 				continue;
1913 			}
1914 
1915 			// dismemberment probability for hands
1916 			if ( !Q_stricmp( token, "dismemberProbHands" ) ) {
1917 				if ( COM_ParseInt( &p, &n ) ) {
1918 					SkipRestOfLine( &p );
1919 					continue;
1920 				}
1921 				if ( n < 0 )
1922 				{
1923 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1924 					continue;
1925 				}
1926 				if ( NPC->NPC )
1927 				{
1928 					NPC->client->dismemberProbHands = n;
1929 				}
1930 				continue;
1931 			}
1932 
1933 			// dismemberment probability for waist
1934 			if ( !Q_stricmp( token, "dismemberProbWaist" ) ) {
1935 				if ( COM_ParseInt( &p, &n ) ) {
1936 					SkipRestOfLine( &p );
1937 					continue;
1938 				}
1939 				if ( n < 0 )
1940 				{
1941 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1942 					continue;
1943 				}
1944 				if ( NPC->NPC )
1945 				{
1946 					NPC->client->dismemberProbWaist = n;
1947 				}
1948 				continue;
1949 			}
1950 
1951 			// dismemberment probability for legs
1952 			if ( !Q_stricmp( token, "dismemberProbLegs" ) ) {
1953 				if ( COM_ParseInt( &p, &n ) ) {
1954 					SkipRestOfLine( &p );
1955 					continue;
1956 				}
1957 				if ( n < 0 )
1958 				{
1959 					gi.Printf( "bad %s in NPC '%s'\n", token, NPCName );
1960 					continue;
1961 				}
1962 				if ( NPC->NPC )
1963 				{
1964 					NPC->client->dismemberProbLegs = n;
1965 				}
1966 				continue;
1967 			}
1968 
1969 	//===MOVEMENT STATS============================================================
1970 
1971 			if ( !Q_stricmp( token, "width" ) )
1972 			{
1973 				if ( COM_ParseInt( &p, &n ) )
1974 				{
1975 					continue;
1976 				}
1977 
1978 				NPC->mins[0] = NPC->mins[1] = -n;
1979 				NPC->maxs[0] = NPC->maxs[1] = n;
1980 				continue;
1981 			}
1982 
1983 			if ( !Q_stricmp( token, "height" ) )
1984 			{
1985 				if ( COM_ParseInt( &p, &n ) )
1986 				{
1987 					continue;
1988 				}
1989 
1990 				NPC->mins[2] = DEFAULT_MINS_2;//Cannot change
1991 				NPC->maxs[2] = NPC->client->standheight = n + DEFAULT_MINS_2;
1992 				NPC->radius = n;
1993 				continue;
1994 			}
1995 
1996 			if ( !Q_stricmp( token, "crouchheight" ) )
1997 			{
1998 				if ( COM_ParseInt( &p, &n ) )
1999 				{
2000 					continue;
2001 				}
2002 
2003 				NPC->client->crouchheight = n + DEFAULT_MINS_2;
2004 				continue;
2005 			}
2006 
2007 			if ( !Q_stricmp( token, "movetype" ) )
2008 			{
2009 				if ( COM_ParseString( &p, &value ) )
2010 				{
2011 					continue;
2012 				}
2013 
2014 				if ( NPC->NPC )
2015 				{
2016 					stats->moveType = (movetype_t)MoveTypeNameToEnum(value);
2017 				}
2018 				continue;
2019 			}
2020 
2021 			// yawSpeed
2022 			if ( !Q_stricmp( token, "yawSpeed" ) ) {
2023 				if ( COM_ParseInt( &p, &n ) ) {
2024 					SkipRestOfLine( &p );
2025 					continue;
2026 				}
2027 				if ( n <= 0) {
2028 					gi.Printf(  "bad %s in NPC '%s'\n", token, NPCName );
2029 					continue;
2030 				}
2031 				if ( NPC->NPC )
2032 				{
2033 					stats->yawSpeed = ((float)(n));
2034 				}
2035 				continue;
2036 			}
2037 
2038 			// walkSpeed
2039 			if ( !Q_stricmp( token, "walkSpeed" ) )
2040 			{
2041 				if ( COM_ParseInt( &p, &n ) )
2042 				{
2043 					SkipRestOfLine( &p );
2044 					continue;
2045 				}
2046 				if ( n < 0 )
2047 				{
2048 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
2049 					continue;
2050 				}
2051 				if ( NPC->NPC )
2052 				{
2053 					stats->walkSpeed = n;
2054 				}
2055 				continue;
2056 			}
2057 
2058 			//runSpeed
2059 			if ( !Q_stricmp( token, "runSpeed" ) )
2060 			{
2061 				if ( COM_ParseInt( &p, &n ) )
2062 				{
2063 					SkipRestOfLine( &p );
2064 					continue;
2065 				}
2066 				if ( n < 0 )
2067 				{
2068 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
2069 					continue;
2070 				}
2071 				if ( NPC->NPC )
2072 				{
2073 					stats->runSpeed = n;
2074 				}
2075 				continue;
2076 			}
2077 
2078 			//acceleration
2079 			if ( !Q_stricmp( token, "acceleration" ) )
2080 			{
2081 				if ( COM_ParseInt( &p, &n ) )
2082 				{
2083 					SkipRestOfLine( &p );
2084 					continue;
2085 				}
2086 				if ( n < 0 )
2087 				{
2088 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
2089 					continue;
2090 				}
2091 				if ( NPC->NPC )
2092 				{
2093 					stats->acceleration = n;
2094 				}
2095 				continue;
2096 			}
2097 	//===MISC===============================================================================
2098 			// default behavior
2099 			if ( !Q_stricmp( token, "behavior" ) )
2100 			{
2101 				if ( COM_ParseInt( &p, &n ) )
2102 				{
2103 					SkipRestOfLine( &p );
2104 					continue;
2105 				}
2106 				if ( n < BS_DEFAULT || n >= NUM_BSTATES )
2107 				{
2108 					gi.Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
2109 					continue;
2110 				}
2111 				if ( NPC->NPC )
2112 				{
2113 					NPC->NPC->defaultBehavior = (bState_t)(n);
2114 				}
2115 				continue;
2116 			}
2117 
2118 			// snd
2119 			if ( !Q_stricmp( token, "snd" ) ) {
2120 				if ( COM_ParseString( &p, &value ) ) {
2121 					continue;
2122 				}
2123 				if ( !(NPC->svFlags&SVF_NO_BASIC_SOUNDS) )
2124 				{
2125 					//FIXME: store this in some sound field or parse in the soundTable like the animTable...
2126 					Q_strncpyz( sound, value, sizeof( sound ) );
2127 					patch = strstr( sound, "/" );
2128 					if ( patch )
2129 					{
2130 						*patch = 0;
2131 					}
2132 					ci->customBasicSoundDir = G_NewString( sound );
2133 				}
2134 				continue;
2135 			}
2136 
2137 			// sndcombat
2138 			if ( !Q_stricmp( token, "sndcombat" ) ) {
2139 				if ( COM_ParseString( &p, &value ) ) {
2140 					continue;
2141 				}
2142 				if ( !(NPC->svFlags&SVF_NO_COMBAT_SOUNDS) )
2143 				{
2144 					//FIXME: store this in some sound field or parse in the soundTable like the animTable...
2145 					Q_strncpyz( sound, value, sizeof( sound ) );
2146 					patch = strstr( sound, "/" );
2147 					if ( patch )
2148 					{
2149 						*patch = 0;
2150 					}
2151 					ci->customCombatSoundDir = G_NewString( sound );
2152 				}
2153 				continue;
2154 			}
2155 
2156 			// sndextra
2157 			if ( !Q_stricmp( token, "sndextra" ) ) {
2158 				if ( COM_ParseString( &p, &value ) ) {
2159 					continue;
2160 				}
2161 				if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) )
2162 				{
2163 					//FIXME: store this in some sound field or parse in the soundTable like the animTable...
2164 					Q_strncpyz( sound, value, sizeof( sound ) );
2165 					patch = strstr( sound, "/" );
2166 					if ( patch )
2167 					{
2168 						*patch = 0;
2169 					}
2170 					ci->customExtraSoundDir = G_NewString( sound );
2171 				}
2172 				continue;
2173 			}
2174 
2175 			// sndjedi
2176 			if ( !Q_stricmp( token, "sndjedi" ) ) {
2177 				if ( COM_ParseString( &p, &value ) ) {
2178 					continue;
2179 				}
2180 				if ( !(NPC->svFlags&SVF_NO_EXTRA_SOUNDS) )
2181 				{
2182 					//FIXME: store this in some sound field or parse in the soundTable like the animTable...
2183 					Q_strncpyz( sound, value, sizeof( sound ) );
2184 					patch = strstr( sound, "/" );
2185 					if ( patch )
2186 					{
2187 						*patch = 0;
2188 					}
2189 					ci->customJediSoundDir = G_NewString( sound );
2190 				}
2191 				continue;
2192 			}
2193 
2194 			gi.Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName );
2195 			SkipRestOfLine( &p );
2196 		}
2197 		COM_EndParseSession(  );
2198 	}
2199 
2200 	ci->infoValid = qfalse;
2201 
2202 /*
2203 Ghoul2 Insert Start
2204 */
2205 	if ( !md3Model )
2206 	{
2207 		NPC->weaponModel = -1;
2208 		G_SetG2PlayerModel( NPC, playerModel, customSkin, surfOff, surfOn );
2209 	}
2210 /*
2211 Ghoul2 Insert End
2212 */
2213 	if(	NPCsPrecached )
2214 	{//Spawning in after initial precache, our models are precached, we just need to set our clientInfo
2215 		CG_RegisterClientModels( NPC->s.number );
2216 		CG_RegisterNPCCustomSounds( ci );
2217 		CG_RegisterNPCEffects( NPC->client->playerTeam );
2218 	}
2219 
2220 	return qtrue;
2221 }
2222 
NPC_LoadParms(void)2223 void NPC_LoadParms( void )
2224 {
2225 	int			len, totallen, npcExtFNLen, mainBlockLen, fileCnt, i;
2226 	const char	filename[] = "ext_data/NPCs.cfg";
2227 	char		*buffer, *holdChar, *marker;
2228 	char		npcExtensionListBuf[2048];			//	The list of file names read in
2229 
2230 	//First, load in the npcs.cfg
2231 	len = gi.FS_ReadFile( filename, (void **) &buffer );
2232 	if ( len == -1 ) {
2233 		gi.Printf( "file not found\n" );
2234 		return;
2235 	}
2236 
2237 	if ( len >= MAX_NPC_DATA_SIZE ) {
2238 		G_Error( "ext_data/NPCs.cfg is too large" );
2239 	}
2240 
2241 	strncpy( NPCParms, buffer, sizeof( NPCParms ) - 1 );
2242 	gi.FS_FreeFile( buffer );
2243 
2244 	//remember where to store the next one
2245 	totallen = mainBlockLen = len;
2246 	marker = NPCParms+totallen;
2247 
2248 	//now load in the extra .npc extensions
2249 	fileCnt = gi.FS_GetFileList("ext_data", ".npc", npcExtensionListBuf, sizeof(npcExtensionListBuf) );
2250 
2251 	holdChar = npcExtensionListBuf;
2252 	for ( i = 0; i < fileCnt; i++, holdChar += npcExtFNLen + 1 )
2253 	{
2254 		npcExtFNLen = strlen( holdChar );
2255 
2256 		len = gi.FS_ReadFile( va( "ext_data/%s", holdChar), (void **) &buffer );
2257 
2258 		if ( len == -1 )
2259 		{
2260 			gi.Printf( "error reading file\n" );
2261 		}
2262 		else
2263 		{
2264 			if ( totallen + len >= MAX_NPC_DATA_SIZE ) {
2265 				G_Error( "NPC extensions (*.npc) are too large" );
2266 			}
2267 			strcat( marker, buffer );
2268 			gi.FS_FreeFile( buffer );
2269 
2270 			totallen += len;
2271 			marker = NPCParms+totallen;
2272 		}
2273 	}
2274 }
2275