1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 Copyright (C) 2000-2006 Tim Angus
5 
6 This file is part of Tremulous.
7 
8 Tremulous is free software; you can redistribute it
9 and/or modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the License,
11 or (at your option) any later version.
12 
13 Tremulous is distributed in the hope that it will be
14 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with Tremulous; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21 ===========================================================================
22 */
23 
24 #include "g_local.h"
25 
26 // g_client.c -- client functions that don't happen every frame
27 
28 static vec3_t playerMins = {-15, -15, -24};
29 static vec3_t playerMaxs = {15, 15, 32};
30 
31 /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
32 potential spawning position for deathmatch games.
33 The first time a player enters the game, they will be at an 'initial' spot.
34 Targets will be fired when someone spawns in on them.
35 "nobots" will prevent bots from using this spot.
36 "nohumans" will prevent non-bots from using this spot.
37 */
SP_info_player_deathmatch(gentity_t * ent)38 void SP_info_player_deathmatch( gentity_t *ent )
39 {
40   int   i;
41 
42   G_SpawnInt( "nobots", "0", &i);
43 
44   if( i )
45     ent->flags |= FL_NO_BOTS;
46 
47   G_SpawnInt( "nohumans", "0", &i );
48   if( i )
49     ent->flags |= FL_NO_HUMANS;
50 }
51 
52 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
53 equivelant to info_player_deathmatch
54 */
SP_info_player_start(gentity_t * ent)55 void SP_info_player_start( gentity_t *ent )
56 {
57   ent->classname = "info_player_deathmatch";
58   SP_info_player_deathmatch( ent );
59 }
60 
61 /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
62 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
63 */
SP_info_player_intermission(gentity_t * ent)64 void SP_info_player_intermission( gentity_t *ent )
65 {
66 }
67 
68 /*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32)
69 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
70 */
SP_info_alien_intermission(gentity_t * ent)71 void SP_info_alien_intermission( gentity_t *ent )
72 {
73 }
74 
75 /*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32)
76 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
77 */
SP_info_human_intermission(gentity_t * ent)78 void SP_info_human_intermission( gentity_t *ent )
79 {
80 }
81 
82 /*
83 ===============
84 G_AddCreditToClient
85 ===============
86 */
G_AddCreditToClient(gclient_t * client,short credit,qboolean cap)87 void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap )
88 {
89   if( !client )
90     return;
91 
92   //if we're already at the max and trying to add credit then stop
93   if( cap )
94   {
95     if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
96     {
97       if( client->ps.persistant[ PERS_CREDIT ] >= ALIEN_MAX_KILLS &&
98           credit > 0 )
99         return;
100     }
101     else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
102     {
103       if( client->ps.persistant[ PERS_CREDIT ] >= HUMAN_MAX_CREDITS &&
104           credit > 0 )
105         return;
106     }
107   }
108 
109   client->ps.persistant[ PERS_CREDIT ] += credit;
110 
111   if( cap )
112   {
113     if( client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
114     {
115       if( client->ps.persistant[ PERS_CREDIT ] > ALIEN_MAX_KILLS )
116         client->ps.persistant[ PERS_CREDIT ] = ALIEN_MAX_KILLS;
117     }
118     else if( client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
119     {
120       if( client->ps.persistant[ PERS_CREDIT ] > HUMAN_MAX_CREDITS )
121         client->ps.persistant[ PERS_CREDIT ] = HUMAN_MAX_CREDITS;
122     }
123   }
124 
125   if( client->ps.persistant[ PERS_CREDIT ] < 0 )
126     client->ps.persistant[ PERS_CREDIT ] = 0;
127 }
128 
129 
130 /*
131 =======================================================================
132 
133   SelectSpawnPoint
134 
135 =======================================================================
136 */
137 
138 /*
139 ================
140 SpotWouldTelefrag
141 
142 ================
143 */
SpotWouldTelefrag(gentity_t * spot)144 qboolean SpotWouldTelefrag( gentity_t *spot )
145 {
146   int       i, num;
147   int       touch[ MAX_GENTITIES ];
148   gentity_t *hit;
149   vec3_t    mins, maxs;
150 
151   VectorAdd( spot->s.origin, playerMins, mins );
152   VectorAdd( spot->s.origin, playerMaxs, maxs );
153   num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
154 
155   for( i = 0; i < num; i++ )
156   {
157     hit = &g_entities[ touch[ i ] ];
158     //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
159     if( hit->client )
160       return qtrue;
161   }
162 
163   return qfalse;
164 }
165 
166 /*
167 ================
168 SelectNearestDeathmatchSpawnPoint
169 
170 Find the spot that we DON'T want to use
171 ================
172 */
173 #define MAX_SPAWN_POINTS  128
SelectNearestDeathmatchSpawnPoint(vec3_t from)174 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from )
175 {
176   gentity_t *spot;
177   vec3_t    delta;
178   float     dist, nearestDist;
179   gentity_t *nearestSpot;
180 
181   nearestDist = 999999;
182   nearestSpot = NULL;
183   spot = NULL;
184 
185   while( (spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
186   {
187     VectorSubtract( spot->s.origin, from, delta );
188     dist = VectorLength( delta );
189 
190     if( dist < nearestDist )
191     {
192       nearestDist = dist;
193       nearestSpot = spot;
194     }
195   }
196 
197   return nearestSpot;
198 }
199 
200 
201 /*
202 ================
203 SelectRandomDeathmatchSpawnPoint
204 
205 go to a random point that doesn't telefrag
206 ================
207 */
208 #define MAX_SPAWN_POINTS  128
SelectRandomDeathmatchSpawnPoint(void)209 gentity_t *SelectRandomDeathmatchSpawnPoint( void )
210 {
211   gentity_t *spot;
212   int       count;
213   int       selection;
214   gentity_t *spots[ MAX_SPAWN_POINTS ];
215 
216   count = 0;
217   spot = NULL;
218 
219   while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
220   {
221     if( SpotWouldTelefrag( spot ) )
222       continue;
223 
224     spots[ count ] = spot;
225     count++;
226   }
227 
228   if( !count ) // no spots that won't telefrag
229     return G_Find( NULL, FOFS( classname ), "info_player_deathmatch" );
230 
231   selection = rand( ) % count;
232   return spots[ selection ];
233 }
234 
235 
236 /*
237 ===========
238 SelectRandomFurthestSpawnPoint
239 
240 Chooses a player start, deathmatch start, etc
241 ============
242 */
SelectRandomFurthestSpawnPoint(vec3_t avoidPoint,vec3_t origin,vec3_t angles)243 gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
244 {
245   gentity_t *spot;
246   vec3_t    delta;
247   float     dist;
248   float     list_dist[ 64 ];
249   gentity_t *list_spot[ 64 ];
250   int       numSpots, rnd, i, j;
251 
252   numSpots = 0;
253   spot = NULL;
254 
255   while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
256   {
257     if( SpotWouldTelefrag( spot ) )
258       continue;
259 
260     VectorSubtract( spot->s.origin, avoidPoint, delta );
261     dist = VectorLength( delta );
262 
263     for( i = 0; i < numSpots; i++ )
264     {
265       if( dist > list_dist[ i ] )
266       {
267         if( numSpots >= 64 )
268           numSpots = 64 - 1;
269 
270         for( j = numSpots; j > i; j-- )
271         {
272           list_dist[ j ] = list_dist[ j - 1 ];
273           list_spot[ j ] = list_spot[ j - 1 ];
274         }
275 
276         list_dist[ i ] = dist;
277         list_spot[ i ] = spot;
278         numSpots++;
279 
280         if( numSpots > 64 )
281           numSpots = 64;
282 
283         break;
284       }
285     }
286 
287     if( i >= numSpots && numSpots < 64 )
288     {
289       list_dist[ numSpots ] = dist;
290       list_spot[ numSpots ] = spot;
291       numSpots++;
292     }
293   }
294 
295   if( !numSpots )
296   {
297     spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" );
298 
299     if( !spot )
300       G_Error( "Couldn't find a spawn point" );
301 
302     VectorCopy( spot->s.origin, origin );
303     origin[ 2 ] += 9;
304     VectorCopy( spot->s.angles, angles );
305     return spot;
306   }
307 
308   // select a random spot from the spawn points furthest away
309   rnd = random( ) * ( numSpots / 2 );
310 
311   VectorCopy( list_spot[ rnd ]->s.origin, origin );
312   origin[ 2 ] += 9;
313   VectorCopy( list_spot[ rnd ]->s.angles, angles );
314 
315   return list_spot[ rnd ];
316 }
317 
318 
319 /*
320 ================
321 SelectAlienSpawnPoint
322 
323 go to a random point that doesn't telefrag
324 ================
325 */
SelectAlienSpawnPoint(vec3_t preference)326 gentity_t *SelectAlienSpawnPoint( vec3_t preference )
327 {
328   gentity_t *spot;
329   int       count;
330   gentity_t *spots[ MAX_SPAWN_POINTS ];
331 
332   if( level.numAlienSpawns <= 0 )
333     return NULL;
334 
335   count = 0;
336   spot = NULL;
337 
338   while( ( spot = G_Find( spot, FOFS( classname ),
339     BG_FindEntityNameForBuildable( BA_A_SPAWN ) ) ) != NULL )
340   {
341     if( !spot->spawned )
342       continue;
343 
344     if( spot->health <= 0 )
345       continue;
346 
347     if( !spot->s.groundEntityNum )
348       continue;
349 
350     if( spot->clientSpawnTime > 0 )
351       continue;
352 
353     if( G_CheckSpawnPoint( spot->s.number, spot->s.origin,
354           spot->s.origin2, BA_A_SPAWN, NULL ) != NULL )
355       continue;
356 
357     spots[ count ] = spot;
358     count++;
359   }
360 
361   if( !count )
362     return NULL;
363 
364   return G_ClosestEnt( preference, spots, count );
365 }
366 
367 
368 /*
369 ================
370 SelectHumanSpawnPoint
371 
372 go to a random point that doesn't telefrag
373 ================
374 */
SelectHumanSpawnPoint(vec3_t preference)375 gentity_t *SelectHumanSpawnPoint( vec3_t preference )
376 {
377   gentity_t *spot;
378   int       count;
379   gentity_t *spots[ MAX_SPAWN_POINTS ];
380 
381   if( level.numHumanSpawns <= 0 )
382     return NULL;
383 
384   count = 0;
385   spot = NULL;
386 
387   while( ( spot = G_Find( spot, FOFS( classname ),
388     BG_FindEntityNameForBuildable( BA_H_SPAWN ) ) ) != NULL )
389   {
390     if( !spot->spawned )
391       continue;
392 
393     if( spot->health <= 0 )
394       continue;
395 
396     if( !spot->s.groundEntityNum )
397       continue;
398 
399     if( spot->clientSpawnTime > 0 )
400       continue;
401 
402     if( G_CheckSpawnPoint( spot->s.number, spot->s.origin,
403           spot->s.origin2, BA_H_SPAWN, NULL ) != NULL )
404       continue;
405 
406     spots[ count ] = spot;
407     count++;
408   }
409 
410   if( !count )
411     return NULL;
412 
413   return G_ClosestEnt( preference, spots, count );
414 }
415 
416 
417 /*
418 ===========
419 SelectSpawnPoint
420 
421 Chooses a player start, deathmatch start, etc
422 ============
423 */
SelectSpawnPoint(vec3_t avoidPoint,vec3_t origin,vec3_t angles)424 gentity_t *SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles )
425 {
426   return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
427 }
428 
429 
430 /*
431 ===========
432 SelectTremulousSpawnPoint
433 
434 Chooses a player start, deathmatch start, etc
435 ============
436 */
SelectTremulousSpawnPoint(pTeam_t team,vec3_t preference,vec3_t origin,vec3_t angles)437 gentity_t *SelectTremulousSpawnPoint( pTeam_t team, vec3_t preference, vec3_t origin, vec3_t angles )
438 {
439   gentity_t *spot = NULL;
440 
441   if( team == PTE_ALIENS )
442     spot = SelectAlienSpawnPoint( preference );
443   else if( team == PTE_HUMANS )
444     spot = SelectHumanSpawnPoint( preference );
445 
446   //no available spots
447   if( !spot )
448     return NULL;
449 
450   if( team == PTE_ALIENS )
451     G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin );
452   else if( team == PTE_HUMANS )
453     G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin );
454 
455   VectorCopy( spot->s.angles, angles );
456   angles[ ROLL ] = 0;
457 
458   return spot;
459 
460 }
461 
462 
463 /*
464 ===========
465 SelectInitialSpawnPoint
466 
467 Try to find a spawn point marked 'initial', otherwise
468 use normal spawn selection.
469 ============
470 */
SelectInitialSpawnPoint(vec3_t origin,vec3_t angles)471 gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles )
472 {
473   gentity_t *spot;
474 
475   spot = NULL;
476   while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL )
477   {
478     if( spot->spawnflags & 1 )
479       break;
480   }
481 
482   if( !spot || SpotWouldTelefrag( spot ) )
483   {
484     return SelectSpawnPoint( vec3_origin, origin, angles );
485   }
486 
487   VectorCopy( spot->s.origin, origin );
488   origin[ 2 ] += 9;
489   VectorCopy( spot->s.angles, angles );
490 
491   return spot;
492 }
493 
494 /*
495 ===========
496 SelectSpectatorSpawnPoint
497 
498 ============
499 */
SelectSpectatorSpawnPoint(vec3_t origin,vec3_t angles)500 gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles )
501 {
502   FindIntermissionPoint( );
503 
504   VectorCopy( level.intermission_origin, origin );
505   VectorCopy( level.intermission_angle, angles );
506 
507   return NULL;
508 }
509 
510 
511 /*
512 ===========
513 SelectAlienLockSpawnPoint
514 
515 Try to find a spawn point for alien intermission otherwise
516 use normal intermission spawn.
517 ============
518 */
SelectAlienLockSpawnPoint(vec3_t origin,vec3_t angles)519 gentity_t *SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles )
520 {
521   gentity_t *spot;
522 
523   spot = NULL;
524   spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" );
525 
526   if( !spot )
527     return SelectSpectatorSpawnPoint( origin, angles );
528 
529   VectorCopy( spot->s.origin, origin );
530   VectorCopy( spot->s.angles, angles );
531 
532   return spot;
533 }
534 
535 
536 /*
537 ===========
538 SelectHumanLockSpawnPoint
539 
540 Try to find a spawn point for human intermission otherwise
541 use normal intermission spawn.
542 ============
543 */
SelectHumanLockSpawnPoint(vec3_t origin,vec3_t angles)544 gentity_t *SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles )
545 {
546   gentity_t *spot;
547 
548   spot = NULL;
549   spot = G_Find( spot, FOFS( classname ), "info_human_intermission" );
550 
551   if( !spot )
552     return SelectSpectatorSpawnPoint( origin, angles );
553 
554   VectorCopy( spot->s.origin, origin );
555   VectorCopy( spot->s.angles, angles );
556 
557   return spot;
558 }
559 
560 
561 /*
562 =======================================================================
563 
564 BODYQUE
565 
566 =======================================================================
567 */
568 
569 
570 /*
571 =============
572 BodySink
573 
574 After sitting around for five seconds, fall into the ground and dissapear
575 =============
576 */
BodySink(gentity_t * ent)577 void BodySink( gentity_t *ent )
578 {
579   //run on first BodySink call
580   if( !ent->active )
581   {
582     ent->active = qtrue;
583 
584     //sinking bodies can't be infested
585     ent->killedBy = ent->s.powerups = MAX_CLIENTS;
586     ent->timestamp = level.time;
587   }
588 
589   if( level.time - ent->timestamp > 6500 )
590   {
591     G_FreeEntity( ent );
592     return;
593   }
594 
595   ent->nextthink = level.time + 100;
596   ent->s.pos.trBase[ 2 ] -= 1;
597 }
598 
599 
600 /*
601 =============
602 BodyFree
603 
604 After sitting around for a while the body becomes a freebie
605 =============
606 */
BodyFree(gentity_t * ent)607 void BodyFree( gentity_t *ent )
608 {
609   ent->killedBy = -1;
610 
611   //if not claimed in the next minute destroy
612   ent->think = BodySink;
613   ent->nextthink = level.time + 60000;
614 }
615 
616 
617 /*
618 =============
619 SpawnCorpse
620 
621 A player is respawning, so make an entity that looks
622 just like the existing corpse to leave behind.
623 =============
624 */
SpawnCorpse(gentity_t * ent)625 void SpawnCorpse( gentity_t *ent )
626 {
627   gentity_t   *body;
628   int         contents;
629   vec3_t      origin, dest;
630   trace_t     tr;
631   float       vDiff;
632 
633   VectorCopy( ent->r.currentOrigin, origin );
634 
635   trap_UnlinkEntity( ent );
636 
637   // if client is in a nodrop area, don't leave the body
638   contents = trap_PointContents( origin, -1 );
639   if( contents & CONTENTS_NODROP )
640     return;
641 
642   body = G_Spawn( );
643 
644   VectorCopy( ent->s.apos.trBase, body->s.angles );
645   body->s.eFlags = EF_DEAD;
646   body->s.eType = ET_CORPSE;
647   body->s.number = body - g_entities;
648   body->timestamp = level.time;
649   body->s.event = 0;
650   body->r.contents = CONTENTS_CORPSE;
651   body->s.clientNum = ent->client->ps.stats[ STAT_PCLASS ];
652   body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL;
653 
654   if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
655     body->classname = "humanCorpse";
656   else
657     body->classname = "alienCorpse";
658 
659   body->s.powerups = MAX_CLIENTS;
660 
661   body->think = BodySink;
662   body->nextthink = level.time + 20000;
663 
664   body->s.legsAnim = ent->s.legsAnim;
665 
666   if( !body->nonSegModel )
667   {
668     switch( body->s.legsAnim & ~ANIM_TOGGLEBIT )
669     {
670       case BOTH_DEATH1:
671       case BOTH_DEAD1:
672         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
673         break;
674       case BOTH_DEATH2:
675       case BOTH_DEAD2:
676         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
677         break;
678       case BOTH_DEATH3:
679       case BOTH_DEAD3:
680       default:
681         body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
682         break;
683     }
684   }
685   else
686   {
687     switch( body->s.legsAnim & ~ANIM_TOGGLEBIT )
688     {
689       case NSPA_DEATH1:
690       case NSPA_DEAD1:
691         body->s.legsAnim = NSPA_DEAD1;
692         break;
693       case NSPA_DEATH2:
694       case NSPA_DEAD2:
695         body->s.legsAnim = NSPA_DEAD2;
696         break;
697       case NSPA_DEATH3:
698       case NSPA_DEAD3:
699       default:
700         body->s.legsAnim = NSPA_DEAD3;
701         break;
702     }
703   }
704 
705   body->takedamage = qfalse;
706 
707   body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ];
708   ent->health = 0;
709 
710   //change body dimensions
711   BG_FindBBoxForClass( ent->client->ps.stats[ STAT_PCLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs );
712   vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ];
713 
714   //drop down to match the *model* origins of ent and body
715   VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff );
716   trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask );
717   VectorCopy( tr.endpos, origin );
718 
719   G_SetOrigin( body, origin );
720   VectorCopy( origin, body->s.origin );
721   body->s.pos.trType = TR_GRAVITY;
722   body->s.pos.trTime = level.time;
723   VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
724 
725   VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
726   trap_LinkEntity( body );
727 }
728 
729 //======================================================================
730 
731 
732 /*
733 ==================
734 SetClientViewAngle
735 
736 ==================
737 */
SetClientViewAngle(gentity_t * ent,vec3_t angle)738 void SetClientViewAngle( gentity_t *ent, vec3_t angle )
739 {
740   int     i;
741 
742   // set the delta angle
743   for( i = 0; i < 3; i++ )
744   {
745     int   cmdAngle;
746 
747     cmdAngle = ANGLE2SHORT( angle[ i ] );
748     ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ];
749   }
750 
751   VectorCopy( angle, ent->s.angles );
752   VectorCopy( ent->s.angles, ent->client->ps.viewangles );
753 }
754 
755 /*
756 ================
757 respawn
758 ================
759 */
respawn(gentity_t * ent)760 void respawn( gentity_t *ent )
761 {
762   SpawnCorpse( ent );
763 
764   //TA: Clients can't respawn - they must go thru the class cmd
765   ClientSpawn( ent, NULL, NULL, NULL );
766 }
767 
768 /*
769 ================
770 TeamCount
771 
772 Returns number of players on a team
773 ================
774 */
TeamCount(int ignoreClientNum,int team)775 team_t TeamCount( int ignoreClientNum, int team )
776 {
777   int   i;
778   int   count = 0;
779 
780   for( i = 0 ; i < level.maxclients ; i++ )
781   {
782     if( i == ignoreClientNum )
783       continue;
784 
785     if( level.clients[ i ].pers.connected == CON_DISCONNECTED )
786       continue;
787 
788     if( level.clients[ i ].sess.sessionTeam == team )
789       count++;
790   }
791 
792   return count;
793 }
794 
795 
796 /*
797 ===========
798 ClientCheckName
799 ============
800 */
ClientCleanName(const char * in,char * out,int outSize)801 static void ClientCleanName( const char *in, char *out, int outSize )
802 {
803   int   len, colorlessLen;
804   char  ch;
805   char  *p;
806   int   spaces;
807 
808   //save room for trailing null byte
809   outSize--;
810 
811   len = 0;
812   colorlessLen = 0;
813   p = out;
814   *p = 0;
815   spaces = 0;
816 
817   while( 1 )
818   {
819     ch = *in++;
820     if( !ch )
821       break;
822 
823     // don't allow leading spaces
824     if( !*p && ch == ' ' )
825       continue;
826 
827     // check colors
828     if( ch == Q_COLOR_ESCAPE )
829     {
830       // solo trailing carat is not a color prefix
831       if( !*in )
832         break;
833 
834       // don't allow black in a name, period
835       if( ColorIndex( *in ) == 0 )
836       {
837         in++;
838         continue;
839       }
840 
841       // make sure room in dest for both chars
842       if( len > outSize - 2 )
843         break;
844 
845       *out++ = ch;
846       *out++ = *in++;
847       len += 2;
848       continue;
849     }
850 
851     // don't allow too many consecutive spaces
852     if( ch == ' ' )
853     {
854       spaces++;
855       if( spaces > 3 )
856         continue;
857     }
858     else
859       spaces = 0;
860 
861     if( len > outSize - 1 )
862       break;
863 
864     *out++ = ch;
865     colorlessLen++;
866     len++;
867   }
868 
869   *out = 0;
870 
871   // don't allow empty names
872   if( *p == 0 || colorlessLen == 0 )
873     Q_strncpyz( p, "UnnamedPlayer", outSize );
874 }
875 
876 
877 /*
878 ======================
879 G_NonSegModel
880 
881 Reads an animation.cfg to check for nonsegmentation
882 ======================
883 */
G_NonSegModel(const char * filename)884 static qboolean G_NonSegModel( const char *filename )
885 {
886   char          *text_p;
887   int           len;
888   char          *token;
889   char          text[ 20000 ];
890   fileHandle_t  f;
891 
892   // load the file
893   len = trap_FS_FOpenFile( filename, &f, FS_READ );
894   if( !f )
895   {
896     G_Printf( "File not found: %s\n", filename );
897     return qfalse;
898   }
899 
900   if( len <= 0 )
901     return qfalse;
902 
903   if( len >= sizeof( text ) - 1 )
904   {
905     G_Printf( "File %s too long\n", filename );
906     return qfalse;
907   }
908 
909   trap_FS_Read( text, len, f );
910   text[ len ] = 0;
911   trap_FS_FCloseFile( f );
912 
913   // parse the text
914   text_p = text;
915 
916   // read optional parameters
917   while( 1 )
918   {
919     token = COM_Parse( &text_p );
920 
921     //EOF
922     if( !token[ 0 ] )
923       break;
924 
925     if( !Q_stricmp( token, "nonsegmented" ) )
926       return qtrue;
927   }
928 
929   return qfalse;
930 }
931 
932 /*
933 ===========
934 ClientUserInfoChanged
935 
936 Called from ClientConnect when the player first connects and
937 directly by the server system when the player updates a userinfo variable.
938 
939 The game can override any of the settings and call trap_SetUserinfo
940 if desired.
941 ============
942 */
ClientUserinfoChanged(int clientNum)943 void ClientUserinfoChanged( int clientNum )
944 {
945   gentity_t *ent;
946   int       teamTask, teamLeader, health;
947   char      *s;
948   char      model[ MAX_QPATH ];
949   char      buffer[ MAX_QPATH ];
950   char      filename[ MAX_QPATH ];
951   char      oldname[ MAX_STRING_CHARS ];
952   gclient_t *client;
953   char      c1[ MAX_INFO_STRING ];
954   char      c2[ MAX_INFO_STRING ];
955   char      redTeam[ MAX_INFO_STRING ];
956   char      blueTeam[ MAX_INFO_STRING ];
957   char      userinfo[ MAX_INFO_STRING ];
958   team_t    team;
959 
960   ent = g_entities + clientNum;
961   client = ent->client;
962 
963   trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
964 
965   // check for malformed or illegal info strings
966   if( !Info_Validate(userinfo) )
967     strcpy( userinfo, "\\name\\badinfo" );
968 
969   // check for local client
970   s = Info_ValueForKey( userinfo, "ip" );
971 
972   if( !strcmp( s, "localhost" ) )
973     client->pers.localClient = qtrue;
974 
975   // check the item prediction
976   s = Info_ValueForKey( userinfo, "cg_predictItems" );
977 
978   if( !atoi( s ) )
979     client->pers.predictItemPickup = qfalse;
980   else
981     client->pers.predictItemPickup = qtrue;
982 
983   // set name
984   Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) );
985   s = Info_ValueForKey( userinfo, "name" );
986   ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) );
987 
988   if( client->sess.sessionTeam == TEAM_SPECTATOR )
989   {
990     if( client->sess.spectatorState == SPECTATOR_SCOREBOARD )
991       Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) );
992   }
993 
994   if( client->pers.connected == CON_CONNECTED )
995   {
996     if( strcmp( oldname, client->pers.netname ) )
997     {
998       G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname,
999         client->pers.netname ) );
1000     }
1001   }
1002 
1003   // set max health
1004   health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
1005   client->pers.maxHealth = health;
1006 
1007   if( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 )
1008     client->pers.maxHealth = 100;
1009 
1010   //hack to force a client update if the config string does not change between spawning
1011   if( client->pers.classSelection == PCL_NONE )
1012     client->pers.maxHealth = 0;
1013 
1014   // set model
1015   if( client->ps.stats[ STAT_PCLASS ] == PCL_HUMAN && BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) )
1016   {
1017     Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( PCL_HUMAN_BSUIT ),
1018                                               BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) );
1019   }
1020   else if( client->pers.classSelection == PCL_NONE )
1021   {
1022     //This looks hacky and frankly it is. The clientInfo string needs to hold different
1023     //model details to that of the spawning class or the info change will not be
1024     //registered and an axis appears instead of the player model. There is zero chance
1025     //the player can spawn with the battlesuit, hence this choice.
1026     Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( PCL_HUMAN_BSUIT ),
1027                                               BG_FindSkinNameForClass( PCL_HUMAN_BSUIT ) );
1028   }
1029   else
1030   {
1031     Com_sprintf( buffer, MAX_QPATH, "%s/%s",  BG_FindModelNameForClass( client->pers.classSelection ),
1032                                               BG_FindSkinNameForClass( client->pers.classSelection ) );
1033   }
1034   Q_strncpyz( model, buffer, sizeof( model ) );
1035 
1036   //don't bother setting model type if spectating
1037   if( client->pers.classSelection != PCL_NONE )
1038   {
1039     //model segmentation
1040     Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg",
1041                  BG_FindModelNameForClass( client->pers.classSelection ) );
1042 
1043     if( G_NonSegModel( filename ) )
1044       client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL;
1045     else
1046       client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL;
1047   }
1048 
1049   // wallwalk follow
1050   s = Info_ValueForKey( userinfo, "cg_wwFollow" );
1051 
1052   if( atoi( s ) )
1053     client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW;
1054   else
1055     client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW;
1056 
1057   // wallwalk toggle
1058   s = Info_ValueForKey( userinfo, "cg_wwToggle" );
1059 
1060   if( atoi( s ) )
1061     client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE;
1062   else
1063     client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE;
1064 
1065   // teamInfo
1066   s = Info_ValueForKey( userinfo, "teamoverlay" );
1067 
1068   if( ! *s || atoi( s ) != 0 )
1069     client->pers.teamInfo = qtrue;
1070   else
1071     client->pers.teamInfo = qfalse;
1072 
1073   // team task (0 = none, 1 = offence, 2 = defence)
1074   teamTask = atoi( Info_ValueForKey( userinfo, "teamtask" ) );
1075   // team Leader (1 = leader, 0 is normal player)
1076   teamLeader = client->sess.teamLeader;
1077 
1078   // colors
1079   strcpy( c1, Info_ValueForKey( userinfo, "color1" ) );
1080   strcpy( c2, Info_ValueForKey( userinfo, "color2" ) );
1081   strcpy( redTeam, "humans" );
1082   strcpy( blueTeam, "aliens" );
1083 
1084   if( client->ps.pm_flags & PMF_FOLLOW )
1085     team = PTE_NONE;
1086   else
1087     team = client->ps.stats[ STAT_PTEAM ];
1088 
1089   // send over a subset of the userinfo keys so other clients can
1090   // print scoreboards, display models, and play custom sounds
1091   s = va( "n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d",
1092     client->pers.netname, team, model, model, redTeam, blueTeam, c1, c2,
1093     client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader);
1094 
1095   trap_SetConfigstring( CS_PLAYERS + clientNum, s );
1096 
1097   /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );*/
1098 }
1099 
1100 
1101 /*
1102 ===========
1103 ClientConnect
1104 
1105 Called when a player begins connecting to the server.
1106 Called again for every map change or tournement restart.
1107 
1108 The session information will be valid after exit.
1109 
1110 Return NULL if the client should be allowed, otherwise return
1111 a string with the reason for denial.
1112 
1113 Otherwise, the client will be sent the current gamestate
1114 and will eventually get to ClientBegin.
1115 
1116 firstTime will be qtrue the very first time a client connects
1117 to the server machine, but qfalse on map changes and tournement
1118 restarts.
1119 ============
1120 */
ClientConnect(int clientNum,qboolean firstTime,qboolean isBot)1121 char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot )
1122 {
1123   char      *value;
1124   gclient_t *client;
1125   char      userinfo[ MAX_INFO_STRING ];
1126   gentity_t *ent;
1127 
1128   ent = &g_entities[ clientNum ];
1129 
1130   trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
1131 
1132   // IP filtering
1133   // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500
1134   // recommanding PB based IP / GUID banning, the builtin system is pretty limited
1135   // check to see if they are on the banned IP list
1136   value = Info_ValueForKey( userinfo, "ip" );
1137   if( G_FilterPacket( value ) )
1138     return "You are banned from this server.";
1139 
1140   // check for a password
1141   value = Info_ValueForKey( userinfo, "password" );
1142 
1143   if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) &&
1144       strcmp( g_password.string, value ) != 0 )
1145     return "Invalid password";
1146 
1147   // they can connect
1148   ent->client = level.clients + clientNum;
1149   client = ent->client;
1150 
1151   memset( client, 0, sizeof(*client) );
1152 
1153   client->pers.connected = CON_CONNECTING;
1154 
1155   // read or initialize the session data
1156   if( firstTime || level.newSession )
1157     G_InitSessionData( client, userinfo );
1158 
1159   G_ReadSessionData( client );
1160 
1161   // get and distribute relevent paramters
1162   G_LogPrintf( "ClientConnect: %i\n", clientNum );
1163   ClientUserinfoChanged( clientNum );
1164 
1165   // don't do the "xxx connected" messages if they were caried over from previous level
1166   if( firstTime )
1167     G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname ) );
1168 
1169   // count current clients and rank for scoreboard
1170   CalculateRanks( );
1171 
1172   return NULL;
1173 }
1174 
1175 /*
1176 ===========
1177 ClientBegin
1178 
1179 called when a client has finished connecting, and is ready
1180 to be placed into the level.  This will happen every level load,
1181 and on transition between teams, but doesn't happen on respawns
1182 ============
1183 */
ClientBegin(int clientNum)1184 void ClientBegin( int clientNum )
1185 {
1186   gentity_t *ent;
1187   gclient_t *client;
1188   int       flags;
1189 
1190   ent = g_entities + clientNum;
1191 
1192   client = level.clients + clientNum;
1193 
1194   if( ent->r.linked )
1195     trap_UnlinkEntity( ent );
1196 
1197   G_InitGentity( ent );
1198   ent->touch = 0;
1199   ent->pain = 0;
1200   ent->client = client;
1201 
1202   client->pers.connected = CON_CONNECTED;
1203   client->pers.enterTime = level.time;
1204   client->pers.teamState.state = TEAM_BEGIN;
1205 
1206   // save eflags around this, because changing teams will
1207   // cause this to happen with a valid entity, and we
1208   // want to make sure the teleport bit is set right
1209   // so the viewpoint doesn't interpolate through the
1210   // world to the new position
1211   flags = client->ps.eFlags;
1212   memset( &client->ps, 0, sizeof( client->ps ) );
1213   client->ps.eFlags = flags;
1214 
1215   // locate ent at a spawn point
1216 
1217   ClientSpawn( ent, NULL, NULL, NULL );
1218 
1219   G_InitCommandQueue( clientNum );
1220 
1221   G_SendCommandFromServer( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) );
1222 
1223   // request the clients PTR code
1224   G_SendCommandFromServer( ent - g_entities, "ptrcrequest" );
1225 
1226   G_LogPrintf( "ClientBegin: %i\n", clientNum );
1227 
1228   // count current clients and rank for scoreboard
1229   CalculateRanks( );
1230 }
1231 
1232 /*
1233 ===========
1234 ClientSpawn
1235 
1236 Called every time a client is placed fresh in the world:
1237 after the first ClientBegin, and after each respawn
1238 Initializes all non-persistant parts of playerState
1239 ============
1240 */
ClientSpawn(gentity_t * ent,gentity_t * spawn,vec3_t origin,vec3_t angles)1241 void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles )
1242 {
1243   int                 index;
1244   vec3_t              spawn_origin, spawn_angles;
1245   gclient_t           *client;
1246   int                 i;
1247   clientPersistant_t  saved;
1248   clientSession_t     savedSess;
1249   int                 persistant[ MAX_PERSISTANT ];
1250   gentity_t           *spawnPoint = NULL;
1251   int                 flags;
1252   int                 savedPing;
1253   int                 teamLocal;
1254   int                 eventSequence;
1255   char                userinfo[ MAX_INFO_STRING ];
1256   vec3_t              up = { 0.0f, 0.0f, 1.0f };
1257   int                 maxAmmo, maxClips;
1258   weapon_t            weapon;
1259 
1260 
1261   index = ent - g_entities;
1262   client = ent->client;
1263 
1264   teamLocal = client->pers.teamSelection;
1265 
1266   //TA: only start client if chosen a class and joined a team
1267   if( client->pers.classSelection == PCL_NONE && teamLocal == PTE_NONE )
1268   {
1269     client->sess.sessionTeam = TEAM_SPECTATOR;
1270     client->sess.spectatorState = SPECTATOR_FREE;
1271   }
1272   else if( client->pers.classSelection == PCL_NONE )
1273   {
1274     client->sess.sessionTeam = TEAM_SPECTATOR;
1275     client->sess.spectatorState = SPECTATOR_LOCKED;
1276   }
1277 
1278   if( origin != NULL )
1279     VectorCopy( origin, spawn_origin );
1280 
1281   if( angles != NULL )
1282     VectorCopy( angles, spawn_angles );
1283 
1284   // find a spawn point
1285   // do it before setting health back up, so farthest
1286   // ranging doesn't count this client
1287   if( client->sess.sessionTeam == TEAM_SPECTATOR )
1288   {
1289     if( teamLocal == PTE_NONE )
1290       spawnPoint = SelectSpectatorSpawnPoint( spawn_origin, spawn_angles );
1291     else if( teamLocal == PTE_ALIENS )
1292       spawnPoint = SelectAlienLockSpawnPoint( spawn_origin, spawn_angles );
1293     else if( teamLocal == PTE_HUMANS )
1294       spawnPoint = SelectHumanLockSpawnPoint( spawn_origin, spawn_angles );
1295   }
1296   else
1297   {
1298     if( spawn == NULL )
1299     {
1300       G_Error( "ClientSpawn: spawn is NULL\n" );
1301       return;
1302     }
1303 
1304     spawnPoint = spawn;
1305 
1306     if( ent != spawn )
1307     {
1308       //start spawn animation on spawnPoint
1309       G_setBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue );
1310 
1311       if( spawnPoint->biteam == PTE_ALIENS )
1312         spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME;
1313       else if( spawnPoint->biteam == PTE_HUMANS )
1314         spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME;
1315     }
1316   }
1317   client->pers.teamState.state = TEAM_ACTIVE;
1318 
1319   // toggle the teleport bit so the client knows to not lerp
1320   flags = ent->client->ps.eFlags & ( EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED );
1321   flags ^= EF_TELEPORT_BIT;
1322 
1323   // clear everything but the persistant data
1324 
1325   saved = client->pers;
1326   savedSess = client->sess;
1327   savedPing = client->ps.ping;
1328 
1329   for( i = 0; i < MAX_PERSISTANT; i++ )
1330     persistant[ i ] = client->ps.persistant[ i ];
1331 
1332   eventSequence = client->ps.eventSequence;
1333   memset( client, 0, sizeof( *client ) );
1334 
1335   client->pers = saved;
1336   client->sess = savedSess;
1337   client->ps.ping = savedPing;
1338   client->lastkilled_client = -1;
1339 
1340   for( i = 0; i < MAX_PERSISTANT; i++ )
1341     client->ps.persistant[ i ] = persistant[ i ];
1342 
1343   client->ps.eventSequence = eventSequence;
1344 
1345   // increment the spawncount so the client will detect the respawn
1346   client->ps.persistant[ PERS_SPAWN_COUNT ]++;
1347   client->ps.persistant[ PERS_TEAM ] = client->sess.sessionTeam;
1348 
1349   client->airOutTime = level.time + 12000;
1350 
1351   trap_GetUserinfo( index, userinfo, sizeof( userinfo ) );
1352   client->ps.eFlags = flags;
1353 
1354   //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection );
1355 
1356   ent->s.groundEntityNum = ENTITYNUM_NONE;
1357   ent->client = &level.clients[ index ];
1358   ent->takedamage = qtrue;
1359   ent->inuse = qtrue;
1360   ent->classname = "player";
1361   ent->r.contents = CONTENTS_BODY;
1362   ent->clipmask = MASK_PLAYERSOLID;
1363   ent->die = player_die;
1364   ent->waterlevel = 0;
1365   ent->watertype = 0;
1366   ent->flags = 0;
1367 
1368   //TA: calculate each client's acceleration
1369   ent->evaluateAcceleration = qtrue;
1370 
1371   client->ps.stats[ STAT_WEAPONS ] = 0;
1372   client->ps.stats[ STAT_WEAPONS2 ] = 0;
1373   client->ps.stats[ STAT_SLOTS ] = 0;
1374 
1375   client->ps.eFlags = flags;
1376   client->ps.clientNum = index;
1377 
1378   BG_FindBBoxForClass( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL );
1379 
1380   if( client->sess.sessionTeam != TEAM_SPECTATOR )
1381     client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] =
1382       BG_FindHealthForClass( ent->client->pers.classSelection );
1383   else
1384     client->pers.maxHealth = client->ps.stats[ STAT_MAX_HEALTH ] = 100;
1385 
1386   // clear entity values
1387   if( ent->client->pers.classSelection == PCL_HUMAN )
1388   {
1389     BG_AddWeaponToInventory( WP_BLASTER, client->ps.stats );
1390     BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats );
1391     weapon = client->pers.humanItemSelection;
1392   }
1393   else if( client->sess.sessionTeam != TEAM_SPECTATOR )
1394     weapon = BG_FindStartWeaponForClass( ent->client->pers.classSelection );
1395   else
1396     weapon = WP_NONE;
1397 
1398   BG_FindAmmoForWeapon( weapon, &maxAmmo, &maxClips );
1399   BG_AddWeaponToInventory( weapon, client->ps.stats );
1400   BG_PackAmmoArray( weapon, client->ps.ammo, client->ps.powerups, maxAmmo, maxClips );
1401 
1402   ent->client->ps.stats[ STAT_PCLASS ] = ent->client->pers.classSelection;
1403   ent->client->ps.stats[ STAT_PTEAM ] = ent->client->pers.teamSelection;
1404 
1405   ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE;
1406   ent->client->ps.stats[ STAT_STATE ] = 0;
1407   VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f );
1408 
1409   // health will count down towards max_health
1410   ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25;
1411 
1412   //if evolving scale health
1413   if( ent == spawn )
1414   {
1415     ent->health *= ent->client->pers.evolveHealthFraction;
1416     client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction;
1417   }
1418 
1419   //clear the credits array
1420   for( i = 0; i < MAX_CLIENTS; i++ )
1421     ent->credits[ i ] = 0;
1422 
1423   client->ps.stats[ STAT_STAMINA ] = MAX_STAMINA;
1424 
1425   G_SetOrigin( ent, spawn_origin );
1426   VectorCopy( spawn_origin, client->ps.origin );
1427 
1428 #define UP_VEL  150.0f
1429 #define F_VEL   50.0f
1430 
1431   //give aliens some spawn velocity
1432   if( client->sess.sessionTeam != TEAM_SPECTATOR &&
1433       client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS )
1434   {
1435     if( ent == spawn )
1436     {
1437       //evolution particle system
1438       G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) );
1439     }
1440     else
1441     {
1442       spawn_angles[ YAW ] += 180.0f;
1443       AngleNormalize360( spawn_angles[ YAW ] );
1444 
1445       if( spawnPoint->s.origin2[ 2 ] > 0.0f )
1446       {
1447         vec3_t  forward, dir;
1448 
1449         AngleVectors( spawn_angles, forward, NULL, NULL );
1450         VectorScale( forward, F_VEL, forward );
1451         VectorAdd( spawnPoint->s.origin2, forward, dir );
1452         VectorNormalize( dir );
1453 
1454         VectorScale( dir, UP_VEL, client->ps.velocity );
1455       }
1456 
1457       G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 );
1458     }
1459   }
1460   else if( client->sess.sessionTeam != TEAM_SPECTATOR &&
1461            client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS )
1462   {
1463     spawn_angles[ YAW ] += 180.0f;
1464     AngleNormalize360( spawn_angles[ YAW ] );
1465   }
1466 
1467   // the respawned flag will be cleared after the attack and jump keys come up
1468   client->ps.pm_flags |= PMF_RESPAWNED;
1469 
1470   trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
1471   SetClientViewAngle( ent, spawn_angles );
1472 
1473   if( !( client->sess.sessionTeam == TEAM_SPECTATOR ) )
1474   {
1475     /*G_KillBox( ent );*/ //blame this if a newly spawned client gets stuck in another
1476     trap_LinkEntity( ent );
1477 
1478     // force the base weapon up
1479     client->ps.weapon = WP_NONE;
1480     client->ps.weaponstate = WEAPON_READY;
1481   }
1482 
1483   // don't allow full run speed for a bit
1484   client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
1485   client->ps.pm_time = 100;
1486 
1487   client->respawnTime = level.time;
1488   client->lastKillTime = level.time;
1489 
1490   client->inactivityTime = level.time + g_inactivity.integer * 1000;
1491   client->latched_buttons = 0;
1492 
1493   // set default animations
1494   client->ps.torsoAnim = TORSO_STAND;
1495   client->ps.legsAnim = LEGS_IDLE;
1496 
1497   if( level.intermissiontime )
1498     MoveClientToIntermission( ent );
1499   else
1500   {
1501     // fire the targets of the spawn point
1502     if( !spawn )
1503       G_UseTargets( spawnPoint, ent );
1504 
1505     // select the highest weapon number available, after any
1506     // spawn given items have fired
1507     client->ps.weapon = 1;
1508 
1509     for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- )
1510     {
1511       if( BG_InventoryContainsWeapon( i, client->ps.stats ) )
1512       {
1513         client->ps.weapon = i;
1514         break;
1515       }
1516     }
1517   }
1518 
1519   // run a client frame to drop exactly to the floor,
1520   // initialize animations and other things
1521   client->ps.commandTime = level.time - 100;
1522   ent->client->pers.cmd.serverTime = level.time;
1523   ClientThink( ent-g_entities );
1524 
1525   // positively link the client, even if the command times are weird
1526   if( client->sess.sessionTeam != TEAM_SPECTATOR )
1527   {
1528     BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
1529     VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
1530     trap_LinkEntity( ent );
1531   }
1532 
1533   //TA: must do this here so the number of active clients is calculated
1534   CalculateRanks( );
1535 
1536   // run the presend to set anything else
1537   ClientEndFrame( ent );
1538 
1539   // clear entity state values
1540   BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
1541 }
1542 
1543 
1544 /*
1545 ===========
1546 ClientDisconnect
1547 
1548 Called when a player drops from the server.
1549 Will not be called between levels.
1550 
1551 This should NOT be called directly by any game logic,
1552 call trap_DropClient(), which will call this and do
1553 server system housekeeping.
1554 ============
1555 */
ClientDisconnect(int clientNum)1556 void ClientDisconnect( int clientNum )
1557 {
1558   gentity_t *ent;
1559   gentity_t *tent;
1560   int       i;
1561 
1562   ent = g_entities + clientNum;
1563 
1564   if( !ent->client )
1565     return;
1566 
1567   // stop any following clients
1568   for( i = 0; i < level.maxclients; i++ )
1569   {
1570     if( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR &&
1571         level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW &&
1572         level.clients[ i ].sess.spectatorClient == clientNum )
1573     {
1574       if( !G_FollowNewClient( &g_entities[ i ], 1 ) )
1575         G_StopFollowing( &g_entities[ i ] );
1576     }
1577   }
1578 
1579   // send effect if they were completely connected
1580   if( ent->client->pers.connected == CON_CONNECTED &&
1581       ent->client->sess.sessionTeam != TEAM_SPECTATOR )
1582   {
1583     tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
1584     tent->s.clientNum = ent->s.clientNum;
1585   }
1586 
1587   G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
1588 
1589   trap_UnlinkEntity( ent );
1590   ent->s.modelindex = 0;
1591   ent->inuse = qfalse;
1592   ent->classname = "disconnected";
1593   ent->client->pers.connected = CON_DISCONNECTED;
1594   ent->client->ps.persistant[ PERS_TEAM ] = TEAM_FREE;
1595   ent->client->sess.sessionTeam = TEAM_FREE;
1596 
1597   trap_SetConfigstring( CS_PLAYERS + clientNum, "");
1598 
1599   CalculateRanks( );
1600 }
1601