root/src/game/g_client.c @ 65:9e928bb0b01b

Revision 65:9e928bb0b01b, 44.2 kB (checked in by mdoison, 3 years ago)

Using eggy beta r2 for tjw 1.2 branch

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