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