~ubuntu-branches/ubuntu/hardy/openarena/hardy-backports

« back to all changes in this revision

Viewing changes to code/game/g_client.c

  • Committer: Bazaar Package Importer
  • Author(s): Bruno "Fuddl" Kleinert
  • Date: 2007-01-20 12:28:09 UTC
  • Revision ID: james.westby@ubuntu.com-20070120122809-2yza5ojt7nqiyiam
Tags: upstream-0.6.0
ImportĀ upstreamĀ versionĀ 0.6.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
===========================================================================
 
3
Copyright (C) 1999-2005 Id Software, Inc.
 
4
 
 
5
This file is part of Quake III Arena source code.
 
6
 
 
7
Quake III Arena source code is free software; you can redistribute it
 
8
and/or modify it under the terms of the GNU General Public License as
 
9
published by the Free Software Foundation; either version 2 of the License,
 
10
or (at your option) any later version.
 
11
 
 
12
Quake III Arena source code is distributed in the hope that it will be
 
13
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
GNU General Public License for more details.
 
16
 
 
17
You should have received a copy of the GNU General Public License
 
18
along with Quake III Arena source code; if not, write to the Free Software
 
19
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
20
===========================================================================
 
21
*/
 
22
//
 
23
#include "g_local.h"
 
24
 
 
25
// g_client.c -- client functions that don't happen every frame
 
26
 
 
27
static vec3_t   playerMins = {-15, -15, -24};
 
28
static vec3_t   playerMaxs = {15, 15, 32};
 
29
 
 
30
/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
 
31
potential spawning position for deathmatch games.
 
32
The first time a player enters the game, they will be at an 'initial' spot.
 
33
Targets will be fired when someone spawns in on them.
 
34
"nobots" will prevent bots from using this spot.
 
35
"nohumans" will prevent non-bots from using this spot.
 
36
*/
 
37
void SP_info_player_deathmatch( gentity_t *ent ) {
 
38
        int             i;
 
39
 
 
40
        G_SpawnInt( "nobots", "0", &i);
 
41
        if ( i ) {
 
42
                ent->flags |= FL_NO_BOTS;
 
43
        }
 
44
        G_SpawnInt( "nohumans", "0", &i );
 
45
        if ( i ) {
 
46
                ent->flags |= FL_NO_HUMANS;
 
47
        }
 
48
}
 
49
 
 
50
/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
 
51
equivelant to info_player_deathmatch
 
52
*/
 
53
void SP_info_player_start(gentity_t *ent) {
 
54
        ent->classname = "info_player_deathmatch";
 
55
        SP_info_player_deathmatch( ent );
 
56
}
 
57
 
 
58
/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32)
 
59
The intermission will be viewed from this point.  Target an info_notnull for the view direction.
 
60
*/
 
61
void SP_info_player_intermission( gentity_t *ent ) {
 
62
 
 
63
}
 
64
 
 
65
 
 
66
 
 
67
/*
 
68
=======================================================================
 
69
 
 
70
  SelectSpawnPoint
 
71
 
 
72
=======================================================================
 
73
*/
 
74
 
 
75
/*
 
76
================
 
77
SpotWouldTelefrag
 
78
 
 
79
================
 
80
*/
 
81
qboolean SpotWouldTelefrag( gentity_t *spot ) {
 
82
        int                     i, num;
 
83
        int                     touch[MAX_GENTITIES];
 
84
        gentity_t       *hit;
 
85
        vec3_t          mins, maxs;
 
86
 
 
87
        VectorAdd( spot->s.origin, playerMins, mins );
 
88
        VectorAdd( spot->s.origin, playerMaxs, maxs );
 
89
        num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
 
90
 
 
91
        for (i=0 ; i<num ; i++) {
 
92
                hit = &g_entities[touch[i]];
 
93
                //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
 
94
                if ( hit->client) {
 
95
                        return qtrue;
 
96
                }
 
97
 
 
98
        }
 
99
 
 
100
        return qfalse;
 
101
}
 
102
 
 
103
/*
 
104
================
 
105
SelectNearestDeathmatchSpawnPoint
 
106
 
 
107
Find the spot that we DON'T want to use
 
108
================
 
109
*/
 
110
#define MAX_SPAWN_POINTS        128
 
111
gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) {
 
112
        gentity_t       *spot;
 
113
        vec3_t          delta;
 
114
        float           dist, nearestDist;
 
115
        gentity_t       *nearestSpot;
 
116
 
 
117
        nearestDist = 999999;
 
118
        nearestSpot = NULL;
 
119
        spot = NULL;
 
120
 
 
121
        while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
 
122
 
 
123
                VectorSubtract( spot->s.origin, from, delta );
 
124
                dist = VectorLength( delta );
 
125
                if ( dist < nearestDist ) {
 
126
                        nearestDist = dist;
 
127
                        nearestSpot = spot;
 
128
                }
 
129
        }
 
130
 
 
131
        return nearestSpot;
 
132
}
 
133
 
 
134
 
 
135
/*
 
136
================
 
137
SelectRandomDeathmatchSpawnPoint
 
138
 
 
139
go to a random point that doesn't telefrag
 
140
================
 
141
*/
 
142
#define MAX_SPAWN_POINTS        128
 
143
gentity_t *SelectRandomDeathmatchSpawnPoint( void ) {
 
144
        gentity_t       *spot;
 
145
        int                     count;
 
146
        int                     selection;
 
147
        gentity_t       *spots[MAX_SPAWN_POINTS];
 
148
 
 
149
        count = 0;
 
150
        spot = NULL;
 
151
 
 
152
        while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
 
153
                if ( SpotWouldTelefrag( spot ) ) {
 
154
                        continue;
 
155
                }
 
156
                spots[ count ] = spot;
 
157
                count++;
 
158
        }
 
159
 
 
160
        if ( !count ) { // no spots that won't telefrag
 
161
                return G_Find( NULL, FOFS(classname), "info_player_deathmatch");
 
162
        }
 
163
 
 
164
        selection = rand() % count;
 
165
        return spots[ selection ];
 
166
}
 
