1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7 
8 This file is part of the OpenJK source code.
9 
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23 
24 #include "g_headers.h"
25 
26 #include "g_local.h"
27 #include "g_functions.h"
28 #include "anims.h"
29 #include "g_icarus.h"
30 #include "wp_saber.h"
31 
32 extern void Q3_DebugPrint( int level, const char *format, ... );
33 extern void WP_SaberInitBladeData( gentity_t *ent );
34 extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel );
35 extern qboolean	CheatsOk( gentity_t *ent );
36 extern vmCvar_t	cg_thirdPersonAlpha;
37 
38 // g_client.c -- client functions that don't happen every frame
39 
40 float DEFAULT_MINS_0 = -16;
41 float DEFAULT_MINS_1 = -16;
42 float DEFAULT_MAXS_0 = 16;
43 float DEFAULT_MAXS_1 = 16;
44 float DEFAULT_PLAYER_RADIUS	= sqrt((DEFAULT_MAXS_0*DEFAULT_MAXS_0) + (DEFAULT_MAXS_1*DEFAULT_MAXS_1));
45 vec3_t playerMins = {DEFAULT_MINS_0, DEFAULT_MINS_1, DEFAULT_MINS_2};
46 vec3_t playerMaxs = {DEFAULT_MAXS_0, DEFAULT_MAXS_1, DEFAULT_MAXS_2};
47 
48 void SP_misc_teleporter_dest (gentity_t *ent);
49 
50 /*QUAK-ED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) - - NODRAW
51 potential spawning position for deathmatch games.
52 Targets will be fired when someone spawns in on them.
53 */
SP_info_player_deathmatch(gentity_t * ent)54 void SP_info_player_deathmatch(gentity_t *ent) {
55 	SP_misc_teleporter_dest (ent);
56 
57 	if ( ent->spawnflags & 32 ) // STUN_BATON
58 	{
59 		RegisterItem( FindItemForWeapon( WP_STUN_BATON ));
60 	}
61 	else
62 	{
63 		RegisterItem( FindItemForWeapon( WP_SABER ) );	//these are given in ClientSpawn(), but we register them now before cgame starts
64 		G_SkinIndex("models/players/kyle/model_fpls2.skin");	//preache the skin used in cg_players.cpp
65 	}
66 }
67 
68 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) KEEP_PREV DROPTOFLOOR x x x STUN_BATON NOWEAPON x
69 KEEP_PREV - keep previous health/ammo/etc
70 DROPTOFLOOR - Player will start on the first solid structure under it
71 STUN_BATON - Gives player the stun baton and bryar pistol, but not the saber, plus any weapons they may have carried over from previous levels.
72 
73 Targets will be fired when someone spawns in on them.
74 equivalant to info_player_deathmatch
75 */
SP_info_player_start(gentity_t * ent)76 void SP_info_player_start(gentity_t *ent) {
77 	ent->classname = "info_player_deathmatch";
78 
79 	ent->spawnflags |= 1;	// James suggests force-ORing the KEEP_PREV flag in for now
80 
81 	SP_info_player_deathmatch( ent );
82 }
83 
84 
85 
86 /*
87 =======================================================================
88 
89   SelectSpawnPoint
90 
91 =======================================================================
92 */
93 
94 /*
95 ================
96 SpotWouldTelefrag
97 
98 ================
99 */
SpotWouldTelefrag(gentity_t * spot,team_t checkteam)100 qboolean SpotWouldTelefrag( gentity_t *spot, team_t checkteam )
101 {
102 	int			i, num;
103 	gentity_t	*touch[MAX_GENTITIES], *hit;
104 	vec3_t		mins, maxs;
105 
106 	// If we have a mins, use that instead of the hardcoded bounding box
107 	if ( !VectorCompare(spot->mins, vec3_origin) && VectorLength( spot->mins ) )
108 		VectorAdd( spot->s.origin, spot->mins, mins );
109 	else
110 		VectorAdd( spot->s.origin, playerMins, mins );
111 
112 	// If we have a maxs, use that instead of the hardcoded bounding box
113 	if ( !VectorCompare(spot->maxs, vec3_origin) && VectorLength( spot->maxs ) )
114 		VectorAdd( spot->s.origin, spot->maxs, maxs );
115 	else
116 		VectorAdd( spot->s.origin, playerMaxs, maxs );
117 
118 	num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
119 
120 	for (i=0 ; i<num ; i++)
121 	{
122 		hit = touch[i];
123 		if ( hit != spot && hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 )
124 		{
125 			if ( hit->contents & CONTENTS_BODY )
126 			{
127 				if( checkteam == TEAM_FREE || hit->client->playerTeam == checkteam )
128 				{//checking against teammates only...?
129 					return qtrue;
130 				}
131 			}
132 		}
133 	}
134 
135 	return qfalse;
136 }
137 
SpotWouldTelefrag2(gentity_t * mover,vec3_t dest)138 qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest )
139 {
140 	int			i, num;
141 	gentity_t	*touch[MAX_GENTITIES], *hit;
142 	vec3_t		mins, maxs;
143 
144 	VectorAdd( dest, mover->mins, mins );
145 	VectorAdd( dest, mover->maxs, maxs );
146 	num = gi.EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
147 
148 	for (i=0 ; i<num ; i++)
149 	{
150 		hit = touch[i];
151 		if ( hit == mover )
152 		{
153 			continue;
154 		}
155 
156 		if ( hit->contents & mover->contents )
157 		{
158 			return qtrue;
159 		}
160 	}
161 
162 	return qfalse;
163 }
164 /*
165 ================
166 SelectNearestDeathmatchSpawnPoint
167 
168 Find the spot that we DON'T want to use
169 ================
170 */
171 #define	MAX_SPAWN_POINTS	128
SelectNearestDeathmatchSpawnPoint(vec3_t from,team_t team)172 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from, team_t team ) {
173 	gentity_t	*spot;
174 	float		dist, nearestDist;
175 	gentity_t	*nearestSpot;
176 
177 	nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE;
178 	nearestSpot = NULL;
179 	spot = NULL;
180 
181 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
182 		/*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) {
183 			continue;
184 		}
185 		if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) {
186 			continue;
187 		}*/
188 
189 		if ( spot->targetname != NULL ) {
190 			//this search routine should never find a spot that is targetted
191 			continue;
192 		}
193 		dist = DistanceSquared( spot->s.origin, from );
194 		if ( dist < nearestDist ) {
195 			nearestDist = dist;
196 			nearestSpot = spot;
197 		}
198 	}
199 
200 	return nearestSpot;
201 }
202 
203 
204 /*
205 ================
206 SelectRandomDeathmatchSpawnPoint
207 
208 go to a random point that doesn't telefrag
209 ================
210 */
211 #define	MAX_SPAWN_POINTS	128
SelectRandomDeathmatchSpawnPoint(team_t team)212 gentity_t *SelectRandomDeathmatchSpawnPoint( team_t team ) {
213 	gentity_t	*spot;
214 	int			count;
215 	int			selection;
216 	gentity_t	*spots[MAX_SPAWN_POINTS];
217 
218 	count = 0;
219 	spot = NULL;
220 
221 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
222 		/*if ( team == TEAM_RED && ( spot->spawnflags & 2 ) ) {
223 			continue;
224 		}
225 		if ( team == TEAM_BLUE && ( spot->spawnflags & 1 ) ) {
226 			continue;
227 		}*/
228 
229 		if ( spot->targetname != NULL ) {
230 			//this search routine should never find a spot that is targetted
231 			continue;
232 		}
233 		if ( SpotWouldTelefrag( spot, TEAM_FREE ) ) {
234 			continue;
235 		}
236 		spots[ count ] = spot;
237 		count++;
238 	}
239 
240 	if ( !count ) {	// no spots that won't telefrag
241 		spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
242 		if ( !spot )
243 		{
244 			return NULL;
245 		}
246 		if ( spot->targetname != NULL )
247 		{
248 			//this search routine should never find a spot that is targetted
249 			return NULL;
250 		}
251 		else
252 		{
253 			return spot;
254 		}
255 	}
256 
257 	selection = rand() % count;
258 	return spots[ selection ];
259 }
260 
261 
262 /*
263 ===========
264 SelectSpawnPoint
265 
266 Chooses a player start, deathmatch start, etc
267 ============
268 */
SelectSpawnPoint(vec3_t avoidPoint,team_t team,vec3_t origin,vec3_t angles)269 gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, team_t team, vec3_t origin, vec3_t angles ) {
270 	gentity_t	*spot;
271 	gentity_t	*nearestSpot;
272 
273 	if ( level.spawntarget[0] )
274 	{//we have a spawnpoint specified, try to find it
275 		if ( (nearestSpot = spot = G_Find( NULL, FOFS(targetname), level.spawntarget )) == NULL )
276 		{//you HAVE to be able to find the desired spot
277 			G_Error( "Couldn't find spawntarget %s", level.spawntarget );
278 			return NULL;
279 		}
280 	}
281 	else
282 	{//not looking for a special startspot
283 		nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint, team );
284 
285 		spot = SelectRandomDeathmatchSpawnPoint ( team );
286 		if ( spot == nearestSpot ) {
287 			// roll again if it would be real close to point of death
288 			spot = SelectRandomDeathmatchSpawnPoint ( team );
289 		}
290 	}
291 
292 	// find a single player start spot
293 	if (!spot) {
294 		G_Error( "Couldn't find a spawn point" );
295 	}
296 
297 
298 	VectorCopy( spot->s.origin, origin );
299 	if ( spot->spawnflags & 2 )
300 	{
301 		trace_t		tr;
302 
303 		origin[2] = MIN_WORLD_COORD;
304 		gi.trace(&tr, spot->s.origin, playerMins, playerMaxs, origin, ENTITYNUM_NONE, MASK_PLAYERSOLID, G2_NOCOLLIDE, 0 );
305 		if ( tr.fraction < 1.0 && !tr.allsolid && !tr.startsolid )
306 		{//found a floor
307 			VectorCopy(tr.endpos, origin );
308 		}
309 		else
310 		{//In solid or too far
311 			VectorCopy( spot->s.origin, origin );
312 		}
313 	}
314 
315 	origin[2] += 9;
316 	VectorCopy (spot->s.angles, angles);
317 
318 	return spot;
319 }
320 
321 
322 //======================================================================
323 
324 
325 /*
326 ==================
327 SetClientViewAngle
328 
329 ==================
330 */
SetClientViewAngle(gentity_t * ent,vec3_t angle)331 void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
332 	int			i;
333 
334 	// set the delta angle
335 	for (i=0 ; i<3 ; i++)
336 	{
337 		ent->client->ps.delta_angles[i] = (ANGLE2SHORT(angle[i]) - ent->client->pers.cmd_angles[i])&0xffff;
338 	}
339 	VectorCopy( angle, ent->s.angles );
340 	VectorCopy (ent->s.angles, ent->client->ps.viewangles);
341 }
342 
343 /*
344 ================
345 respawn
346 ================
347 */
respawn(gentity_t * ent)348 void respawn( gentity_t *ent ) {
349 
350 	gi.SendConsoleCommand("load *respawn\n");	// special case
351 }
352 
353 
354 /*
355 ================
356 PickTeam
357 
358 ================
359 */
PickTeam(int ignoreClientNum)360 team_t PickTeam( int ignoreClientNum ) {
361 	int		i;
362 	int		counts[TEAM_NUM_TEAMS];
363 
364 	memset( counts, 0, sizeof( counts ) );
365 
366 	for ( i = 0 ; i < level.maxclients ; i++ ) {
367 		if ( i == ignoreClientNum ) {
368 			continue;
369 		}
370 		if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
371 			continue;
372 		}
373 	}
374 
375 	return TEAM_FREE;
376 }
377 
378 /*
379 ===========
380 ForceClientSkin
381 
382 Forces a client's skin (for teamplay)
383 ===========
384 */
ForceClientSkin(gclient_t * client,char * model,const char * skin)385 void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
386 	char *p;
387 
388 	if ((p = strchr(model, '/')) != NULL) {
389 		*p = 0;
390 	}
391 
392 	Q_strcat(model, MAX_QPATH, "/");
393 	Q_strcat(model, MAX_QPATH, skin);
394 }
395 
396 /*
397 ===========
398 ClientCheckName
399 ============
400 */
ClientCleanName(const char * in,char * out,int outSize)401 static void ClientCleanName( const char *in, char *out, int outSize )
402 {
403 	int outpos = 0, colorlessLen = 0, spaces = 0;
404 
405 	// discard leading spaces
406 	for ( ; *in == ' '; in++);
407 
408 	// discard leading asterisk's (fail raven for using * as a skipnotify)
409 	// apparently .* causes the issue too so... derp
410 	//for(; *in == '*'; in++);
411 
412 	for(; *in && outpos < outSize - 1; in++)
413 	{
414 		out[outpos] = *in;
415 
416 		if ( *in == ' ' )
417 		{// don't allow too many consecutive spaces
418 			if ( spaces > 2 )
419 				continue;
420 
421 			spaces++;
422 		}
423 		else if ( outpos > 0 && out[outpos-1] == Q_COLOR_ESCAPE )
424 		{
425 			if ( Q_IsColorStringExt( &out[outpos-1] ) )
426 			{
427 				colorlessLen--;
428 
429 #if 0
430 				if ( ColorIndex( *in ) == 0 )
431 				{// Disallow color black in names to prevent players from getting advantage playing in front of black backgrounds
432 					outpos--;
433 					continue;
434 				}
435 #endif
436 			}
437 			else
438 			{
439 				spaces = 0;
440 				colorlessLen++;
441 			}
442 		}
443 		else
444 		{
445 			spaces = 0;
446 			colorlessLen++;
447 		}
448 
449 		outpos++;
450 	}
451 
452 	out[outpos] = '\0';
453 
454 	// don't allow empty names
455 	if ( *out == '\0' || colorlessLen == 0 )
456 		Q_strncpyz( out, "Padawan", outSize );
457 }
458 
459 /*
460 ===========
461 ClientUserInfoChanged
462 
463 Called from ClientConnect when the player first connects and
464 directly by the server system when the player updates a userinfo variable.
465 
466 The game can override any of the settings and call gi.SetUserinfo
467 if desired.
468 ============
469 */
ClientUserinfoChanged(int clientNum)470 void ClientUserinfoChanged( int clientNum ) {
471 	gentity_t	*ent = g_entities + clientNum;
472 	gclient_t	*client = ent->client;
473 	int			health=100, maxHealth=100;
474 	const char	*s=NULL, *sex=NULL;
475 	char		userinfo[MAX_INFO_STRING]={0},	buf[MAX_INFO_STRING]={0},
476 				oldname[34]={0};
477 
478 	gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
479 
480 	// set name
481 	Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
482 	s = Info_ValueForKey (userinfo, "name");
483 	ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) );
484 
485 	// set max health
486 	maxHealth = 100;
487 	health = Com_Clampi( 1, 100, atoi( Info_ValueForKey( userinfo, "handicap" ) ) );
488 	client->pers.maxHealth = health;
489 	if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth )
490 		client->pers.maxHealth = 100;
491 	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
492 
493 	// sex
494 	sex = Info_ValueForKey( userinfo, "sex" );
495 	if ( !sex[0] ) {
496 		sex = "m";
497 	}
498 
499 	// send over a subset of the userinfo keys so other clients can
500 	// print scoreboards, display models, and play custom sounds
501 	buf[0] = '\0';
502 	Q_strcat( buf, sizeof( buf ), va( "n\\%s\\", client->pers.netname ) );
503 	Q_strcat( buf, sizeof( buf ), va( "t\\%i\\", client->sess.sessionTeam ) );
504 	Q_strcat( buf, sizeof( buf ),	  "headModel\\\\" );
505 	Q_strcat( buf, sizeof( buf ),	  "torsoModel\\\\" );
506 	Q_strcat( buf, sizeof( buf ),	  "legsModel\\\\" );
507 	Q_strcat( buf, sizeof( buf ), va( "sex\\%s\\", sex ) );
508 	Q_strcat( buf, sizeof( buf ), va( "hc\\%i\\", client->pers.maxHealth ) );
509 
510 	gi.SetConfigstring( CS_PLAYERS+clientNum, buf );
511 }
512 
513 
514 /*
515 ===========
516 ClientConnect
517 
518 Called when a player begins connecting to the server.
519 Called again for every map change or tournement restart.
520 
521 The session information will be valid after exit.
522 
523 Return NULL if the client should be allowed, otherwise return
524 a string with the reason for denial.
525 
526 Otherwise, the client will be sent the current gamestate
527 and will eventually get to ClientBegin.
528 
529 firstTime will be qtrue the very first time a client connects
530 to the server machine, but qfalse on map changes and tournement
531 restarts.
532 ============
533 */
ClientConnect(int clientNum,qboolean firstTime,SavedGameJustLoaded_e eSavedGameJustLoaded)534 char *ClientConnect( int clientNum, qboolean firstTime, SavedGameJustLoaded_e eSavedGameJustLoaded )
535 {
536 	gentity_t	*ent = &g_entities[ clientNum ];
537 	char		userinfo[MAX_INFO_STRING] = {0};
538 
539 	gi.GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
540 
541 	// they can connect
542 	ent->client = level.clients + clientNum;
543 	gclient_t *client = ent->client;
544 
545 //	if (!qbFromSavedGame)
546 	if (eSavedGameJustLoaded != eFULL)
547 	{
548 		clientSession_t savedSess = client->sess;	//
549 		memset( client, 0, sizeof(*client) );
550 		client->sess = savedSess;
551 	}
552 
553 	client->pers.connected = CON_CONNECTING;
554 
555 	if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame)
556 	{
557 		// G_WriteClientSessionData( client ); // forget it, this is DM stuff anyway
558 		// get and distribute relevent paramters
559 		ClientUserinfoChanged( clientNum );
560 	}
561 	else
562 	{
563 		// read or initialize the session data
564 		if ( firstTime ) {
565 			G_InitSessionData( client, userinfo );
566 		}
567 		G_ReadSessionData( client );
568 
569 		// get and distribute relevent paramters
570 		ClientUserinfoChanged( clientNum );
571 
572 		// don't do the "xxx connected" messages if they were caried over from previous level
573 		if ( firstTime ) {
574 			gi.SendServerCommand( -1, "print \"%s connected\n\"", client->pers.netname);
575 		}
576 	}
577 
578 	return NULL;
579 }
580 
581 /*
582 ===========
583 ClientBegin
584 
585 called when a client has finished connecting, and is ready
586 to be placed into the level.  This will happen every level load,
587 and on transition between teams, but doesn't happen on respawns
588 ============
589 */
ClientBegin(int clientNum,usercmd_t * cmd,SavedGameJustLoaded_e eSavedGameJustLoaded)590 void ClientBegin( int clientNum, usercmd_t *cmd, SavedGameJustLoaded_e eSavedGameJustLoaded)
591 //												qboolean qbFromSavedGame
592 {
593 	gentity_t	*ent;
594 	gclient_t	*client;
595 
596 	ent = g_entities + clientNum;
597 	client = level.clients + clientNum;
598 
599 	if (eSavedGameJustLoaded == eFULL)//qbFromSavedGame)
600 	{
601 		client->pers.connected = CON_CONNECTED;
602 		ent->client = client;
603 		ClientSpawn( ent, eSavedGameJustLoaded );
604 	}
605 	else
606 	{
607 		if ( ent->linked ) {
608 			gi.unlinkentity( ent );
609 		}
610 		G_InitGentity( ent );
611 		ent->e_TouchFunc = touchF_NULL;
612 		ent->e_PainFunc  = painF_PlayerPain;//painF_NULL;
613 		ent->client = client;
614 
615 		client->pers.connected = CON_CONNECTED;
616 		client->pers.teamState.state = TEAM_BEGIN;
617 		VectorCopyM( cmd->angles, client->pers.cmd_angles );
618 
619 		memset( &client->ps, 0, sizeof( client->ps ) );
620 		memset( &client->sess.missionStats, 0, sizeof( client->sess.missionStats ) );
621 		client->sess.missionStats.totalSecrets = gi.Cvar_VariableIntegerValue("newTotalSecrets");
622 
623 		// locate ent at a spawn point
624 		if ( ClientSpawn( ent, eSavedGameJustLoaded) )	// SavedGameJustLoaded_e
625 		{
626 			// send teleport event
627 		}
628 		client->ps.inventory[INV_GOODIE_KEY] = 0;
629 		client->ps.inventory[INV_SECURITY_KEY] = 0;
630 	}
631 }
632 
633 
634 
635 /*
636 ============
637 Player_CacheFromPrevLevel
638   Description	: just need to grab the weapon items we're going to have when we spawn so they'll be cached
639   Return type	: void
640   Argument		: void
641 ============
642 */
Player_CacheFromPrevLevel(void)643 void Player_CacheFromPrevLevel(void)
644 {
645 	char	s[MAX_STRING_CHARS];
646 	int		i;
647 
648 	gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
649 
650 	if (strlen(s))	// actually this would be safe anyway because of the way sscanf() works, but this is clearer
651 	{
652 		int iDummy, bits, ibits;
653 
654 		sscanf( s, "%i %i %i %i",
655 			&iDummy,//client->ps.stats[STAT_HEALTH],
656 			&iDummy,//client->ps.stats[STAT_ARMOR],
657 			&bits,	//client->ps.stats[STAT_WEAPONS]
658 			&ibits	//client->ps.stats[STAT_ITEMS]
659 			);
660 
661 		for ( i = 1 ; i < 16 ; i++ )
662 		{
663 			if ( bits & ( 1 << i ) )
664 			{
665 				RegisterItem( FindItemForWeapon( (weapon_t)i ) );
666 			}
667 		}
668 
669 extern gitem_t	*FindItemForInventory( int inv );
670 
671 		for ( i = 1 ; i < 16 ; i++ )
672 		{
673 			if ( ibits & ( 1 << i ) )
674 			{
675 				RegisterItem( FindItemForInventory( i-1 ));
676 			}
677 		}
678 	}
679 }
680 
681 /*
682 ============
683 Player_RestoreFromPrevLevel
684   Description	: retrieve maptransition data recorded by server when exiting previous level (to carry over weapons/ammo/health/etc)
685   Return type	: void
686   Argument		: gentity_t *ent
687 ============
688 */
Player_RestoreFromPrevLevel(gentity_t * ent)689 void Player_RestoreFromPrevLevel(gentity_t *ent)
690 {
691 	gclient_t	*client = ent->client;
692 	int			i;
693 
694 	assert(client);
695 	if (client)	// though I can't see it not being true...
696 	{
697 		char	s[MAX_STRING_CHARS];
698 		const char	*var;
699 		int saberActive;
700 
701 		gi.Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
702 
703 		if (strlen(s))	// actually this would be safe anyway because of the way sscanf() works, but this is clearer
704 		{
705 			sscanf( s, "%i %i %i %i %i %i %i %f %f %f %i %i %i %i %i %i",
706 								&client->ps.stats[STAT_HEALTH],
707 								&client->ps.stats[STAT_ARMOR],
708 								&client->ps.stats[STAT_WEAPONS],
709 								&client->ps.stats[STAT_ITEMS],
710 								&client->ps.weapon,
711 								&client->ps.weaponstate,
712 								&client->ps.batteryCharge,
713 								&client->ps.viewangles[0],
714 								&client->ps.viewangles[1],
715 								&client->ps.viewangles[2],
716 								&client->ps.forcePowersKnown,
717 								&client->ps.forcePower,
718 								&saberActive,
719 								&client->ps.saberAnimLevel,
720 								&client->ps.saberLockEnemy,
721 								&client->ps.saberLockTime
722 					);
723 			client->ps.saberActive = (saberActive ? qtrue : qfalse);
724 			ent->health = client->ps.stats[STAT_HEALTH];
725 
726 // slight issue with ths for the moment in that although it'll correctly restore angles it doesn't take into account
727 //	the overall map orientation, so (eg) exiting east to enter south will be out by 90 degrees, best keep spawn angles for now
728 //
729 //			VectorClear (ent->client->pers.cmd_angles);
730 //
731 //			SetClientViewAngle( ent, ent->client->ps.viewangles);
732 
733 			//ammo
734 			gi.Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) );
735 			i=0;
736 			var = strtok( s, " " );
737 			while( var != NULL )
738 			{
739 			  /* While there are tokens in "s" */
740 			  client->ps.ammo[i++] = atoi(var);
741 			  /* Get next token: */
742 			  var = strtok( NULL, " " );
743 			}
744 			assert (i==AMMO_MAX);
745 
746 			//inventory
747 			gi.Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) );
748 			i=0;
749 			var = strtok( s, " " );
750 			while( var != NULL )
751 			{
752 			  /* While there are tokens in "s" */
753 			  client->ps.inventory[i++] = atoi(var);
754 			  /* Get next token: */
755 			  var = strtok( NULL, " " );
756 			}
757 			assert (i==INV_MAX);
758 
759 
760 			// the new JK2 stuff - force powers, etc...
761 			//
762 			gi.Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) );
763 			i=0;
764 			var = strtok( s, " " );
765 			while( var != NULL )
766 			{
767 			  /* While there are tokens in "s" */
768 			  client->ps.forcePowerLevel[i++] = atoi(var);
769 			  /* Get next token: */
770 			  var = strtok( NULL, " " );
771 			}
772 			assert (i==NUM_FORCE_POWERS);
773 
774 			client->ps.forcePowerMax = FORCE_POWER_MAX;
775 			client->ps.forceGripEntityNum = ENTITYNUM_NONE;
776 		}
777 	}
778 }
779 
780 /*
781 Ghoul2 Insert Start
782 */
G_SetSkin(gentity_t * ent,const char * modelName,const char * customSkin)783 void G_SetSkin( gentity_t *ent, const char *modelName, const char *customSkin )
784 {
785 	char	skinName[MAX_QPATH];
786 	//ok, lets register the skin name, and then pass that name to the config strings so the client can get it too.
787 	//FIXME: is have an alternate skin (in modelName, after '/'), replace "default" with that skin name
788 	if ( !customSkin )
789 	{
790 		Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName );
791 	}
792 	else
793 	{
794 		Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", modelName, customSkin );
795 	}
796 	// lets see if it's out there
797 	int skin = gi.RE_RegisterSkin( skinName );
798 	if ( skin )
799 	{
800 		// put it in the config strings
801 		// and set the ghoul2 model to use it
802 		gi.G2API_SetSkin( &ent->ghoul2[ent->playerModel], G_SkinIndex( skinName ), skin );
803 	}
804 }
805 
G_StandardHumanoid(const char * modelName)806 qboolean G_StandardHumanoid( const char *modelName )
807 {
808 	if ( !modelName )
809 	{
810 		return qfalse;
811 	}
812 	if ( !Q_stricmp( "kyle", modelName ) ||
813 		!Q_strncmp( "st", modelName, 2 ) ||
814 		!Q_strncmp( "imp", modelName, 3 ) ||
815 		!Q_strncmp( "gran", modelName, 4 ) ||
816 		!Q_strncmp( "rodian", modelName, 6 ) ||
817 		!Q_strncmp( "weequay", modelName, 7 ) ||
818 		!Q_strncmp( "reborn", modelName, 6 ) ||
819 		!Q_strncmp( "shadowtrooper", modelName, 13 ) ||
820 		!Q_strncmp( "swamptrooper", modelName, 12 ) ||
821 		!Q_stricmp( "rockettrooper", modelName ) ||
822 		!Q_stricmp( "bespin_cop", modelName ) ||
823 		!Q_strncmp( "bespincop", modelName, 9 ) ||
824 		!Q_strncmp( "rebel", modelName, 5 ) ||
825 		!Q_strncmp( "ugnaught", modelName, 8 ) ||
826 		!Q_strncmp( "morgan", modelName,6 ) ||
827 		!Q_strncmp( "protocol", modelName, 8 ) ||
828 		!Q_strncmp( "jedi", modelName, 4 ) ||
829 		!Q_strncmp( "prisoner", modelName, 8 ) ||
830 		!Q_stricmp( "tavion", modelName ) ||
831 		!Q_stricmp( "desann", modelName ) ||
832 		!Q_stricmp( "trandoshan", modelName ) ||
833 		!Q_stricmp( "jan", modelName ) ||
834 		!Q_stricmp( "luke", modelName ) ||
835 		!Q_stricmp( "lando", modelName ) ||
836 		!Q_stricmp( "reelo", modelName ) ||
837 		!Q_stricmp( "bartender", modelName ) ||
838 		!Q_stricmp( "monmothma", modelName ) ||
839 		!Q_stricmp( "chiss", modelName ) ||
840 		!Q_stricmp( "galak", modelName ) )
841 	{
842 		return qtrue;
843 	}
844 	return qfalse;
845 }
846 extern void G_LoadAnimFileSet( gentity_t *ent, const char *modelName );
G_SetG2PlayerModelInfo(gentity_t * ent,const char * modelName,const char * customSkin,const char * surfOff,const char * surfOn)847 qboolean G_SetG2PlayerModelInfo( gentity_t *ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn )
848 {
849 	if ( ent->playerModel != -1 )
850 	{// we found the model ok
851 		vec3_t	angles = {0,0,0};
852 		const char	*token;
853 		const char	*p;
854 
855 		//Now turn on/off any surfaces
856 		if ( surfOff && surfOff[0] )
857 		{
858 			p = surfOff;
859 			COM_BeginParseSession();
860 			while ( 1 )
861 			{
862 				token = COM_ParseExt( &p, qtrue );
863 				if ( !token[0] )
864 				{//reached end of list
865 					break;
866 				}
867 				//turn off this surf
868 				gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0x00000002/*G2SURFACEFLAG_OFF*/ );
869 			}
870 			COM_EndParseSession();
871 		}
872 		if ( surfOn && surfOn[0] )
873 		{
874 			p = surfOn;
875 			COM_BeginParseSession();
876 			while ( 1 )
877 			{
878 				token = COM_ParseExt( &p, qtrue );
879 				if ( !token[0] )
880 				{//reached end of list
881 					break;
882 				}
883 				//turn on this surf
884 				gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], token, 0 );
885 			}
886 			COM_EndParseSession();
887 		}
888 		if ( ent->client->NPC_class == CLASS_IMPERIAL && ent->message )
889 		{//carrying a key, turn on the key sleeve surface (assuming we have one)
890 			gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "l_arm_key", 0 );
891 		}
892 
893 		G_LoadAnimFileSet( ent, modelName );
894 		//we shouldn't actually have to do this anymore
895 		//G_SetSkin( ent, modelName, customSkin );
896 
897 		ent->headBolt = ent->cervicalBolt = ent->torsoBolt = ent->gutBolt = ent->chestBolt =
898 			ent->crotchBolt = ent->elbowLBolt = ent->elbowRBolt = ent->handLBolt =
899 			ent->handRBolt = ent->kneeLBolt = ent->kneeRBolt = ent->footLBolt =
900 			ent->footRBolt = -1;
901 		// now turn on the bolt in the hand - this one would be best always turned on.
902 		if ( G_StandardHumanoid( modelName ) )
903 		{//temp hack because only the gran are set up right so far
904 			ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes");
905 			ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" );
906 			if ( !Q_stricmp("protocol", modelName ) )
907 			{//*sigh*, no thoracic bone
908 				ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
909 				ent->chestBolt = ent->gutBolt;
910 			}
911 			else
912 			{
913 				ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic");
914 				ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
915 			}
916 			ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar");
917 			ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis");
918 			ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_arm_elbow");
919 			ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_arm_elbow");
920 			ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_hand");
921 			ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_hand");
922 			ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_l_knee");
923 			ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hips_r_knee");
924 			ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_leg_foot");
925 			ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_leg_foot");
926 		}
927 		else
928 		{
929 			if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName )
930 						|| !Q_strncmp( "r2d2", modelName, 4 ) || !Q_strncmp( "r5d2", modelName, 4 ) )
931 			{//TEMP HACK: not a non-humanoid droid
932 				ent->headBolt = -1;
933 			}
934 			else if (!Q_stricmp( "interrogator",modelName))
935 			{
936 				ent->headBolt = -1;
937 			}
938 			else if (!Q_strncmp( "probe",modelName, 5))
939 			{
940 				ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium");		// head pivot point
941 				ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash");		// Gun 1
942 			}
943 			/*
944 			else if (!Q_strncmp( "protocol",modelName, 8))
945 			{
946 				ent->headBolt = -1;
947 			}
948 			*/
949 			else if (!Q_stricmp( "sentry",modelName))
950 			{
951 				ent->headBolt = -1;
952 				ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1");		// Gun 1
953 				ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2");		// Gun 2
954 				ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash03");		// Gun 3
955 			}
956 			else if (!Q_stricmp( "mark1",modelName))
957 			{
958 				ent->headBolt = -1;
959 				ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1");		// Blaster Gun 1
960 				ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2");		// Blaster Gun 2
961 				ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3");		// Blaster Gun 3
962 				ent->genericBolt4 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4");		// Blaster Gun 4
963 				ent->genericBolt5 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash5");		// Missile Gun 1
964 			}
965 			else if (!Q_stricmp( "mark2",modelName))
966 			{
967 				ent->headBolt = -1;
968 				ent->handRBolt = ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash");		// Blaster Gun 1
969 			}
970 			else if (!Q_stricmp( "atst",modelName) )//&& (ent->client->playerTeam != TEAM_PLAYER))
971 			{
972 				ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head");
973 
974 				ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash1");		// Front guns
975 				ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash2");
976 
977 				ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash3");		// Left side gun
978 				ent->genericBolt2 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flash4");		// Right side missle launcher
979 
980 				ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*l_foot");
981 				ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*r_foot");
982 			}
983 			else if ( !Q_stricmp( "minemonster", modelName ))
984 			{
985 				ent->handRBolt = ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_f1");
986 			}
987 			else if ( !Q_stricmp( "howler", modelName ))
988 			{
989 				ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cranium"); // FIXME!
990 			}
991 			else if ( !Q_stricmp( "galak_mech", modelName ))
992 			{
993 				ent->genericBolt1 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*antenna_effect");
994 				ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*head_eyes");
995 				ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "torso");
996 				ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "hips");
997 				ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flasha");
998 				ent->genericBolt3 = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flashb");
999 				ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*flashc");
1000 			}
1001 			else
1002 			{//TEMP HACK: not a non-humanoid droid
1003 				ent->handRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*weapon");//should be r_hand
1004 				if ( Q_stricmp( "atst", modelName ) )
1005 				{//not an ATST
1006 					ent->headBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*headg");
1007 					ent->cervicalBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "cervical" );
1008 					ent->torsoBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "lower_lumbar");
1009 					ent->gutBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "upper_lumbar");
1010 					ent->chestBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "thoracic");
1011 					ent->crotchBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "pelvis");
1012 					ent->elbowLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_lg");
1013 					ent->elbowRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*bicep_rg");
1014 					ent->handLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*hand_l");
1015 					ent->kneeLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_lg");
1016 					ent->kneeRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*thigh_rg");
1017 					ent->footLBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_lg");
1018 					ent->footRBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "*foot_rg");
1019 				}
1020 			}
1021 		}
1022 
1023 		ent->faceBone = BONE_INDEX_INVALID;
1024 		ent->craniumBone = BONE_INDEX_INVALID;
1025 		ent->cervicalBone = BONE_INDEX_INVALID;
1026 		ent->thoracicBone = BONE_INDEX_INVALID;
1027 		ent->upperLumbarBone = BONE_INDEX_INVALID;
1028 		ent->lowerLumbarBone = BONE_INDEX_INVALID;
1029 		ent->motionBone = BONE_INDEX_INVALID;
1030 		ent->hipsBone = BONE_INDEX_INVALID;
1031 		ent->rootBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "model_root", qtrue );
1032 		ent->footLBone = BONE_INDEX_INVALID;
1033 		ent->footRBone = BONE_INDEX_INVALID;
1034 		// now add overrides on specific joints so the client can set angle overrides on the legs, torso and head
1035 		if ( !Q_stricmp( "gonk", modelName ) || !Q_stricmp( "seeker", modelName ) || !Q_stricmp( "remote", modelName ) )
1036 		{//
1037 		}
1038 		else if (!Q_stricmp( "sentry",modelName))
1039 		{
1040 		}
1041 		else if (!Q_strncmp( "probe", modelName, 5 ))
1042 		{
1043 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1044 			if (ent->craniumBone>=0)
1045 			{
1046 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1047 			}
1048 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue );
1049 			if (ent->thoracicBone>=0)
1050 			{
1051 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1052 			}
1053 		}
1054 		/*
1055 		else if (!Q_strncmp( "protocol",modelName,8))
1056 		{
1057 		}
1058 		*/
1059 		else if (!Q_stricmp( "interrogator", modelName ))
1060 		{
1061 			ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "left_arm", qtrue );
1062 			if (ent->genericBone1>=0)
1063 			{
1064 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1065 			}
1066 			ent->genericBone2 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "right_arm", qtrue );
1067 			if (ent->genericBone2>=0)
1068 			{
1069 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone2, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1070 			}
1071 			ent->genericBone3 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "claw", qtrue );
1072 			if (ent->genericBone3>=0)
1073 			{
1074 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone3, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1075 			}
1076 		}
1077 		else if (!Q_strncmp( "r2d2", modelName, 4 ))
1078 		{
1079 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1080 			if (ent->craniumBone>=0)
1081 			{
1082 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1083 			}
1084 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue );
1085 			if (ent->thoracicBone>=0)
1086 			{
1087 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1088 			}
1089 			ent->genericBone1 = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "f_eye", qtrue );
1090 			if (ent->genericBone1>=0)
1091 			{
1092 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->genericBone1, angles, BONE_ANGLES_POSTMULT, NEGATIVE_Y, NEGATIVE_X, NEGATIVE_Z, NULL, 0, 0 );
1093 			}
1094 		}
1095 		else if (!Q_strncmp( "r5d2", modelName, 4 ))
1096 		{
1097 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1098 			if (ent->craniumBone>=0)
1099 			{
1100 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1101 			}
1102 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "body", qtrue );
1103 			if (ent->thoracicBone>=0)
1104 			{
1105 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1106 			}
1107 		}
1108 		else if ( !Q_stricmp( "atst", modelName ))
1109 		{
1110 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1111 			if (ent->craniumBone>=0)
1112 			{
1113 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1114 			}
1115 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1116 			if (ent->thoracicBone>=0)
1117 			{
1118 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1119 			}
1120 			ent->footLBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "l_tarsal", qtrue );
1121 			if (ent->footLBone>=0)
1122 			{
1123 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footLBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL, 0, 0 );
1124 			}
1125 			ent->footRBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "r_tarsal", qtrue );
1126 			if (ent->footRBone>=0)
1127 			{
1128 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->footRBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, NEGATIVE_X, NULL, 0, 0 );
1129 			}
1130 		}
1131 		else if ( !Q_stricmp( "mark1", modelName ))
1132 		{
1133 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1134 			if (ent->craniumBone>=0)
1135 			{
1136 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1137 			}
1138 			ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1139 			if (ent->upperLumbarBone>=0)
1140 			{
1141 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1142 			}
1143 		}
1144 		else if ( !Q_stricmp( "mark2", modelName ))
1145 		{
1146 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1147 			if (ent->craniumBone>=0)
1148 			{
1149 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1150 			}
1151 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1152 			if (ent->thoracicBone>=0)
1153 			{
1154 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1155 			}
1156 		}
1157 		else if ( !Q_stricmp( "minemonster", modelName ))
1158 		{
1159 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic1", qtrue );
1160 			if (ent->thoracicBone>=0)
1161 			{
1162 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1163 			}
1164 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1165 			if (ent->craniumBone>=0)
1166 			{
1167 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1168 			}
1169 		}
1170 		else if ( !Q_stricmp( "howler", modelName ))
1171 		{
1172 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1173 			if (ent->thoracicBone>=0)
1174 			{
1175 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1176 			}
1177 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1178 			if (ent->craniumBone>=0)
1179 			{
1180 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1181 			}
1182 		}
1183 		else
1184 		{
1185 			//special case motion bone - to match up split anims
1186 			ent->motionBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "Motion", qtrue );
1187 			if (ent->motionBone>=0)
1188 			{
1189 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->motionBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_X, NEGATIVE_Y, NULL, 0, 0 );
1190 			}
1191 			ent->motionBolt = gi.G2API_AddBolt(&ent->ghoul2[ent->playerModel], "Motion");
1192 			//bone needed for turning anims
1193 			ent->hipsBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "pelvis", qtrue );
1194 			if (ent->hipsBone>=0)
1195 			{
1196 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->hipsBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1197 			}
1198 			//regular bones we need
1199 			ent->upperLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "upper_lumbar", qtrue );
1200 			if (ent->upperLumbarBone>=0)
1201 			{
1202 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->upperLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1203 			}
1204 			ent->lowerLumbarBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "lower_lumbar", qtrue );
1205 			if (ent->lowerLumbarBone>=0)
1206 			{
1207 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->lowerLumbarBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1208 			}
1209 
1210 			ent->faceBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "face", qtrue );
1211 			if (ent->faceBone>=0)
1212 			{
1213 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->faceBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1214 			}
1215 			ent->craniumBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cranium", qtrue );
1216 			if (ent->craniumBone>=0)
1217 			{
1218 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->craniumBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1219 			}
1220 			ent->cervicalBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "cervical", qtrue );
1221 			if (ent->cervicalBone>=0)
1222 			{
1223 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->cervicalBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1224 			}
1225 			ent->thoracicBone = gi.G2API_GetBoneIndex( &ent->ghoul2[ent->playerModel], "thoracic", qtrue );
1226 			if (ent->thoracicBone>=0)
1227 			{
1228 				gi.G2API_SetBoneAnglesIndex( &ent->ghoul2[ent->playerModel], ent->thoracicBone, angles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, 0 );
1229 			}
1230 		}
1231 		ent->client->clientInfo.infoValid = qtrue;
1232 
1233 	}
1234 
1235 	int		max;
1236 	if ( ent->s.radius <= 0 )//radius cannot be negative or zero
1237 	{//set the radius to be the largest axial distance on the entity
1238 		max = ent->mins[0];//NOTE: mins is always negative
1239 		if ( max > ent->mins[1] )
1240 		{
1241 			max = ent->mins[1];
1242 		}
1243 
1244 		if ( max > ent->mins[2] )
1245 		{
1246 			max = ent->mins[2];
1247 		}
1248 
1249 		max = fabs((double)max);//convert to positive to compare with maxs
1250 		if ( max < ent->maxs[0] )
1251 		{
1252 			max = ent->maxs[0];
1253 		}
1254 
1255 		if ( max < ent->maxs[1] )
1256 		{
1257 			max = ent->maxs[1];
1258 		}
1259 
1260 		if ( max < ent->maxs[2] )
1261 		{
1262 			max = ent->maxs[2];
1263 		}
1264 
1265 		ent->s.radius = max;
1266 
1267 		if (!ent->s.radius)	// Still no radius?
1268 		{
1269 			ent->s.radius = 60;
1270 		}
1271 	}
1272 
1273 	// set the weaponmodel to -1 so we don't try and remove it in Pmove before we have it built
1274 	ent->weaponModel = -1;
1275 
1276 	if ( ent->playerModel == -1 )
1277 	{
1278 		return qfalse;
1279 	}
1280 	return qtrue;
1281 }
1282 
G_SetG2PlayerModel(gentity_t * const ent,const char * modelName,const char * customSkin,const char * surfOff,const char * surfOn)1283 void G_SetG2PlayerModel( gentity_t * const ent, const char *modelName, const char *customSkin, const char *surfOff, const char *surfOn )
1284 {
1285 	char	skinName[MAX_QPATH];
1286 
1287 	//ok, lets register the skin name, and then pass that name to the config strings so the client can get it too.
1288 	if ( !customSkin )
1289 	{//use the default
1290 		Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_default.skin", modelName );
1291 	}
1292 	else
1293 	{
1294 		Com_sprintf( skinName, sizeof( skinName ), "models/players/%s/model_%s.skin", modelName, customSkin );
1295 	}
1296 	gi.RE_RegisterSkin( skinName );
1297 	//now generate the ghoul2 model this client should be.
1298 	//NOTE: for some reason, it still loads the default skin's tga's?  Because they're referenced in the .glm?
1299 	ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName),
1300 		G_ModelIndex( va("models/players/%s/model.glm", modelName) ), G_SkinIndex( skinName ), NULL_HANDLE, 0, 0 );
1301 	if (ent->playerModel == -1)
1302 	{//try the stormtrooper as a default
1303 		modelName = "stormtrooper";
1304 		ent->playerModel = gi.G2API_InitGhoul2Model( ent->ghoul2, va("models/players/%s/model.glm", modelName),
1305 			G_ModelIndex( va("models/players/%s/model.glm", modelName) ), NULL_HANDLE, NULL_HANDLE, 0, 0 );
1306 	}
1307 
1308 	if ( !Q_stricmp( "kyle", modelName ))
1309 	{
1310 		// Try to get the skin we'll use when we switch to the first person light saber.
1311 		//	We use a new skin to disable certain surfaces so they are not drawn but we can still collide against them
1312 		int skin = gi.RE_RegisterSkin( "models/players/kyle/model_fpls.skin" );
1313 		if ( skin )
1314 		{
1315 			// put it in the config strings
1316 			G_SkinIndex( skinName );
1317 		}
1318 	}
1319 
1320 	// did we find a ghoul2 model? if so, load the animation.cfg file
1321 	if ( !G_SetG2PlayerModelInfo( ent, modelName, customSkin, surfOff, surfOn ) )
1322 	{//couldn't set g2 info, fall back to a mouse md3
1323 		NPC_ParseParms( "mouse", ent );
1324 		//Com_Error( ERR_DROP, "couldn't load playerModel %s!\n", va("models/players/%s/model.glm", modelName) );
1325 		Com_Printf( S_COLOR_RED"couldn't load playerModel %s!\n", va("models/players/%s/model.glm", modelName) );
1326 	}
1327 
1328 }
1329 /*
1330 Ghoul2 Insert End
1331 */
1332 
G_ActivatePersonalShield(gentity_t * ent)1333 void G_ActivatePersonalShield( gentity_t *ent )
1334 {
1335 	ent->client->ps.stats[STAT_ARMOR] = 100;//FIXME: define?
1336 	ent->client->ps.powerups[PW_BATTLESUIT] = Q3_INFINITE;//Doesn't go away until armor does
1337 }
1338 
1339 //HACK FOR FLYING
1340 extern void CG_ChangeWeapon( int num );
G_PilotXWing(gentity_t * ent)1341 void G_PilotXWing( gentity_t *ent )
1342 {
1343 	if ( !CheatsOk( ent ) )
1344 	{
1345 		return;
1346 	}
1347 	if ( ent->client->ps.vehicleModel != 0 )
1348 	{
1349 		CG_ChangeWeapon( WP_SABER );
1350 		ent->client->ps.vehicleModel = 0;
1351 		ent->svFlags &= ~SVF_CUSTOM_GRAVITY;
1352 		ent->client->ps.stats[STAT_ARMOR] = 0;//HACK
1353 		//ent->mass = 10;
1354 		//gi.cvar_set( "m_pitchOverride", "0" );
1355 		//gi.cvar_set( "m_yawOverride", "0" );
1356 		if ( ent->client->ps.weapon != WP_SABER )
1357 		{
1358 			gi.cvar_set( "cg_thirdperson", "0" );
1359 		}
1360 		cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
1361 		cg.overrides.thirdPersonRange = 240;
1362 		cg.overrides.active &= ~CG_OVERRIDE_FOV;
1363 		cg.overrides.fov = 0;
1364 	}
1365 	else
1366 	{
1367 		ent->client->ps.vehicleModel = G_ModelIndex( "models/map_objects/ships/x_wing.md3" );
1368 
1369 		ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ATST_SIDE );
1370 		ent->client->ps.ammo[weaponData[WP_ATST_SIDE].ammoIndex] = ammoData[weaponData[WP_ATST_SIDE].ammoIndex].max;
1371 		gitem_t *item = FindItemForWeapon( WP_ATST_SIDE );
1372 		RegisterItem( item );	//make sure the weapon is cached in case this runs at startup
1373 		G_AddEvent( ent, EV_ITEM_PICKUP, (item - bg_itemlist) );
1374 		CG_ChangeWeapon( WP_ATST_SIDE );
1375 
1376 		ent->client->ps.gravity = 0;
1377 		ent->svFlags |= SVF_CUSTOM_GRAVITY;
1378 		ent->client->ps.stats[STAT_ARMOR] = 200;//FIXME: define?
1379 		//ent->mass = 300;
1380 		ent->client->ps.speed = 0;
1381 		//gi.cvar_set( "m_pitchOverride", "0.01" );//ignore inverse mouse look
1382 		//gi.cvar_set( "m_yawOverride", "0.0075" );
1383 		gi.cvar_set( "cg_thirdperson", "1" );
1384 		cg.overrides.active |= (CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_FOV);
1385 		cg.overrides.thirdPersonRange = 240;
1386 		cg.overrides.fov = 100;
1387 	}
1388 }
1389 //HACK FOR FLYING
1390 
1391 //HACK FOR ATST
G_DrivableATSTDie(gentity_t * self)1392 void G_DrivableATSTDie( gentity_t *self )
1393 {
1394 }
1395 
G_DriveATST(gentity_t * ent,gentity_t * atst)1396 void G_DriveATST( gentity_t *ent, gentity_t *atst )
1397 {
1398 	if ( ent->NPC_type && ent->client && (ent->client->NPC_class == CLASS_ATST) )
1399 	{//already an atst, switch back
1400 		//open hatch
1401 		if ( ent->playerModel >= 0 )
1402 		{
1403 			gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->playerModel );
1404 		}
1405 		ent->NPC_type = "kyle";
1406 		ent->client->NPC_class = CLASS_KYLE;
1407 		ent->flags &= ~FL_SHIELDED;
1408 		ent->client->ps.eFlags &= ~EF_IN_ATST;
1409 		//size
1410 		VectorCopy( playerMins, ent->mins );
1411 		VectorCopy( playerMaxs, ent->maxs );
1412 		ent->client->crouchheight = CROUCH_MAXS_2;
1413 		ent->client->standheight = DEFAULT_MAXS_2;
1414 		G_SetG2PlayerModel( ent, "kyle", NULL, NULL, NULL );
1415 		//FIXME: reset/initialize their weapon
1416 		ent->client->ps.stats[STAT_WEAPONS] &= ~(( 1 << WP_ATST_MAIN )|( 1 << WP_ATST_SIDE ));
1417 		ent->client->ps.ammo[weaponData[WP_ATST_MAIN].ammoIndex] = 0;
1418 		ent->client->ps.ammo[weaponData[WP_ATST_SIDE].ammoIndex] = 0;
1419 		CG_ChangeWeapon( WP_BRYAR_PISTOL );
1420 		//camera
1421 		//if ( ent->client->ps.weapon != WP_SABER )
1422 		{
1423 			gi.cvar_set( "cg_thirdperson", "0" );
1424 		}
1425 		cg.overrides.active &= ~(CG_OVERRIDE_3RD_PERSON_RNG|CG_OVERRIDE_3RD_PERSON_VOF|CG_OVERRIDE_3RD_PERSON_POF|CG_OVERRIDE_3RD_PERSON_APH);
1426 		cg.overrides.thirdPersonRange = cg.overrides.thirdPersonVertOffset = cg.overrides.thirdPersonPitchOffset = 0;
1427 		cg.overrides.thirdPersonAlpha = cg_thirdPersonAlpha.value;
1428 		ent->client->ps.viewheight = ent->maxs[2] + STANDARD_VIEWHEIGHT_OFFSET;
1429 		//ent->mass = 10;
1430 	}
1431 	else
1432 	{//become an atst
1433 		ent->NPC_type = "atst";
1434 		ent->client->NPC_class = CLASS_ATST;
1435 		ent->client->ps.eFlags |= EF_IN_ATST;
1436 		ent->flags |= FL_SHIELDED;
1437 		//size
1438 		VectorSet( ent->mins, ATST_MINS0, ATST_MINS1, ATST_MINS2 );
1439 		VectorSet( ent->maxs, ATST_MAXS0, ATST_MAXS1, ATST_MAXS2 );
1440 		ent->client->crouchheight = ATST_MAXS2;
1441 		ent->client->standheight = ATST_MAXS2;
1442 		if ( ent->playerModel >= 0 )
1443 		{
1444 			gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->playerModel );
1445 			ent->playerModel = -1;
1446 		}
1447 		if ( ent->weaponModel >= 0 )
1448 		{
1449 			gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel );
1450 			ent->weaponModel = -1;
1451 		}
1452 		if ( !atst )
1453 		{//no ent to copy from
1454 			G_SetG2PlayerModel( ent, "atst", NULL, NULL, NULL );
1455 			NPC_SetAnim( ent, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE );
1456 		}
1457 		else
1458 		{
1459 			gi.G2API_CopyGhoul2Instance( atst->ghoul2, ent->ghoul2, -1 );
1460 			ent->playerModel = 0;
1461 			G_SetG2PlayerModelInfo( ent, "atst", NULL, NULL, NULL );
1462 			//turn off hatch underside
1463 			gi.G2API_SetSurfaceOnOff( &ent->ghoul2[ent->playerModel], "head_hatchcover_off", 0x00000002/*G2SURFACEFLAG_OFF*/ );
1464 			G_Sound( ent, G_SoundIndex( "sound/chars/atst/atst_hatch_close" ));
1465 		}
1466 		ent->s.radius = 320;
1467 		//weapon
1468 		gitem_t	*item = FindItemForWeapon( WP_ATST_MAIN );	//precache the weapon
1469 		CG_RegisterItemSounds( (item-bg_itemlist) );
1470 		CG_RegisterItemVisuals( (item-bg_itemlist) );
1471 		item = FindItemForWeapon( WP_ATST_SIDE );	//precache the weapon
1472 		CG_RegisterItemSounds( (item-bg_itemlist) );
1473 		CG_RegisterItemVisuals( (item-bg_itemlist) );
1474 		ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ATST_MAIN )|( 1 << WP_ATST_SIDE );
1475 		ent->client->ps.ammo[weaponData[WP_ATST_MAIN].ammoIndex] = ammoData[weaponData[WP_ATST_MAIN].ammoIndex].max;
1476 		ent->client->ps.ammo[weaponData[WP_ATST_SIDE].ammoIndex] = ammoData[weaponData[WP_ATST_SIDE].ammoIndex].max;
1477 		CG_ChangeWeapon( WP_ATST_MAIN );
1478 		//HACKHACKHACKTEMP
1479 		item = FindItemForWeapon( WP_EMPLACED_GUN );
1480 		CG_RegisterItemSounds( (item-bg_itemlist) );
1481 		CG_RegisterItemVisuals( (item-bg_itemlist) );
1482 		item = FindItemForWeapon( WP_ROCKET_LAUNCHER );
1483 		CG_RegisterItemSounds( (item-bg_itemlist) );
1484 		CG_RegisterItemVisuals( (item-bg_itemlist) );
1485 		item = FindItemForWeapon( WP_BOWCASTER );
1486 		CG_RegisterItemSounds( (item-bg_itemlist) );
1487 		CG_RegisterItemVisuals( (item-bg_itemlist) );
1488 		//HACKHACKHACKTEMP
1489 		//FIXME: these get lost in load/save!  Must use variables that are set every frame or saved/loaded
1490 		//camera
1491 		gi.cvar_set( "cg_thirdperson", "1" );
1492 		cg.overrides.active |= CG_OVERRIDE_3RD_PERSON_RNG;
1493 		cg.overrides.thirdPersonRange = 240;
1494 		//cg.overrides.thirdPersonVertOffset = 100;
1495 		//cg.overrides.thirdPersonPitchOffset = -30;
1496 		//FIXME: this gets stomped in pmove?
1497 		ent->client->ps.viewheight = 120;
1498 		//FIXME: setting these broke things very badly...?
1499 		//ent->client->standheight = 200;
1500 		//ent->client->crouchheight = 200;
1501 		//ent->mass = 300;
1502 		//movement
1503 		//ent->client->ps.speed = 0;//FIXME: override speed?
1504 		//FIXME: slow turn turning/can't turn if not moving?
1505 	}
1506 }
1507 //HACK FOR ATST
1508 
1509 /*
1510 ===========
1511 ClientSpawn
1512 
1513 Called every time a client is placed fresh in the world:
1514 after the first ClientBegin, and after each respawn
1515 Initializes all non-persistant parts of playerState
1516 ============
1517 */
1518 
ClientSpawn(gentity_t * ent,SavedGameJustLoaded_e eSavedGameJustLoaded)1519 qboolean ClientSpawn(gentity_t *ent, SavedGameJustLoaded_e eSavedGameJustLoaded )
1520 {
1521 	int		index;
1522 	vec3_t	spawn_origin, spawn_angles;
1523 	gclient_t	*client;
1524 	int		i;
1525 	clientPersistant_t	saved;
1526 	clientSession_t		savedSess;
1527 	clientInfo_t		savedCi;
1528 	int		persistant[MAX_PERSISTANT];
1529 	usercmd_t	ucmd;
1530 	gentity_t	*spawnPoint;
1531 	qboolean	beamInEffect = qfalse;
1532 	extern qboolean g_qbLoadTransition;
1533 
1534 	index = ent - g_entities;
1535 	client = ent->client;
1536 
1537 	if ( eSavedGameJustLoaded == eFULL && g_qbLoadTransition == qfalse )//qbFromSavedGame)
1538 	{
1539 		ent->client->pers.teamState.state = TEAM_ACTIVE;
1540 
1541 		// increment the spawncount so the client will detect the respawn
1542 		client->ps.persistant[PERS_SPAWN_COUNT]++;
1543 		client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
1544 
1545 		client->airOutTime = level.time + 12000;
1546 
1547 		for (i=0; i<3; i++)
1548 		{
1549 			ent->client->pers.cmd_angles[i] = 0.0f;
1550 		}
1551 
1552 		SetClientViewAngle( ent, ent->client->ps.viewangles);//spawn_angles );
1553 
1554 		gi.linkentity (ent);
1555 
1556 		// run the presend to set anything else
1557 		ClientEndFrame( ent );
1558 
1559 		// clear entity state values
1560 		PlayerStateToEntityState( &client->ps, &ent->s );
1561 
1562 		if ( ent->client->NPC_class == CLASS_ATST )
1563 		{
1564 			G_LoadAnimFileSet( ent, "atst" );
1565 			G_SetSkin( ent, "atst", NULL );
1566 		}
1567 		else
1568 		{
1569 			G_LoadAnimFileSet( ent, "kyle" );
1570 			G_SetSkin( ent, "kyle", NULL );
1571 		}
1572 	}
1573 	else
1574 	{
1575 		// find a spawn point
1576 		// do it before setting health back up, so farthest
1577 		// ranging doesn't count this client
1578 		// don't spawn near existing origin if possible
1579 		spawnPoint = SelectSpawnPoint ( ent->client->ps.origin,
1580 			(team_t) ent->client->ps.persistant[PERS_TEAM], spawn_origin, spawn_angles);
1581 
1582 		ent->client->pers.teamState.state = TEAM_ACTIVE;
1583 
1584 		// clear everything but the persistant data
1585 		saved = client->pers;
1586 		savedSess = client->sess;
1587 		for ( i = 0 ; i < MAX_PERSISTANT ; i++ )
1588 		{
1589 			persistant[i] = client->ps.persistant[i];
1590 		}
1591 		//Preserve clientInfo
1592 		memcpy (&savedCi, &client->clientInfo, sizeof(clientInfo_t));
1593 
1594 		memset (client, 0, sizeof(*client));
1595 
1596 		memcpy (&client->clientInfo, &savedCi, sizeof(clientInfo_t));
1597 
1598 		client->pers = saved;
1599 		client->sess = savedSess;
1600 		for ( i = 0 ; i < MAX_PERSISTANT ; i++ )
1601 		{
1602 			client->ps.persistant[i] = persistant[i];
1603 		}
1604 
1605 		// increment the spawncount so the client will detect the respawn
1606 		client->ps.persistant[PERS_SPAWN_COUNT]++;
1607 		client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
1608 
1609 		client->airOutTime = level.time + 12000;
1610 
1611 		// clear entity values
1612 		client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
1613 		ent->s.groundEntityNum = ENTITYNUM_NONE;
1614 		ent->client = &level.clients[index];
1615 		ent->mass = 10;
1616 		ent->takedamage = qtrue;
1617 		ent->inuse = qtrue;
1618 		SetInUse(ent);
1619 		ent->classname = "player";
1620 		client->squadname = ent->targetname = ent->script_targetname = ent->NPC_type = "kyle";
1621 		if ( ent->client->NPC_class == CLASS_NONE )
1622 		{
1623 			ent->client->NPC_class = CLASS_KYLE;
1624 		}
1625 		client->playerTeam = TEAM_PLAYER;
1626 		client->enemyTeam = TEAM_ENEMY;
1627 		ent->contents = CONTENTS_BODY;
1628 		ent->clipmask = MASK_PLAYERSOLID;
1629 		ent->e_DieFunc = dieF_player_die;
1630 		ent->waterlevel = 0;
1631 		ent->watertype = 0;
1632 		client->ps.friction = 6;
1633 		client->ps.gravity = g_gravity->value;
1634 		ent->flags &= ~FL_NO_KNOCKBACK;
1635 		client->renderInfo.lookTarget = ENTITYNUM_NONE;
1636 		client->renderInfo.lookTargetClearTime = 0;
1637 		client->renderInfo.lookMode = LM_ENT;
1638 
1639 		VectorCopy (playerMins, ent->mins);
1640 		VectorCopy (playerMaxs, ent->maxs);
1641 		client->crouchheight = CROUCH_MAXS_2;
1642 		client->standheight = DEFAULT_MAXS_2;
1643 
1644 		client->ps.clientNum = index;
1645 
1646 		// give default weapons
1647 		client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
1648 		client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );	//these are precached in g_items, ClearRegisteredItems()
1649 		client->ps.inventory[INV_ELECTROBINOCULARS] = 1;
1650 
1651 		// always give the bryar pistol, but we have to give EITHER the saber or the stun baton..never both
1652 		if ( spawnPoint->spawnflags & 32 ) // STUN_BATON
1653 		{
1654 			client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_STUN_BATON );
1655 		}
1656 		else
1657 		{	// give the saber AND the blaster because most test maps will not have the STUN BATON flag set
1658 			client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER );	//this is precached in SP_info_player_deathmatch
1659 		}
1660 
1661 		for ( i = 0; i < AMMO_THERMAL; i++ ) // don't give ammo for explosives
1662 		{
1663 			client->ps.ammo[i] = ammoData[i].max;
1664 		}
1665 
1666 		client->ps.saberColor = SABER_BLUE;
1667 		client->ps.saberActive = qfalse;
1668 		client->ps.saberLength = 0;
1669 		//Initialize force powers
1670 		WP_InitForcePowers( ent );
1671 		//
1672 
1673 		ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];
1674 		ent->client->dismemberProbHead = 0;
1675 		ent->client->dismemberProbArms = 5;
1676 		ent->client->dismemberProbHands = 20;
1677 		ent->client->dismemberProbWaist = 0;
1678 		ent->client->dismemberProbLegs = 0;
1679 
1680 		ent->client->ps.batteryCharge = 2500;
1681 
1682 		VectorCopy( spawn_origin, client->ps.origin );
1683 		VectorCopy( spawn_origin, ent->currentOrigin );
1684 
1685 		// the respawned flag will be cleared after the attack and jump keys come up
1686 		client->ps.pm_flags |= PMF_RESPAWNED;
1687 
1688 		SetClientViewAngle( ent, spawn_angles );
1689 
1690 		{
1691 			G_KillBox( ent );
1692 			gi.linkentity (ent);
1693 			// force the base weapon up
1694 			client->ps.weapon = WP_BRYAR_PISTOL;
1695 			client->ps.weaponstate = WEAPON_READY;
1696 		}
1697 
1698 		// don't allow full run speed for a bit
1699 		client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
1700 		client->ps.pm_time = 100;
1701 
1702 		client->respawnTime = level.time;
1703 		client->inactivityTime = level.time + g_inactivity->integer * 1000;
1704 		client->latched_buttons = 0;
1705 
1706 		// set default animations
1707 		client->ps.torsoAnim = BOTH_STAND2;
1708 		client->ps.legsAnim = BOTH_STAND2;
1709 
1710 		// restore some player data if this is a spawn point with KEEP_REV (spawnflags&1) set...
1711 		//
1712 		if ( eSavedGameJustLoaded == eAUTO ||
1713 			(spawnPoint->spawnflags&1) ||		// KEEP_PREV
1714 			g_qbLoadTransition == qtrue )
1715 		{
1716 			Player_RestoreFromPrevLevel(ent);
1717 		}
1718 
1719 
1720 		/*
1721 		Ghoul2 Insert Start
1722 		*/
1723 
1724 		if (eSavedGameJustLoaded == eNO)
1725 		{
1726 			ent->weaponModel = -1;
1727 			G_SetG2PlayerModel( ent, "kyle", NULL, NULL, NULL );
1728 		}
1729 		else
1730 		{
1731 			if ( ent->client->NPC_class == CLASS_ATST )
1732 			{
1733 				G_LoadAnimFileSet( ent, "atst" );
1734 				G_SetSkin( ent, "atst", NULL );
1735 			}
1736 			else
1737 			{
1738 				G_LoadAnimFileSet( ent, "kyle" );
1739 				G_SetSkin( ent, "kyle", NULL );
1740 			}
1741 		}
1742 		/*
1743 		Ghoul2 Insert End
1744 		*/
1745 
1746 		// run a client frame to drop exactly to the floor,
1747 		// initialize animations and other things
1748 		client->ps.commandTime = level.time - 100;
1749 		ucmd = client->pers.lastCommand;
1750 		ucmd.serverTime = level.time;
1751 		VectorCopyM( client->pers.cmd_angles, ucmd.angles );
1752 		ucmd.weapon = client->ps.weapon;	// client think calls Pmove which sets the client->ps.weapon to ucmd.weapon, so ...
1753 		ent->client->ps.groundEntityNum = ENTITYNUM_NONE;
1754 		ClientThink( ent-g_entities, &ucmd );
1755 
1756 		// run the presend to set anything else
1757 		ClientEndFrame( ent );
1758 
1759 		// clear entity state values
1760 		PlayerStateToEntityState( &client->ps, &ent->s );
1761 
1762 		//ICARUS include
1763 		ICARUS_FreeEnt( ent );	//FIXME: This shouldn't need to be done...?
1764 		ICARUS_InitEnt( ent );
1765 
1766 		if ( spawnPoint->spawnflags & 64 )
1767 		{//player starts with absolutely no weapons
1768 			ent->client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
1769 			ent->client->ps.ammo[weaponData[WP_NONE].ammoIndex] = 32000;	// checkme
1770 			ent->client->ps.weapon = WP_NONE;
1771 			ent->client->ps.weaponstate = WEAPON_READY;
1772 		}
1773 
1774 		if ( ent->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_SABER ) )
1775 		{//set up so has lightsaber
1776 			WP_SaberInitBladeData( ent );
1777 			if ( ent->weaponModel == -1 && ent->client->ps.weapon == WP_SABER )
1778 			{
1779 				G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saberModel );
1780 			}
1781 		}
1782 		if ( ent->weaponModel == -1 && ent->client->ps.weapon != WP_NONE )
1783 		{
1784 			G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl );
1785 		}
1786 
1787 		{
1788 			// fire the targets of the spawn point
1789 			G_UseTargets( spawnPoint, ent );
1790 			//Designers needed them to fire off target2's as well... this is kind of messy
1791 			G_UseTargets2( spawnPoint, ent, spawnPoint->target2 );
1792 
1793 			/*
1794 			// select the highest weapon number available, after any
1795 			// spawn given items have fired
1796 			client->ps.weapon = 1;
1797 			for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
1798 				if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
1799 					client->ps.weapon = i;
1800 					break;
1801 				}
1802 			}*/
1803 		}
1804 	}
1805 
1806 	client->pers.enterTime = level.time;//needed mainly to stop the weapon switch to WP_NONE that happens on loads
1807 	ent->max_health = client->ps.stats[STAT_MAX_HEALTH];
1808 
1809 	if ( eSavedGameJustLoaded == eNO )
1810 	{//on map transitions, Ghoul2 frame gets reset to zero, restart our anim
1811 		NPC_SetAnim( ent, SETANIM_LEGS, ent->client->ps.legsAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
1812 		NPC_SetAnim( ent, SETANIM_TORSO, ent->client->ps.torsoAnim, SETANIM_FLAG_NORMAL|SETANIM_FLAG_RESTART );
1813 	}
1814 	return beamInEffect;
1815 }
1816 
1817 
1818 /*
1819 ===========
1820 ClientDisconnect
1821 
1822 Called when a player drops from the server.
1823 Will not be called between levels.
1824 ============
1825 */
ClientDisconnect(int clientNum)1826 void ClientDisconnect( int clientNum ) {
1827 	gentity_t	*ent;
1828 
1829 	ent = g_entities + clientNum;
1830 	if ( !ent->client ) {
1831 		return;
1832 	}
1833 
1834 	// send effect if they were completely connected
1835 /*	if ( ent->client->pers.connected == CON_CONNECTED ) {
1836 		// They don't get to take powerups with them!
1837 		// Especially important for stuff like CTF flags
1838 		TossClientItems ( ent );
1839 	}
1840 */
1841 	gi.unlinkentity (ent);
1842 	ent->s.modelindex = 0;
1843 	ent->inuse = qfalse;
1844 	ClearInUse(ent);
1845 	ent->classname = "disconnected";
1846 	ent->client->pers.connected = CON_DISCONNECTED;
1847 	ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
1848 
1849 	gi.SetConfigstring( CS_PLAYERS + clientNum, "");
1850 
1851 }
1852 
1853 
1854