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