167
 
 
168
/*
 
169
===========
 
170
SelectRandomFurthestSpawnPoint
 
171
 
 
172
Chooses a player start, deathmatch start, etc
 
173
============
 
174
*/
 
175
gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
 
176
        gentity_t       *spot;
 
177
        vec3_t          delta;
 
178
        float           dist;
 
179
        float           list_dist[64];
 
180
        gentity_t       *list_spot[64];
 
181
        int                     numSpots, rnd, i, j;
 
182
 
 
183
        numSpots = 0;
 
184
        spot = NULL;
 
185
 
 
186
        while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
 
187
                if ( SpotWouldTelefrag( spot ) ) {
 
188
                        continue;
 
189
                }
 
190
                VectorSubtract( spot->s.origin, avoidPoint, delta );
 
191
                dist = VectorLength( delta );
 
192
                for (i = 0; i < numSpots; i++) {
 
193
                        if ( dist > list_dist[i] ) {
 
194
                                if ( numSpots >= 64 )
 
195
                                        numSpots = 64-1;
 
196
                                for (j = numSpots; j > i; j--) {
 
197
                                        list_dist[j] = list_dist[j-1];
 
198
                                        list_spot[j] = list_spot[j-1];
 
199
                                }
 
200
                                list_dist[i] = dist;
 
201
                                list_spot[i] = spot;
 
202
                                numSpots++;
 
203
                                if (numSpots > 64)
 
204
                                        numSpots = 64;
 
205
                                break;
 
206
                        }
 
207
                }
 
208
                if (i >= numSpots && numSpots < 64) {
 
209
                        list_dist[numSpots] = dist;
 
210
                        list_spot[numSpots] = spot;
 
211
                        numSpots++;
 
212
                }
 
213
        }
 
214
        if (!numSpots) {
 
215
                spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
 
216
                if (!spot)
 
217
                        G_Error( "Couldn't find a spawn point" );
 
218
                VectorCopy (spot->s.origin, origin);
 
219
                origin[2] += 9;
 
220
                VectorCopy (spot->s.angles, angles);
 
221
                return spot;
 
222
        }
 
223
 
 
224
        // select a random spot from the spawn points furthest away
 
225
        rnd = random() * (numSpots / 2);
 
226
 
 
227
        VectorCopy (list_spot[rnd]->s.origin, origin);
 
228
        origin[2] += 9;
 
229
        VectorCopy (list_spot[rnd]->s.angles, angles);
 
230
 
 
231
        return list_spot[rnd];
 
232
}
 
233
 
 
234
/*
 
235
===========
 
236
SelectSpawnPoint
 
237
 
 
238
Chooses a player start, deathmatch start, etc
 
239
============
 
240
*/
 
241
gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) {
 
242
        return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles );
 
243
 
 
244
        /*
 
245
        gentity_t       *spot;
 
246
        gentity_t       *nearestSpot;
 
247
 
 
248
        nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint );
 
249
 
 
250
        spot = SelectRandomDeathmatchSpawnPoint ( );
 
251
        if ( spot == nearestSpot ) {
 
252
                // roll again if it would be real close to point of death
 
253
                spot = SelectRandomDeathmatchSpawnPoint ( );
 
254
                if ( spot == nearestSpot ) {
 
255
                        // last try
 
256
                        spot = SelectRandomDeathmatchSpawnPoint ( );
 
257
                }               
 
258
        }
 
259
 
 
260
        // find a single player start spot
 
261
        if (!spot) {
 
262
                G_Error( "Couldn't find a spawn point" );
 
263
        }
 
264
 
 
265
        VectorCopy (spot->s.origin, origin);
 
266
        origin[2] += 9;
 
267
        VectorCopy (spot->s.angles, angles);
 
268
 
 
269
        return spot;
 
270
        */
 
271
}
 
272
 
 
273
/*
 
274
===========
 
275
SelectInitialSpawnPoint
 
276
 
 
277
Try to find a spawn point marked 'initial', otherwise
 
278
use normal spawn selection.
 
279
============
 
280
*/
 
281
gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) {
 
282
        gentity_t       *spot;
 
283
 
 
284
        spot = NULL;
 
285
        while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
 
286
                if ( spot->spawnflags & 1 ) {
 
287
                        break;
 
288
                }
 
289
        }
 
290
 
 
291
        if ( !spot || SpotWouldTelefrag( spot ) ) {
 
292
                return SelectSpawnPoint( vec3_origin, origin, angles );
 
293
        }
 
294
 
 
295
        VectorCopy (spot->s.origin, origin);
 
296
        origin[2] += 9;
 
297
        VectorCopy (spot->s.angles, angles);
 
298
 
 
299
        return spot;
 
300
}
 
301
 
 
302
/*
 
303
===========
 
304
SelectSpectatorSpawnPoint
 
305
 
 
306
============
 
307
*/
 
308
gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
 
309
        FindIntermissionPoint();
 
310
 
 
311
        VectorCopy( level.intermission_origin, origin );
 
312
        VectorCopy( level.intermission_angle, angles );
 
313
 
 
314
        return NULL;
 
315
}
 
316
 
 
317
/*
 
318
=======================================================================
 
319
 
 
320
BODYQUE
 
321
 
 
322
=======================================================================
 
323
*/
 
324
 
 
325
/*
 
326
===============
 
327
InitBodyQue
 
328
===============
 
329
*/
 
330
void InitBodyQue (void) {
 
331
        int             i;
 
332
        gentity_t       *ent;
 
333
 
 
334
        level.bodyQueIndex = 0;
 
335
        for (i=0; i<BODY_QUEUE_SIZE ; i++) {
 
336
                ent = G_Spawn();
 
337
                ent->classname = "bodyque";
 
338
                ent->neverFree = qtrue;
 
339
                level.bodyQue[i] = ent;
 
340
        }
 
341
}
 
342
 
 
343
/*
 
344
=============
 
345
BodySink
 
346
 
 
347
After sitting around for five seconds, fall into the ground and dissapear
 
348
=============
 
349
*/
 
