codemp/game/NPC_stats.c

Go to the documentation of this file.
00001 //NPC_stats.cpp
00002 #include "b_local.h"
00003 #include "b_public.h"
00004 #include "anims.h"
00005 #include "../ghoul2/G2.h"
00006 
00007 extern qboolean NPCsPrecached;
00008 
00009 #include "../namespace_begin.h"
00010 extern qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber );
00011 extern void WP_RemoveSaber( saberInfo_t *sabers, int saberNum );
00012 #include "../namespace_end.h"
00013 
00014 stringID_table_t TeamTable[] =
00015 {
00016         ENUM2STRING(NPCTEAM_FREE),                      // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc
00017         ENUM2STRING(NPCTEAM_PLAYER),
00018         ENUM2STRING(NPCTEAM_ENEMY),
00019         ENUM2STRING(NPCTEAM_NEUTRAL),   // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator
00020         "",     -1
00021 };
00022 
00023 // this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h
00024 stringID_table_t ClassTable[] =
00025 {
00026         ENUM2STRING(CLASS_NONE),                                // hopefully this will never be used by an npc), just covering all bases
00027         ENUM2STRING(CLASS_ATST),                                // technically droid...
00028         ENUM2STRING(CLASS_BARTENDER),
00029         ENUM2STRING(CLASS_BESPIN_COP),          
00030         ENUM2STRING(CLASS_CLAW),
00031         ENUM2STRING(CLASS_COMMANDO),
00032         ENUM2STRING(CLASS_DESANN),                      
00033         ENUM2STRING(CLASS_FISH),
00034         ENUM2STRING(CLASS_FLIER2),
00035         ENUM2STRING(CLASS_GALAK),
00036         ENUM2STRING(CLASS_GLIDER),
00037         ENUM2STRING(CLASS_GONK),                                // droid
00038         ENUM2STRING(CLASS_GRAN),
00039         ENUM2STRING(CLASS_HOWLER),
00040 //      ENUM2STRING(CLASS_RANCOR),
00041         ENUM2STRING(CLASS_IMPERIAL),
00042         ENUM2STRING(CLASS_IMPWORKER),
00043         ENUM2STRING(CLASS_INTERROGATOR),                // droid 
00044         ENUM2STRING(CLASS_JAN),                         
00045         ENUM2STRING(CLASS_JEDI),                                
00046         ENUM2STRING(CLASS_KYLE),
00047         ENUM2STRING(CLASS_LANDO),                       
00048         ENUM2STRING(CLASS_LIZARD),
00049         ENUM2STRING(CLASS_LUKE),                                
00050         ENUM2STRING(CLASS_MARK1),                       // droid
00051         ENUM2STRING(CLASS_MARK2),                       // droid
00052         ENUM2STRING(CLASS_GALAKMECH),           // droid
00053         ENUM2STRING(CLASS_MINEMONSTER),
00054         ENUM2STRING(CLASS_MONMOTHA),                    
00055         ENUM2STRING(CLASS_MORGANKATARN),
00056         ENUM2STRING(CLASS_MOUSE),                       // droid
00057         ENUM2STRING(CLASS_MURJJ),
00058         ENUM2STRING(CLASS_PRISONER),
00059         ENUM2STRING(CLASS_PROBE),                       // droid
00060         ENUM2STRING(CLASS_PROTOCOL),                    // droid
00061         ENUM2STRING(CLASS_R2D2),                                // droid
00062         ENUM2STRING(CLASS_R5D2),                                // droid
00063         ENUM2STRING(CLASS_REBEL),
00064         ENUM2STRING(CLASS_REBORN),
00065         ENUM2STRING(CLASS_REELO),
00066         ENUM2STRING(CLASS_REMOTE),
00067         ENUM2STRING(CLASS_RODIAN),
00068         ENUM2STRING(CLASS_SEEKER),                      // droid
00069         ENUM2STRING(CLASS_SENTRY),
00070         ENUM2STRING(CLASS_SHADOWTROOPER),
00071         ENUM2STRING(CLASS_STORMTROOPER),
00072         ENUM2STRING(CLASS_SWAMP),
00073         ENUM2STRING(CLASS_SWAMPTROOPER),
00074         ENUM2STRING(CLASS_TAVION),
00075         ENUM2STRING(CLASS_TRANDOSHAN),
00076         ENUM2STRING(CLASS_UGNAUGHT),
00077         ENUM2STRING(CLASS_JAWA),
00078         ENUM2STRING(CLASS_WEEQUAY),
00079         ENUM2STRING(CLASS_BOBAFETT),
00080         //ENUM2STRING(CLASS_ROCKETTROOPER),
00081         //ENUM2STRING(CLASS_PLAYER),
00082         ENUM2STRING(CLASS_VEHICLE),
00083         ENUM2STRING(CLASS_RANCOR),
00084         ENUM2STRING(CLASS_WAMPA),
00085         "",     -1
00086 };
00087 
00088 stringID_table_t BSTable[] =
00089 {
00090         ENUM2STRING(BS_DEFAULT),//# default behavior for that NPC
00091         ENUM2STRING(BS_ADVANCE_FIGHT),//# Advance to captureGoal and shoot enemies if you can
00092         ENUM2STRING(BS_SLEEP),//# Play awake script when startled by sound
00093         ENUM2STRING(BS_FOLLOW_LEADER),//# Follow your leader and shoot any enemies you come across
00094         ENUM2STRING(BS_JUMP),//# Face navgoal and jump to it.
00095         ENUM2STRING(BS_SEARCH),//# Using current waypoint as a base), search the immediate branches of waypoints for enemies
00096         ENUM2STRING(BS_WANDER),//# Wander down random waypoint paths
00097         ENUM2STRING(BS_NOCLIP),//# Moves through walls), etc.
00098         ENUM2STRING(BS_REMOVE),//# Waits for player to leave PVS then removes itself
00099         ENUM2STRING(BS_CINEMATIC),//# Does nothing but face it's angles and move to a goal if it has one
00100         //the rest are internal only
00101         "",                             -1,
00102 };
00103 
00104 #define stringIDExpand(str, strEnum)    str, strEnum, ENUM2STRING(strEnum)
00105 
00106 stringID_table_t BSETTable[] =
00107 {
00108         ENUM2STRING(BSET_SPAWN),//# script to use when first spawned
00109         ENUM2STRING(BSET_USE),//# script to use when used
00110         ENUM2STRING(BSET_AWAKE),//# script to use when awoken/startled
00111         ENUM2STRING(BSET_ANGER),//# script to use when aquire an enemy
00112         ENUM2STRING(BSET_ATTACK),//# script to run when you attack
00113         ENUM2STRING(BSET_VICTORY),//# script to run when you kill someone
00114         ENUM2STRING(BSET_LOSTENEMY),//# script to run when you can't find your enemy
00115         ENUM2STRING(BSET_PAIN),//# script to use when take pain
00116         ENUM2STRING(BSET_FLEE),//# script to use when take pain below 50% of health
00117         ENUM2STRING(BSET_DEATH),//# script to use when killed
00118         ENUM2STRING(BSET_DELAYED),//# script to run when self->delayScriptTime is reached
00119         ENUM2STRING(BSET_BLOCKED),//# script to run when blocked by a friendly NPC or player
00120         ENUM2STRING(BSET_BUMPED),//# script to run when bumped into a friendly NPC or player (can set bumpRadius)
00121         ENUM2STRING(BSET_STUCK),//# script to run when blocked by a wall
00122         ENUM2STRING(BSET_FFIRE),//# script to run when player shoots their own teammates
00123         ENUM2STRING(BSET_FFDEATH),//# script to run when player kills a teammate
00124         stringIDExpand("", BSET_INVALID),
00125         "",                             -1,
00126 };
00127 
00128 #include "../namespace_begin.h"
00129 extern stringID_table_t WPTable[];
00130 extern stringID_table_t FPTable[];
00131 #include "../namespace_end.h"
00132 
00133 char    *TeamNames[TEAM_NUM_TEAMS] = 
00134 {
00135         "",
00136 //      "starfleet",
00137 //      "borg",
00138 //      "parasite",
00139 //      "scavengers",
00140 //      "klingon",
00141 //      "malon",
00142 //      "hirogen",
00143 //      "imperial",
00144 //      "stasis",
00145 //      "species8472",
00146 //      "dreadnought",
00147 //      "forge",
00148 //      "disguise",
00149 //      "player (not valid)"
00150         "player",
00151         "enemy",
00152         "neutral"
00153 };
00154 
00155 // this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h
00156 char    *ClassNames[CLASS_NUM_CLASSES] = 
00157 {
00158         "",                             // class none
00159         "atst",
00160         "bartender",
00161         "bespin_cop",
00162         "claw",
00163         "commando",
00164         "desann",
00165         "fish",
00166         "flier2",
00167         "galak",
00168         "glider",
00169         "gonk",                 
00170         "gran",
00171         "howler",
00172         "imperial",
00173         "impworker",
00174         "interrogator",
00175         "jan",
00176         "jedi",
00177         "kyle",
00178         "lando",
00179         "lizard",
00180         "luke",
00181         "mark1",
00182         "mark2",
00183         "galak_mech",
00184         "minemonster",
00185         "monmotha",
00186         "morgankatarn",
00187         "mouse",
00188         "murjj",
00189         "prisoner",
00190         "probe",
00191         "protocol",
00192         "r2d2",
00193         "r5d2",
00194         "rebel",
00195         "reborn",
00196         "reelo",
00197         "remote",
00198         "rodian",
00199         "seeker",
00200         "sentry",
00201         "shadowtrooper",
00202         "stormtrooper",
00203         "swamp",
00204         "swamptrooper",
00205         "tavion",
00206         "trandoshan",
00207         "ugnaught",
00208         "weequay",
00209         "bobafett",
00210         "vehicle",
00211         "rancor",
00212         "wampa",
00213 };
00214 
00215 
00216 /*
00217 NPC_ReactionTime
00218 */
00219 //FIXME use grandom in here
00220 int NPC_ReactionTime ( void ) 
00221 {
00222         return 200 * ( 6 - NPCInfo->stats.reactions );
00223 }
00224 
00225 //
00226 // parse support routines
00227 //
00228 
00229 #include "../namespace_begin.h"
00230 extern qboolean BG_ParseLiteral( const char **data, const char *string );
00231 #include "../namespace_end.h"
00232 
00233 //
00234 // NPC parameters file : scripts/NPCs.cfg
00235 //
00236 #define MAX_NPC_DATA_SIZE 0x20000
00237 char    NPCParms[MAX_NPC_DATA_SIZE];
00238 char    NPCFile[MAX_QPATH];
00239 
00240 /*
00241 team_t TranslateTeamName( const char *name ) 
00242 {
00243         int n;
00244 
00245         for ( n = (NPCTEAM_FREE + 1); n < NPCTEAM_NUM_TEAMS; n++ )
00246         {
00247                 if ( Q_stricmp( TeamNames[n], name ) == 0 )
00248                 {
00249                         return ((team_t) n);
00250                 }
00251         }
00252         
00253         return NPCTEAM_FREE;
00254 }
00255 
00256 class_t TranslateClassName( const char *name ) 
00257 {
00258         int n;
00259 
00260         for ( n = (CLASS_NONE + 1); n < CLASS_NUM_CLASSES; n++ )
00261         {
00262                 if ( Q_stricmp( ClassNames[n], name ) == 0 )
00263                 {
00264                         return ((class_t) n);
00265                 }
00266         }
00267         
00268         return CLASS_NONE;  // I hope this never happens, maybe print a warning
00269 }
00270 */
00271 
00272 /*
00273 static race_t TranslateRaceName( const char *name ) 
00274 {
00275         if ( !Q_stricmp( name, "human" ) ) 
00276         {
00277                 return RACE_HUMAN;
00278         }
00279         return RACE_NONE;
00280 }
00281 */
00282 /*
00283 static rank_t TranslateRankName( const char *name ) 
00284 
00285   Should be used to determine pip bolt-ons
00286 */
00287 static rank_t TranslateRankName( const char *name ) 
00288 {
00289         if ( !Q_stricmp( name, "civilian" ) ) 
00290         {
00291                 return RANK_CIVILIAN;
00292         }
00293 
00294         if ( !Q_stricmp( name, "crewman" ) ) 
00295         {
00296                 return RANK_CREWMAN;
00297         }
00298         
00299         if ( !Q_stricmp( name, "ensign" ) ) 
00300         {
00301                 return RANK_ENSIGN;
00302         }
00303         
00304         if ( !Q_stricmp( name, "ltjg" ) ) 
00305         {
00306                 return RANK_LT_JG;
00307         }
00308         
00309         if ( !Q_stricmp( name, "lt" ) ) 
00310         {
00311                 return RANK_LT;
00312         }
00313         
00314         if ( !Q_stricmp( name, "ltcomm" ) ) 
00315         {
00316                 return RANK_LT_COMM;
00317         }
00318         
00319         if ( !Q_stricmp( name, "commander" ) ) 
00320         {
00321                 return RANK_COMMANDER;
00322         }
00323         
00324         if ( !Q_stricmp( name, "captain" ) ) 
00325         {
00326                 return RANK_CAPTAIN;
00327         }
00328 
00329         return RANK_CIVILIAN;
00330 }
00331 
00332 #include "../namespace_begin.h"
00333 extern saber_colors_t TranslateSaberColor( const char *name );
00334 #include "../namespace_end.h"
00335 
00336 /* static int MethodNameToNumber( const char *name ) {
00337         if ( !Q_stricmp( name, "EXPONENTIAL" ) ) {
00338                 return METHOD_EXPONENTIAL;
00339         }
00340         if ( !Q_stricmp( name, "LINEAR" ) ) {
00341                 return METHOD_LINEAR;
00342         }
00343         if ( !Q_stricmp( name, "LOGRITHMIC" ) ) {
00344                 return METHOD_LOGRITHMIC;
00345         }
00346         if ( !Q_stricmp( name, "ALWAYS" ) ) {
00347                 return METHOD_ALWAYS;
00348         }
00349         if ( !Q_stricmp( name, "NEVER" ) ) {
00350                 return METHOD_NEVER;
00351         }
00352         return -1;
00353 }
00354 
00355 static int ItemNameToNumber( const char *name, int itemType ) {
00356 //      int             n;
00357 
00358         for ( n = 0; n < bg_numItems; n++ ) {
00359                 if ( bg_itemlist[n].type != itemType ) {
00360                         continue;
00361                 }
00362                 if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) {
00363                         return bg_itemlist[n].tag;
00364                 }
00365         }
00366         return -1;
00367 }
00368 */
00369 
00370 //rwwFIXMEFIXME: movetypes
00371 /*
00372 static int MoveTypeNameToEnum( const char *name ) 
00373 {
00374         if(!Q_stricmp("runjump", name))
00375         {
00376                 return MT_RUNJUMP;
00377         }
00378         else if(!Q_stricmp("walk", name))
00379         {
00380                 return MT_WALK;
00381         }
00382         else if(!Q_stricmp("flyswim", name))
00383         {
00384                 return MT_FLYSWIM;
00385         }
00386         else if(!Q_stricmp("static", name))
00387         {
00388                 return MT_STATIC;
00389         }
00390 
00391         return MT_STATIC;
00392 }
00393 */
00394 
00395 //#define CONVENIENT_ANIMATION_FILE_DEBUG_THING
00396 
00397 #ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING
00398 void SpewDebugStuffToFile(animation_t *anims)
00399 {
00400         char BGPAFtext[40000];
00401         fileHandle_t f;
00402         int i = 0;
00403 
00404         trap_FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE);
00405 
00406         if (!f)
00407         {
00408                 return;
00409         }
00410 
00411         BGPAFtext[0] = 0;
00412 
00413         while (i < MAX_ANIMATIONS)
00414         {
00415                 strcat(BGPAFtext, va("%i %i\n", i, anims[i].frameLerp));
00416                 i++;
00417         }
00418 
00419         trap_FS_Write(BGPAFtext, strlen(BGPAFtext), f);
00420         trap_FS_FCloseFile(f);
00421 }
00422 #endif
00423 
00424 qboolean G_ParseAnimFileSet( const char *filename, const char *animCFG, int *animFileIndex )
00425 {
00426         *animFileIndex = BG_ParseAnimationFile(filename, NULL, qfalse);
00427         //if it's humanoid we should have it cached and return it, if it is not it will be loaded (unless it's also cached already)
00428 
00429         if (*animFileIndex == -1)
00430         {
00431                 return qfalse;
00432         }
00433 
00434         //I guess this isn't really even needed game-side.
00435         //BG_ParseAnimationSndFile(filename, *animFileIndex);
00436         return qtrue;
00437 }
00438 
00439 void NPC_PrecacheAnimationCFG( const char *NPC_type )
00440 {
00441 #if 0 //rwwFIXMEFIXME: Actually precache stuff here.
00442         char    filename[MAX_QPATH];
00443         const char      *token;
00444         const char      *value;
00445         const char      *p;
00446         int             junk;
00447 
00448         if ( !Q_stricmp( "random", NPC_type ) )
00449         {//sorry, can't precache a random just yet
00450                 return;
00451         }
00452 
00453         p = NPCParms;
00454         COM_BeginParseSession(NPCFile);
00455 
00456         // look for the right NPC
00457         while ( p ) 
00458         {
00459                 token = COM_ParseExt( &p, qtrue );
00460                 if ( token[0] == 0 )
00461                         return;
00462 
00463                 if ( !Q_stricmp( token, NPC_type ) ) 
00464                 {
00465                         break;
00466                 }
00467 
00468                 SkipBracedSection( &p );
00469         }
00470 
00471         if ( !p ) 
00472         {
00473                 return;
00474         }
00475 
00476         if ( BG_ParseLiteral( &p, "{" ) ) 
00477         {
00478                 return;
00479         }
00480 
00481         // parse the NPC info block
00482         while ( 1 ) 
00483         {
00484                 token = COM_ParseExt( &p, qtrue );
00485                 if ( !token[0] ) 
00486                 {
00487                         Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type );
00488                         return;
00489                 }
00490 
00491                 if ( !Q_stricmp( token, "}" ) ) 
00492                 {
00493                         break;
00494                 }
00495 
00496                 // legsmodel
00497                 if ( !Q_stricmp( token, "legsmodel" ) ) 
00498                 {
00499                         if ( COM_ParseString( &p, &value ) ) 
00500                         {
00501                                 continue;
00502                         }
00503                         //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
00504                         Q_strncpyz( filename, value, sizeof( filename ) );
00505                         G_ParseAnimFileSet( filename, filename, &junk );
00506                         return;
00507                 }
00508 
00509                 // playerModel
00510                 if ( !Q_stricmp( token, "playerModel" ) ) 
00511                 {
00512                         if ( COM_ParseString( &p, &value ) ) 
00513                         {
00514                                 continue;
00515                         }
00516                         /*
00517                         char    animName[MAX_QPATH];
00518                         char    *GLAName;
00519                         char    *slash = NULL;
00520                         char    *strippedName;
00521                         
00522                         int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) );
00523                         if ( handle > 0 )//FIXME: isn't 0 a valid handle?
00524                         {
00525                                 GLAName = gi.G2API_GetAnimFileNameIndex( handle );
00526                                 if ( GLAName )
00527                                 {
00528                                         Q_strncpyz( animName, GLAName, sizeof( animName ), qtrue );
00529                                         slash = strrchr( animName, '/' );
00530                                         if ( slash )
00531                                         {
00532                                                 *slash = 0;
00533                                         }
00534                                         strippedName = COM_SkipPath( animName );
00535 
00536                                         //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
00537                                         Q_strncpyz( filename, value, sizeof( filename ), qtrue );
00538                                         G_ParseAnimFileSet( value, strippedName, &junk );//qfalse );
00539                                         //FIXME: still not precaching the animsounds.cfg?
00540                                         return;
00541                                 }
00542                         }
00543                         */
00544                         //rwwFIXMEFIXME: Do this properly.
00545                 }
00546         }
00547 #endif
00548 }
00549 
00550 extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type );
00551 void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype )
00552 {
00553         int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype );
00554         int curWeap;
00555 
00556         for ( curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ )
00557         {
00558                 if (weapons & (1 << curWeap))
00559                 {
00560                         RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap));
00561                 }
00562         }
00563 
00564 #if 0 //rwwFIXMEFIXME: actually precache weapons here
00565         int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype );
00566         gitem_t *item;
00567         for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ )
00568         {
00569                 if ( (weapons & ( 1 << curWeap )) )
00570                 {
00571                         item = FindItemForWeapon( ((weapon_t)(curWeap)) );      //precache the weapon
00572                         CG_RegisterItemSounds( (item-bg_itemlist) );
00573                         CG_RegisterItemVisuals( (item-bg_itemlist) );
00574                         //precache the in-hand/in-world ghoul2 weapon model
00575 
00576                         char weaponModel[64];
00577                         
00578                         strcpy (weaponModel, weaponData[curWeap].weaponMdl);    
00579                         if (char *spot = strstr(weaponModel, ".md3") ) {
00580                                 *spot = 0;
00581                                 spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on
00582                                 if (!spot) {
00583                                         strcat (weaponModel, "_w");
00584                                 }
00585                                 strcat (weaponModel, ".glm");   //and change to ghoul2
00586                         }
00587                         gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model
00588                 }
00589         }
00590 #endif
00591 }
00592 
00593 /*
00594 void NPC_Precache ( char *NPCName )
00595 
00596 Precaches NPC skins, tgas and md3s.
00597 
00598 */
00599 void NPC_Precache ( gentity_t *spawner )
00600 {
00601         npcteam_t                       playerTeam = NPCTEAM_FREE;
00602         const char      *token;
00603         const char      *value;
00604         const char      *p;
00605         char    *patch;
00606         char    sound[MAX_QPATH];
00607         qboolean        md3Model = qfalse;
00608         char    playerModel[MAX_QPATH];
00609         char    customSkin[MAX_QPATH];
00610 
00611         if ( !Q_stricmp( "random", spawner->NPC_type ) )
00612         {//sorry, can't precache a random just yet
00613                 return;
00614         }
00615         strcpy(customSkin,"default");
00616 
00617         p = NPCParms;
00618         COM_BeginParseSession(NPCFile);
00619 
00620         // look for the right NPC
00621         while ( p ) 
00622         {
00623                 token = COM_ParseExt( &p, qtrue );
00624                 if ( token[0] == 0 )
00625                         return;
00626 
00627                 if ( !Q_stricmp( token, spawner->NPC_type ) ) 
00628                 {
00629                         break;
00630                 }
00631 
00632                 SkipBracedSection( &p );
00633         }
00634 
00635         if ( !p ) 
00636         {
00637                 return;
00638         }
00639 
00640         if ( BG_ParseLiteral( &p, "{" ) ) 
00641         {
00642                 return;
00643         }
00644 
00645         // parse the NPC info block
00646         while ( 1 ) 
00647         {
00648                 token = COM_ParseExt( &p, qtrue );
00649                 if ( !token[0] ) 
00650                 {
00651                         Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type );
00652                         return;
00653                 }
00654 
00655                 if ( !Q_stricmp( token, "}" ) ) 
00656                 {
00657                         break;
00658                 }
00659 
00660                 // headmodel
00661                 if ( !Q_stricmp( token, "headmodel" ) ) 
00662                 {
00663                         if ( COM_ParseString( &p, &value ) ) 
00664                         {
00665                                 continue;
00666                         }
00667 
00668                         if(!Q_stricmp("none", value))
00669                         {
00670                         }
00671                         else
00672                         {
00673                                 //Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName), qtrue);
00674                         }
00675                         md3Model = qtrue;
00676                         continue;
00677                 }
00678                 
00679                 // torsomodel
00680                 if ( !Q_stricmp( token, "torsomodel" ) ) 
00681                 {
00682                         if ( COM_ParseString( &p, &value ) ) 
00683                         {
00684                                 continue;
00685                         }
00686 
00687                         if(!Q_stricmp("none", value))
00688                         {
00689                         }
00690                         else
00691                         {
00692                                 //Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName), qtrue);
00693                         }
00694                         md3Model = qtrue;
00695                         continue;
00696                 }
00697 
00698                 // legsmodel
00699                 if ( !Q_stricmp( token, "legsmodel" ) ) 
00700                 {
00701                         if ( COM_ParseString( &p, &value ) ) 
00702                         {
00703                                 continue;
00704                         }
00705                         //Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName), qtrue);                        
00706                         md3Model = qtrue;
00707                         continue;
00708                 }
00709 
00710                 // playerModel
00711                 if ( !Q_stricmp( token, "playerModel" ) ) 
00712                 {
00713                         if ( COM_ParseString( &p, &value ) ) 
00714                         {
00715                                 continue;
00716                         }
00717                         Q_strncpyz( playerModel, value, sizeof(playerModel));                   
00718                         md3Model = qfalse;
00719                         continue;
00720                 }
00721 
00722                 // customSkin
00723                 if ( !Q_stricmp( token, "customSkin" ) ) 
00724                 {
00725                         if ( COM_ParseString( &p, &value ) ) 
00726                         {
00727                                 continue;
00728                         }
00729                         Q_strncpyz( customSkin, value, sizeof(customSkin));                     
00730                         continue;
00731                 }
00732 
00733                 // playerTeam
00734                 if ( !Q_stricmp( token, "playerTeam" ) ) 
00735                 {
00736                         char tk[4096]; //rww - hackilicious!
00737 
00738                         if ( COM_ParseString( &p, &value ) ) 
00739                         {
00740                                 continue;
00741                         }
00742                         //playerTeam = TranslateTeamName(value);
00743                         Com_sprintf(tk, sizeof(tk), "NPC%s", token);
00744                         playerTeam = (team_t)GetIDForString( TeamTable, tk );
00745                         continue;
00746                 }
00747 
00748         
00749                 // snd
00750                 if ( !Q_stricmp( token, "snd" ) ) {
00751                         if ( COM_ParseString( &p, &value ) ) {
00752                                 continue;
00753                         }
00754                         if ( !(spawner->r.svFlags&SVF_NO_BASIC_SOUNDS) )
00755                         {
00756                                 //FIXME: store this in some sound field or parse in the soundTable like the animTable...
00757                                 Q_strncpyz( sound, value, sizeof( sound ) );
00758                                 patch = strstr( sound, "/" );
00759                                 if ( patch ) 
00760                                 {
00761                                         *patch = 0;
00762                                 }
00763                                 spawner->s.csSounds_Std = G_SoundIndex( va("*$%s", sound) );
00764                         }
00765                         continue;
00766                 }
00767 
00768                 // sndcombat
00769                 if ( !Q_stricmp( token, "sndcombat" ) ) {
00770                         if ( COM_ParseString( &p, &value ) ) {
00771                                 continue;
00772                         }
00773                         if ( !(spawner->r.svFlags&SVF_NO_COMBAT_SOUNDS) )
00774                         {
00775                                 //FIXME: store this in some sound field or parse in the soundTable like the animTable...
00776                                 Q_strncpyz( sound, value, sizeof( sound ) );
00777                                 patch = strstr( sound, "/" );
00778                                 if ( patch ) 
00779                                 {
00780                                         *patch = 0;
00781                                 }
00782                                 spawner->s.csSounds_Combat = G_SoundIndex( va("*$%s", sound) );
00783                         }
00784                         continue;
00785                 }
00786                 
00787                 // sndextra
00788                 if ( !Q_stricmp( token, "sndextra" ) ) {
00789                         if ( COM_ParseString( &p, &value ) ) {
00790                                 continue;
00791                         }
00792                         if ( !(spawner->r.svFlags&SVF_NO_EXTRA_SOUNDS) )
00793                         {
00794                                 //FIXME: store this in some sound field or parse in the soundTable like the animTable...
00795                                 Q_strncpyz( sound, value, sizeof( sound ) );
00796                                 patch = strstr( sound, "/" );
00797                                 if ( patch ) 
00798                                 {
00799                                         *patch = 0;
00800                                 }
00801                                 spawner->s.csSounds_Extra = G_SoundIndex( va("*$%s", sound) );
00802                         }
00803                         continue;
00804                 }
00805 
00806                 // sndjedi
00807                 if ( !Q_stricmp( token, "sndjedi" ) ) {
00808                         if ( COM_ParseString( &p, &value ) ) {
00809                                 continue;
00810                         }
00811                         if ( !(spawner->r.svFlags&SVF_NO_EXTRA_SOUNDS) )
00812                         {
00813                                 //FIXME: store this in some sound field or parse in the soundTable like the animTable...
00814                                 Q_strncpyz( sound, value, sizeof( sound ) );
00815                                 patch = strstr( sound, "/" );
00816                                 if ( patch ) 
00817                                 {
00818                                         *patch = 0;
00819                                 }
00820                                 spawner->s.csSounds_Jedi = G_SoundIndex( va("*$%s", sound) );
00821                         }
00822                         continue;
00823                 }
00824 
00825                 if (!Q_stricmp(token, "weapon"))
00826                 {
00827                         int curWeap;
00828 
00829                         if (COM_ParseString(&p, &value))
00830                         {
00831                                 continue;
00832                         }
00833 
00834                         curWeap = GetIDForString( WPTable, value );
00835 
00836                         if (curWeap > WP_NONE && curWeap < WP_NUM_WEAPONS)
00837                         {
00838                                 RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap));
00839                         }
00840                         continue;
00841                 }
00842         }
00843 
00844         // If we're not a vehicle, then an error here would be valid...
00845         if ( !spawner->client || spawner->client->NPC_class != CLASS_VEHICLE )
00846         {
00847                 if ( md3Model )
00848                 {
00849                         Com_Printf("MD3 model using NPCs are not supported in MP\n");
00850                 }
00851                 else
00852                 { //if we have a model/skin then index them so they'll be registered immediately
00853                         //when the client gets a configstring update.
00854                         char modelName[MAX_QPATH];
00855 
00856                         Com_sprintf(modelName, sizeof(modelName), "models/players/%s/model.glm", playerModel);
00857                         if (customSkin[0])
00858                         { //append it after a *
00859                                 strcat( modelName, va("*%s", customSkin) );
00860                         }
00861 
00862                         G_ModelIndex(modelName);
00863                 }
00864         }
00865 
00866         //precache this NPC's possible weapons
00867         NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type );
00868 
00869 //      CG_RegisterNPCCustomSounds( &ci );
00870 //      CG_RegisterNPCEffects( playerTeam );
00871         //rwwFIXMEFIXME: same
00872         //FIXME: Look for a "sounds" directory and precache death, pain, alert sounds
00873 }
00874 
00875 #if 0
00876 void NPC_BuildRandom( gentity_t *NPC )
00877 {
00878         int     sex, color, head;
00879 
00880         sex = Q_irand(0, 2);
00881         color = Q_irand(0, 2);
00882         switch( sex )
00883         {
00884         case 0://female
00885                 head = Q_irand(0, 2);
00886                 switch( head )
00887                 {
00888                 default:
00889                 case 0:
00890                         Q_strncpyz( NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00891                         break;
00892                 case 1:
00893                         Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00894                         break;
00895                 case 2:
00896                         Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00897                         color = Q_irand(3, 5);//torso needs to be afam
00898                         break;
00899                 }
00900                 switch( color )
00901                 {
00902                 default:
00903                 case 0:
00904                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00905                         break;
00906                 case 1:
00907                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00908                         break;
00909                 case 2:
00910                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00911                         break;
00912                 case 3:
00913                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00914                         break;
00915                 case 4:
00916                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00917                         break;
00918                 case 5:
00919                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00920                         break;
00921                 }
00922                 Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName), qtrue );
00923                 break;
00924         default:
00925         case 1://male
00926         case 2://male
00927                 head = Q_irand(0, 4);
00928                 switch( head )
00929                 {
00930                 default:
00931                 case 0:
00932                         Q_strncpyz( NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00933                         break;
00934                 case 1:
00935                         Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00936                         break;
00937                 case 2:
00938                         Q_strncpyz( NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00939                         break;
00940                 case 3:
00941                         Q_strncpyz( NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00942                         break;
00943                 case 4:
00944                         Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName), qtrue );
00945                         break;
00946                 }
00947                 switch( color )
00948                 {
00949                 default:
00950                 case 0:
00951                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00952                         break;
00953                 case 1:
00954                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00955                         break;
00956                 case 2:
00957                         Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue );
00958                         break;
00959                         //NOTE: 3 - 5 should be red, gold & blue, afram hands
00960                 }
00961                 Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName), qtrue );
00962                 break;
00963         }
00964 
00965         NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = Q_irand(87, 102)/100.0f;
00966 //      NPC->client->race = RACE_HUMAN;
00967         NPC->NPC->rank = RANK_CREWMAN;
00968         NPC->client->playerTeam = NPC->s.teamowner = TEAM_PLAYER;
00969         NPC->client->clientInfo.customBasicSoundDir = "kyle";//FIXME: generic default?
00970 }
00971 #endif
00972 
00973 extern void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName);
00974 qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ) 
00975 {
00976         const char      *token;
00977         const char      *value;
00978         const char      *p;
00979         int             n;
00980         float   f;
00981         char    *patch;
00982         char    sound[MAX_QPATH];
00983         char    playerModel[MAX_QPATH];
00984         char    customSkin[MAX_QPATH];
00985         renderInfo_t    *ri = &NPC->client->renderInfo;
00986         gNPCstats_t             *stats = NULL;
00987         qboolean        md3Model = qtrue;
00988         char    surfOff[1024];
00989         char    surfOn[1024];
00990         qboolean parsingPlayer = qfalse;
00991         vec3_t playerMins;
00992         vec3_t playerMaxs;
00993         int npcSaber1 = 0;
00994         int npcSaber2 = 0;
00995 
00996         VectorSet(playerMins, -15, -15, DEFAULT_MINS_2);
00997         VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2);
00998 
00999         strcpy(customSkin,"default");
01000         if ( !NPCName || !NPCName[0]) 
01001         {
01002                 NPCName = "Player";
01003         }
01004 
01005         if ( !NPC->s.number && NPC->client != NULL )
01006         {//player, only want certain data
01007                 parsingPlayer = qtrue;
01008         }
01009 
01010         if ( NPC->NPC )
01011         {
01012                 stats = &NPC->NPC->stats;
01013 /*
01014         NPC->NPC->allWeaponOrder[0]     = WP_BRYAR_PISTOL;
01015         NPC->NPC->allWeaponOrder[1]     = WP_SABER;
01016         NPC->NPC->allWeaponOrder[2]     = WP_IMOD;
01017         NPC->NPC->allWeaponOrder[3]     = WP_SCAVENGER_RIFLE;
01018         NPC->NPC->allWeaponOrder[4]     = WP_TRICORDER;
01019         NPC->NPC->allWeaponOrder[6]     = WP_NONE;
01020         NPC->NPC->allWeaponOrder[6]     = WP_NONE;
01021         NPC->NPC->allWeaponOrder[7]     = WP_NONE;
01022 */
01023                 // fill in defaults
01024                 stats->aggression       = 3;
01025                 stats->aim                      = 3;
01026                 stats->earshot          = 1024;
01027                 stats->evasion          = 3;
01028                 stats->hfov                     = 90;
01029                 stats->intelligence     = 3;
01030                 stats->move                     = 3;
01031                 stats->reactions        = 3;
01032                 stats->vfov                     = 60;
01033                 stats->vigilance        = 0.1f;
01034                 stats->visrange         = 1024;
01035 
01036                 stats->health           = 0;
01037 
01038                 stats->yawSpeed         = 90;
01039                 stats->walkSpeed        = 90;
01040                 stats->runSpeed         = 300;
01041                 stats->acceleration     = 15;//Increase/descrease speed this much per frame (20fps)
01042         }
01043         else
01044         {
01045                 stats = NULL;
01046         }
01047 
01048         //Set defaults
01049         //FIXME: should probably put default torso and head models, but what about enemies
01050         //that don't have any- like Stasis?
01051         //Q_strncpyz( ri->headModelName,        DEFAULT_HEADMODEL,  sizeof(ri->headModelName),  qtrue);
01052         //Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName),       qtrue);
01053         //Q_strncpyz( ri->legsModelName,        DEFAULT_LEGSMODEL,  sizeof(ri->legsModelName),  qtrue);
01054         //FIXME: should we have one for weapon too?
01055         memset( (char *)surfOff, 0, sizeof(surfOff) );
01056         memset( (char *)surfOn, 0, sizeof(surfOn) );
01057         
01058         /*
01059         ri->headYawRangeLeft = 50;
01060         ri->headYawRangeRight = 50;
01061         ri->headPitchRangeUp = 40;
01062         ri->headPitchRangeDown = 50;
01063         ri->torsoYawRangeLeft = 60;
01064         ri->torsoYawRangeRight = 60;
01065         ri->torsoPitchRangeUp = 30;
01066         ri->torsoPitchRangeDown = 70;
01067         */
01068 
01069         ri->headYawRangeLeft = 80;
01070         ri->headYawRangeRight = 80;
01071         ri->headPitchRangeUp = 45;
01072         ri->headPitchRangeDown = 45;
01073         ri->torsoYawRangeLeft = 60;
01074         ri->torsoYawRangeRight = 60;
01075         ri->torsoPitchRangeUp = 30;
01076         ri->torsoPitchRangeDown = 50;
01077 
01078         VectorCopy(playerMins, NPC->r.mins);
01079         VectorCopy(playerMaxs, NPC->r.maxs);
01080         NPC->client->ps.crouchheight = CROUCH_MAXS_2;
01081         NPC->client->ps.standheight = DEFAULT_MAXS_2;
01082 
01083         //rwwFIXMEFIXME: ...
01084         /*
01085         NPC->client->moveType           = MT_RUNJUMP;
01086 
01087         NPC->client->dismemberProbHead = 100;
01088         NPC->client->dismemberProbArms = 100;
01089         NPC->client->dismemberProbHands = 100;
01090         NPC->client->dismemberProbWaist = 100;
01091         NPC->client->dismemberProbLegs = 100;
01092 
01093         NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = 1.0f;
01094         */
01095 
01096         NPC->client->ps.customRGBA[0]=255;
01097         NPC->client->ps.customRGBA[1]=255;
01098         NPC->client->ps.customRGBA[2]=255;
01099         NPC->client->ps.customRGBA[3]=255;
01100 
01101         if ( !Q_stricmp( "random", NPCName ) )
01102         {//Randomly assemble a starfleet guy
01103                 //NPC_BuildRandom( NPC );
01104                 Com_Printf("RANDOM NPC NOT SUPPORTED IN MP\n");
01105                 return qfalse;
01106         }
01107         else
01108         {
01109                 int fp;
01110 
01111                 p = NPCParms;
01112                 COM_BeginParseSession(NPCFile);
01113 
01114                 // look for the right NPC
01115                 while ( p ) 
01116                 {
01117                         token = COM_ParseExt( &p, qtrue );
01118                         if ( token[0] == 0 )
01119                         {
01120                                 return qfalse;
01121                         }
01122 
01123                         if ( !Q_stricmp( token, NPCName ) ) 
01124                         {
01125                                 break;
01126                         }
01127 
01128                         SkipBracedSection( &p );
01129                 }
01130                 if ( !p ) 
01131                 {
01132                         return qfalse;
01133                 }
01134 
01135                 if ( BG_ParseLiteral( &p, "{" ) ) 
01136                 {
01137                         return qfalse;
01138                 }
01139                         
01140                 // parse the NPC info block
01141                 while ( 1 ) 
01142                 {
01143                         token = COM_ParseExt( &p, qtrue );
01144                         if ( !token[0] ) 
01145                         {
01146                                 Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName );
01147                                 return qfalse;
01148                         }
01149 
01150                         if ( !Q_stricmp( token, "}" ) ) 
01151                         {
01152                                 break;
01153                         }
01154         //===MODEL PROPERTIES===========================================================
01155                         // custom color
01156                         if ( !Q_stricmp( token, "customRGBA" ) ) 
01157                         {
01158                                 if ( COM_ParseString( &p, &value ) ) 
01159                                 {
01160                                         continue;
01161                                 }
01162                                 if ( !Q_stricmp( value, "random") )
01163                                 {
01164                                         NPC->client->ps.customRGBA[0]=Q_irand(0,255);
01165                                         NPC->client->ps.customRGBA[1]=Q_irand(0,255);
01166                                         NPC->client->ps.customRGBA[2]=Q_irand(0,255);
01167                                         NPC->client->ps.customRGBA[3]=255;
01168                                 } 
01169                                 else 
01170                                 {
01171                                         NPC->client->ps.customRGBA[0]=atoi(value);
01172                                         
01173                                         if ( COM_ParseInt( &p, &n ) ) 
01174                                         {
01175                                                 continue;
01176                                         }
01177                                         NPC->client->ps.customRGBA[1]=n;
01178                                         
01179                                         if ( COM_ParseInt( &p, &n ) ) 
01180                                         {
01181                                                 continue;
01182                                         }
01183                                         NPC->client->ps.customRGBA[2]=n;
01184                                         
01185                                         if ( COM_ParseInt( &p, &n ) ) 
01186                                         {
01187                                                 continue;
01188                                         }
01189                                         NPC->client->ps.customRGBA[3]=n;
01190                                 }
01191                                 continue;
01192                         }
01193 
01194                         // headmodel
01195                         if ( !Q_stricmp( token, "headmodel" ) ) 
01196                         {
01197                                 if ( COM_ParseString( &p, &value ) ) 
01198                                 {
01199                                         continue;
01200                                 }
01201 
01202                                 if(!Q_stricmp("none", value))
01203                                 {
01204                                         //Zero the head clamp range so the torso & legs don't lag behind
01205                                         ri->headYawRangeLeft = 
01206                                         ri->headYawRangeRight = 
01207                                         ri->headPitchRangeUp = 
01208                                         ri->headPitchRangeDown = 0;
01209                                 }
01210                                 continue;
01211                         }
01212                         
01213                         // torsomodel
01214                         if ( !Q_stricmp( token, "torsomodel" ) ) 
01215                         {
01216                                 if ( COM_ParseString( &p, &value ) ) 
01217                                 {
01218                                         continue;
01219                                 }
01220 
01221                                 if(!Q_stricmp("none", value))
01222                                 {
01223                                         //Zero the torso clamp range so the legs don't lag behind
01224                                         ri->torsoYawRangeLeft = 
01225                                         ri->torsoYawRangeRight = 
01226                                         ri->torsoPitchRangeUp = 
01227                                         ri->torsoPitchRangeDown = 0;
01228                                 }
01229                                 continue;
01230                         }
01231 
01232                         // legsmodel
01233                         if ( !Q_stricmp( token, "legsmodel" ) ) 
01234                         {
01235                                 if ( COM_ParseString( &p, &value ) ) 
01236                                 {
01237                                         continue;
01238                                 }
01239                                 /*
01240                                 Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName), qtrue);                        
01241                                 //Need to do this here to get the right index
01242                                 G_ParseAnimFileSet( ri->legsModelName, ri->legsModelName, &ci->animFileIndex );
01243                                 */
01244                                 continue;
01245                         }
01246 
01247                         // playerModel
01248                         if ( !Q_stricmp( token, "playerModel" ) ) 
01249                         {
01250                                 if ( COM_ParseString( &p, &value ) ) 
01251                                 {
01252                                         continue;
01253                                 }
01254                                 Q_strncpyz( playerModel, value, sizeof(playerModel));                   
01255                                 md3Model = qfalse;
01256                                 continue;
01257                         }
01258                         
01259                         // customSkin
01260                         if ( !Q_stricmp( token, "customSkin" ) ) 
01261                         {
01262                                 if ( COM_ParseString( &p, &value ) ) 
01263                                 {
01264                                         continue;
01265                                 }
01266                                 Q_strncpyz( customSkin, value, sizeof(customSkin));                     
01267                                 continue;
01268                         }
01269 
01270                         // surfOff
01271                         if ( !Q_stricmp( token, "surfOff" ) ) 
01272                         {
01273                                 if ( COM_ParseString( &p, &value ) ) 
01274                                 {
01275                                         continue;
01276                                 }
01277                                 if ( surfOff[0] )
01278                                 {
01279                                         Q_strcat( (char *)surfOff, sizeof(surfOff), "," );
01280                                         Q_strcat( (char *)surfOff, sizeof(surfOff), value );
01281                                 }
01282                                 else
01283                                 {
01284                                         Q_strncpyz( surfOff, value, sizeof(surfOff));
01285                                 }
01286                                 continue;
01287                         }
01288                         
01289                         // surfOn
01290                         if ( !Q_stricmp( token, "surfOn" ) ) 
01291                         {
01292                                 if ( COM_ParseString( &p, &value ) ) 
01293                                 {
01294                                         continue;
01295                                 }
01296                                 if ( surfOn[0] )
01297                                 {
01298                                         Q_strcat( (char *)surfOn, sizeof(surfOn), "," );
01299                                         Q_strcat( (char *)surfOn, sizeof(surfOn), value );
01300                                 }
01301                                 else
01302                                 {
01303                                         Q_strncpyz( surfOn, value, sizeof(surfOn));
01304                                 }
01305                                 continue;
01306                         }
01307                         
01308                         //headYawRangeLeft
01309                         if ( !Q_stricmp( token, "headYawRangeLeft" ) ) 
01310                         {
01311                                 if ( COM_ParseInt( &p, &n ) ) 
01312                                 {
01313                                         SkipRestOfLine( &p );
01314                                         continue;
01315                                 }
01316                                 if ( n < 0 ) 
01317                                 {
01318                                         Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
01319                                         continue;
01320                                 }
01321                                 ri->headYawRangeLeft = n;
01322                                 continue;
01323                         }
01324 
01325                         //headYawRangeRight
01326                         if ( !Q_stricmp( token, "headYawRangeRight" ) ) 
01327                         {
01328                                 if ( COM_ParseInt( &p, &n ) ) 
01329                                 {
01330                                         SkipRestOfLine( &p );
01331                                         continue;
01332                                 }
01333                                 if ( n < 0 ) 
01334                                 {
01335                                         Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName );
01336                                         continue;
01337                                 }
01338                                 ri->headYawRangeRight = n;
01339                                 continue;
01340                         }
01341 
01342                         //headPitchRangeUp
01343                         if ( !Q_stricmp( token, "headPitchRangeUp" ) ) 
01344                         {
01345                                 if ( COM_ParseInt( &p, &n ) ) 
01346                                 {
01347