350
void BodySink( gentity_t *ent ) {
 
351
        if ( level.time - ent->timestamp > 6500 ) {
 
352
                // the body ques are never actually freed, they are just unlinked
 
353
                trap_UnlinkEntity( ent );
 
354
                ent->physicsObject = qfalse;
 
355
                return; 
 
356
        }
 
357
        ent->nextthink = level.time + 100;
 
358
        ent->s.pos.trBase[2] -= 1;
 
359
}
 
360
 
 
361
/*
 
362
=============
 
363
CopyToBodyQue
 
364
 
 
365
A player is respawning, so make an entity that looks
 
366
just like the existing corpse to leave behind.
 
367
=============
 
368
*/
 
369
void CopyToBodyQue( gentity_t *ent ) {
 
370
#ifdef MISSIONPACK
 
371
        gentity_t       *e;
 
372
        int i;
 
373
#endif
 
374
        gentity_t               *body;
 
375
        int                     contents;
 
376
 
 
377
        trap_UnlinkEntity (ent);
 
378
 
 
379
        // if client is in a nodrop area, don't leave the body
 
380
        contents = trap_PointContents( ent->s.origin, -1 );
 
381
        if ( contents & CONTENTS_NODROP ) {
 
382
                return;
 
383
        }
 
384
 
 
385
        // grab a body que and cycle to the next one
 
386
        body = level.bodyQue[ level.bodyQueIndex ];
 
387
        level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
 
388
 
 
389
        trap_UnlinkEntity (body);
 
390
 
 
391
        body->s = ent->s;
 
392
        body->s.eFlags = EF_DEAD;               // clear EF_TALK, etc
 
393
#ifdef MISSIONPACK
 
394
        if ( ent->s.eFlags & EF_KAMIKAZE ) {
 
395
                body->s.eFlags |= EF_KAMIKAZE;
 
396
 
 
397
                // check if there is a kamikaze timer around for this owner
 
398
                for (i = 0; i < MAX_GENTITIES; i++) {
 
399
                        e = &g_entities[i];
 
400
                        if (!e->inuse)
 
401
                                continue;
 
402
                        if (e->activator != ent)
 
403
                                continue;
 
404
                        if (strcmp(e->classname, "kamikaze timer"))
 
405
                                continue;
 
406
                        e->activator = body;
 
407
                        break;
 
408
                }
 
409
        }
 
410
#endif
 
411
        body->s.powerups = 0;   // clear powerups
 
412
        body->s.loopSound = 0;  // clear lava burning
 
413
        body->s.number = body - g_entities;
 
414
        body->timestamp = level.time;
 
415
        body->physicsObject = qtrue;
 
416
        body->physicsBounce = 0;                // don't bounce
 
417
        if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
 
418
                body->s.pos.trType = TR_GRAVITY;
 
419
                body->s.pos.trTime = level.time;
 
420
                VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
 
421
        } else {
 
422
                body->s.pos.trType = TR_STATIONARY;
 
423
        }
 
424
        body->s.event = 0;
 
425
 
 
426
        // change the animation to the last-frame only, so the sequence
 
427
        // doesn't repeat anew for the body
 
428
        switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) {
 
429
        case BOTH_DEATH1:
 
430
        case BOTH_DEAD1:
 
431
                body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1;
 
432
                break;
 
433
        case BOTH_DEATH2:
 
434
        case BOTH_DEAD2:
 
435
                body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2;
 
436
                break;
 
437
        case BOTH_DEATH3:
 
438
        case BOTH_DEAD3:
 
439
        default:
 
440
                body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3;
 
441
                break;
 
442
        }
 
443
 
 
444
        body->r.svFlags = ent->r.svFlags;
 
445
        VectorCopy (ent->r.mins, body->r.mins);
 
446
        VectorCopy (ent->r.maxs, body->r.maxs);
 
447
        VectorCopy (ent->r.absmin, body->r.absmin);
 
448
        VectorCopy (ent->r.absmax, body->r.absmax);
 
449
 
 
450
        body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
 
451
        body->r.contents = CONTENTS_CORPSE;
 
452
        body->r.ownerNum = ent->s.number;
 
453
 
 
454
        body->nextthink = level.time + 5000;
 
455
        body->think = BodySink;
 
456
 
 
457
        body->die = body_die;
 
458
 
 
459
        // don't take more damage if already gibbed
 
460
        if ( ent->health <= GIB_HEALTH ) {
 
461
                body->takedamage = qfalse;
 
462
        } else {
 
463
                body->takedamage = qtrue;
 
464
        }
 
465
 
 
466
 
 
467
        VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
 
468
        trap_LinkEntity (body);
 
469
}
 
470
 
 
471
//======================================================================
 
472
 
 
473
 
 
474
/*
 
475
==================
 
476
SetClientViewAngle
 
477
 
 
478
==================
 
479
*/
 
480
void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
 
481
        int                     i;
 
482
 
 
483
        // set the delta angle
 
484
        for (i=0 ; i<3 ; i++) {
 
485
                int             cmdAngle;
 
486
 
 
487
                cmdAngle = ANGLE2SHORT(angle[i]);
 
488
                ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
 
489
        }
 
490
        VectorCopy( angle, ent->s.angles );
 
491
        VectorCopy (ent->s.angles, ent->client->ps.viewangles);
 
492
}
 
493
 
 
494
/*
 
495
================
 
496
respawn
 
497
================
 
498
*/
 
499
void respawn( gentity_t *ent ) {
 
500
        gentity_t       *tent;
 
501
 
 
502
        CopyToBodyQue (ent);
 
503
        ClientSpawn(ent);
 
504
 
 
505
        // add a teleportation effect
 
506
        tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
 
507
        tent->s.clientNum = ent->s.clientNum;
 
508
}
 
509
 
 
510
/*
 
511
================
 
512
TeamCount
 
513
 
 
514
Returns number of players on a team
 
515
================
 
516
*/
 
517
team_t TeamCount( int ignoreClientNum, int team ) {
 
518
        int             i;
 
519
        int             count = 0;
 
520
 
 
521
        for ( i = 0 ; i < level.maxclients ; i++ ) {
 
522
                if ( i == ignoreClientNum ) {
 
523
                        continue;
 
524
                }
 
525
                if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
 
526
                        continue;
 
527
                }
 
528
                if ( level.clients[i].sess.sessionTeam == team ) {
 
529
                        count++;
 
530
                }
 
531
        }
 
532
 
 
533
        return count;
 
534
}
 
535
 
 
536
/*
 
537
================
 
538
TeamLeader
 
539
 
 
540
Returns the client number of the team leader
 
541
================
 
542
*/
 
543
int TeamLeader( int team ) {
 
544
        int             i;
 
545
 
 
546
        for ( i = 0 ; i < level.maxclients ; i++ ) {
 
547
                if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
 
548
                        continue;
 
549
                }
 
550
                if ( level.clients[i].sess.sessionTeam == team ) {
 
551
                        if ( level.clients[i].sess.teamLeader )
 
552
                                return i;
 
553
                }
 
554
        }
 
555
 
 
556
        return -1;
 
557
}
 
558
 
 
559
 
 
560
/*
 
561
================
 
562
PickTeam
 
563
 
 
564
================
 
565
*/
 
566
team_t PickTeam( int ignoreClientNum ) {
 
567
        int             counts[TEAM_NUM_TEAMS];
 
568
 
 
569
        counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
 
570
        counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED );
 
571
 
 
572
        if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) {
 
573
                return TEAM_RED;
 
574
        }
 
575
        if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) {
 
576
                return TEAM_BLUE;
 
577
        }
 
578
        // equal team count, so join the team with the lowest score
 
579
        if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) {
 
580
                return TEAM_RED;
 
581
        }
 
582
        return TEAM_BLUE;
 
583
}
 
584
 
 
585
/*
 
586
===========
 
587
ForceClientSkin
 
588
 
 
589
Forces a client's skin (for teamplay)
 
590
===========
 
591
*/
 
592
/*
 
593
static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
 
594
        char *p;
 
595
 
 
596
        if ((p = Q_strrchr(model, '/')) != 0) {
 
597
                *p = 0;
 
598
        }
 
599
 
 
600
        Q_strcat(model, MAX_QPATH, "/");
 
601
        Q_strcat(model, MAX_QPATH, skin);
 
602
}
 
603
*/
 
604
 
 
605
/*
 
606
===========
 
607
ClientCheckName
 
608
============
 
609
*/
 
610
static void ClientCleanName( const char *in, char *out, int outSize ) {
 
611
        int             len, colorlessLen;
 
612
        char    ch;
 
613
        char    *p;
 
614
        int             spaces;
 
615
 
 
616
        //save room for trailing null byte
 
617
        outSize--;
 
618
 
 
619
        len = 0;
 
620
        colorlessLen = 0;
 
621
        p = out;
 
622
        *p = 0;
 
623
        spaces = 0;
 
624
 
 
625
        while( 1 ) {
 
626
                ch = *in++;
 
627
                if( !ch ) {
 
628
                        break;
 
629
                }
 
630
 
 
631
                // don't allow leading spaces
 
632
                if( !*p && ch == ' ' ) {
 
633
                        continue;
 
634
                }
 
635
 
 
636
                // check colors
 
637
                if( ch == Q_COLOR_ESCAPE ) {
 
638
                        // solo trailing carat is not a color prefix
 
639
                        if( !*in ) {
 
640
                                break;
 
641
                        }
 
642
 
 
643
                        // don't allow black in a name, period
 
644
                        if( ColorIndex(*in) == 0 ) {
 
645
                                in++;
 
646
                                continue;
 
647
                        }
 
648
 
 
649
                        // make sure room in dest for both chars
 
650
                        if( len > outSize - 2 ) {
 
651
                                break;
 
652
                        }
 
653
 
 
654
                        *out++ = ch;
 
655
                        *out++ = *in++;
 
656
                        len += 2;
 
657
                        continue;
 
658
                }
 
659
 
 
660
                // don't allow too many consecutive spaces
 
661
                if( ch == ' ' ) {
 
662
                        spaces++;
 
663
                        if( spaces > 3 ) {
 
664
                                continue;
 
665
                        }
 
666
                }
 
667
                else {
 
668
                        spaces = 0;
 
669
                }
 
670
 
 
671
                if( len > outSize - 1 ) {
 
672
                        break;
 
673
                }
 
674
 
 
675
                *out++ = ch;
 
676
                colorlessLen++;
 
677
                len++;
 
678
        }
 
679
        *out = 0;
 
680
 
 
681
        // don't allow empty names
 
682
        if( *p == 0 || colorlessLen == 0 ) {
 
683
                Q_strncpyz( p, "UnnamedPlayer", outSize );
 
684
        }
 
685
}
 
686
 
 
687
 
 
688
/*
 
689
===========
 
690
ClientUserInfoChanged
 
691
 
 
692
Called from ClientConnect when the player first connects and
 
693
directly by the server system when the player updates a userinfo variable.
 
694
 
 
695
The game can override any of the settings and call trap_SetUserinfo
 
696
if desired.
 
697
============
 
698
*/
 
699
void ClientUserinfoChanged( int clientNum ) {
 
700
        gentity_t *ent;
 
701
        int             teamTask, teamLeader, team, health;
 
702
        char    *s;
 
703
        char    model[MAX_QPATH];
 
704
        char    headModel[MAX_QPATH];
 
705
        char    oldname[MAX_STRING_CHARS];
 
706
        gclient_t       *client;
 
707
        char    c1[MAX_INFO_STRING];
 
708
        char    c2[MAX_INFO_STRING];
 
709
        char    redTeam[MAX_INFO_STRING];
 
710
        char    blueTeam[MAX_INFO_STRING];
 
711
        char    userinfo[MAX_INFO_STRING];
 
712
 
 
713
        ent = g_entities + clientNum;
 
714
        client = ent->client;
 
715
 
 
716
        trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
 
717
 
 
718
        // check for malformed or illegal info strings
 
719
        if ( !Info_Validate(userinfo) ) {
 
720
                strcpy (userinfo, "\\name\\badinfo");
 
721
        }
 
722
 
 
723
        // check for local client
 
724
        s = Info_ValueForKey( userinfo, "ip" );
 
725
        if ( !strcmp( s, "localhost" ) ) {
 
726
                client->pers.localClient = qtrue;
 
727
        }
 
728
 
 
729
        // check the item prediction
 
730
        s = Info_ValueForKey( userinfo, "cg_predictItems" );
 
731
        if ( !atoi( s ) ) {
 
732
                client->pers.predictItemPickup = qfalse;
 
733
        } else {
 
734
                client->pers.predictItemPickup = qtrue;
 
735
        }
 
736
 
 
737
        // set name
 
738
        Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) );
 
739
        s = Info_ValueForKey (userinfo, "name");
 
740
        ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) );
 
741
 
 
742
        if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
 
743
                if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
 
744
                        Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) );
 
745
                }
 
746
        }
 
747
 
 
748
        if ( client->pers.connected == CON_CONNECTED ) {
 
749
                if ( strcmp( oldname, client->pers.netname ) ) {
 
750
                        trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, 
 
751
                                client->pers.netname) );
 
752
                }
 
753
        }
 
754
 
 
755
        // set max health
 
756
#ifdef MISSIONPACK
 
757
        if (client->ps.powerups[PW_GUARD]) {
 
758
                client->pers.maxHealth = 200;
 
759
        } else {
 
760
                health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
 
761
                client->pers.maxHealth = health;
 
762
                if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
 
763
                        client->pers.maxHealth = 100;
 
764
                }
 
765
        }
 
766
#else
 
767
        health = atoi( Info_ValueForKey( userinfo, "handicap" ) );
 
768
        client->pers.maxHealth = health;
 
769
        if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
 
770
                client->pers.maxHealth = 100;
 
771
        }
 
772
#endif
 
773
        client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
 
774
 
 
775
        // set model
 
776
        if( g_gametype.integer >= GT_TEAM ) {
 
777
                Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) );
 
778
                Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) );
 
779
        } else {
 
780
                Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) );
 
781
                Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) );
 
782
        }
 
783
 
 
784
        // bots set their team a few frames later
 
785
        if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) {
 
786
                s = Info_ValueForKey( userinfo, "team" );
 
787
                if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) {
 
788
                        team = TEAM_RED;
 
789
                } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) {
 
790
                        team = TEAM_BLUE;
 
791
                } else {
 
792
                        // pick the team with the least number of players
 
793
                        team = PickTeam( clientNum );
 
794
                }
 
795
        }
 
796
        else {
 
797
                team = client->sess.sessionTeam;
 
798
        }
 
799
 
 
800
/*      NOTE: all client side now
 
801
 
 
802
        // team
 
803
        switch( team ) {
 
804
        case TEAM_RED:
 
805
                ForceClientSkin(client, model, "red");
 
806
//              ForceClientSkin(client, headModel, "red");
 
807
                break;
 
808
        case TEAM_BLUE:
 
809
                ForceClientSkin(client, model, "blue");
 
810
//              ForceClientSkin(client, headModel, "blue");
 
811
                break;
 
812
        }
 
813
        // don't ever use a default skin in teamplay, it would just waste memory
 
814
        // however bots will always join a team but they spawn in as spectator
 
815
        if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) {
 
816
                ForceClientSkin(client, model, "red");
 
817
//              ForceClientSkin(client, headModel, "red");
 
818
        }
 
819
*/
 
820
 
 
821
#ifdef MISSIONPACK
 
822
        if (g_gametype.integer >= GT_TEAM) {
 
823
                client->pers.teamInfo = qtrue;
 
824
        } else {
 
825
                s = Info_ValueForKey( userinfo, "teamoverlay" );
 
826
                if ( ! *s || atoi( s ) != 0 ) {
 
827
                        client->pers.teamInfo = qtrue;
 
828
                } else {
 
829
                        client->pers.teamInfo = qfalse;
 
830
                }
 
831
        }
 
832
#else
 
833
        // teamInfo
 
834
        s = Info_ValueForKey( userinfo, "teamoverlay" );
 
835
        if ( ! *s || atoi( s ) != 0 ) {
 
836
                client->pers.teamInfo = qtrue;
 
837
        } else {
 
838
                client->pers.teamInfo = qfalse;
 
839
        }
 
840
#endif
 
841
        /*
 
842
        s = Info_ValueForKey( userinfo, "cg_pmove_fixed" );
 
843
        if ( !*s || atoi( s ) == 0 ) {
 
844
                client->pers.pmoveFixed = qfalse;
 
845
        }
 
846
        else {
 
847
                client->pers.pmoveFixed = qtrue;
 
848
        }
 
849
        */
 
850
 
 
851
        // team task (0 = none, 1 = offence, 2 = defence)
 
852
        teamTask = atoi(Info_ValueForKey(userinfo, "teamtask"));
 
853
        // team Leader (1 = leader, 0 is normal player)
 
854
        teamLeader = client->sess.teamLeader;
 
855
 
 
856
        // colors
 
857
        strcpy(c1, Info_ValueForKey( userinfo, "color1" ));
 
858
        strcpy(c2, Info_ValueForKey( userinfo, "color2" ));
 
859
 
 
860
        strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" ));
 
861
        strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" ));
 
862
 
 
863
        // send over a subset of the userinfo keys so other clients can
 
864
        // print scoreboards, display models, and play custom sounds
 
865
        if ( ent->r.svFlags & SVF_BOT ) {
 
866
                s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d",
 
867
                        client->pers.netname, team, model, headModel, c1, c2, 
 
868
                        client->pers.maxHealth, client->sess.wins, client->sess.losses,
 
869
                        Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader );
 
870
        } else {
 
871
                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",
 
872
                        client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, 
 
873
                        client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader);
 
874
        }
 
875
 
 
876
        trap_SetConfigstring( CS_PLAYERS+clientNum, s );
 
877
 
 
878
        // this is not the userinfo, more like the configstring actually
 
879
        G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s );
 
880
}
 
881
 
 
882
 
 
883
/*
 
884
===========
 
885
ClientConnect
 
886
 
 
887
Called when a player begins connecting to the server.
 
888
Called again for every map change or tournement restart.
 
889
 
 
890
The session information will be valid after exit.
 
891
 
 
892
Return NULL if the client should be allowed, otherwise return
 
893
a string with the reason for denial.
 
894
 
 
895
Otherwise, the client will be sent the current gamestate
 
896
and will eventually get to ClientBegin.
 
897
 
 
898
firstTime will be qtrue the very first time a client connects
 
899
to the server machine, but qfalse on map changes and tournement
 
900
restarts.
 
901
============
 
902
*/
 
903
char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
 
904
        char            *value;
 
905
//      char            *areabits;
 
906
        gclient_t       *client;
 
907
        char            userinfo[MAX_INFO_STRING];
 
908
        gentity_t       *ent;
 
909
 
 
910
        ent = &g_entities[ clientNum ];
 
911
 
 
912
        trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
 
913
 
 
914
        // IP filtering
 
915
        // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500
 
916
        // recommanding PB based IP / GUID banning, the builtin system is pretty limited
 
917
        // check to see if they are on the banned IP list
 
918
        value = Info_ValueForKey (userinfo, "ip");
 
919
        if ( G_FilterPacket( value ) ) {
 
920
                return "You are banned from this server.";
 
921
        }
 
922
 
 
923
  // we don't check password for bots and local client
 
924
  // NOTE: local client <-> "ip" "localhost"
 
925
  //   this means this client is not running in our current process
 
926
        if ( !isBot && (strcmp(value, "localhost") != 0)) {
 
927
                // check for a password
 
928
                value = Info_ValueForKey (userinfo, "password");
 
929
                if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
 
930
                        strcmp( g_password.string, value) != 0) {
 
931
                        return "Invalid password";
 
932
                }
 
933
        }
 
934
 
 
935
        // they can connect
 
936
        ent->client = level.clients + clientNum;
 
937
        client = ent->client;
 
938
 
 
939
//      areabits = client->areabits;
 
940
 
 
941
        memset( client, 0, sizeof(*client) );
 
942
 
 
943
        client->pers.connected = CON_CONNECTING;
 
944
 
 
945
        // read or initialize the session data
 
946
        if ( firstTime || level.newSession ) {
 
947
                G_InitSessionData( client, userinfo );
 
948
        }
 
949
        G_ReadSessionData( client );
 
950
 
 
951
        if( isBot ) {
 
952
                ent->r.svFlags |= SVF_BOT;
 
953
                ent->inuse = qtrue;
 
954
                if( !G_BotConnect( clientNum, !firstTime ) ) {
 
955
                        return "BotConnectfailed";
 
956
                }
 
957
        }
 
958
 
 
959
        // get and distribute relevent paramters
 
960
        G_LogPrintf( "ClientConnect: %i\n", clientNum );
 
961
        ClientUserinfoChanged( clientNum );
 
962
 
 
963
        // don't do the "xxx connected" messages if they were caried over from previous level
 
964
        if ( firstTime ) {
 
965
                trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) );
 
966
        }
 
967
 
 
968
        if ( g_gametype.integer >= GT_TEAM &&
 
969
                client->sess.sessionTeam != TEAM_SPECTATOR ) {
 
970
                BroadcastTeamChange( client, -1 );
 
971
        }
 
972
 
 
973
        // count current clients and rank for scoreboard
 
974
        CalculateRanks();
 
975
 
 
976
        // for statistics
 
977
//      client->areabits = areabits;
 
978
//      if ( !client->areabits )
 
979
//              client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 );
 
980
 
 
981
        return NULL;
 
982
}
 
983
 
 
984
/*
 
985
===========
 
986
ClientBegin
 
987
 
 
988
called when a client has finished connecting, and is ready
 
989
to be placed into the level.  This will happen every level load,
 
990
and on transition between teams, but doesn't happen on respawns
 
991
============
 
992
*/
 
993
void ClientBegin( int clientNum ) {
 
994
        gentity_t       *ent;
 
995
        gclient_t       *client;
 
996
        gentity_t       *tent;
 
997
        int                     flags;
 
998
 
 
999
        ent = g_entities + clientNum;
 
1000
 
 
1001
        client = level.clients + clientNum;
 
1002
 
 
1003
        if ( ent->r.linked ) {
 
1004
                trap_UnlinkEntity( ent );
 
1005
        }
 
1006
        G_InitGentity( ent );
 
1007
        ent->touch = 0;
 
1008
        ent->pain = 0;
 
1009
        ent->client = client;
 
1010
 
 
1011
        client->pers.connected = CON_CONNECTED;
 
1012
        client->pers.enterTime = level.time;
 
1013
        client->pers.teamState.state = TEAM_BEGIN;
 
1014
 
 
1015
        // save eflags around this, because changing teams will
 
1016
        // cause this to happen with a valid entity, and we
 
1017
        // want to make sure the teleport bit is set right
 
1018
        // so the viewpoint doesn't interpolate through the
 
1019
        // world to the new position
 
1020
        flags = client->ps.eFlags;
 
1021
        memset( &client->ps, 0, sizeof( client->ps ) );
 
1022
        client->ps.eFlags = flags;
 
1023
 
 
1024
        // locate ent at a spawn point
 
1025
        ClientSpawn( ent );
 
1026
 
 
1027
        if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
 
1028
                // send event
 
1029
                tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN );
 
1030
                tent->s.clientNum = ent->s.clientNum;
 
1031
 
 
1032
                if ( g_gametype.integer != GT_TOURNAMENT  ) {
 
1033
                        trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) );
 
1034
                }
 
1035
        }
 
1036
        G_LogPrintf( "ClientBegin: %i\n", clientNum );
 
1037
 
 
1038
        // count current clients and rank for scoreboard
 
1039
        CalculateRanks();
 
1040
}
 
1041
 
 
1042
/*
 
1043
===========
 
1044
ClientSpawn
 
1045
 
 
1046
Called every time a client is placed fresh in the world:
 
1047
after the first ClientBegin, and after each respawn
 
1048
Initializes all non-persistant parts of playerState
 
1049
============
 
1050
*/
 
1051
void ClientSpawn(gentity_t *ent) {
 
1052
        int             index;
 
1053
        vec3_t  spawn_origin, spawn_angles;
 
1054
        gclient_t       *client;
 
1055
        int             i;
 
1056
        clientPersistant_t      saved;
 
1057
        clientSession_t         savedSess;
 
1058
        int             persistant[MAX_PERSISTANT];
 
1059
        gentity_t       *spawnPoint;
 
1060
        int             flags;
 
1061
        int             savedPing;
 
1062
//      char    *savedAreaBits;
 
1063
        int             accuracy_hits, accuracy_shots;
 
1064
        int             eventSequence;
 
1065
        char    userinfo[MAX_INFO_STRING];
 
1066
 
 
1067
        index = ent - g_entities;
 
1068
        client = ent->client;
 
1069
 
 
1070
        // find a spawn point
 
1071
        // do it before setting health back up, so farthest
 
1072
        // ranging doesn't count this client
 
1073
        if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
 
1074
                spawnPoint = SelectSpectatorSpawnPoint ( 
 
1075
                                                spawn_origin, spawn_angles);
 
1076
        } else if (g_gametype.integer >= GT_CTF ) {
 
1077
                // all base oriented team games use the CTF spawn points
 
1078
                spawnPoint = SelectCTFSpawnPoint ( 
 
1079
                                                client->sess.sessionTeam, 
 
1080
                                                client->pers.teamState.state, 
 
1081
                                                spawn_origin, spawn_angles);
 
1082
        } else {
 
1083
                do {
 
1084
                        // the first spawn should be at a good looking spot
 
1085
                        if ( !client->pers.initialSpawn && client->pers.localClient ) {
 
1086
                                client->pers.initialSpawn = qtrue;
 
1087
                                spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles );
 
1088
                        } else {
 
1089
                                // don't spawn near existing origin if possible
 
1090
                                spawnPoint = SelectSpawnPoint ( 
 
1091
                                        client->ps.origin, 
 
1092
                                        spawn_origin, spawn_angles);
 
1093
                        }
 
1094
 
 
1095
                        // Tim needs to prevent bots from spawning at the initial point
 
1096
                        // on q3dm0...
 
1097
                        if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) {
 
1098
                                continue;       // try again
 
1099
                        }
 
1100
                        // just to be symetric, we have a nohumans option...
 
1101
                        if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) {
 
1102
                                continue;       // try again
 
1103
                        }
 
1104
 
 
1105
                        break;
 
1106
 
 
1107
                } while ( 1 );
 
1108
        }
 
1109
        client->pers.teamState.state = TEAM_ACTIVE;
 
1110
 
 
1111
        // always clear the kamikaze flag
 
1112
        ent->s.eFlags &= ~EF_KAMIKAZE;
 
1113
 
 
1114
        // toggle the teleport bit so the client knows to not lerp
 
1115
        // and never clear the voted flag
 
1116
        flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED);
 
1117
        flags ^= EF_TELEPORT_BIT;
 
1118
 
 
1119
        // clear everything but the persistant data
 
1120
 
 
1121
        saved = client->pers;
 
1122
        savedSess = client->sess;
 
1123
        savedPing = client->ps.ping;
 
1124
//      savedAreaBits = client->areabits;
 
1125
        accuracy_hits = client->accuracy_hits;
 
1126
        accuracy_shots = client->accuracy_shots;
 
1127
        for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
 
1128
                persistant[i] = client->ps.persistant[i];
 
1129
        }
 
1130
        eventSequence = client->ps.eventSequence;
 
1131
 
 
1132
        memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset?
 
1133
 
 
1134
        client->pers = saved;
 
1135
        client->sess = savedSess;
 
1136
        client->ps.ping = savedPing;
 
1137
//      client->areabits = savedAreaBits;
 
1138
        client->accuracy_hits = accuracy_hits;
 
1139
        client->accuracy_shots = accuracy_shots;
 
1140
        client->lastkilled_client = -1;
 
1141
 
 
1142
        for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) {
 
1143
                client->ps.persistant[i] = persistant[i];
 
1144
        }
 
1145
        client->ps.eventSequence = eventSequence;
 
1146
        // increment the spawncount so the client will detect the respawn
 
1147
        client->ps.persistant[PERS_SPAWN_COUNT]++;
 
1148
        client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
 
1149
 
 
1150
        client->airOutTime = level.time + 12000;
 
1151
 
 
1152
        trap_GetUserinfo( index, userinfo, sizeof(userinfo) );
 
1153
        // set max health
 
1154
        client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) );
 
1155
        if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) {
 
1156
                client->pers.maxHealth = 100;
 
1157
        }
 
1158
        // clear entity values
 
1159
        client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
 
1160
        client->ps.eFlags = flags;
 
1161
 
 
1162
        ent->s.groundEntityNum = ENTITYNUM_NONE;
 
1163
        ent->client = &level.clients[index];
 
1164
        ent->takedamage = qtrue;
 
1165
        ent->inuse = qtrue;
 
1166
        ent->classname = "player";
 
1167
        ent->r.contents = CONTENTS_BODY;
 
1168
        ent->clipmask = MASK_PLAYERSOLID;
 
1169
        ent->die = player_die;
 
1170
        ent->waterlevel = 0;
 
1171
        ent->watertype = 0;
 
1172
        ent->flags = 0;
 
1173
        
 
1174
        VectorCopy (playerMins, ent->r.mins);
 
1175
        VectorCopy (playerMaxs, ent->r.maxs);
 
1176
 
 
1177
        client->ps.clientNum = index;
 
1178
 
 
1179
        client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN );
 
1180
        if ( g_gametype.integer == GT_TEAM ) {
 
1181
                client->ps.ammo[WP_MACHINEGUN] = 50;
 
1182
        } else {
 
1183
                client->ps.ammo[WP_MACHINEGUN] = 100;
 
1184
        }
 
1185
 
 
1186
        client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET );
 
1187
        client->ps.ammo[WP_GAUNTLET] = -1;
 
1188
        client->ps.ammo[WP_GRAPPLING_HOOK] = -1;
 
1189
 
 
1190
        // health will count down towards max_health
 
1191
        ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25;
 
1192
 
 
1193
        G_SetOrigin( ent, spawn_origin );
 
1194
        VectorCopy( spawn_origin, client->ps.origin );
 
1195
 
 
1196
        // the respawned flag will be cleared after the attack and jump keys come up
 
1197
        client->ps.pm_flags |= PMF_RESPAWNED;
 
1198
 
 
1199
        trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd );
 
1200
        SetClientViewAngle( ent, spawn_angles );
 
1201
 
 
1202
        if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
 
1203
 
 
1204
        } else {
 
1205
                G_KillBox( ent );
 
1206
                trap_LinkEntity (ent);
 
1207
 
 
1208
                // force the base weapon up
 
1209
                client->ps.weapon = WP_MACHINEGUN;
 
1210
                client->ps.weaponstate = WEAPON_READY;
 
1211
 
 
1212
        }
 
1213
 
 
1214
        // don't allow full run speed for a bit
 
1215
        client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
 
1216
        client->ps.pm_time = 100;
 
1217
 
 
1218
        client->respawnTime = level.time;
 
1219
        client->inactivityTime = level.time + g_inactivity.integer * 1000;
 
1220
        client->latched_buttons = 0;
 
1221
 
 
1222
        // set default animations
 
1223
        client->ps.torsoAnim = TORSO_STAND;
 
1224
        client->ps.legsAnim = LEGS_IDLE;
 
1225
 
 
1226
        if ( level.intermissiontime ) {
 
1227
                MoveClientToIntermission( ent );
 
1228
        } else {
 
1229
                // fire the targets of the spawn point
 
1230
                G_UseTargets( spawnPoint, ent );
 
1231
 
 
1232
                // select the highest weapon number available, after any
 
1233
                // spawn given items have fired
 
1234
                client->ps.weapon = 1;
 
1235
                for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {
 
1236
                        if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) {
 
1237
                                client->ps.weapon = i;
 
1238
                                break;
 
1239
                        }
 
1240
                }
 
1241
        }
 
1242
 
 
1243
        // run a client frame to drop exactly to the floor,
 
1244
        // initialize animations and other things
 
1245
        client->ps.commandTime = level.time - 100;
 
1246
        ent->client->pers.cmd.serverTime = level.time;
 
1247
        ClientThink( ent-g_entities );
 
1248
 
 
1249
        // positively link the client, even if the command times are weird
 
1250
        if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
 
1251
                BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
 
1252
                VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
 
1253
                trap_LinkEntity( ent );
 
1254
        }
 
1255
 
 
1256
        // run the presend to set anything else
 
1257
        ClientEndFrame( ent );
 
1258
 
 
1259
        // clear entity state values
 
1260
        BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
 
1261
}
 
1262
 
 
1263
 
 
1264
/*
 
1265
===========
 
1266
ClientDisconnect
 
1267
 
 
1268
Called when a player drops from the server.
 
1269
Will not be called between levels.
 
1270
 
 
1271
This should NOT be called directly by any game logic,
 
1272
call trap_DropClient(), which will call this and do
 
1273
server system housekeeping.
 
1274
============
 
1275
*/
 
1276
void ClientDisconnect( int clientNum ) {
 
1277
        gentity_t       *ent;
 
1278
        gentity_t       *tent;
 
1279
        int                     i;
 
1280
 
 
1281
        // cleanup if we are kicking a bot that
 
1282
        // hasn't spawned yet
 
1283
        G_RemoveQueuedBotBegin( clientNum );
 
1284
 
 
1285
        ent = g_entities + clientNum;
 
1286
        if ( !ent->client ) {
 
1287
                return;
 
1288
        }
 
1289
 
 
1290
        // stop any following clients
 
1291
        for ( i = 0 ; i < level.maxclients ; i++ ) {
 
1292
                if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
 
1293
                        && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
 
1294
                        && level.clients[i].sess.spectatorClient == clientNum ) {
 
1295
                        StopFollowing( &g_entities[i] );
 
1296
                }
 
1297
        }
 
1298
 
 
1299
        // send effect if they were completely connected
 
1300
        if ( ent->client->pers.connected == CON_CONNECTED 
 
1301
                && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
 
1302
                tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
 
1303
                tent->s.clientNum = ent->s.clientNum;
 
1304
 
 
1305
                // They don't get to take powerups with them!
 
1306
                // Especially important for stuff like CTF flags
 
1307
                TossClientItems( ent );
 
1308
#ifdef MISSIONPACK
 
1309
                TossClientPersistantPowerups( ent );
 
1310
                if( g_gametype.integer == GT_HARVESTER ) {
 
1311
                        TossClientCubes( ent );
 
1312
                }
 
1313
#endif
 
1314
 
 
1315
        }
 
1316
 
 
1317
        G_LogPrintf( "ClientDisconnect: %i\n", clientNum );
 
1318
 
 
1319
        // if we are playing in tourney mode and losing, give a win to the other player
 
1320
        if ( (g_gametype.integer == GT_TOURNAMENT )
 
1321
                && !level.intermissiontime
 
1322
                && !level.warmupTime && level.sortedClients[1] == clientNum ) {
 
1323
                level.clients[ level.sortedClients[0] ].sess.wins++;
 
1324
                ClientUserinfoChanged( level.sortedClients[0] );
 
1325
        }
 
1326
 
 
1327
        if( g_gametype.integer == GT_TOURNAMENT &&
 
1328
                ent->client->sess.sessionTeam == TEAM_FREE &&
 
1329
                level.intermissiontime ) {
 
1330
 
 
1331
                trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
 
1332
                level.restarted = qtrue;
 
1333
                level.changemap = NULL;
 
1334
                level.intermissiontime = 0;
 
1335
        }
 
1336
 
 
1337
        trap_UnlinkEntity (ent);
 
1338
        ent->s.modelindex = 0;
 
1339
        ent->inuse = qfalse;
 
1340
        ent->classname = "disconnected";
 
1341
        ent->client->pers.connected = CON_DISCONNECTED;
 
1342
        ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
 
1343
        ent->client->sess.sessionTeam = TEAM_FREE;
 
1344
 
 
1345
        trap_SetConfigstring( CS_PLAYERS + clientNum, "");
 
1346
 
 
1347
        CalculateRanks();
 
1348
 
 
1349
        if ( ent->r.svFlags & SVF_BOT ) {
 
1350
                BotAIShutdownClient( clientNum, qfalse );
 
1351
        }
 
1352
}
 
1353
 
 
1354