1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2005 - 2015, ioquake3 contributors
7 Copyright (C) 2013 - 2015, OpenJK contributors
8 
9 This file is part of the OpenJK source code.
10 
11 OpenJK is free software; you can redistribute it and/or modify it
12 under the terms of the GNU General Public License version 2 as
13 published by the Free Software Foundation.
14 
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, see <http://www.gnu.org/licenses/>.
22 ===========================================================================
23 */
24 
25 #include "g_local.h"
26 #include "ghoul2/G2.h"
27 #include "bg_saga.h"
28 
29 // g_client.c -- client functions that don't happen every frame
30 
31 static vec3_t	playerMins = {-15, -15, DEFAULT_MINS_2};
32 static vec3_t	playerMaxs = {15, 15, DEFAULT_MAXS_2};
33 
34 extern int g_siegeRespawnCheck;
35 
36 void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin );
37 void WP_SaberRemoveG2Model( gentity_t *saberent );
38 extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel );
39 extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel );
40 
41 forcedata_t Client_Force[MAX_CLIENTS];
42 
43 /*QUAKED info_player_duel (1 0 1) (-16 -16 -24) (16 16 32) initial
44 potential spawning position for duelists in duel.
45 Targets will be fired when someone spawns in on them.
46 "nobots" will prevent bots from using this spot.
47 "nohumans" will prevent non-bots from using this spot.
48 */
SP_info_player_duel(gentity_t * ent)49 void SP_info_player_duel( gentity_t *ent )
50 {
51 	int		i;
52 
53 	G_SpawnInt( "nobots", "0", &i);
54 	if ( i ) {
55 		ent->flags |= FL_NO_BOTS;
56 	}
57 	G_SpawnInt( "nohumans", "0", &i );
58 	if ( i ) {
59 		ent->flags |= FL_NO_HUMANS;
60 	}
61 }
62 
63 /*QUAKED info_player_duel1 (1 0 1) (-16 -16 -24) (16 16 32) initial
64 potential spawning position for lone duelists in powerduel.
65 Targets will be fired when someone spawns in on them.
66 "nobots" will prevent bots from using this spot.
67 "nohumans" will prevent non-bots from using this spot.
68 */
SP_info_player_duel1(gentity_t * ent)69 void SP_info_player_duel1( gentity_t *ent )
70 {
71 	int		i;
72 
73 	G_SpawnInt( "nobots", "0", &i);
74 	if ( i ) {
75 		ent->flags |= FL_NO_BOTS;
76 	}
77 	G_SpawnInt( "nohumans", "0", &i );
78 	if ( i ) {
79 		ent->flags |= FL_NO_HUMANS;
80 	}
81 }
82 
83 /*QUAKED info_player_duel2 (1 0 1) (-16 -16 -24) (16 16 32) initial
84 potential spawning position for paired duelists in powerduel.
85 Targets will be fired when someone spawns in on them.
86 "nobots" will prevent bots from using this spot.
87 "nohumans" will prevent non-bots from using this spot.
88 */
SP_info_player_duel2(gentity_t * ent)89 void SP_info_player_duel2( gentity_t *ent )
90 {
91 	int		i;
92 
93 	G_SpawnInt( "nobots", "0", &i);
94 	if ( i ) {
95 		ent->flags |= FL_NO_BOTS;
96 	}
97 	G_SpawnInt( "nohumans", "0", &i );
98 	if ( i ) {
99 		ent->flags |= FL_NO_HUMANS;
100 	}
101 }
102 
103 /*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial
104 potential spawning position for deathmatch games.
105 The first time a player enters the game, they will be at an 'initial' spot.
106 Targets will be fired when someone spawns in on them.
107 "nobots" will prevent bots from using this spot.
108 "nohumans" will prevent non-bots from using this spot.
109 */
SP_info_player_deathmatch(gentity_t * ent)110 void SP_info_player_deathmatch( gentity_t *ent ) {
111 	int		i;
112 
113 	G_SpawnInt( "nobots", "0", &i);
114 	if ( i ) {
115 		ent->flags |= FL_NO_BOTS;
116 	}
117 	G_SpawnInt( "nohumans", "0", &i );
118 	if ( i ) {
119 		ent->flags |= FL_NO_HUMANS;
120 	}
121 }
122 
123 /*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32)
124 Targets will be fired when someone spawns in on them.
125 equivelant to info_player_deathmatch
126 */
SP_info_player_start(gentity_t * ent)127 void SP_info_player_start(gentity_t *ent) {
128 	ent->classname = "info_player_deathmatch";
129 	SP_info_player_deathmatch( ent );
130 }
131 
132 /*QUAKED info_player_start_red (1 0 0) (-16 -16 -24) (16 16 32) INITIAL
133 For Red Team DM starts
134 
135 Targets will be fired when someone spawns in on them.
136 equivalent to info_player_deathmatch
137 
138 INITIAL - The first time a player enters the game, they will be at an 'initial' spot.
139 
140 "nobots" will prevent bots from using this spot.
141 "nohumans" will prevent non-bots from using this spot.
142 */
SP_info_player_start_red(gentity_t * ent)143 void SP_info_player_start_red(gentity_t *ent) {
144 	SP_info_player_deathmatch( ent );
145 }
146 
147 /*QUAKED info_player_start_blue (1 0 0) (-16 -16 -24) (16 16 32) INITIAL
148 For Blue Team DM starts
149 
150 Targets will be fired when someone spawns in on them.
151 equivalent to info_player_deathmatch
152 
153 INITIAL - The first time a player enters the game, they will be at an 'initial' spot.
154 
155 "nobots" will prevent bots from using this spot.
156 "nohumans" will prevent non-bots from using this spot.
157 */
SP_info_player_start_blue(gentity_t * ent)158 void SP_info_player_start_blue(gentity_t *ent) {
159 	SP_info_player_deathmatch( ent );
160 }
161 
SiegePointUse(gentity_t * self,gentity_t * other,gentity_t * activator)162 void SiegePointUse( gentity_t *self, gentity_t *other, gentity_t *activator )
163 {
164 	//Toggle the point on/off
165 	if (self->genericValue1)
166 	{
167 		self->genericValue1 = 0;
168 	}
169 	else
170 	{
171 		self->genericValue1 = 1;
172 	}
173 }
174 
175 /*QUAKED info_player_siegeteam1 (1 0 0) (-16 -16 -24) (16 16 32)
176 siege start point - team1
177 name and behavior of team1 depends on what is defined in the
178 .siege file for this level
179 
180 startoff - if non-0 spawn point will be disabled until used
181 idealclass - if specified, this spawn point will be considered
182 "ideal" for players of this class name. Corresponds to the name
183 entry in the .scl (siege class) file.
184 Targets will be fired when someone spawns in on them.
185 */
SP_info_player_siegeteam1(gentity_t * ent)186 void SP_info_player_siegeteam1(gentity_t *ent) {
187 	int soff = 0;
188 
189 	if (level.gametype != GT_SIEGE)
190 	{ //turn into a DM spawn if not in siege game mode
191 		ent->classname = "info_player_deathmatch";
192 		SP_info_player_deathmatch( ent );
193 
194 		return;
195 	}
196 
197 	G_SpawnInt("startoff", "0", &soff);
198 
199 	if (soff)
200 	{ //start disabled
201 		ent->genericValue1 = 0;
202 	}
203 	else
204 	{
205 		ent->genericValue1 = 1;
206 	}
207 
208 	ent->use = SiegePointUse;
209 }
210 
211 /*QUAKED info_player_siegeteam2 (0 0 1) (-16 -16 -24) (16 16 32)
212 siege start point - team2
213 name and behavior of team2 depends on what is defined in the
214 .siege file for this level
215 
216 startoff - if non-0 spawn point will be disabled until used
217 idealclass - if specified, this spawn point will be considered
218 "ideal" for players of this class name. Corresponds to the name
219 entry in the .scl (siege class) file.
220 Targets will be fired when someone spawns in on them.
221 */
SP_info_player_siegeteam2(gentity_t * ent)222 void SP_info_player_siegeteam2(gentity_t *ent) {
223 	int soff = 0;
224 
225 	if (level.gametype != GT_SIEGE)
226 	{ //turn into a DM spawn if not in siege game mode
227 		ent->classname = "info_player_deathmatch";
228 		SP_info_player_deathmatch( ent );
229 
230 		return;
231 	}
232 
233 	G_SpawnInt("startoff", "0", &soff);
234 
235 	if (soff)
236 	{ //start disabled
237 		ent->genericValue1 = 0;
238 	}
239 	else
240 	{
241 		ent->genericValue1 = 1;
242 	}
243 
244 	ent->use = SiegePointUse;
245 }
246 
247 /*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) RED BLUE
248 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
249 RED - In a Siege game, the intermission will happen here if the Red (attacking) team wins
250 BLUE - In a Siege game, the intermission will happen here if the Blue (defending) team wins
251 */
SP_info_player_intermission(gentity_t * ent)252 void SP_info_player_intermission( gentity_t *ent ) {
253 
254 }
255 
256 /*QUAKED info_player_intermission_red (1 0 1) (-16 -16 -24) (16 16 32)
257 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
258 
259 In a Siege game, the intermission will happen here if the Red (attacking) team wins
260 target - ent to look at
261 target2 - ents to use when this intermission point is chosen
262 */
SP_info_player_intermission_red(gentity_t * ent)263 void SP_info_player_intermission_red( gentity_t *ent ) {
264 
265 }
266 
267 /*QUAKED info_player_intermission_blue (1 0 1) (-16 -16 -24) (16 16 32)
268 The intermission will be viewed from this point.  Target an info_notnull for the view direction.
269 
270 In a Siege game, the intermission will happen here if the Blue (defending) team wins
271 target - ent to look at
272 target2 - ents to use when this intermission point is chosen
273 */
SP_info_player_intermission_blue(gentity_t * ent)274 void SP_info_player_intermission_blue( gentity_t *ent ) {
275 
276 }
277 
278 #define JMSABER_RESPAWN_TIME 20000 //in case it gets stuck somewhere no one can reach
279 
ThrowSaberToAttacker(gentity_t * self,gentity_t * attacker)280 void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker)
281 {
282 	gentity_t *ent = &g_entities[self->client->ps.saberIndex];
283 	vec3_t a;
284 	int altVelocity = 0;
285 
286 	if (!ent || ent->enemy != self)
287 	{ //something has gone very wrong (this should never happen)
288 		//but in case it does.. find the saber manually
289 #ifdef _DEBUG
290 		Com_Printf("Lost the saber! Attempting to use global pointer..\n");
291 #endif
292 		ent = gJMSaberEnt;
293 
294 		if (!ent)
295 		{
296 #ifdef _DEBUG
297 			Com_Printf("The global pointer was NULL. This is a bad thing.\n");
298 #endif
299 			return;
300 		}
301 
302 #ifdef _DEBUG
303 		Com_Printf("Got it (%i). Setting enemy to client %i.\n", ent->s.number, self->s.number);
304 #endif
305 
306 		ent->enemy = self;
307 		self->client->ps.saberIndex = ent->s.number;
308 	}
309 
310 	trap->SetConfigstring ( CS_CLIENT_JEDIMASTER, "-1" );
311 
312 	if (attacker && attacker->client && self->client->ps.saberInFlight)
313 	{ //someone killed us and we had the saber thrown, so actually move this saber to the saber location
314 	  //if we killed ourselves with saber thrown, however, same suicide rules of respawning at spawn spot still
315 	  //apply.
316 		gentity_t *flyingsaber = &g_entities[self->client->ps.saberEntityNum];
317 
318 		if (flyingsaber && flyingsaber->inuse)
319 		{
320 			VectorCopy(flyingsaber->s.pos.trBase, ent->s.pos.trBase);
321 			VectorCopy(flyingsaber->s.pos.trDelta, ent->s.pos.trDelta);
322 			VectorCopy(flyingsaber->s.apos.trBase, ent->s.apos.trBase);
323 			VectorCopy(flyingsaber->s.apos.trDelta, ent->s.apos.trDelta);
324 
325 			VectorCopy(flyingsaber->r.currentOrigin, ent->r.currentOrigin);
326 			VectorCopy(flyingsaber->r.currentAngles, ent->r.currentAngles);
327 			altVelocity = 1;
328 		}
329 	}
330 
331 	self->client->ps.saberInFlight = qtrue; //say he threw it anyway in order to properly remove from dead body
332 
333 	WP_SaberAddG2Model( ent, self->client->saber[0].model, self->client->saber[0].skin );
334 
335 	ent->s.eFlags &= ~(EF_NODRAW);
336 	ent->s.modelGhoul2 = 1;
337 	ent->s.eType = ET_MISSILE;
338 	ent->enemy = NULL;
339 
340 	if (!attacker || !attacker->client)
341 	{
342 		VectorCopy(ent->s.origin2, ent->s.pos.trBase);
343 		VectorCopy(ent->s.origin2, ent->s.origin);
344 		VectorCopy(ent->s.origin2, ent->r.currentOrigin);
345 		ent->pos2[0] = 0;
346 		trap->LinkEntity((sharedEntity_t *)ent);
347 		return;
348 	}
349 
350 	if (!altVelocity)
351 	{
352 		VectorCopy(self->s.pos.trBase, ent->s.pos.trBase);
353 		VectorCopy(self->s.pos.trBase, ent->s.origin);
354 		VectorCopy(self->s.pos.trBase, ent->r.currentOrigin);
355 
356 		VectorSubtract(attacker->client->ps.origin, ent->s.pos.trBase, a);
357 
358 		VectorNormalize(a);
359 
360 		ent->s.pos.trDelta[0] = a[0]*256;
361 		ent->s.pos.trDelta[1] = a[1]*256;
362 		ent->s.pos.trDelta[2] = 256;
363 	}
364 
365 	trap->LinkEntity((sharedEntity_t *)ent);
366 }
367 
JMSaberThink(gentity_t * ent)368 void JMSaberThink(gentity_t *ent)
369 {
370 	gJMSaberEnt = ent;
371 
372 	if (ent->enemy)
373 	{
374 		if (!ent->enemy->client || !ent->enemy->inuse)
375 		{ //disconnected?
376 			VectorCopy(ent->enemy->s.pos.trBase, ent->s.pos.trBase);
377 			VectorCopy(ent->enemy->s.pos.trBase, ent->s.origin);
378 			VectorCopy(ent->enemy->s.pos.trBase, ent->r.currentOrigin);
379 			ent->s.modelindex = G_ModelIndex( DEFAULT_SABER_MODEL );
380 			ent->s.eFlags &= ~(EF_NODRAW);
381 			ent->s.modelGhoul2 = 1;
382 			ent->s.eType = ET_MISSILE;
383 			ent->enemy = NULL;
384 
385 			ent->pos2[0] = 1;
386 			ent->pos2[1] = 0; //respawn next think
387 			trap->LinkEntity((sharedEntity_t *)ent);
388 		}
389 		else
390 		{
391 			ent->pos2[1] = level.time + JMSABER_RESPAWN_TIME;
392 		}
393 	}
394 	else if (ent->pos2[0] && ent->pos2[1] < level.time)
395 	{
396 		VectorCopy(ent->s.origin2, ent->s.pos.trBase);
397 		VectorCopy(ent->s.origin2, ent->s.origin);
398 		VectorCopy(ent->s.origin2, ent->r.currentOrigin);
399 		ent->pos2[0] = 0;
400 		trap->LinkEntity((sharedEntity_t *)ent);
401 	}
402 
403 	ent->nextthink = level.time + 50;
404 	G_RunObject(ent);
405 }
406 
JMSaberTouch(gentity_t * self,gentity_t * other,trace_t * trace)407 void JMSaberTouch(gentity_t *self, gentity_t *other, trace_t *trace)
408 {
409 	int i = 0;
410 //	gentity_t *te;
411 
412 	if (!other || !other->client || other->health < 1)
413 	{
414 		return;
415 	}
416 
417 	if (self->enemy)
418 	{
419 		return;
420 	}
421 
422 	if (!self->s.modelindex)
423 	{
424 		return;
425 	}
426 
427 	if (other->client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
428 	{
429 		return;
430 	}
431 
432 	if (other->client->ps.isJediMaster)
433 	{
434 		return;
435 	}
436 
437 	self->enemy = other;
438 	other->client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER);
439 	other->client->ps.weapon = WP_SABER;
440 	other->s.weapon = WP_SABER;
441 	other->client->ps.zoomMode = 0;
442 	G_AddEvent(other, EV_BECOME_JEDIMASTER, 0);
443 
444 	// Track the jedi master
445 	trap->SetConfigstring ( CS_CLIENT_JEDIMASTER, va("%i", other->s.number ) );
446 
447 	if (g_spawnInvulnerability.integer)
448 	{
449 		other->client->ps.eFlags |= EF_INVULNERABLE;
450 		other->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer;
451 	}
452 
453 	trap->SendServerCommand( -1, va("cp \"%s %s\n\"", other->client->pers.netname, G_GetStringEdString("MP_SVGAME", "BECOMEJM")) );
454 
455 	other->client->ps.isJediMaster = qtrue;
456 	other->client->ps.saberIndex = self->s.number;
457 
458 	if (other->health < 200 && other->health > 0)
459 	{ //full health when you become the Jedi Master
460 		other->client->ps.stats[STAT_HEALTH] = other->health = 200;
461 	}
462 
463 	if (other->client->ps.fd.forcePower < 100)
464 	{
465 		other->client->ps.fd.forcePower = 100;
466 	}
467 
468 	while (i < NUM_FORCE_POWERS)
469 	{
470 		other->client->ps.fd.forcePowersKnown |= (1 << i);
471 		other->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3;
472 
473 		i++;
474 	}
475 
476 	self->pos2[0] = 1;
477 	self->pos2[1] = level.time + JMSABER_RESPAWN_TIME;
478 
479 	self->s.modelindex = 0;
480 	self->s.eFlags |= EF_NODRAW;
481 	self->s.modelGhoul2 = 0;
482 	self->s.eType = ET_GENERAL;
483 
484 	/*
485 	te = G_TempEntity( vec3_origin, EV_DESTROY_GHOUL2_INSTANCE );
486 	te->r.svFlags |= SVF_BROADCAST;
487 	te->s.eventParm = self->s.number;
488 	*/
489 	G_KillG2Queue(self->s.number);
490 
491 	return;
492 }
493 
494 gentity_t *gJMSaberEnt = NULL;
495 
496 /*QUAKED info_jedimaster_start (1 0 0) (-16 -16 -24) (16 16 32)
497 "jedi master" saber spawn point
498 */
SP_info_jedimaster_start(gentity_t * ent)499 void SP_info_jedimaster_start(gentity_t *ent)
500 {
501 	if (level.gametype != GT_JEDIMASTER)
502 	{
503 		gJMSaberEnt = NULL;
504 		G_FreeEntity(ent);
505 		return;
506 	}
507 
508 	ent->enemy = NULL;
509 
510 	ent->flags = FL_BOUNCE_HALF;
511 
512 	ent->s.modelindex = G_ModelIndex( DEFAULT_SABER_MODEL );
513 	ent->s.modelGhoul2 = 1;
514 	ent->s.g2radius = 20;
515 	//ent->s.eType = ET_GENERAL;
516 	ent->s.eType = ET_MISSILE;
517 	ent->s.weapon = WP_SABER;
518 	ent->s.pos.trType = TR_GRAVITY;
519 	ent->s.pos.trTime = level.time;
520 	VectorSet( ent->r.maxs, 3, 3, 3 );
521 	VectorSet( ent->r.mins, -3, -3, -3 );
522 	ent->r.contents = CONTENTS_TRIGGER;
523 	ent->clipmask = MASK_SOLID;
524 
525 	ent->isSaberEntity = qtrue;
526 
527 	ent->bounceCount = -5;
528 
529 	ent->physicsObject = qtrue;
530 
531 	VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot
532 
533 	ent->touch = JMSaberTouch;
534 
535 	trap->LinkEntity((sharedEntity_t *)ent);
536 
537 	ent->think = JMSaberThink;
538 	ent->nextthink = level.time + 50;
539 }
540 
541 /*
542 =======================================================================
543 
544   SelectSpawnPoint
545 
546 =======================================================================
547 */
548 
549 /*
550 ================
551 SpotWouldTelefrag
552 
553 ================
554 */
SpotWouldTelefrag(gentity_t * spot)555 qboolean SpotWouldTelefrag( gentity_t *spot ) {
556 	int			i, num;
557 	int			touch[MAX_GENTITIES];
558 	gentity_t	*hit;
559 	vec3_t		mins, maxs;
560 
561 	VectorAdd( spot->s.origin, playerMins, mins );
562 	VectorAdd( spot->s.origin, playerMaxs, maxs );
563 	num = trap->EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
564 
565 	for (i=0 ; i<num ; i++) {
566 		hit = &g_entities[touch[i]];
567 		//if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) {
568 		if ( hit->client) {
569 			return qtrue;
570 		}
571 
572 	}
573 
574 	return qfalse;
575 }
576 
SpotWouldTelefrag2(gentity_t * mover,vec3_t dest)577 qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest )
578 {
579 	int			i, num;
580 	int			touch[MAX_GENTITIES];
581 	gentity_t	*hit;
582 	vec3_t		mins, maxs;
583 
584 	VectorAdd( dest, mover->r.mins, mins );
585 	VectorAdd( dest, mover->r.maxs, maxs );
586 	num = trap->EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
587 
588 	for (i=0 ; i<num ; i++)
589 	{
590 		hit = &g_entities[touch[i]];
591 		if ( hit == mover )
592 		{
593 			continue;
594 		}
595 
596 		if ( hit->r.contents & mover->r.contents )
597 		{
598 			return qtrue;
599 		}
600 	}
601 
602 	return qfalse;
603 }
604 
605 /*
606 ================
607 SelectNearestDeathmatchSpawnPoint
608 
609 Find the spot that we DON'T want to use
610 ================
611 */
612 #define	MAX_SPAWN_POINTS	128
SelectNearestDeathmatchSpawnPoint(vec3_t from)613 gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) {
614 	gentity_t	*spot;
615 	vec3_t		delta;
616 	float		dist, nearestDist;
617 	gentity_t	*nearestSpot;
618 
619 	nearestDist = 999999;
620 	nearestSpot = NULL;
621 	spot = NULL;
622 
623 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
624 
625 		VectorSubtract( spot->s.origin, from, delta );
626 		dist = VectorLength( delta );
627 		if ( dist < nearestDist ) {
628 			nearestDist = dist;
629 			nearestSpot = spot;
630 		}
631 	}
632 
633 	return nearestSpot;
634 }
635 
636 
637 /*
638 ================
639 SelectRandomDeathmatchSpawnPoint
640 
641 go to a random point that doesn't telefrag
642 ================
643 */
644 #define	MAX_SPAWN_POINTS	128
SelectRandomDeathmatchSpawnPoint(qboolean isbot)645 gentity_t *SelectRandomDeathmatchSpawnPoint( qboolean isbot ) {
646 	gentity_t	*spot;
647 	int			count;
648 	int			selection;
649 	gentity_t	*spots[MAX_SPAWN_POINTS];
650 
651 	count = 0;
652 	spot = NULL;
653 
654 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL && count < MAX_SPAWN_POINTS) {
655 		if ( SpotWouldTelefrag( spot ) ) {
656 			continue;
657 		}
658 
659 		if(((spot->flags & FL_NO_BOTS) && isbot) ||
660 		   ((spot->flags & FL_NO_HUMANS) && !isbot))
661 		{
662 			// spot is not for this human/bot player
663 			continue;
664 		}
665 
666 		spots[ count ] = spot;
667 		count++;
668 	}
669 
670 	if ( !count ) {	// no spots that won't telefrag
671 		return G_Find( NULL, FOFS(classname), "info_player_deathmatch");
672 	}
673 
674 	selection = rand() % count;
675 	return spots[ selection ];
676 }
677 
678 /*
679 ===========
680 SelectRandomFurthestSpawnPoint
681 
682 Chooses a player start, deathmatch start, etc
683 ============
684 */
SelectRandomFurthestSpawnPoint(vec3_t avoidPoint,vec3_t origin,vec3_t angles,team_t team,qboolean isbot)685 gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team, qboolean isbot ) {
686 	gentity_t	*spot;
687 	vec3_t		delta;
688 	float		dist;
689 	float		list_dist[MAX_SPAWN_POINTS];
690 	gentity_t	*list_spot[MAX_SPAWN_POINTS];
691 	int			numSpots, rnd, i, j;
692 
693 	numSpots = 0;
694 	spot = NULL;
695 
696 	//in Team DM, look for a team start spot first, if any
697 	if ( level.gametype == GT_TEAM
698 		&& team != TEAM_FREE
699 		&& team != TEAM_SPECTATOR )
700 	{
701 		char *classname = NULL;
702 		if ( team == TEAM_RED )
703 		{
704 			classname = "info_player_start_red";
705 		}
706 		else
707 		{
708 			classname = "info_player_start_blue";
709 		}
710 		while ((spot = G_Find (spot, FOFS(classname), classname)) != NULL) {
711 			if ( SpotWouldTelefrag( spot ) ) {
712 				continue;
713 			}
714 
715 			if(((spot->flags & FL_NO_BOTS) && isbot) ||
716 			   ((spot->flags & FL_NO_HUMANS) && !isbot))
717 			{
718 				// spot is not for this human/bot player
719 				continue;
720 			}
721 
722 			VectorSubtract( spot->s.origin, avoidPoint, delta );
723 			dist = VectorLength( delta );
724 			for (i = 0; i < numSpots; i++) {
725 				if ( dist > list_dist[i] ) {
726 					if ( numSpots >= MAX_SPAWN_POINTS )
727 						numSpots = MAX_SPAWN_POINTS-1;
728 					for (j = numSpots; j > i; j--) {
729 						list_dist[j] = list_dist[j-1];
730 						list_spot[j] = list_spot[j-1];
731 					}
732 					list_dist[i] = dist;
733 					list_spot[i] = spot;
734 					numSpots++;
735 					break;
736 				}
737 			}
738 			if (i >= numSpots && numSpots < MAX_SPAWN_POINTS) {
739 				list_dist[numSpots] = dist;
740 				list_spot[numSpots] = spot;
741 				numSpots++;
742 			}
743 		}
744 	}
745 
746 	if ( !numSpots )
747 	{//couldn't find any of the above
748 		while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
749 			if ( SpotWouldTelefrag( spot ) ) {
750 				continue;
751 			}
752 
753 			if(((spot->flags & FL_NO_BOTS) && isbot) ||
754 			   ((spot->flags & FL_NO_HUMANS) && !isbot))
755 			{
756 				// spot is not for this human/bot player
757 				continue;
758 			}
759 
760 			VectorSubtract( spot->s.origin, avoidPoint, delta );
761 			dist = VectorLength( delta );
762 			for (i = 0; i < numSpots; i++) {
763 				if ( dist > list_dist[i] ) {
764 					if ( numSpots >= MAX_SPAWN_POINTS )
765 						numSpots = MAX_SPAWN_POINTS-1;
766 					for (j = numSpots; j > i; j--) {
767 						list_dist[j] = list_dist[j-1];
768 						list_spot[j] = list_spot[j-1];
769 					}
770 					list_dist[i] = dist;
771 					list_spot[i] = spot;
772 					numSpots++;
773 					break;
774 				}
775 			}
776 			if (i >= numSpots && numSpots < MAX_SPAWN_POINTS) {
777 				list_dist[numSpots] = dist;
778 				list_spot[numSpots] = spot;
779 				numSpots++;
780 			}
781 		}
782 		if (!numSpots) {
783 			spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
784 			if (!spot)
785 				trap->Error( ERR_DROP, "Couldn't find a spawn point" );
786 			VectorCopy (spot->s.origin, origin);
787 			origin[2] += 9;
788 			VectorCopy (spot->s.angles, angles);
789 			return spot;
790 		}
791 	}
792 
793 	// select a random spot from the spawn points furthest away
794 	rnd = Q_flrand(0.0f, 1.0f) * (numSpots / 2);
795 
796 	VectorCopy (list_spot[rnd]->s.origin, origin);
797 	origin[2] += 9;
798 	VectorCopy (list_spot[rnd]->s.angles, angles);
799 
800 	return list_spot[rnd];
801 }
802 
SelectDuelSpawnPoint(int team,vec3_t avoidPoint,vec3_t origin,vec3_t angles,qboolean isbot)803 gentity_t *SelectDuelSpawnPoint( int team, vec3_t avoidPoint, vec3_t origin, vec3_t angles, qboolean isbot )
804 {
805 	gentity_t	*spot;
806 	vec3_t		delta;
807 	float		dist;
808 	float		list_dist[MAX_SPAWN_POINTS];
809 	gentity_t	*list_spot[MAX_SPAWN_POINTS];
810 	int			numSpots, rnd, i, j;
811 	char		*spotName;
812 
813 	if (team == DUELTEAM_LONE)
814 	{
815 		spotName = "info_player_duel1";
816 	}
817 	else if (team == DUELTEAM_DOUBLE)
818 	{
819 		spotName = "info_player_duel2";
820 	}
821 	else if (team == DUELTEAM_SINGLE)
822 	{
823 		spotName = "info_player_duel";
824 	}
825 	else
826 	{
827 		spotName = "info_player_deathmatch";
828 	}
829 tryAgain:
830 
831 	numSpots = 0;
832 	spot = NULL;
833 
834 	while ((spot = G_Find (spot, FOFS(classname), spotName)) != NULL) {
835 		if ( SpotWouldTelefrag( spot ) ) {
836 			continue;
837 		}
838 
839 		if(((spot->flags & FL_NO_BOTS) && isbot) ||
840 			((spot->flags & FL_NO_HUMANS) && !isbot))
841 		{
842 			// spot is not for this human/bot player
843 			continue;
844 		}
845 
846 		VectorSubtract( spot->s.origin, avoidPoint, delta );
847 		dist = VectorLength( delta );
848 		for (i = 0; i < numSpots; i++) {
849 			if ( dist > list_dist[i] ) {
850 				if ( numSpots >= MAX_SPAWN_POINTS )
851 					numSpots = MAX_SPAWN_POINTS-1;
852 				for (j = numSpots; j > i; j--) {
853 					list_dist[j] = list_dist[j-1];
854 					list_spot[j] = list_spot[j-1];
855 				}
856 				list_dist[i] = dist;
857 				list_spot[i] = spot;
858 				numSpots++;
859 				break;
860 			}
861 		}
862 		if (i >= numSpots && numSpots < MAX_SPAWN_POINTS) {
863 			list_dist[numSpots] = dist;
864 			list_spot[numSpots] = spot;
865 			numSpots++;
866 		}
867 	}
868 	if (!numSpots)
869 	{
870 		if (Q_stricmp(spotName, "info_player_deathmatch"))
871 		{ //try the loop again with info_player_deathmatch as the target if we couldn't find a duel spot
872 			spotName = "info_player_deathmatch";
873 			goto tryAgain;
874 		}
875 
876 		//If we got here we found no free duel or DM spots, just try the first DM spot
877 		spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch");
878 		if (!spot)
879 			trap->Error( ERR_DROP, "Couldn't find a spawn point" );
880 		VectorCopy (spot->s.origin, origin);
881 		origin[2] += 9;
882 		VectorCopy (spot->s.angles, angles);
883 		return spot;
884 	}
885 
886 	// select a random spot from the spawn points furthest away
887 	rnd = Q_flrand(0.0f, 1.0f) * (numSpots / 2);
888 
889 	VectorCopy (list_spot[rnd]->s.origin, origin);
890 	origin[2] += 9;
891 	VectorCopy (list_spot[rnd]->s.angles, angles);
892 
893 	return list_spot[rnd];
894 }
895 
896 /*
897 ===========
898 SelectSpawnPoint
899 
900 Chooses a player start, deathmatch start, etc
901 ============
902 */
SelectSpawnPoint(vec3_t avoidPoint,vec3_t origin,vec3_t angles,team_t team,qboolean isbot)903 gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team, qboolean isbot ) {
904 	return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles, team, isbot );
905 
906 	/*
907 	gentity_t	*spot;
908 	gentity_t	*nearestSpot;
909 
910 	nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint );
911 
912 	spot = SelectRandomDeathmatchSpawnPoint ( );
913 	if ( spot == nearestSpot ) {
914 		// roll again if it would be real close to point of death
915 		spot = SelectRandomDeathmatchSpawnPoint ( );
916 		if ( spot == nearestSpot ) {
917 			// last try
918 			spot = SelectRandomDeathmatchSpawnPoint ( );
919 		}
920 	}
921 
922 	// find a single player start spot
923 	if (!spot) {
924 		trap->Error( ERR_DROP, "Couldn't find a spawn point" );
925 	}
926 
927 	VectorCopy (spot->s.origin, origin);
928 	origin[2] += 9;
929 	VectorCopy (spot->s.angles, angles);
930 
931 	return spot;
932 	*/
933 }
934 
935 /*
936 ===========
937 SelectInitialSpawnPoint
938 
939 Try to find a spawn point marked 'initial', otherwise
940 use normal spawn selection.
941 ============
942 */
SelectInitialSpawnPoint(vec3_t origin,vec3_t angles,team_t team,qboolean isbot)943 gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles, team_t team, qboolean isbot ) {
944 	gentity_t	*spot;
945 
946 	spot = NULL;
947 	while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) {
948 		if(((spot->flags & FL_NO_BOTS) && isbot) ||
949 		   ((spot->flags & FL_NO_HUMANS) && !isbot))
950 		{
951 			continue;
952 		}
953 
954 		if ( spot->spawnflags & 1 ) {
955 			break;
956 		}
957 	}
958 
959 	if ( !spot || SpotWouldTelefrag( spot ) ) {
960 		return SelectSpawnPoint( vec3_origin, origin, angles, team, isbot );
961 	}
962 
963 	VectorCopy (spot->s.origin, origin);
964 	origin[2] += 9;
965 	VectorCopy (spot->s.angles, angles);
966 
967 	return spot;
968 }
969 
970 /*
971 ===========
972 SelectSpectatorSpawnPoint
973 
974 ============
975 */
SelectSpectatorSpawnPoint(vec3_t origin,vec3_t angles)976 gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) {
977 	FindIntermissionPoint();
978 
979 	VectorCopy( level.intermission_origin, origin );
980 	VectorCopy( level.intermission_angle, angles );
981 
982 	return NULL;
983 }
984 
985 /*
986 =======================================================================
987 
988 BODYQUE
989 
990 =======================================================================
991 */
992 
993 /*
994 =======================================================================
995 
996 BODYQUE
997 
998 =======================================================================
999 */
1000 
1001 #define BODY_SINK_TIME		30000//45000
1002 
1003 /*
1004 ===============
1005 InitBodyQue
1006 ===============
1007 */
InitBodyQue(void)1008 void InitBodyQue (void) {
1009 	int		i;
1010 	gentity_t	*ent;
1011 
1012 	level.bodyQueIndex = 0;
1013 	for (i=0; i<BODY_QUEUE_SIZE ; i++) {
1014 		ent = G_Spawn();
1015 		ent->classname = "bodyque";
1016 		ent->neverFree = qtrue;
1017 		level.bodyQue[i] = ent;
1018 	}
1019 }
1020 
1021 /*
1022 =============
1023 BodySink
1024 
1025 After sitting around for five seconds, fall into the ground and disappear
1026 =============
1027 */
BodySink(gentity_t * ent)1028 void BodySink( gentity_t *ent ) {
1029 	if ( level.time - ent->timestamp > BODY_SINK_TIME + 2500 ) {
1030 		// the body ques are never actually freed, they are just unlinked
1031 		trap->UnlinkEntity( (sharedEntity_t *)ent );
1032 		ent->physicsObject = qfalse;
1033 		return;
1034 	}
1035 //	ent->nextthink = level.time + 100;
1036 //	ent->s.pos.trBase[2] -= 1;
1037 
1038 	G_AddEvent(ent, EV_BODYFADE, 0);
1039 	ent->nextthink = level.time + 18000;
1040 	ent->takedamage = qfalse;
1041 }
1042 
1043 /*
1044 =============
1045 CopyToBodyQue
1046 
1047 A player is respawning, so make an entity that looks
1048 just like the existing corpse to leave behind.
1049 =============
1050 */
CopyToBodyQue(gentity_t * ent)1051 static qboolean CopyToBodyQue( gentity_t *ent ) {
1052 	gentity_t		*body;
1053 	int			contents;
1054 	int			islight = 0;
1055 
1056 	if (level.intermissiontime)
1057 	{
1058 		return qfalse;
1059 	}
1060 
1061 	trap->UnlinkEntity ((sharedEntity_t *)ent);
1062 
1063 	// if client is in a nodrop area, don't leave the body
1064 	contents = trap->PointContents( ent->s.origin, -1 );
1065 	if ( contents & CONTENTS_NODROP ) {
1066 		return qfalse;
1067 	}
1068 
1069 	if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION))
1070 	{ //for now, just don't spawn a body if you got disint'd
1071 		return qfalse;
1072 	}
1073 
1074 	// grab a body que and cycle to the next one
1075 	body = level.bodyQue[ level.bodyQueIndex ];
1076 	level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE;
1077 
1078 	trap->UnlinkEntity ((sharedEntity_t *)body);
1079 	body->s = ent->s;
1080 
1081 	//avoid oddly angled corpses floating around
1082 	body->s.angles[PITCH] = body->s.angles[ROLL] = body->s.apos.trBase[PITCH] = body->s.apos.trBase[ROLL] = 0;
1083 
1084 	body->s.g2radius = 100;
1085 
1086 	body->s.eType = ET_BODY;
1087 	body->s.eFlags = EF_DEAD;		// clear EF_TALK, etc
1088 
1089 	if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION))
1090 	{
1091 		body->s.eFlags |= EF_DISINTEGRATION;
1092 	}
1093 
1094 	VectorCopy(ent->client->ps.lastHitLoc, body->s.origin2);
1095 
1096 	body->s.powerups = 0;	// clear powerups
1097 	body->s.loopSound = 0;	// clear lava burning
1098 	body->s.loopIsSoundset = qfalse;
1099 	body->s.number = body - g_entities;
1100 	body->timestamp = level.time;
1101 	body->physicsObject = qtrue;
1102 	body->physicsBounce = 0;		// don't bounce
1103 	if ( body->s.groundEntityNum == ENTITYNUM_NONE ) {
1104 		body->s.pos.trType = TR_GRAVITY;
1105 		body->s.pos.trTime = level.time;
1106 		VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta );
1107 	} else {
1108 		body->s.pos.trType = TR_STATIONARY;
1109 	}
1110 	body->s.event = 0;
1111 
1112 	body->s.weapon = ent->s.bolt2;
1113 
1114 	if (body->s.weapon == WP_SABER && ent->client->ps.saberInFlight)
1115 	{
1116 		body->s.weapon = WP_BLASTER; //lie to keep from putting a saber on the corpse, because it was thrown at death
1117 	}
1118 
1119 	//G_AddEvent(body, EV_BODY_QUEUE_COPY, ent->s.clientNum);
1120 	//Now doing this through a modified version of the rcg reliable command.
1121 	if (ent->client && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE)
1122 	{
1123 		islight = 1;
1124 	}
1125 	trap->SendServerCommand(-1, va("ircg %i %i %i %i", ent->s.number, body->s.number, body->s.weapon, islight));
1126 
1127 	body->r.svFlags = ent->r.svFlags | SVF_BROADCAST;
1128 	VectorCopy (ent->r.mins, body->r.mins);
1129 	VectorCopy (ent->r.maxs, body->r.maxs);
1130 	VectorCopy (ent->r.absmin, body->r.absmin);
1131 	VectorCopy (ent->r.absmax, body->r.absmax);
1132 
1133 	body->s.torsoAnim = body->s.legsAnim = ent->client->ps.legsAnim;
1134 
1135 	body->s.customRGBA[0] = ent->client->ps.customRGBA[0];
1136 	body->s.customRGBA[1] = ent->client->ps.customRGBA[1];
1137 	body->s.customRGBA[2] = ent->client->ps.customRGBA[2];
1138 	body->s.customRGBA[3] = ent->client->ps.customRGBA[3];
1139 
1140 	body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP;
1141 	body->r.contents = CONTENTS_CORPSE;
1142 	body->r.ownerNum = ent->s.number;
1143 
1144 	body->nextthink = level.time + BODY_SINK_TIME;
1145 	body->think = BodySink;
1146 
1147 	body->die = body_die;
1148 
1149 	// don't take more damage if already gibbed
1150 	if ( ent->health <= GIB_HEALTH ) {
1151 		body->takedamage = qfalse;
1152 	} else {
1153 		body->takedamage = qtrue;
1154 	}
1155 
1156 	VectorCopy ( body->s.pos.trBase, body->r.currentOrigin );
1157 	trap->LinkEntity ((sharedEntity_t *)body);
1158 
1159 	return qtrue;
1160 }
1161 
1162 //======================================================================
1163 
1164 
1165 /*
1166 ==================
1167 SetClientViewAngle
1168 
1169 ==================
1170 */
SetClientViewAngle(gentity_t * ent,vec3_t angle)1171 void SetClientViewAngle( gentity_t *ent, vec3_t angle ) {
1172 	int			i;
1173 
1174 	// set the delta angle
1175 	for (i=0 ; i<3 ; i++) {
1176 		int		cmdAngle;
1177 
1178 		cmdAngle = ANGLE2SHORT(angle[i]);
1179 		ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i];
1180 	}
1181 	VectorCopy( angle, ent->s.angles );
1182 	VectorCopy (ent->s.angles, ent->client->ps.viewangles);
1183 }
1184 
MaintainBodyQueue(gentity_t * ent)1185 void MaintainBodyQueue(gentity_t *ent)
1186 { //do whatever should be done taking ragdoll and dismemberment states into account.
1187 	qboolean doRCG = qfalse;
1188 
1189 	assert(ent && ent->client);
1190 	if (ent->client->tempSpectate >= level.time ||
1191 		(ent->client->ps.eFlags2 & EF2_SHIP_DEATH))
1192 	{
1193 		ent->client->noCorpse = qtrue;
1194 	}
1195 
1196 	if (!ent->client->noCorpse && !ent->client->ps.fallingToDeath)
1197 	{
1198 		if (!CopyToBodyQue (ent))
1199 		{
1200 			doRCG = qtrue;
1201 		}
1202 	}
1203 	else
1204 	{
1205 		ent->client->noCorpse = qfalse; //clear it for next time
1206 		ent->client->ps.fallingToDeath = qfalse;
1207 		doRCG = qtrue;
1208 	}
1209 
1210 	if (doRCG)
1211 	{ //bodyque func didn't manage to call ircg so call this to assure our limbs and ragdoll states are proper on the client.
1212 		trap->SendServerCommand(-1, va("rcg %i", ent->s.clientNum));
1213 	}
1214 }
1215 
1216 /*
1217 ================
1218 ClientRespawn
1219 ================
1220 */
1221 void SiegeRespawn(gentity_t *ent);
ClientRespawn(gentity_t * ent)1222 void ClientRespawn( gentity_t *ent ) {
1223 	MaintainBodyQueue(ent);
1224 
1225 	if (gEscaping || level.gametype == GT_POWERDUEL)
1226 	{
1227 		ent->client->sess.sessionTeam = TEAM_SPECTATOR;
1228 		ent->client->sess.spectatorState = SPECTATOR_FREE;
1229 		ent->client->sess.spectatorClient = 0;
1230 
1231 		ent->client->pers.teamState.state = TEAM_BEGIN;
1232 		AddTournamentQueue(ent->client);
1233 		ClientSpawn(ent);
1234 		ent->client->iAmALoser = qtrue;
1235 		return;
1236 	}
1237 
1238 	trap->UnlinkEntity ((sharedEntity_t *)ent);
1239 
1240 	if (level.gametype == GT_SIEGE)
1241 	{
1242 		if (g_siegeRespawn.integer)
1243 		{
1244 			if (ent->client->tempSpectate < level.time)
1245 			{
1246 				int minDel = g_siegeRespawn.integer* 2000;
1247 				if (minDel < 20000)
1248 				{
1249 					minDel = 20000;
1250 				}
1251 				ent->client->tempSpectate = level.time + minDel;
1252 				ent->health = ent->client->ps.stats[STAT_HEALTH] = 1;
1253 				ent->waterlevel = ent->watertype = 0;
1254 				ent->client->ps.weapon = WP_NONE;
1255 				ent->client->ps.stats[STAT_WEAPONS] = 0;
1256 				ent->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
1257 				ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
1258 				ent->takedamage = qfalse;
1259 				trap->LinkEntity((sharedEntity_t *)ent);
1260 
1261 				// Respawn time.
1262 				if ( ent->s.number < MAX_CLIENTS )
1263 				{
1264 					gentity_t *te = G_TempEntity( ent->client->ps.origin, EV_SIEGESPEC );
1265 					te->s.time = g_siegeRespawnCheck;
1266 					te->s.owner = ent->s.number;
1267 				}
1268 
1269 				return;
1270 			}
1271 		}
1272 		SiegeRespawn(ent);
1273 	}
1274 	else
1275 	{
1276 		ClientSpawn(ent);
1277 	}
1278 }
1279 
1280 /*
1281 ================
1282 TeamCount
1283 
1284 Returns number of players on a team
1285 ================
1286 */
TeamCount(int ignoreClientNum,team_t team)1287 int TeamCount( int ignoreClientNum, team_t team ) {
1288 	int		i;
1289 	int		count = 0;
1290 
1291 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1292 		if ( i == ignoreClientNum ) {
1293 			continue;
1294 		}
1295 		if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
1296 			continue;
1297 		}
1298 		if ( level.clients[i].sess.sessionTeam == team ) {
1299 			count++;
1300 		}
1301 		else if (level.gametype == GT_SIEGE &&
1302             level.clients[i].sess.siegeDesiredTeam == team)
1303 		{
1304 			count++;
1305 		}
1306 	}
1307 
1308 	return count;
1309 }
1310 
1311 /*
1312 ================
1313 TeamLeader
1314 
1315 Returns the client number of the team leader
1316 ================
1317 */
TeamLeader(int team)1318 int TeamLeader( int team ) {
1319 	int		i;
1320 
1321 	for ( i = 0 ; i < level.maxclients ; i++ ) {
1322 		if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
1323 			continue;
1324 		}
1325 		if ( level.clients[i].sess.sessionTeam == team ) {
1326 			if ( level.clients[i].sess.teamLeader )
1327 				return i;
1328 		}
1329 	}
1330 
1331 	return -1;
1332 }
1333 
1334 
1335 /*
1336 ================
1337 PickTeam
1338 
1339 ================
1340 */
PickTeam(int ignoreClientNum)1341 team_t PickTeam( int ignoreClientNum ) {
1342 	int		counts[TEAM_NUM_TEAMS];
1343 
1344 	counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE );
1345 	counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED );
1346 
1347 	if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) {
1348 		return TEAM_RED;
1349 	}
1350 	if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) {
1351 		return TEAM_BLUE;
1352 	}
1353 	// equal team count, so join the team with the lowest score
1354 	if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) {
1355 		return TEAM_RED;
1356 	}
1357 	return TEAM_BLUE;
1358 }
1359 
1360 /*
1361 ===========
1362 ForceClientSkin
1363 
1364 Forces a client's skin (for teamplay)
1365 ===========
1366 */
1367 /*
1368 static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) {
1369 	char *p;
1370 
1371 	if ((p = Q_strrchr(model, '/')) != 0) {
1372 		*p = 0;
1373 	}
1374 
1375 	Q_strcat(model, MAX_QPATH, "/");
1376 	Q_strcat(model, MAX_QPATH, skin);
1377 }
1378 */
1379 
1380 /*
1381 ===========
1382 ClientCheckName
1383 ============
1384 */
ClientCleanName(const char * in,char * out,int outSize)1385 static void ClientCleanName( const char *in, char *out, int outSize )
1386 {
1387 	int outpos = 0, colorlessLen = 0, spaces = 0, ats = 0;
1388 
1389 	// discard leading spaces
1390 	for ( ; *in == ' '; in++);
1391 
1392 	// discard leading asterisk's (fail raven for using * as a skipnotify)
1393 	// apparently .* causes the issue too so... derp
1394 	//for(; *in == '*'; in++);
1395 
1396 	for(; *in && outpos < outSize - 1; in++)
1397 	{
1398 		out[outpos] = *in;
1399 
1400 		if ( *in == ' ' )
1401 		{// don't allow too many consecutive spaces
1402 			if ( spaces > 2 )
1403 				continue;
1404 
1405 			spaces++;
1406 		}
1407 		else if ( *in == '@' )
1408 		{// don't allow too many consecutive at signs
1409 			if ( ++ats > 2 ) {
1410 				outpos -= 2;
1411 				ats = 0;
1412 				continue;
1413 			}
1414 		}
1415 		else if ( (byte)*in < 0x20
1416 				|| (byte)*in == 0x81 || (byte)*in == 0x8D || (byte)*in == 0x8F || (byte)*in == 0x90 || (byte)*in == 0x9D
1417 				|| (byte)*in == 0xA0 || (byte)*in == 0xAD )
1418 		{
1419 			continue;
1420 		}
1421 		else if ( outpos > 0 && out[outpos-1] == Q_COLOR_ESCAPE )
1422 		{
1423 			if ( Q_IsColorStringExt( &out[outpos-1] ) )
1424 			{
1425 				colorlessLen--;
1426 
1427 #if 0
1428 				if ( ColorIndex( *in ) == 0 )
1429 				{// Disallow color black in names to prevent players from getting advantage playing in front of black backgrounds
1430 					outpos--;
1431 					continue;
1432 				}
1433 #endif
1434 			}
1435 			else
1436 			{
1437 				spaces = ats = 0;
1438 				colorlessLen++;
1439 			}
1440 		}
1441 		else
1442 		{
1443 			spaces = ats = 0;
1444 			colorlessLen++;
1445 		}
1446 
1447 		outpos++;
1448 	}
1449 
1450 	out[outpos] = '\0';
1451 
1452 	// don't allow empty names
1453 	if ( *out == '\0' || colorlessLen == 0 )
1454 		Q_strncpyz( out, "Padawan", outSize );
1455 }
1456 
1457 #ifdef _DEBUG
G_DebugWrite(const char * path,const char * text)1458 void G_DebugWrite(const char *path, const char *text)
1459 {
1460 	fileHandle_t f;
1461 
1462 	trap->FS_Open( path, &f, FS_APPEND );
1463 	trap->FS_Write(text, strlen(text), f);
1464 	trap->FS_Close(f);
1465 }
1466 #endif
1467 
G_SaberModelSetup(gentity_t * ent)1468 qboolean G_SaberModelSetup(gentity_t *ent)
1469 {
1470 	int i = 0;
1471 	qboolean fallbackForSaber = qtrue;
1472 
1473 	while (i < MAX_SABERS)
1474 	{
1475 		if (ent->client->saber[i].model[0])
1476 		{
1477 			//first kill it off if we've already got it
1478 			if (ent->client->weaponGhoul2[i])
1479 			{
1480 				trap->G2API_CleanGhoul2Models(&(ent->client->weaponGhoul2[i]));
1481 			}
1482 			trap->G2API_InitGhoul2Model(&ent->client->weaponGhoul2[i], ent->client->saber[i].model, 0, 0, -20, 0, 0);
1483 
1484 			if (ent->client->weaponGhoul2[i])
1485 			{
1486 				int j = 0;
1487 				char *tagName;
1488 				int tagBolt;
1489 
1490 				if (ent->client->saber[i].skin)
1491 				{
1492 					trap->G2API_SetSkin(ent->client->weaponGhoul2[i], 0, ent->client->saber[i].skin, ent->client->saber[i].skin);
1493 				}
1494 
1495 				if (ent->client->saber[i].saberFlags & SFL_BOLT_TO_WRIST)
1496 				{
1497 					trap->G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, 3+i);
1498 				}
1499 				else
1500 				{ // bolt to right hand for 0, or left hand for 1
1501 					trap->G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, i);
1502 				}
1503 
1504 				//Add all the bolt points
1505 				while (j < ent->client->saber[i].numBlades)
1506 				{
1507 					tagName = va("*blade%i", j+1);
1508 					tagBolt = trap->G2API_AddBolt(ent->client->weaponGhoul2[i], 0, tagName);
1509 
1510 					if (tagBolt == -1)
1511 					{
1512 						if (j == 0)
1513 						{ //guess this is an 0ldsk3wl saber
1514 							tagBolt = trap->G2API_AddBolt(ent->client->weaponGhoul2[i], 0, "*flash");
1515 							fallbackForSaber = qfalse;
1516 							break;
1517 						}
1518 
1519 						if (tagBolt == -1)
1520 						{
1521 							assert(0);
1522 							break;
1523 
1524 						}
1525 					}
1526 					j++;
1527 
1528 					fallbackForSaber = qfalse; //got at least one custom saber so don't need default
1529 				}
1530 
1531 				//Copy it into the main instance
1532 				trap->G2API_CopySpecificGhoul2Model(ent->client->weaponGhoul2[i], 0, ent->ghoul2, i+1);
1533 			}
1534 		}
1535 		else
1536 		{
1537 			break;
1538 		}
1539 
1540 		i++;
1541 	}
1542 
1543 	return fallbackForSaber;
1544 }
1545 
1546 /*
1547 ===========
1548 SetupGameGhoul2Model
1549 
1550 There are two ghoul2 model instances per player (actually three).  One is on the clientinfo (the base for the client side
1551 player, and copied for player spawns and for corpses).  One is attached to the centity itself, which is the model acutally
1552 animated and rendered by the system.  The final is the game ghoul2 model.  This is animated by pmove on the server, and
1553 is used for determining where the lightsaber should be, and for per-poly collision tests.
1554 ===========
1555 */
1556 void *g2SaberInstance = NULL;
1557 
1558 qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName);
1559 qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors );
1560 void BG_GetVehicleModelName(char *modelName, const char *vehicleName, size_t len);
1561 
SetupGameGhoul2Model(gentity_t * ent,char * modelname,char * skinName)1562 void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName)
1563 {
1564 	int handle;
1565 	char		afilename[MAX_QPATH];
1566 #if 0
1567 	char		/**GLAName,*/ *slash;
1568 #endif
1569 	char		GLAName[MAX_QPATH];
1570 	vec3_t	tempVec = {0,0,0};
1571 
1572 	if (strlen(modelname) >= MAX_QPATH )
1573 	{
1574 		Com_Error( ERR_FATAL, "SetupGameGhoul2Model(%s): modelname exceeds MAX_QPATH.\n", modelname );
1575 	}
1576 	if (skinName && strlen(skinName) >= MAX_QPATH )
1577 	{
1578 		Com_Error( ERR_FATAL, "SetupGameGhoul2Model(%s): skinName exceeds MAX_QPATH.\n", skinName );
1579 	}
1580 
1581 	// First things first.  If this is a ghoul2 model, then let's make sure we demolish this first.
1582 	if (ent->ghoul2 && trap->G2API_HaveWeGhoul2Models(ent->ghoul2))
1583 	{
1584 		trap->G2API_CleanGhoul2Models(&(ent->ghoul2));
1585 	}
1586 
1587 	//rww - just load the "standard" model for the server"
1588 	if (!precachedKyle)
1589 	{
1590 		int defSkin;
1591 
1592 		Com_sprintf( afilename, sizeof( afilename ), "models/players/" DEFAULT_MODEL "/model.glm" );
1593 		handle = trap->G2API_InitGhoul2Model(&precachedKyle, afilename, 0, 0, -20, 0, 0);
1594 
1595 		if (handle<0)
1596 		{
1597 			return;
1598 		}
1599 
1600 		defSkin = trap->R_RegisterSkin("models/players/" DEFAULT_MODEL "/model_default.skin");
1601 		trap->G2API_SetSkin(precachedKyle, 0, defSkin, defSkin);
1602 	}
1603 
1604 	if (precachedKyle && trap->G2API_HaveWeGhoul2Models(precachedKyle))
1605 	{
1606 		if (d_perPlayerGhoul2.integer || ent->s.number >= MAX_CLIENTS ||
1607 			G_PlayerHasCustomSkeleton(ent))
1608 		{ //rww - allow option for perplayer models on server for collision and bolt stuff.
1609 			char modelFullPath[MAX_QPATH];
1610 			char truncModelName[MAX_QPATH];
1611 			char skin[MAX_QPATH];
1612 			char vehicleName[MAX_QPATH];
1613 			int skinHandle = 0;
1614 			int i = 0;
1615 			char *p;
1616 
1617 			// If this is a vehicle, get it's model name.
1618 			if ( ent->client->NPC_class == CLASS_VEHICLE )
1619 			{
1620 				char realModelName[MAX_QPATH];
1621 
1622 				Q_strncpyz( vehicleName, modelname, sizeof( vehicleName ) );
1623 				BG_GetVehicleModelName(realModelName, modelname, sizeof( realModelName ));
1624 				strcpy(truncModelName, realModelName);
1625 				skin[0] = 0;
1626 				if ( ent->m_pVehicle
1627 					&& ent->m_pVehicle->m_pVehicleInfo
1628 					&& ent->m_pVehicle->m_pVehicleInfo->skin
1629 					&& ent->m_pVehicle->m_pVehicleInfo->skin[0] )
1630 				{
1631 					skinHandle = trap->R_RegisterSkin(va("models/players/%s/model_%s.skin", realModelName, ent->m_pVehicle->m_pVehicleInfo->skin));
1632 				}
1633 				else
1634 				{
1635 					skinHandle = trap->R_RegisterSkin(va("models/players/%s/model_default.skin", realModelName));
1636 				}
1637 			}
1638 			else
1639 			{
1640 				if (skinName && skinName[0])
1641 				{
1642 					strcpy(skin, skinName);
1643 					strcpy(truncModelName, modelname);
1644 				}
1645 				else
1646 				{
1647 					strcpy(skin, "default");
1648 
1649 					strcpy(truncModelName, modelname);
1650 					p = Q_strrchr(truncModelName, '/');
1651 
1652 					if (p)
1653 					{
1654 						*p = 0;
1655 						p++;
1656 
1657 						while (p && *p)
1658 						{
1659 							skin[i] = *p;
1660 							i++;
1661 							p++;
1662 						}
1663 						skin[i] = 0;
1664 						i = 0;
1665 					}
1666 
1667 					if (!BG_IsValidCharacterModel(truncModelName, skin))
1668 					{
1669 						strcpy(truncModelName, DEFAULT_MODEL);
1670 						strcpy(skin, "default");
1671 					}
1672 
1673 					if ( level.gametype >= GT_TEAM && level.gametype != GT_SIEGE && !g_jediVmerc.integer )
1674 					{
1675 						float colorOverride[3];
1676 
1677 						colorOverride[0] = colorOverride[1] = colorOverride[2] = 0.0f;
1678 
1679 						BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, colorOverride);
1680 						if (colorOverride[0] != 0.0f ||
1681 							colorOverride[1] != 0.0f ||
1682 							colorOverride[2] != 0.0f)
1683 						{
1684 							ent->client->ps.customRGBA[0] = colorOverride[0]*255.0f;
1685 							ent->client->ps.customRGBA[1] = colorOverride[1]*255.0f;
1686 							ent->client->ps.customRGBA[2] = colorOverride[2]*255.0f;
1687 						}
1688 
1689 						//BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, NULL );
1690 					}
1691 					else if (level.gametype == GT_SIEGE)
1692 					{ //force skin for class if appropriate
1693 						if (ent->client->siegeClass != -1)
1694 						{
1695 							siegeClass_t *scl = &bgSiegeClasses[ent->client->siegeClass];
1696 							if (scl->forcedSkin[0])
1697 							{
1698 								Q_strncpyz( skin, scl->forcedSkin, sizeof( skin ) );
1699 							}
1700 						}
1701 					}
1702 				}
1703 			}
1704 
1705 			if (skin[0])
1706 			{
1707 				char *useSkinName;
1708 
1709 				if (strchr(skin, '|'))
1710 				{//three part skin
1711 					useSkinName = va("models/players/%s/|%s", truncModelName, skin);
1712 				}
1713 				else
1714 				{
1715 					useSkinName = va("models/players/%s/model_%s.skin", truncModelName, skin);
1716 				}
1717 
1718 				skinHandle = trap->R_RegisterSkin(useSkinName);
1719 			}
1720 
1721 			strcpy(modelFullPath, va("models/players/%s/model.glm", truncModelName));
1722 			handle = trap->G2API_InitGhoul2Model(&ent->ghoul2, modelFullPath, 0, skinHandle, -20, 0, 0);
1723 
1724 			if (handle<0)
1725 			{ //Huh. Guess we don't have this model. Use the default.
1726 
1727 				if (ent->ghoul2 && trap->G2API_HaveWeGhoul2Models(ent->ghoul2))
1728 				{
1729 					trap->G2API_CleanGhoul2Models(&(ent->ghoul2));
1730 				}
1731 				ent->ghoul2 = NULL;
1732 				trap->G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
1733 			}
1734 			else
1735 			{
1736 				trap->G2API_SetSkin(ent->ghoul2, 0, skinHandle, skinHandle);
1737 
1738 				GLAName[0] = 0;
1739 				trap->G2API_GetGLAName( ent->ghoul2, 0, GLAName);
1740 
1741 				if (!GLAName[0] || (!strstr(GLAName, "players/_humanoid/") && ent->s.number < MAX_CLIENTS && !G_PlayerHasCustomSkeleton(ent)))
1742 				{ //a bad model
1743 					trap->G2API_CleanGhoul2Models(&(ent->ghoul2));
1744 					ent->ghoul2 = NULL;
1745 					trap->G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
1746 				}
1747 
1748 				if (ent->s.number >= MAX_CLIENTS)
1749 				{
1750 					ent->s.modelGhoul2 = 1; //so we know to free it on the client when we're removed.
1751 
1752 					if (skin[0])
1753 					{ //append it after a *
1754 						strcat( modelFullPath, va("*%s", skin) );
1755 					}
1756 
1757 					if ( ent->client->NPC_class == CLASS_VEHICLE )
1758 					{ //vehicles are tricky and send over their vehicle names as the model (the model is then retrieved based on the vehicle name)
1759 						ent->s.modelindex = G_ModelIndex(vehicleName);
1760 					}
1761 					else
1762 					{
1763 						ent->s.modelindex = G_ModelIndex(modelFullPath);
1764 					}
1765 				}
1766 			}
1767 		}
1768 		else
1769 		{
1770 			trap->G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2);
1771 		}
1772 	}
1773 	else
1774 	{
1775 		return;
1776 	}
1777 
1778 	//Attach the instance to this entity num so we can make use of client-server
1779 	//shared operations if possible.
1780 	trap->G2API_AttachInstanceToEntNum(ent->ghoul2, ent->s.number, qtrue);
1781 
1782 	// The model is now loaded.
1783 
1784 	GLAName[0] = 0;
1785 
1786 	if (!BGPAFtextLoaded)
1787 	{
1788 		if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1)
1789 		{
1790 			Com_Printf( "Failed to load humanoid animation file\n");
1791 			return;
1792 		}
1793 	}
1794 
1795 	if (ent->s.number >= MAX_CLIENTS || G_PlayerHasCustomSkeleton(ent))
1796 	{
1797 		ent->localAnimIndex = -1;
1798 
1799 		GLAName[0] = 0;
1800 		trap->G2API_GetGLAName(ent->ghoul2, 0, GLAName);
1801 
1802 		if (GLAName[0] &&
1803 			!strstr(GLAName, "players/_humanoid/") /*&&
1804 			!strstr(GLAName, "players/rockettrooper/")*/)
1805 		{ //it doesn't use humanoid anims.
1806 			char *slash = Q_strrchr( GLAName, '/' );
1807 			if ( slash )
1808 			{
1809 				strcpy(slash, "/animation.cfg");
1810 
1811 				ent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse);
1812 			}
1813 		}
1814 		else
1815 		{ //humanoid index.
1816 			if (strstr(GLAName, "players/rockettrooper/"))
1817 			{
1818 				ent->localAnimIndex = 1;
1819 			}
1820 			else
1821 			{
1822 				ent->localAnimIndex = 0;
1823 			}
1824 		}
1825 
1826 		if (ent->localAnimIndex == -1)
1827 		{
1828 			Com_Error(ERR_DROP, "NPC had an invalid GLA\n");
1829 		}
1830 	}
1831 	else
1832 	{
1833 		GLAName[0] = 0;
1834 		trap->G2API_GetGLAName(ent->ghoul2, 0, GLAName);
1835 
1836 		if (strstr(GLAName, "players/rockettrooper/"))
1837 		{
1838 			//assert(!"Should not have gotten in here with rockettrooper skel");
1839 			ent->localAnimIndex = 1;
1840 		}
1841 		else
1842 		{
1843 			ent->localAnimIndex = 0;
1844 		}
1845 	}
1846 
1847 	if (ent->s.NPC_class == CLASS_VEHICLE &&
1848 		ent->m_pVehicle)
1849 	{ //do special vehicle stuff
1850 		char strTemp[128];
1851 		int i;
1852 
1853 		// Setup the default first bolt
1854 		i = trap->G2API_AddBolt( ent->ghoul2, 0, "model_root" );
1855 
1856 		// Setup the droid unit.
1857 		ent->m_pVehicle->m_iDroidUnitTag = trap->G2API_AddBolt( ent->ghoul2, 0, "*droidunit" );
1858 
1859 		// Setup the Exhausts.
1860 		for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ )
1861 		{
1862 			Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 );
1863 			ent->m_pVehicle->m_iExhaustTag[i] = trap->G2API_AddBolt( ent->ghoul2, 0, strTemp );
1864 		}
1865 
1866 		// Setup the Muzzles.
1867 		for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ )
1868 		{
1869 			Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 );
1870 			ent->m_pVehicle->m_iMuzzleTag[i] = trap->G2API_AddBolt( ent->ghoul2, 0, strTemp );
1871 			if ( ent->m_pVehicle->m_iMuzzleTag[i] == -1 )
1872 			{//ergh, try *flash?
1873 				Com_sprintf( strTemp, 128, "*flash%i", i + 1 );
1874 				ent->m_pVehicle->m_iMuzzleTag[i] = trap->G2API_AddBolt( ent->ghoul2, 0, strTemp );
1875 			}
1876 		}
1877 
1878 		// Setup the Turrets.
1879 		for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ )
1880 		{
1881 			if ( ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag )
1882 			{
1883 				ent->m_pVehicle->m_iGunnerViewTag[i] = trap->G2API_AddBolt( ent->ghoul2, 0, ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag );
1884 			}
1885 			else
1886 			{
1887 				ent->m_pVehicle->m_iGunnerViewTag[i] = -1;
1888 			}
1889 		}
1890 	}
1891 
1892 	if (ent->client->ps.weapon == WP_SABER || ent->s.number < MAX_CLIENTS)
1893 	{ //a player or NPC saber user
1894 		trap->G2API_AddBolt(ent->ghoul2, 0, "*r_hand");
1895 		trap->G2API_AddBolt(ent->ghoul2, 0, "*l_hand");
1896 
1897 		//rhand must always be first bolt. lhand always second. Whichever you want the
1898 		//jetpack bolted to must always be third.
1899 		trap->G2API_AddBolt(ent->ghoul2, 0, "*chestg");
1900 
1901 		//claw bolts
1902 		trap->G2API_AddBolt(ent->ghoul2, 0, "*r_hand_cap_r_arm");
1903 		trap->G2API_AddBolt(ent->ghoul2, 0, "*l_hand_cap_l_arm");
1904 
1905 		trap->G2API_SetBoneAnim(ent->ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1);
1906 		trap->G2API_SetBoneAngles(ent->ghoul2, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time);
1907 		trap->G2API_SetBoneAngles(ent->ghoul2, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, level.time);
1908 
1909 		if (!g2SaberInstance)
1910 		{
1911 			trap->G2API_InitGhoul2Model(&g2SaberInstance, DEFAULT_SABER_MODEL, 0, 0, -20, 0, 0);
1912 
1913 			if (g2SaberInstance)
1914 			{
1915 				// indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied
1916 				trap->G2API_SetBoltInfo(g2SaberInstance, 0, 0);
1917 				// now set up the gun bolt on it
1918 				trap->G2API_AddBolt(g2SaberInstance, 0, "*blade1");
1919 			}
1920 		}
1921 
1922 		if (G_SaberModelSetup(ent))
1923 		{
1924 			if (g2SaberInstance)
1925 			{
1926 				trap->G2API_CopySpecificGhoul2Model(g2SaberInstance, 0, ent->ghoul2, 1);
1927 			}
1928 		}
1929 	}
1930 
1931 	if (ent->s.number >= MAX_CLIENTS)
1932 	{ //some extra NPC stuff
1933 		if (trap->G2API_AddBolt(ent->ghoul2, 0, "lower_lumbar") == -1)
1934 		{ //check now to see if we have this bone for setting anims and such
1935 			ent->noLumbar = qtrue;
1936 		}
1937 	}
1938 }
1939 
1940 
1941 
1942 
1943 /*
1944 ===========
1945 ClientUserInfoChanged
1946 
1947 Called from ClientConnect when the player first connects and
1948 directly by the server system when the player updates a userinfo variable.
1949 
1950 The game can override any of the settings and call trap->SetUserinfo
1951 if desired.
1952 ============
1953 */
1954 
1955 qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride);
1956 void G_ValidateSiegeClassForTeam(gentity_t *ent, int team);
1957 
1958 typedef struct userinfoValidate_s {
1959 	const char		*field, *fieldClean;
1960 	unsigned int	minCount, maxCount;
1961 } userinfoValidate_t;
1962 
1963 #define UIF( x, _min, _max ) { STRING(\\) #x STRING(\\), STRING( x ), _min, _max }
1964 static userinfoValidate_t userinfoFields[] = {
1965 	UIF( cl_guid,			0, 0 ), // not allowed, q3fill protection
1966 	UIF( cl_punkbuster,		0, 0 ), // not allowed, q3fill protection
1967 	UIF( ip,				0, 1 ), // engine adds this at the end
1968 	UIF( name,				1, 1 ),
1969 	UIF( rate,				1, 1 ),
1970 	UIF( snaps,				1, 1 ),
1971 	UIF( model,				1, 1 ),
1972 	UIF( forcepowers,		1, 1 ),
1973 	UIF( color1,			1, 1 ),
1974 	UIF( color2,			1, 1 ),
1975 	UIF( handicap,			1, 1 ),
1976 	UIF( sex,				0, 1 ),
1977 	UIF( cg_predictItems,	1, 1 ),
1978 	UIF( saber1,			1, 1 ),
1979 	UIF( saber2,			1, 1 ),
1980 	UIF( char_color_red,	1, 1 ),
1981 	UIF( char_color_green,	1, 1 ),
1982 	UIF( char_color_blue,	1, 1 ),
1983 	UIF( teamtask,			0, 1 ), // optional
1984 	UIF( password,			0, 1 ), // optional
1985 	UIF( teamoverlay,		0, 1 ), // only registered in cgame, not sent when connecting
1986 };
1987 static const size_t numUserinfoFields = ARRAY_LEN( userinfoFields );
1988 
1989 static const char *userinfoValidateExtra[USERINFO_VALIDATION_MAX] = {
1990 	"Size",					// USERINFO_VALIDATION_SIZE
1991 	"# of slashes",			// USERINFO_VALIDATION_SLASH
1992 	"Extended ascii",		// USERINFO_VALIDATION_EXTASCII
1993 	"Control characters",	// USERINFO_VALIDATION_CONTROLCHARS
1994 };
1995 
Svcmd_ToggleUserinfoValidation_f(void)1996 void Svcmd_ToggleUserinfoValidation_f( void ) {
1997 	if ( trap->Argc() == 1 ) {
1998 		int i=0;
1999 		for ( i=0; i<numUserinfoFields; i++ ) {
2000 			if ( (g_userinfoValidate.integer & (1<<i)) )	trap->Print( "%2d [X] %s\n", i, userinfoFields[i].fieldClean );
2001 			else											trap->Print( "%2d [ ] %s\n", i, userinfoFields[i].fieldClean );
2002 		}
2003 		for ( ; i<numUserinfoFields+USERINFO_VALIDATION_MAX; i++ ) {
2004 			if ( (g_userinfoValidate.integer & (1<<i)) )	trap->Print( "%2d [X] %s\n", i, userinfoValidateExtra[i-numUserinfoFields] );
2005 			else											trap->Print( "%2d [ ] %s\n", i, userinfoValidateExtra[i-numUserinfoFields] );
2006 		}
2007 		return;
2008 	}
2009 	else {
2010 		char arg[8]={0};
2011 		int index;
2012 
2013 		trap->Argv( 1, arg, sizeof( arg ) );
2014 		index = atoi( arg );
2015 
2016 		if ( index < 0 || index > numUserinfoFields+USERINFO_VALIDATION_MAX-1 ) {
2017 			Com_Printf( "ToggleUserinfoValidation: Invalid range: %i [0, %i]\n", index, numUserinfoFields+USERINFO_VALIDATION_MAX-1 );
2018 			return;
2019 		}
2020 
2021 		trap->Cvar_Set( "g_userinfoValidate", va( "%i", (1 << index) ^ (g_userinfoValidate.integer & ((1 << (numUserinfoFields + USERINFO_VALIDATION_MAX)) - 1)) ) );
2022 		trap->Cvar_Update( &g_userinfoValidate );
2023 
2024 		if ( index < numUserinfoFields )	Com_Printf( "%s %s\n", userinfoFields[index].fieldClean,				((g_userinfoValidate.integer & (1<<index)) ? "Validated" : "Ignored") );
2025 		else								Com_Printf( "%s %s\n", userinfoValidateExtra[index-numUserinfoFields],	((g_userinfoValidate.integer & (1<<index)) ? "Validated" : "Ignored") );
2026 	}
2027 }
2028 
G_ValidateUserinfo(const char * userinfo)2029 char *G_ValidateUserinfo( const char *userinfo ) {
2030 	unsigned int		i=0, count=0;
2031 	size_t				length = strlen( userinfo );
2032 	userinfoValidate_t	*info = NULL;
2033 	char				key[BIG_INFO_KEY], value[BIG_INFO_VALUE];
2034 	const char			*s;
2035 	unsigned int		fieldCount[ARRAY_LEN( userinfoFields )];
2036 
2037 	memset( fieldCount, 0, sizeof( fieldCount ) );
2038 
2039 	// size checks
2040 	if ( g_userinfoValidate.integer & (1<<(numUserinfoFields+USERINFO_VALIDATION_SIZE)) ) {
2041 		if ( length < 1 )
2042 			return "Userinfo too short";
2043 		else if ( length >= MAX_INFO_STRING )
2044 			return "Userinfo too long";
2045 	}
2046 
2047 	// slash checks
2048 	if ( g_userinfoValidate.integer & (1<<(numUserinfoFields+USERINFO_VALIDATION_SLASH)) ) {
2049 		// there must be a leading slash
2050 		if ( userinfo[0] != '\\' )
2051 			return "Missing leading slash";
2052 
2053 		// no trailing slashes allowed, engine will append ip\\ip:port
2054 		if ( userinfo[length-1] == '\\' )
2055 			return "Trailing slash";
2056 
2057 		// format for userinfo field is: \\key\\value
2058 		// so there must be an even amount of slashes
2059 		for ( i=0, count=0; i<length; i++ ) {
2060 			if ( userinfo[i] == '\\' )
2061 				count++;
2062 		}
2063 		if ( (count&1) ) // odd
2064 			return "Bad number of slashes";
2065 	}
2066 
2067 	// extended characters are impossible to type, may want to disable
2068 	if ( g_userinfoValidate.integer & (1<<(numUserinfoFields+USERINFO_VALIDATION_EXTASCII)) ) {
2069 		for ( i=0, count=0; i<length; i++ ) {
2070 			if ( userinfo[i] < 0 )
2071 				count++;
2072 		}
2073 		if ( count )
2074 			return "Extended ASCII characters found";
2075 	}
2076 
2077 	// disallow \n \r ; and \"
2078 	if ( g_userinfoValidate.integer & (1<<(numUserinfoFields+USERINFO_VALIDATION_CONTROLCHARS)) ) {
2079 		if ( Q_strchrs( userinfo, "\n\r;\"" ) )
2080 			return "Invalid characters found";
2081 	}
2082 
2083 	s = userinfo;
2084 	while ( s ) {
2085 		Info_NextPair( &s, key, value );
2086 
2087 		if ( !key[0] )
2088 			break;
2089 
2090 		for ( i=0; i<numUserinfoFields; i++ ) {
2091 			if ( !Q_stricmp( key, userinfoFields[i].fieldClean ) )
2092 				fieldCount[i]++;
2093 		}
2094 	}
2095 
2096 	// count the number of fields
2097 	for ( i=0, info=userinfoFields; i<numUserinfoFields; i++, info++ ) {
2098 		if ( g_userinfoValidate.integer & (1<<i) ) {
2099 			if ( info->minCount && !fieldCount[i] )
2100 				return va( "%s field not found", info->fieldClean );
2101 			else if ( fieldCount[i] > info->maxCount )
2102 				return va( "Too many %s fields (%i/%i)", info->fieldClean, fieldCount[i], info->maxCount );
2103 		}
2104 	}
2105 
2106 	return NULL;
2107 }
2108 
ClientUserinfoChanged(int clientNum)2109 qboolean ClientUserinfoChanged( int clientNum ) {
2110 	gentity_t *ent = g_entities + clientNum;
2111 	gclient_t *client = ent->client;
2112 	int team=TEAM_FREE, health=100, maxHealth=100, teamLeader;
2113 	const char *s=NULL;
2114 	char *value=NULL, userinfo[MAX_INFO_STRING], buf[MAX_INFO_STRING], oldClientinfo[MAX_INFO_STRING], model[MAX_QPATH],
2115 		forcePowers[DEFAULT_FORCEPOWERS_LEN], oldname[MAX_NETNAME], className[MAX_QPATH], color1[16], color2[16];
2116 	qboolean modelChanged = qfalse;
2117 	gender_t gender = GENDER_MALE;
2118 
2119 	trap->GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
2120 
2121 	// check for malformed or illegal info strings
2122 	s = G_ValidateUserinfo( userinfo );
2123 	if ( s && *s ) {
2124 		G_SecurityLogPrintf( "Client %d (%s) failed userinfo validation: %s [IP: %s]\n", clientNum, ent->client->pers.netname, s, client->sess.IP );
2125 		trap->DropClient( clientNum, va( "Failed userinfo validation: %s", s ) );
2126 		G_LogPrintf( "Userinfo: %s\n", userinfo );
2127 		return qfalse;
2128 	}
2129 
2130 	// check for local client
2131 	s = Info_ValueForKey( userinfo, "ip" );
2132 	if ( !strcmp( s, "localhost" ) && !(ent->r.svFlags & SVF_BOT) )
2133 		client->pers.localClient = qtrue;
2134 
2135 	// check the item prediction
2136 	s = Info_ValueForKey( userinfo, "cg_predictItems" );
2137 	if ( !atoi( s ) )	client->pers.predictItemPickup = qfalse;
2138 	else				client->pers.predictItemPickup = qtrue;
2139 
2140 	// set name
2141 	Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) );
2142 	s = Info_ValueForKey( userinfo, "name" );
2143 	ClientCleanName( s, client->pers.netname, sizeof( client->pers.netname ) );
2144 	Q_strncpyz( client->pers.netname_nocolor, client->pers.netname, sizeof( client->pers.netname_nocolor ) );
2145 	Q_StripColor( client->pers.netname_nocolor );
2146 
2147 	if ( client->sess.sessionTeam == TEAM_SPECTATOR && client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
2148 		Q_strncpyz( client->pers.netname, "scoreboard", sizeof( client->pers.netname ) );
2149 		Q_strncpyz( client->pers.netname_nocolor, "scoreboard", sizeof( client->pers.netname_nocolor ) );
2150 	}
2151 
2152 	if ( client->pers.connected == CON_CONNECTED && strcmp( oldname, client->pers.netname ) ) {
2153 		if ( client->pers.netnameTime > level.time ) {
2154 			trap->SendServerCommand( clientNum, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NONAMECHANGE" ) ) );
2155 
2156 			Info_SetValueForKey( userinfo, "name", oldname );
2157 			trap->SetUserinfo( clientNum, userinfo );
2158 			Q_strncpyz( client->pers.netname, oldname, sizeof( client->pers.netname ) );
2159 			Q_strncpyz( client->pers.netname_nocolor, oldname, sizeof( client->pers.netname_nocolor ) );
2160 			Q_StripColor( client->pers.netname_nocolor );
2161 		}
2162 		else {
2163 			trap->SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " %s %s\n\"", oldname, G_GetStringEdString( "MP_SVGAME", "PLRENAME" ), client->pers.netname ) );
2164 			G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\"\n", clientNum, ent->client->sess.IP, ent->client->pers.guid, oldname, ent->client->pers.netname );
2165 			client->pers.netnameTime = level.time + 5000;
2166 		}
2167 	}
2168 
2169 	// set model
2170 	Q_strncpyz( model, Info_ValueForKey( userinfo, "model" ), sizeof( model ) );
2171 
2172 	if ( d_perPlayerGhoul2.integer&& Q_stricmp( model, client->modelname ) ) {
2173 		Q_strncpyz( client->modelname, model, sizeof( client->modelname ) );
2174 		modelChanged = qtrue;
2175 	}
2176 
2177 	client->ps.customRGBA[0] = (value=Info_ValueForKey( userinfo, "char_color_red" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
2178 	client->ps.customRGBA[1] = (value=Info_ValueForKey( userinfo, "char_color_green" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
2179 	client->ps.customRGBA[2] = (value=Info_ValueForKey( userinfo, "char_color_blue" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
2180 
2181 	//Prevent skins being too dark
2182 	if ( g_charRestrictRGB.integer && ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) )
2183 		client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255;
2184 
2185 	client->ps.customRGBA[3]=255;
2186 
2187 	Q_strncpyz( forcePowers, Info_ValueForKey( userinfo, "forcepowers" ), sizeof( forcePowers ) );
2188 
2189 	// update our customRGBA for team colors.
2190 	if ( level.gametype >= GT_TEAM && level.gametype != GT_SIEGE && !g_jediVmerc.integer ) {
2191 		char skin[MAX_QPATH] = {0};
2192 		vec3_t colorOverride = {0.0f};
2193 
2194 		VectorClear( colorOverride );
2195 
2196 		BG_ValidateSkinForTeam( model, skin, client->sess.sessionTeam, colorOverride );
2197 		if ( colorOverride[0] != 0.0f || colorOverride[1] != 0.0f || colorOverride[2] != 0.0f )
2198 			VectorScaleM( colorOverride, 255.0f, client->ps.customRGBA );
2199 	}
2200 
2201 	// bots set their team a few frames later
2202 	if ( level.gametype >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT ) {
2203 		s = Info_ValueForKey( userinfo, "team" );
2204 		if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) )
2205 			team = TEAM_RED;
2206 		else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) )
2207 			team = TEAM_BLUE;
2208 		else
2209 			team = PickTeam( clientNum ); // pick the team with the least number of players
2210 	}
2211 	else
2212 		team = client->sess.sessionTeam;
2213 
2214 	//Testing to see if this fixes the problem with a bot's team getting set incorrectly.
2215 	team = client->sess.sessionTeam;
2216 
2217 	//Set the siege class
2218 	if ( level.gametype == GT_SIEGE ) {
2219 		Q_strncpyz( className, client->sess.siegeClass, sizeof( className ) );
2220 
2221 		//Now that the team is legal for sure, we'll go ahead and get an index for it.
2222 		client->siegeClass = BG_SiegeFindClassIndexByName( className );
2223 		if ( client->siegeClass == -1 ) {
2224 			// ok, get the first valid class for the team you're on then, I guess.
2225 			BG_SiegeCheckClassLegality( team, className );
2226 			Q_strncpyz( client->sess.siegeClass, className, sizeof( client->sess.siegeClass ) );
2227 			client->siegeClass = BG_SiegeFindClassIndexByName( className );
2228 		}
2229 		else {
2230 			// otherwise, make sure the class we are using is legal.
2231 			G_ValidateSiegeClassForTeam( ent, team );
2232 			Q_strncpyz( className, client->sess.siegeClass, sizeof( className ) );
2233 		}
2234 
2235 		if ( client->siegeClass != -1 ) {
2236 			// Set the sabers if the class dictates
2237 			siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
2238 
2239 			G_SetSaber( ent, 0, scl->saber1[0] ? scl->saber1 : DEFAULT_SABER, qtrue );
2240 			G_SetSaber( ent, 1, scl->saber2[0] ? scl->saber2 : "none", qtrue );
2241 
2242 			//make sure the saber models are updated
2243 			G_SaberModelSetup( ent );
2244 
2245 			if ( scl->forcedModel[0] ) {
2246 				// be sure to override the model we actually use
2247 				Q_strncpyz( model, scl->forcedModel, sizeof( model ) );
2248 				if ( d_perPlayerGhoul2.integer && Q_stricmp( model, client->modelname ) ) {
2249 					Q_strncpyz( client->modelname, model, sizeof( client->modelname ) );
2250 					modelChanged = qtrue;
2251 				}
2252 			}
2253 
2254 			if ( G_PlayerHasCustomSkeleton( ent ) )
2255 			{//force them to use their class model on the server, if the class dictates
2256 				if ( Q_stricmp( model, client->modelname ) || ent->localAnimIndex == 0 )
2257 				{
2258 					Q_strncpyz( client->modelname, model, sizeof( client->modelname ) );
2259 					modelChanged = qtrue;
2260 				}
2261 			}
2262 		}
2263 	}
2264 	else
2265 		Q_strncpyz( className, "none", sizeof( className ) );
2266 
2267 	// only set the saber name on the first connect.
2268 	//	it will be read from userinfo on ClientSpawn and stored in client->pers.saber1/2
2269 	if ( !VALIDSTRING( client->pers.saber1 ) || !VALIDSTRING( client->pers.saber2 ) ) {
2270 		G_SetSaber( ent, 0, Info_ValueForKey( userinfo, "saber1" ), qfalse );
2271 		G_SetSaber( ent, 1, Info_ValueForKey( userinfo, "saber2" ), qfalse );
2272 	}
2273 
2274 	// set max health
2275 	if ( level.gametype == GT_SIEGE && client->siegeClass != -1 ) {
2276 		siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
2277 
2278 		if ( scl->maxhealth )
2279 			maxHealth = scl->maxhealth;
2280 
2281 		health = maxHealth;
2282 	}
2283 	else
2284 		health = Com_Clampi( 1, 100, atoi( Info_ValueForKey( userinfo, "handicap" ) ) );
2285 
2286 	client->pers.maxHealth = health;
2287 	if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth )
2288 		client->pers.maxHealth = 100;
2289 	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
2290 
2291 	if ( level.gametype >= GT_TEAM )
2292 		client->pers.teamInfo = qtrue;
2293 	else {
2294 		s = Info_ValueForKey( userinfo, "teamoverlay" );
2295 		if ( !*s || atoi( s ) != 0 )
2296 			client->pers.teamInfo = qtrue;
2297 		else
2298 			client->pers.teamInfo = qfalse;
2299 	}
2300 
2301 	// team task (0 = none, 1 = offence, 2 = defence)
2302 //	teamTask = atoi(Info_ValueForKey(userinfo, "teamtask"));
2303 	// team Leader (1 = leader, 0 is normal player)
2304 	teamLeader = client->sess.teamLeader;
2305 
2306 	// colors
2307 	Q_strncpyz( color1, Info_ValueForKey( userinfo, "color1" ), sizeof( color1 ) );
2308 	Q_strncpyz( color2, Info_ValueForKey( userinfo, "color2" ), sizeof( color2 ) );
2309 
2310 	// gender hints
2311 	s = Info_ValueForKey( userinfo, "sex" );
2312 	if ( !Q_stricmp( s, "female" ) )
2313 		gender = GENDER_FEMALE;
2314 	else
2315 		gender = GENDER_MALE;
2316 
2317 	s = Info_ValueForKey( userinfo, "snaps" );
2318 	if ( atoi( s ) < sv_fps.integer )
2319 		trap->SendServerCommand( clientNum, va( "print \"" S_COLOR_YELLOW "Recommend setting /snaps %d or higher to match this server's sv_fps\n\"", sv_fps.integer ) );
2320 
2321 	// send over a subset of the userinfo keys so other clients can
2322 	// print scoreboards, display models, and play custom sounds
2323 	buf[0] = '\0';
2324 	Q_strcat( buf, sizeof( buf ), va( "n\\%s\\", client->pers.netname ) );
2325 	Q_strcat( buf, sizeof( buf ), va( "t\\%i\\", client->sess.sessionTeam ) );
2326 	Q_strcat( buf, sizeof( buf ), va( "model\\%s\\", model ) );
2327 	if ( gender == GENDER_FEMALE )	Q_strcat( buf, sizeof( buf ), va( "ds\\%c\\", 'f' ) );
2328 	else							Q_strcat( buf, sizeof( buf ), va( "ds\\%c\\", 'm' ) );
2329 	Q_strcat( buf, sizeof( buf ), va( "st\\%s\\", client->pers.saber1 ) );
2330 	Q_strcat( buf, sizeof( buf ), va( "st2\\%s\\", client->pers.saber2 ) );
2331 	Q_strcat( buf, sizeof( buf ), va( "c1\\%s\\", color1 ) );
2332 	Q_strcat( buf, sizeof( buf ), va( "c2\\%s\\", color2 ) );
2333 	Q_strcat( buf, sizeof( buf ), va( "hc\\%i\\", client->pers.maxHealth ) );
2334 	if ( ent->r.svFlags & SVF_BOT )
2335 		Q_strcat( buf, sizeof( buf ), va( "skill\\%s\\", Info_ValueForKey( userinfo, "skill" ) ) );
2336 	if ( level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL ) {
2337 		Q_strcat( buf, sizeof( buf ), va( "w\\%i\\", client->sess.wins ) );
2338 		Q_strcat( buf, sizeof( buf ), va( "l\\%i\\", client->sess.losses ) );
2339 	}
2340 	if ( level.gametype == GT_POWERDUEL )
2341 		Q_strcat( buf, sizeof( buf ), va( "dt\\%i\\", client->sess.duelTeam ) );
2342 	if ( level.gametype >= GT_TEAM ) {
2343 	//	Q_strcat( buf, sizeof( buf ), va( "tt\\%d\\", teamTask ) );
2344 		Q_strcat( buf, sizeof( buf ), va( "tl\\%d\\", teamLeader ) );
2345 	}
2346 	if ( level.gametype == GT_SIEGE ) {
2347 		Q_strcat( buf, sizeof( buf ), va( "siegeclass\\%s\\", className ) );
2348 		Q_strcat( buf, sizeof( buf ), va( "sdt\\%i\\", className ) );
2349 	}
2350 
2351 	trap->GetConfigstring( CS_PLAYERS+clientNum, oldClientinfo, sizeof( oldClientinfo ) );
2352 	trap->SetConfigstring( CS_PLAYERS+clientNum, buf );
2353 
2354 	// only going to be true for allowable server-side custom skeleton cases
2355 	if ( modelChanged ) {
2356 		// update the server g2 instance if appropriate
2357 		char *modelname = Info_ValueForKey( userinfo, "model" );
2358 		SetupGameGhoul2Model( ent, modelname, NULL );
2359 
2360 		if ( ent->ghoul2 && ent->client )
2361 			ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update.
2362 
2363 		client->torsoAnimExecute = client->legsAnimExecute = -1;
2364 		client->torsoLastFlip = client->legsLastFlip = qfalse;
2365 	}
2366 
2367 	if ( g_logClientInfo.integer ) {
2368 		if ( strcmp( oldClientinfo, buf ) )
2369 			G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, buf );
2370 		else
2371 			G_LogPrintf( "ClientUserinfoChanged: %i <no change>\n", clientNum );
2372 	}
2373 
2374 	return qtrue;
2375 }
2376 
2377 
2378 /*
2379 ===========
2380 ClientConnect
2381 
2382 Called when a player begins connecting to the server.
2383 Called again for every map change or tournament restart.
2384 
2385 The session information will be valid after exit.
2386 
2387 Return NULL if the client should be allowed, otherwise return
2388 a string with the reason for denial.
2389 
2390 Otherwise, the client will be sent the current gamestate
2391 and will eventually get to ClientBegin.
2392 
2393 firstTime will be qtrue the very first time a client connects
2394 to the server machine, but qfalse on map changes and tournament
2395 restarts.
2396 ============
2397 */
2398 
CompareIPs(const char * ip1,const char * ip2)2399 static qboolean CompareIPs( const char *ip1, const char *ip2 )
2400 {
2401 	while ( 1 ) {
2402 		if ( *ip1 != *ip2 )
2403 			return qfalse;
2404 		if ( !*ip1 || *ip1 == ':' )
2405 			break;
2406 		ip1++;
2407 		ip2++;
2408 	}
2409 
2410 	return qtrue;
2411 }
2412 
ClientConnect(int clientNum,qboolean firstTime,qboolean isBot)2413 char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) {
2414 	char		*value = NULL;
2415 	gentity_t	*ent = NULL, *te = NULL;
2416 	gclient_t	*client;
2417 	char		userinfo[MAX_INFO_STRING] = {0},
2418 				tmpIP[NET_ADDRSTRMAXLEN] = {0},
2419 				guid[33] = {0};
2420 
2421 	ent = &g_entities[ clientNum ];
2422 
2423 	ent->s.number = clientNum;
2424 	ent->classname = "connecting";
2425 
2426 	trap->GetUserinfo( clientNum, userinfo, sizeof( userinfo ) );
2427 
2428 	value = Info_ValueForKey( userinfo, "ja_guid" );
2429 	if( value[0] )
2430 		Q_strncpyz( guid, value, sizeof( guid ) );
2431 	else if( isBot )
2432 		Q_strncpyz( guid, "BOT", sizeof( guid ) );
2433 	else
2434 		Q_strncpyz( guid, "NOGUID", sizeof( guid ) );
2435 
2436 	// check to see if they are on the banned IP list
2437 	value = Info_ValueForKey (userinfo, "ip");
2438 	Q_strncpyz( tmpIP, isBot ? "Bot" : value, sizeof( tmpIP ) );
2439 	if ( G_FilterPacket( value ) ) {
2440 		return "Banned.";
2441 	}
2442 
2443 	if ( !isBot && g_needpass.integer ) {
2444 		// check for a password
2445 		value = Info_ValueForKey (userinfo, "password");
2446 		if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) &&
2447 			strcmp( g_password.string, value) != 0) {
2448 			static char sTemp[1024];
2449 			Q_strncpyz(sTemp, G_GetStringEdString("MP_SVGAME","INVALID_ESCAPE_TO_MAIN"), sizeof (sTemp) );
2450 			return sTemp;// return "Invalid password";
2451 		}
2452 	}
2453 
2454 	if ( !isBot && firstTime )
2455 	{
2456 		if ( g_antiFakePlayer.integer )
2457 		{// patched, check for > g_maxConnPerIP connections from same IP
2458 			int count=0, i=0;
2459 			for ( i=0; i<sv_maxclients.integer; i++ )
2460 			{
2461 				#if 0
2462 					if ( level.clients[i].pers.connected != CON_DISCONNECTED && i != clientNum )
2463 					{
2464 						if ( CompareIPs( clientNum, i ) )
2465 						{
2466 							if ( !level.security.clientConnectionActive[i] )
2467 							{//This IP has a dead connection pending, wait for it to time out
2468 							//	client->pers.connected = CON_DISCONNECTED;
2469 								return "Please wait, another connection from this IP is still pending...";
2470 							}
2471 						}
2472 					}
2473 				#else
2474 					if ( CompareIPs( tmpIP, level.clients[i].sess.IP ) )
2475 						count++;
2476 				#endif
2477 			}
2478 			if ( count > g_maxConnPerIP.integer )
2479 			{
2480 			//	client->pers.connected = CON_DISCONNECTED;
2481 				return "Too many connections from the same IP";
2482 			}
2483 		}
2484 	}
2485 
2486 	if ( ent->inuse )
2487 	{// if a player reconnects quickly after a disconnect, the client disconnect may never be called, thus flag can get lost in the ether
2488 		G_LogPrintf( "Forcing disconnect on active client: %i\n", clientNum );
2489 		// so lets just fix up anything that should happen on a disconnect
2490 		ClientDisconnect( clientNum );
2491 	}
2492 
2493 	// they can connect
2494 	client = &level.clients[ clientNum ];
2495 	ent->client = client;
2496 
2497 	//assign the pointer for bg entity access
2498 	ent->playerState = &ent->client->ps;
2499 
2500 	memset( client, 0, sizeof(*client) );
2501 
2502 	Q_strncpyz( client->pers.guid, guid, sizeof( client->pers.guid ) );
2503 
2504 	client->pers.connected = CON_CONNECTING;
2505 	client->pers.connectTime = level.time;
2506 
2507 	// read or initialize the session data
2508 	if ( firstTime || level.newSession ) {
2509 		G_InitSessionData( client, userinfo, isBot );
2510 	}
2511 	G_ReadSessionData( client );
2512 
2513 	if (level.gametype == GT_SIEGE &&
2514 		(firstTime || level.newSession))
2515 	{ //if this is the first time then auto-assign a desired siege team and show briefing for that team
2516 		client->sess.siegeDesiredTeam = 0;//PickTeam(ent->s.number);
2517 		/*
2518 		trap->SendServerCommand(ent->s.number, va("sb %i", client->sess.siegeDesiredTeam));
2519 		*/
2520 		//don't just show it - they'll see it if they switch to a team on purpose.
2521 	}
2522 
2523 
2524 	if (level.gametype == GT_SIEGE && client->sess.sessionTeam != TEAM_SPECTATOR)
2525 	{
2526 		if (firstTime || level.newSession)
2527 		{ //start as spec
2528 			client->sess.siegeDesiredTeam = client->sess.sessionTeam;
2529 			client->sess.sessionTeam = TEAM_SPECTATOR;
2530 		}
2531 	}
2532 	else if (level.gametype == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR)
2533 	{
2534 		client->sess.sessionTeam = TEAM_SPECTATOR;
2535 	}
2536 
2537 	if( isBot ) {
2538 		ent->r.svFlags |= SVF_BOT;
2539 		ent->inuse = qtrue;
2540 		if( !G_BotConnect( clientNum, !firstTime ) ) {
2541 			return "BotConnectfailed";
2542 		}
2543 	}
2544 
2545 	// get and distribute relevent paramters
2546 	if ( !ClientUserinfoChanged( clientNum ) )
2547 		return "Failed userinfo validation";
2548 
2549 	if ( !isBot && firstTime )
2550 	{
2551 		if ( !tmpIP[0] )
2552 		{//No IP sent when connecting, probably an unban hack attempt
2553 			client->pers.connected = CON_DISCONNECTED;
2554 			G_SecurityLogPrintf( "Client %i (%s) sent no IP when connecting.\n", clientNum, client->pers.netname );
2555 			return "Invalid userinfo detected";
2556 		}
2557 	}
2558 
2559 	if ( firstTime )
2560 		Q_strncpyz( client->sess.IP, tmpIP, sizeof( client->sess.IP ) );
2561 
2562 	G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\"\n", clientNum, tmpIP, guid, client->pers.netname );
2563 
2564 	// don't do the "xxx connected" messages if they were caried over from previous level
2565 	if ( firstTime ) {
2566 		trap->SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLCONNECT")) );
2567 	}
2568 
2569 	if ( level.gametype >= GT_TEAM &&
2570 		client->sess.sessionTeam != TEAM_SPECTATOR ) {
2571 		BroadcastTeamChange( client, -1 );
2572 	}
2573 
2574 	// count current clients and rank for scoreboard
2575 	CalculateRanks();
2576 
2577 	te = G_TempEntity( vec3_origin, EV_CLIENTJOIN );
2578 	te->r.svFlags |= SVF_BROADCAST;
2579 	te->s.eventParm = clientNum;
2580 
2581 	// for statistics
2582 //	client->areabits = areabits;
2583 //	if ( !client->areabits )
2584 //		client->areabits = G_Alloc( (trap->AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 );
2585 
2586 	return NULL;
2587 }
2588 
2589 void G_WriteClientSessionData( gclient_t *client );
2590 
2591 void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName );
2592 
2593 /*
2594 ===========
2595 ClientBegin
2596 
2597 called when a client has finished connecting, and is ready
2598 to be placed into the level.  This will happen every level load,
2599 and on transition between teams, but doesn't happen on respawns
2600 ============
2601 */
2602 extern qboolean	gSiegeRoundBegun;
2603 extern qboolean	gSiegeRoundEnded;
2604 extern qboolean g_dontPenalizeTeam; //g_cmds.c
2605 void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin);
ClientBegin(int clientNum,qboolean allowTeamReset)2606 void ClientBegin( int clientNum, qboolean allowTeamReset ) {
2607 	gentity_t	*ent;
2608 	gclient_t	*client;
2609 	int			flags, i;
2610 	char		userinfo[MAX_INFO_VALUE], *modelname;
2611 	int			spawnCount;
2612 
2613 	ent = g_entities + clientNum;
2614 
2615 	if ((ent->r.svFlags & SVF_BOT) && level.gametype >= GT_TEAM)
2616 	{
2617 		if (allowTeamReset)
2618 		{
2619 			const char *team = "Red";
2620 			int preSess;
2621 
2622 			//SetTeam(ent, "");
2623 			ent->client->sess.sessionTeam = PickTeam(-1);
2624 			trap->GetUserinfo(clientNum, userinfo, MAX_INFO_STRING);
2625 
2626 			if (ent->client->sess.sessionTeam == TEAM_SPECTATOR)
2627 			{
2628 				ent->client->sess.sessionTeam = TEAM_RED;
2629 			}
2630 
2631 			if (ent->client->sess.sessionTeam == TEAM_RED)
2632 			{
2633 				team = "Red";
2634 			}
2635 			else
2636 			{
2637 				team = "Blue";
2638 			}
2639 
2640 			Info_SetValueForKey( userinfo, "team", team );
2641 
2642 			trap->SetUserinfo( clientNum, userinfo );
2643 
2644 			ent->client->ps.persistant[ PERS_TEAM ] = ent->client->sess.sessionTeam;
2645 
2646 			preSess = ent->client->sess.sessionTeam;
2647 			G_ReadSessionData( ent->client );
2648 			ent->client->sess.sessionTeam = preSess;
2649 			G_WriteClientSessionData(ent->client);
2650 			if ( !ClientUserinfoChanged( clientNum ) )
2651 				return;
2652 			ClientBegin(clientNum, qfalse);
2653 			return;
2654 		}
2655 	}
2656 
2657 	client = level.clients + clientNum;
2658 
2659 	if ( ent->r.linked ) {
2660 		trap->UnlinkEntity( (sharedEntity_t *)ent );
2661 	}
2662 	G_InitGentity( ent );
2663 	ent->touch = 0;
2664 	ent->pain = 0;
2665 	ent->client = client;
2666 
2667 	//assign the pointer for bg entity access
2668 	ent->playerState = &ent->client->ps;
2669 
2670 	client->pers.connected = CON_CONNECTED;
2671 	client->pers.enterTime = level.time;
2672 	client->pers.teamState.state = TEAM_BEGIN;
2673 
2674 	// save eflags around this, because changing teams will
2675 	// cause this to happen with a valid entity, and we
2676 	// want to make sure the teleport bit is set right
2677 	// so the viewpoint doesn't interpolate through the
2678 	// world to the new position
2679 	flags = client->ps.eFlags;
2680 	spawnCount = client->ps.persistant[PERS_SPAWN_COUNT];
2681 
2682 	i = 0;
2683 
2684 	while (i < NUM_FORCE_POWERS)
2685 	{
2686 		if (ent->client->ps.fd.forcePowersActive & (1 << i))
2687 		{
2688 			WP_ForcePowerStop(ent, i);
2689 		}
2690 		i++;
2691 	}
2692 
2693 	i = TRACK_CHANNEL_1;
2694 
2695 	while (i < NUM_TRACK_CHANNELS)
2696 	{
2697 		if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0)
2698 		{
2699 			G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE);
2700 		}
2701 		i++;
2702 	}
2703 	i = 0;
2704 
2705 	memset( &client->ps, 0, sizeof( client->ps ) );
2706 	client->ps.eFlags = flags;
2707 	client->ps.persistant[PERS_SPAWN_COUNT] = spawnCount;
2708 
2709 	client->ps.hasDetPackPlanted = qfalse;
2710 
2711 	//first-time force power initialization
2712 	WP_InitForcePowers( ent );
2713 
2714 	//init saber ent
2715 	WP_SaberInitBladeData( ent );
2716 
2717 	// First time model setup for that player.
2718 	trap->GetUserinfo( clientNum, userinfo, sizeof(userinfo) );
2719 	modelname = Info_ValueForKey (userinfo, "model");
2720 	SetupGameGhoul2Model(ent, modelname, NULL);
2721 
2722 	if ( ent->ghoul2 && ent->client )
2723 		ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update.
2724 
2725 	if ( level.gametype == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR && client->sess.duelTeam == DUELTEAM_FREE )
2726 		SetTeam( ent, "s" );
2727 	else
2728 	{
2729 		if ( level.gametype == GT_SIEGE && (!gSiegeRoundBegun || gSiegeRoundEnded) )
2730 			SetTeamQuick( ent, TEAM_SPECTATOR, qfalse );
2731 
2732 		// locate ent at a spawn point
2733 		ClientSpawn( ent );
2734 	}
2735 
2736 	if ( client->sess.sessionTeam != TEAM_SPECTATOR ) {
2737 		if ( level.gametype != GT_DUEL || level.gametype == GT_POWERDUEL ) {
2738 			trap->SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLENTER")) );
2739 		}
2740 	}
2741 	G_LogPrintf( "ClientBegin: %i\n", clientNum );
2742 
2743 	// count current clients and rank for scoreboard
2744 	CalculateRanks();
2745 
2746 	G_ClearClientLog(clientNum);
2747 }
2748 
AllForceDisabled(int force)2749 static qboolean AllForceDisabled(int force)
2750 {
2751 	int i;
2752 
2753 	if (force)
2754 	{
2755 		for (i=0;i<NUM_FORCE_POWERS;i++)
2756 		{
2757 			if (!(force & (1<<i)))
2758 			{
2759 				return qfalse;
2760 			}
2761 		}
2762 
2763 		return qtrue;
2764 	}
2765 
2766 	return qfalse;
2767 }
2768 
2769 //Convenient interface to set all my limb breakage stuff up -rww
G_BreakArm(gentity_t * ent,int arm)2770 void G_BreakArm(gentity_t *ent, int arm)
2771 {
2772 	int anim = -1;
2773 
2774 	assert(ent && ent->client);
2775 
2776 	if (ent->s.NPC_class == CLASS_VEHICLE || ent->localAnimIndex > 1)
2777 	{ //no broken limbs for vehicles and non-humanoids
2778 		return;
2779 	}
2780 
2781 	if (!arm)
2782 	{ //repair him
2783 		ent->client->ps.brokenLimbs = 0;
2784 		return;
2785 	}
2786 
2787 	if (ent->client->ps.fd.saberAnimLevel == SS_STAFF)
2788 	{ //I'm too lazy to deal with this as well for now.
2789 		return;
2790 	}
2791 
2792 	if (arm == BROKENLIMB_LARM)
2793 	{
2794 		if (ent->client->saber[1].model[0] &&
2795 			ent->client->ps.weapon == WP_SABER &&
2796 			!ent->client->ps.saberHolstered &&
2797 			ent->client->saber[1].soundOff)
2798 		{ //the left arm shuts off its saber upon being broken
2799 			G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff);
2800 		}
2801 	}
2802 
2803 	ent->client->ps.brokenLimbs = 0; //make sure it's cleared out
2804 	ent->client->ps.brokenLimbs |= (1 << arm); //this arm is now marked as broken
2805 
2806 	//Do a pain anim based on the side. Since getting your arm broken does tend to hurt.
2807 	if (arm == BROKENLIMB_LARM)
2808 	{
2809 		anim = BOTH_PAIN2;
2810 	}
2811 	else if (arm == BROKENLIMB_RARM)
2812 	{
2813 		anim = BOTH_PAIN3;
2814 	}
2815 
2816 	if (anim == -1)
2817 	{
2818 		return;
2819 	}
2820 
2821 	G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0);
2822 
2823 	//This could be combined into a single event. But I guess limbs don't break often enough to
2824 	//worry about it.
2825 	G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain25.wav") );
2826 	//FIXME: A nice bone snapping sound instead if possible
2827 	G_Sound(ent, CHAN_AUTO, G_SoundIndex( va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3)) ));
2828 }
2829 
2830 //Update the ghoul2 instance anims based on the playerstate values
2831 qboolean BG_SaberStanceAnim( int anim );
2832 qboolean PM_RunningAnim( int anim );
G_UpdateClientAnims(gentity_t * self,float animSpeedScale)2833 void G_UpdateClientAnims(gentity_t *self, float animSpeedScale)
2834 {
2835 	static int f;
2836 	static int torsoAnim;
2837 	static int legsAnim;
2838 	static int firstFrame, lastFrame;
2839 	static int aFlags;
2840 	static float animSpeed, lAnimSpeedScale;
2841 	qboolean setTorso = qfalse;
2842 
2843 	torsoAnim = (self->client->ps.torsoAnim);
2844 	legsAnim = (self->client->ps.legsAnim);
2845 
2846 	if (self->client->ps.saberLockFrame)
2847 	{
2848 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "model_root", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
2849 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
2850 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "Motion", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150);
2851 		return;
2852 	}
2853 
2854 	if (self->localAnimIndex > 1 &&
2855 		bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame == 0 &&
2856 		bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames == 0)
2857 	{ //We'll allow this for non-humanoids.
2858 		goto tryTorso;
2859 	}
2860 
2861 	if (self->client->legsAnimExecute != legsAnim || self->client->legsLastFlip != self->client->ps.legsFlip)
2862 	{
2863 		animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[legsAnim].frameLerp;
2864 		lAnimSpeedScale = (animSpeed *= animSpeedScale);
2865 
2866 		if (bgAllAnims[self->localAnimIndex].anims[legsAnim].loopFrames != -1)
2867 		{
2868 			aFlags = BONE_ANIM_OVERRIDE_LOOP;
2869 		}
2870 		else
2871 		{
2872 			aFlags = BONE_ANIM_OVERRIDE_FREEZE;
2873 		}
2874 
2875 		if (animSpeed < 0)
2876 		{
2877 			lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame;
2878 			firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames;
2879 		}
2880 		else
2881 		{
2882 			firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame;
2883 			lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames;
2884 		}
2885 
2886 		aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on server position, but it's here just for the sake of matching them.
2887 
2888 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "model_root", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
2889 		self->client->legsAnimExecute = legsAnim;
2890 		self->client->legsLastFlip = self->client->ps.legsFlip;
2891 	}
2892 
2893 tryTorso:
2894 	if (self->localAnimIndex > 1 &&
2895 		bgAllAnims[self->localAnimIndex].anims[torsoAnim].firstFrame == 0 &&
2896 		bgAllAnims[self->localAnimIndex].anims[torsoAnim].numFrames == 0)
2897 
2898 	{ //If this fails as well just return.
2899 		return;
2900 	}
2901 	else if (self->s.number >= MAX_CLIENTS &&
2902 		self->s.NPC_class == CLASS_VEHICLE)
2903 	{ //we only want to set the root bone for vehicles
2904 		return;
2905 	}
2906 
2907 	if ((self->client->torsoAnimExecute != torsoAnim || self->client->torsoLastFlip != self->client->ps.torsoFlip) &&
2908 		!self->noLumbar)
2909 	{
2910 		aFlags = 0;
2911 		animSpeed = 0;
2912 
2913 		f = torsoAnim;
2914 
2915 		BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, f, &animSpeedScale, self->client->ps.brokenLimbs);
2916 
2917 		animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[f].frameLerp;
2918 		lAnimSpeedScale = (animSpeed *= animSpeedScale);
2919 
2920 		if (bgAllAnims[self->localAnimIndex].anims[f].loopFrames != -1)
2921 		{
2922 			aFlags = BONE_ANIM_OVERRIDE_LOOP;
2923 		}
2924 		else
2925 		{
2926 			aFlags = BONE_ANIM_OVERRIDE_FREEZE;
2927 		}
2928 
2929 		aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them.
2930 
2931 		if (animSpeed < 0)
2932 		{
2933 			lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame;
2934 			firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames;
2935 		}
2936 		else
2937 		{
2938 			firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame;
2939 			lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames;
2940 		}
2941 
2942 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, /*firstFrame why was it this before?*/-1, 150);
2943 
2944 		self->client->torsoAnimExecute = torsoAnim;
2945 		self->client->torsoLastFlip = self->client->ps.torsoFlip;
2946 
2947 		setTorso = qtrue;
2948 	}
2949 
2950 	if (setTorso &&
2951 		self->localAnimIndex <= 1)
2952 	{ //only set the motion bone for humanoids.
2953 		trap->G2API_SetBoneAnim(self->ghoul2, 0, "Motion", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
2954 	}
2955 
2956 #if 0 //disabled for now
2957 	if (self->client->ps.brokenLimbs != self->client->brokenLimbs ||
2958 		setTorso)
2959 	{
2960 		if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs &&
2961 			(self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM)))
2962 		{ //broken left arm
2963 			char *brokenBone = "lhumerus";
2964 			animation_t *armAnim;
2965 			int armFirstFrame;
2966 			int armLastFrame;
2967 			int armFlags = 0;
2968 			float armAnimSpeed;
2969 
2970 			armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_DEAD21 ];
2971 			self->client->brokenLimbs = self->client->ps.brokenLimbs;
2972 
2973 			armFirstFrame = armAnim->firstFrame;
2974 			armLastFrame = armAnim->firstFrame+armAnim->numFrames;
2975 			armAnimSpeed = 50.0f / armAnim->frameLerp;
2976 			armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
2977 
2978 			trap->G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
2979 		}
2980 		else if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs &&
2981 			(self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM)))
2982 		{ //broken right arm
2983 			char *brokenBone = "rhumerus";
2984 			char *supportBone = "lhumerus";
2985 
2986 			self->client->brokenLimbs = self->client->ps.brokenLimbs;
2987 
2988 			//Only put the arm in a broken pose if the anim is such that we
2989 			//want to allow it.
2990 			if ((//self->client->ps.weapon == WP_MELEE ||
2991 				self->client->ps.weapon != WP_SABER ||
2992 				BG_SaberStanceAnim(self->client->ps.torsoAnim) ||
2993 				PM_RunningAnim(self->client->ps.torsoAnim)) &&
2994 				(!self->client->saber[1].model[0] || self->client->ps.weapon != WP_SABER))
2995 			{
2996 				int armFirstFrame;
2997 				int armLastFrame;
2998 				int armFlags = 0;
2999 				float armAnimSpeed;
3000 				animation_t *armAnim;
3001 
3002 				if (self->client->ps.weapon == WP_MELEE ||
3003 					self->client->ps.weapon == WP_SABER ||
3004 					self->client->ps.weapon == WP_BRYAR_PISTOL)
3005 				{ //don't affect this arm if holding a gun, just make the other arm support it
3006 					armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_ATTACK2 ];
3007 
3008 					//armFirstFrame = armAnim->firstFrame;
3009 					armFirstFrame = armAnim->firstFrame+armAnim->numFrames;
3010 					armLastFrame = armAnim->firstFrame+armAnim->numFrames;
3011 					armAnimSpeed = 50.0f / armAnim->frameLerp;
3012 					armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
3013 
3014 					trap->G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
3015 				}
3016 				else
3017 				{ //we want to keep the broken bone updated for some cases
3018 					trap->G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
3019 				}
3020 
3021 				if (self->client->ps.torsoAnim != BOTH_MELEE1 &&
3022 					self->client->ps.torsoAnim != BOTH_MELEE2 &&
3023 					(self->client->ps.torsoAnim == TORSO_WEAPONREADY2 || self->client->ps.torsoAnim == BOTH_ATTACK2 || self->client->ps.weapon < WP_BRYAR_PISTOL))
3024 				{
3025 					//Now set the left arm to "support" the right one
3026 					armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_STAND2 ];
3027 					armFirstFrame = armAnim->firstFrame;
3028 					armLastFrame = armAnim->firstFrame+armAnim->numFrames;
3029 					armAnimSpeed = 50.0f / armAnim->frameLerp;
3030 					armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND);
3031 
3032 					trap->G2API_SetBoneAnim(self->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150);
3033 				}
3034 				else
3035 				{ //we want to keep the support bone updated for some cases
3036 					trap->G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
3037 				}
3038 			}
3039 			else
3040 			{ //otherwise, keep it set to the same as the torso
3041 				trap->G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
3042 				trap->G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150);
3043 			}
3044 		}
3045 		else if (self->client->brokenLimbs)
3046 		{ //remove the bone now so it can be set again
3047 			char *brokenBone = NULL;
3048 			int broken = 0;
3049 
3050 			//Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index
3051 			//(well, in theory, I haven't actually run into the problem)
3052 			if (self->client->brokenLimbs & (1<<BROKENLIMB_LARM))
3053 			{
3054 				brokenBone = "lhumerus";
3055 				broken |= (1<<BROKENLIMB_LARM);
3056 			}
3057 			else if (self->client->brokenLimbs & (1<<BROKENLIMB_RARM))
3058 			{ //can only have one arm broken at once.
3059 				brokenBone = "rhumerus";
3060 				broken |= (1<<BROKENLIMB_RARM);
3061 
3062 				//want to remove the support bone too then
3063 				trap->G2API_SetBoneAnim(self->ghoul2, 0, "lhumerus", 0, 1, 0, 0, level.time, -1, 0);
3064 				trap->G2API_RemoveBone(self->ghoul2, "lhumerus", 0);
3065 			}
3066 
3067 			assert(brokenBone);
3068 
3069 			//Set the flags and stuff to 0, so that the remove will succeed
3070 			trap->G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, 0, 1, 0, 0, level.time, -1, 0);
3071 
3072 			//Now remove it
3073 			trap->G2API_RemoveBone(self->ghoul2, brokenBone, 0);
3074 			self->client->brokenLimbs &= ~broken;
3075 		}
3076 	}
3077 #endif
3078 }
3079 
3080 /*
3081 ===========
3082 ClientSpawn
3083 
3084 Called every time a client is placed fresh in the world:
3085 after the first ClientBegin, and after each respawn
3086 Initializes all non-persistant parts of playerState
3087 ============
3088 */
3089 extern qboolean WP_HasForcePowers( const playerState_t *ps );
ClientSpawn(gentity_t * ent)3090 void ClientSpawn(gentity_t *ent) {
3091 	int					i = 0, index = 0, saveSaberNum = ENTITYNUM_NONE, wDisable = 0, savedSiegeIndex = 0, maxHealth = 100;
3092 	vec3_t				spawn_origin, spawn_angles;
3093 	gentity_t			*spawnPoint = NULL, *tent = NULL;
3094 	gclient_t			*client = NULL;
3095 	clientPersistant_t	saved;
3096 	clientSession_t		savedSess;
3097 	forcedata_t			savedForce;
3098 	saberInfo_t			saberSaved[MAX_SABERS];
3099 	int					persistant[MAX_PERSISTANT] = {0};
3100 	int					flags, gameFlags, savedPing, accuracy_hits, accuracy_shots, eventSequence;
3101 	void				*g2WeaponPtrs[MAX_SABERS];
3102 	char				userinfo[MAX_INFO_STRING] = {0}, *key = NULL, *value = NULL, *saber = NULL;
3103 	qboolean			changedSaber = qfalse, inSiegeWithClass = qfalse;
3104 
3105 	index = ent - g_entities;
3106 	client = ent->client;
3107 
3108 	//first we want the userinfo so we can see if we should update this client's saber -rww
3109 	trap->GetUserinfo( index, userinfo, sizeof( userinfo ) );
3110 
3111 	for ( i=0; i<MAX_SABERS; i++ )
3112 	{
3113 		saber = (i&1) ? ent->client->pers.saber2 : ent->client->pers.saber1;
3114 		value = Info_ValueForKey( userinfo, va( "saber%i", i+1 ) );
3115 		if ( saber && value &&
3116 			(Q_stricmp( value, saber ) || !saber[0] || !ent->client->saber[0].model[0]) )
3117 		{ //doesn't match up (or our saber is BS), we want to try setting it
3118 			if ( G_SetSaber( ent, i, value, qfalse ) )
3119 				changedSaber = qtrue;
3120 
3121 			//Well, we still want to say they changed then (it means this is siege and we have some overrides)
3122 			else if ( !saber[0] || !ent->client->saber[0].model[0] )
3123 				changedSaber = qtrue;
3124 		}
3125 	}
3126 
3127 	if ( changedSaber )
3128 	{ //make sure our new info is sent out to all the other clients, and give us a valid stance
3129 		if ( !ClientUserinfoChanged( ent->s.number ) )
3130 			return;
3131 
3132 		//make sure the saber models are updated
3133 		G_SaberModelSetup( ent );
3134 
3135 		for ( i=0; i<MAX_SABERS; i++ )
3136 		{
3137 			saber = (i&1) ? ent->client->pers.saber2 : ent->client->pers.saber1;
3138 			key = va( "saber%d", i+1 );
3139 			value = Info_ValueForKey( userinfo, key );
3140 			if ( Q_stricmp( value, saber ) )
3141 			{// they don't match up, force the user info
3142 				Info_SetValueForKey( userinfo, key, saber );
3143 				trap->SetUserinfo( ent->s.number, userinfo );
3144 			}
3145 		}
3146 
3147 		if ( ent->client->saber[0].model[0] && ent->client->saber[1].model[0] )
3148 		{ //dual
3149 			ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_DUAL;
3150 		}
3151 		else if ( (ent->client->saber[0].saberFlags&SFL_TWO_HANDED) )
3152 		{ //staff
3153 			ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_STAFF;
3154 		}
3155 		else
3156 		{
3157 			ent->client->sess.saberLevel = Com_Clampi( SS_FAST, SS_STRONG, ent->client->sess.saberLevel );
3158 			ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel;
3159 
3160 			// limit our saber style to our force points allocated to saber offense
3161 			if ( level.gametype != GT_SIEGE && ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
3162 				ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
3163 		}
3164 		if ( level.gametype != GT_SIEGE )
3165 		{// let's just make sure the styles we chose are cool
3166 			if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) )
3167 			{
3168 				WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel );
3169 				ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel;
3170 			}
3171 		}
3172 	}
3173 
3174 	if (client->ps.fd.forceDoInit)
3175 	{ //force a reread of force powers
3176 		WP_InitForcePowers( ent );
3177 		client->ps.fd.forceDoInit = 0;
3178 	}
3179 
3180 	if (ent->client->ps.fd.saberAnimLevel != SS_STAFF && ent->client->ps.fd.saberAnimLevel != SS_DUAL &&
3181 		ent->client->ps.fd.saberAnimLevel == ent->client->ps.fd.saberDrawAnimLevel &&
3182 		ent->client->ps.fd.saberAnimLevel == ent->client->sess.saberLevel)
3183 	{
3184 		ent->client->sess.saberLevel = Com_Clampi( SS_FAST, SS_STRONG, ent->client->sess.saberLevel );
3185 		ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel;
3186 
3187 		// limit our saber style to our force points allocated to saber offense
3188 		if ( level.gametype != GT_SIEGE && ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] )
3189 			ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE];
3190 	}
3191 
3192 	// find a spawn point
3193 	// do it before setting health back up, so farthest
3194 	// ranging doesn't count this client
3195 	if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
3196 		spawnPoint = SelectSpectatorSpawnPoint (
3197 						spawn_origin, spawn_angles);
3198 	} else if (level.gametype == GT_CTF || level.gametype == GT_CTY) {
3199 		// all base oriented team games use the CTF spawn points
3200 		spawnPoint = SelectCTFSpawnPoint (
3201 						client->sess.sessionTeam,
3202 						client->pers.teamState.state,
3203 						spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT));
3204 	}
3205 	else if (level.gametype == GT_SIEGE)
3206 	{
3207 		spawnPoint = SelectSiegeSpawnPoint (
3208 						client->siegeClass,
3209 						client->sess.sessionTeam,
3210 						client->pers.teamState.state,
3211 						spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT));
3212 	}
3213 	else {
3214 		if (level.gametype == GT_POWERDUEL)
3215 		{
3216 			spawnPoint = SelectDuelSpawnPoint(client->sess.duelTeam, client->ps.origin, spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT));
3217 		}
3218 		else if (level.gametype == GT_DUEL)
3219 		{	// duel
3220 			spawnPoint = SelectDuelSpawnPoint(DUELTEAM_SINGLE, client->ps.origin, spawn_origin, spawn_angles, !!(ent->r.svFlags & SVF_BOT));
3221 		}
3222 		else
3223 		{
3224 			// the first spawn should be at a good looking spot
3225 			if ( !client->pers.initialSpawn && client->pers.localClient ) {
3226 				client->pers.initialSpawn = qtrue;
3227 				spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles, client->sess.sessionTeam, !!(ent->r.svFlags & SVF_BOT) );
3228 			} else {
3229 				// don't spawn near existing origin if possible
3230 				spawnPoint = SelectSpawnPoint (
3231 					client->ps.origin,
3232 					spawn_origin, spawn_angles, client->sess.sessionTeam, !!(ent->r.svFlags & SVF_BOT) );
3233 			}
3234 		}
3235 	}
3236 	client->pers.teamState.state = TEAM_ACTIVE;
3237 
3238 	// toggle the teleport bit so the client knows to not lerp
3239 	// and never clear the voted flag
3240 	flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT);
3241 	flags ^= EF_TELEPORT_BIT;
3242 	gameFlags = ent->client->mGameFlags & ( PSG_VOTED | PSG_TEAMVOTED);
3243 
3244 	// clear everything but the persistant data
3245 
3246 	saved = client->pers;
3247 	savedSess = client->sess;
3248 	savedPing = client->ps.ping;
3249 //	savedAreaBits = client->areabits;
3250 	accuracy_hits = client->accuracy_hits;
3251 	accuracy_shots = client->accuracy_shots;
3252 	for ( i=0; i<MAX_PERSISTANT; i++ )
3253 		persistant[i] = client->ps.persistant[i];
3254 
3255 	eventSequence = client->ps.eventSequence;
3256 
3257 	savedForce = client->ps.fd;
3258 
3259 	saveSaberNum = client->ps.saberEntityNum;
3260 
3261 	savedSiegeIndex = client->siegeClass;
3262 
3263 	for ( i=0; i<MAX_SABERS; i++ )
3264 	{
3265 		saberSaved[i] = client->saber[i];
3266 		g2WeaponPtrs[i] = client->weaponGhoul2[i];
3267 	}
3268 
3269 	for ( i=0; i<HL_MAX; i++ )
3270 		ent->locationDamage[i] = 0;
3271 
3272 	memset( client, 0, sizeof( *client ) ); // bk FIXME: Com_Memset?
3273 	client->bodyGrabIndex = ENTITYNUM_NONE;
3274 
3275 	//Get the skin RGB based on his userinfo
3276 	client->ps.customRGBA[0] = (value=Info_ValueForKey( userinfo, "char_color_red" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
3277 	client->ps.customRGBA[1] = (value=Info_ValueForKey( userinfo, "char_color_green" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
3278 	client->ps.customRGBA[2] = (value=Info_ValueForKey( userinfo, "char_color_blue" ))	? Com_Clampi( 0, 255, atoi( value ) ) : 255;
3279 
3280 	//Prevent skins being too dark
3281 	if ( g_charRestrictRGB.integer && ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) )
3282 		client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255;
3283 
3284 	client->ps.customRGBA[3]=255;
3285 
3286 	if ( level.gametype >= GT_TEAM && level.gametype != GT_SIEGE && !g_jediVmerc.integer )
3287 	{
3288 		char skin[MAX_QPATH] = {0}, model[MAX_QPATH] = {0};
3289 		vec3_t colorOverride = {0.0f};
3290 
3291 		VectorClear( colorOverride );
3292 		Q_strncpyz( model, Info_ValueForKey( userinfo, "model" ), sizeof( model ) );
3293 
3294 		BG_ValidateSkinForTeam( model, skin, savedSess.sessionTeam, colorOverride );
3295 		if ( colorOverride[0] != 0.0f || colorOverride[1] != 0.0f || colorOverride[2] != 0.0f )
3296 			VectorScaleM( colorOverride, 255.0f, client->ps.customRGBA );
3297 	}
3298 
3299 	client->siegeClass = savedSiegeIndex;
3300 
3301 	for ( i=0; i<MAX_SABERS; i++ )
3302 	{
3303 		client->saber[i] = saberSaved[i];
3304 		client->weaponGhoul2[i] = g2WeaponPtrs[i];
3305 	}
3306 
3307 	//or the saber ent num
3308 	client->ps.saberEntityNum = saveSaberNum;
3309 	client->saberStoredIndex = saveSaberNum;
3310 
3311 	client->ps.fd = savedForce;
3312 
3313 	client->ps.duelIndex = ENTITYNUM_NONE;
3314 
3315 	//spawn with 100
3316 	client->ps.jetpackFuel = 100;
3317 	client->ps.cloakFuel = 100;
3318 
3319 	client->pers = saved;
3320 	client->sess = savedSess;
3321 	client->ps.ping = savedPing;
3322 //	client->areabits = savedAreaBits;
3323 	client->accuracy_hits = accuracy_hits;
3324 	client->accuracy_shots = accuracy_shots;
3325 	client->lastkilled_client = -1;
3326 
3327 	for ( i=0; i<MAX_PERSISTANT; i++ )
3328 		client->ps.persistant[i] = persistant[i];
3329 
3330 	client->ps.eventSequence = eventSequence;
3331 	// increment the spawncount so the client will detect the respawn
3332 	client->ps.persistant[PERS_SPAWN_COUNT]++;
3333 	client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam;
3334 
3335 	client->airOutTime = level.time + 12000;
3336 
3337 	// set max health
3338 	if (level.gametype == GT_SIEGE && client->siegeClass != -1)
3339 	{
3340 		siegeClass_t *scl = &bgSiegeClasses[client->siegeClass];
3341 		maxHealth = 100;
3342 
3343 		if (scl->maxhealth)
3344 		{
3345 			maxHealth = scl->maxhealth;
3346 		}
3347 	}
3348 	else
3349 	{
3350 		maxHealth = Com_Clampi( 1, 100, atoi( Info_ValueForKey( userinfo, "handicap" ) ) );
3351 	}
3352 	client->pers.maxHealth = maxHealth;//atoi( Info_ValueForKey( userinfo, "handicap" ) );
3353 	if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) {
3354 		client->pers.maxHealth = 100;
3355 	}
3356 	// clear entity values
3357 	client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth;
3358 	client->ps.eFlags = flags;
3359 	client->mGameFlags = gameFlags;
3360 
3361 	client->ps.groundEntityNum = ent->s.groundEntityNum = ENTITYNUM_NONE;
3362 	ent->client = &level.clients[index];
3363 	ent->playerState = &ent->client->ps;
3364 	ent->takedamage = qtrue;
3365 	ent->inuse = qtrue;
3366 	ent->classname = "player";
3367 	ent->r.contents = CONTENTS_BODY;
3368 	ent->clipmask = MASK_PLAYERSOLID;
3369 	ent->die = player_die;
3370 	ent->waterlevel = 0;
3371 	ent->watertype = 0;
3372 	ent->flags = 0;
3373 
3374 	VectorCopy (playerMins, ent->r.mins);
3375 	VectorCopy (playerMaxs, ent->r.maxs);
3376 	client->ps.crouchheight = CROUCH_MAXS_2;
3377 	client->ps.standheight = DEFAULT_MAXS_2;
3378 
3379 	client->ps.clientNum = index;
3380 	//give default weapons
3381 	client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE );
3382 
3383 	if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL)
3384 	{
3385 		wDisable = g_duelWeaponDisable.integer;
3386 	}
3387 	else
3388 	{
3389 		wDisable = g_weaponDisable.integer;
3390 	}
3391 
3392 
3393 
3394 	if ( level.gametype != GT_HOLOCRON
3395 		&& level.gametype != GT_JEDIMASTER
3396 		&& !HasSetSaberOnly()
3397 		&& !AllForceDisabled( g_forcePowerDisable.integer )
3398 		&& g_jediVmerc.integer )
3399 	{
3400 		if ( level.gametype >= GT_TEAM && (client->sess.sessionTeam == TEAM_BLUE || client->sess.sessionTeam == TEAM_RED) )
3401 		{//In Team games, force one side to be merc and other to be jedi
3402 			if ( level.numPlayingClients > 0 )
3403 			{//already someone in the game
3404 				int forceTeam = TEAM_SPECTATOR;
3405 				for ( i = 0 ; i < level.maxclients ; i++ )
3406 				{
3407 					if ( level.clients[i].pers.connected == CON_DISCONNECTED ) {
3408 						continue;
3409 					}
3410 					if ( level.clients[i].sess.sessionTeam == TEAM_BLUE || level.clients[i].sess.sessionTeam == TEAM_RED )
3411 					{//in-game
3412 						if ( WP_HasForcePowers( &level.clients[i].ps ) )
3413 						{//this side is using force
3414 							forceTeam = level.clients[i].sess.sessionTeam;
3415 						}
3416 						else
3417 						{//other team is using force
3418 							if ( level.clients[i].sess.sessionTeam == TEAM_BLUE )
3419 							{
3420 								forceTeam = TEAM_RED;
3421 							}
3422 							else
3423 							{
3424 								forceTeam = TEAM_BLUE;
3425 							}
3426 						}
3427 						break;
3428 					}
3429 				}
3430 				if ( WP_HasForcePowers( &client->ps ) && client->sess.sessionTeam != forceTeam )
3431 				{//using force but not on right team, switch him over
3432 					const char *teamName = TeamName( forceTeam );
3433 					//client->sess.sessionTeam = forceTeam;
3434 					SetTeam( ent, (char *)teamName );
3435 					return;
3436 				}
3437 			}
3438 		}
3439 
3440 		if ( WP_HasForcePowers( &client->ps ) )
3441 		{
3442 			client->ps.trueNonJedi = qfalse;
3443 			client->ps.trueJedi = qtrue;
3444 			//make sure they only use the saber
3445 			client->ps.weapon = WP_SABER;
3446 			client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER);
3447 		}
3448 		else
3449 		{//no force powers set
3450 			client->ps.trueNonJedi = qtrue;
3451 			client->ps.trueJedi = qfalse;
3452 			if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL)))
3453 			{
3454 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
3455 			}
3456 			if (!wDisable || !(wDisable & (1 << WP_BLASTER)))
3457 			{
3458 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER );
3459 			}
3460 			if (!wDisable || !(wDisable & (1 << WP_BOWCASTER)))
3461 			{
3462 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BOWCASTER );
3463 			}
3464 			client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
3465 			client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
3466 			client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
3467 			client->ps.weapon = WP_BRYAR_PISTOL;
3468 		}
3469 	}
3470 	else
3471 	{//jediVmerc is incompatible with this gametype, turn it off!
3472 		trap->Cvar_Set( "g_jediVmerc", "0" );
3473 		trap->Cvar_Update( &g_jediVmerc );
3474 		if (level.gametype == GT_HOLOCRON)
3475 		{
3476 			//always get free saber level 1 in holocron
3477 			client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER );	//these are precached in g_items, ClearRegisteredItems()
3478 		}
3479 		else
3480 		{
3481 			if (client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE])
3482 			{
3483 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER );	//these are precached in g_items, ClearRegisteredItems()
3484 			}
3485 			else
3486 			{ //if you don't have saber attack rank then you don't get a saber
3487 				client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
3488 			}
3489 		}
3490 
3491 		if (level.gametype != GT_SIEGE)
3492 		{
3493 			if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL)))
3494 			{
3495 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
3496 			}
3497 			else if (level.gametype == GT_JEDIMASTER)
3498 			{
3499 				client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL );
3500 			}
3501 		}
3502 
3503 		if (level.gametype == GT_JEDIMASTER)
3504 		{
3505 			client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER);
3506 			client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE);
3507 		}
3508 
3509 		if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
3510 		{
3511 			client->ps.weapon = WP_SABER;
3512 		}
3513 		else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL))
3514 		{
3515 			client->ps.weapon = WP_BRYAR_PISTOL;
3516 		}
3517 		else
3518 		{
3519 			client->ps.weapon = WP_MELEE;
3520 		}
3521 	}
3522 
3523 	/*
3524 	client->ps.stats[STAT_HOLDABLE_ITEMS] |= ( 1 << HI_BINOCULARS );
3525 	client->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_BINOCULARS, IT_HOLDABLE);
3526 	*/
3527 
3528 	if (level.gametype == GT_SIEGE && client->siegeClass != -1 &&
3529 		client->sess.sessionTeam != TEAM_SPECTATOR)
3530 	{ //well then, we will use a custom weaponset for our class
3531 		int m = 0;
3532 
3533 		client->ps.stats[STAT_WEAPONS] = bgSiegeClasses[client->siegeClass].weapons;
3534 
3535 		if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER))
3536 		{
3537 			client->ps.weapon = WP_SABER;
3538 		}
3539 		else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL))
3540 		{
3541 			client->ps.weapon = WP_BRYAR_PISTOL;
3542 		}
3543 		else
3544 		{
3545 			client->ps.weapon = WP_MELEE;
3546 		}
3547 		inSiegeWithClass = qtrue;
3548 
3549 		while (m < WP_NUM_WEAPONS)
3550 		{
3551 			if (client->ps.stats[STAT_WEAPONS] & (1 << m))
3552 			{
3553 				if (client->ps.weapon != WP_SABER)
3554 				{ //try to find the highest ranking weapon we have
3555 					if (m > client->ps.weapon)
3556 					{
3557 						client->ps.weapon = m;
3558 					}
3559 				}
3560 
3561 				if (m >= WP_BRYAR_PISTOL)
3562 				{ //Max his ammo out for all the weapons he has.
3563 					if ( level.gametype == GT_SIEGE
3564 						&& m == WP_ROCKET_LAUNCHER )
3565 					{//don't give full ammo!
3566 						//FIXME: extern this and check it when getting ammo from supplier, pickups or ammo stations!
3567 						if ( client->siegeClass != -1 &&
3568 							(bgSiegeClasses[client->siegeClass].classflags & (1<<CFL_SINGLE_ROCKET)) )
3569 						{
3570 							client->ps.ammo[weaponData[m].ammoIndex] = 1;
3571 						}
3572 						else
3573 						{
3574 							client->ps.ammo[weaponData[m].ammoIndex] = 10;
3575 						}
3576 					}
3577 					else
3578 					{
3579 						if ( level.gametype == GT_SIEGE
3580 							&& client->siegeClass != -1
3581 							&& (bgSiegeClasses[client->siegeClass].classflags & (1<<CFL_EXTRA_AMMO)) )
3582 						{//double ammo
3583 							client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max*2;
3584 							client->ps.eFlags |= EF_DOUBLE_AMMO;
3585 						}
3586 						else
3587 						{
3588 							client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max;
3589 						}
3590 					}
3591 				}
3592 			}
3593 			m++;
3594 		}
3595 	}
3596 
3597 	if (level.gametype == GT_SIEGE &&
3598 		client->siegeClass != -1 &&
3599 		client->sess.sessionTeam != TEAM_SPECTATOR)
3600 	{ //use class-specified inventory
3601 		client->ps.stats[STAT_HOLDABLE_ITEMS] = bgSiegeClasses[client->siegeClass].invenItems;
3602 		client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
3603 	}
3604 	else
3605 	{
3606 		client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
3607 		client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
3608 	}
3609 
3610 	if (level.gametype == GT_SIEGE &&
3611 		client->siegeClass != -1 &&
3612 		bgSiegeClasses[client->siegeClass].powerups &&
3613 		client->sess.sessionTeam != TEAM_SPECTATOR)
3614 	{ //this class has some start powerups
3615 		i = 0;
3616 		while (i < PW_NUM_POWERUPS)
3617 		{
3618 			if (bgSiegeClasses[client->siegeClass].powerups & (1 << i))
3619 			{
3620 				client->ps.powerups[i] = Q3_INFINITE;
3621 			}
3622 			i++;
3623 		}
3624 	}
3625 
3626 	if ( client->sess.sessionTeam == TEAM_SPECTATOR )
3627 	{
3628 		client->ps.stats[STAT_WEAPONS] = 0;
3629 		client->ps.stats[STAT_HOLDABLE_ITEMS] = 0;
3630 		client->ps.stats[STAT_HOLDABLE_ITEM] = 0;
3631 	}
3632 
3633 // nmckenzie: DESERT_SIEGE... or well, siege generally.  This was over-writing the max value, which was NOT good for siege.
3634 	if ( inSiegeWithClass == qfalse )
3635 	{
3636 		client->ps.ammo[AMMO_BLASTER] = 100; //ammoData[AMMO_BLASTER].max; //100 seems fair.
3637 	}
3638 //	client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max;
3639 //	client->ps.ammo[AMMO_FORCE] = ammoData[AMMO_FORCE].max;
3640 //	client->ps.ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max;
3641 //	client->ps.ammo[AMMO_ROCKETS] = ammoData[AMMO_ROCKETS].max;
3642 /*
3643 	client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BRYAR_PISTOL);
3644 	if ( level.gametype == GT_TEAM ) {
3645 		client->ps.ammo[WP_BRYAR_PISTOL] = 50;
3646 	} else {
3647 		client->ps.ammo[WP_BRYAR_PISTOL] = 100;
3648 	}
3649 */
3650 	client->ps.rocketLockIndex = ENTITYNUM_NONE;
3651 	client->ps.rocketLockTime = 0;
3652 
3653 	//rww - Set here to initialize the circling seeker drone to off.
3654 	//A quick note about this so I don't forget how it works again:
3655 	//ps.genericEnemyIndex is kept in sync between the server and client.
3656 	//When it gets set then an entitystate value of the same name gets
3657 	//set along with an entitystate flag in the shared bg code. Which
3658 	//is why a value needs to be both on the player state and entity state.
3659 	//(it doesn't seem to just carry over the entitystate value automatically
3660 	//because entity state value is derived from player state data or some
3661 	//such)
3662 	client->ps.genericEnemyIndex = -1;
3663 
3664 	client->ps.isJediMaster = qfalse;
3665 
3666 	if (client->ps.fallingToDeath)
3667 	{
3668 		client->ps.fallingToDeath = 0;
3669 		client->noCorpse = qtrue;
3670 	}
3671 
3672 	//Do per-spawn force power initialization
3673 	WP_SpawnInitForcePowers( ent );
3674 
3675 	// health will count down towards max_health
3676 	if (level.gametype == GT_SIEGE &&
3677 		client->siegeClass != -1 &&
3678 		bgSiegeClasses[client->siegeClass].starthealth)
3679 	{ //class specifies a start health, so use it
3680 		ent->health = client->ps.stats[STAT_HEALTH] = bgSiegeClasses[client->siegeClass].starthealth;
3681 	}
3682 	else if ( level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL )
3683 	{//only start with 100 health in Duel
3684 		if ( level.gametype == GT_POWERDUEL && client->sess.duelTeam == DUELTEAM_LONE )
3685 		{
3686 			if ( duel_fraglimit.integer )
3687 			{
3688 
3689 				ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] =
3690 					g_powerDuelStartHealth.integer - ((g_powerDuelStartHealth.integer - g_powerDuelEndHealth.integer) * (float)client->sess.wins / (float)duel_fraglimit.integer);
3691 			}
3692 			else
3693 			{
3694 				ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 150;
3695 			}
3696 		}
3697 		else
3698 		{
3699 			ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 100;
3700 		}
3701 	}
3702 	else if (client->ps.stats[STAT_MAX_HEALTH] <= 100)
3703 	{
3704 		ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25;
3705 	}
3706 	else if (client->ps.stats[STAT_MAX_HEALTH] < 125)
3707 	{
3708 		ent->health = client->ps.stats[STAT_HEALTH] = 125;
3709 	}
3710 	else
3711 	{
3712 		ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH];
3713 	}
3714 
3715 	// Start with a small amount of armor as well.
3716 	if (level.gametype == GT_SIEGE &&
3717 		client->siegeClass != -1 /*&&
3718 		bgSiegeClasses[client->siegeClass].startarmor*/)
3719 	{ //class specifies a start armor amount, so use it
3720 		client->ps.stats[STAT_ARMOR] = bgSiegeClasses[client->siegeClass].startarmor;
3721 	}
3722 	else if ( level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL )
3723 	{//no armor in duel
3724 		client->ps.stats[STAT_ARMOR] = 0;
3725 	}
3726 	else
3727 	{
3728 		client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25;
3729 	}
3730 
3731 	G_SetOrigin( ent, spawn_origin );
3732 	VectorCopy( spawn_origin, client->ps.origin );
3733 
3734 	// the respawned flag will be cleared after the attack and jump keys come up
3735 	client->ps.pm_flags |= PMF_RESPAWNED;
3736 
3737 	trap->GetUsercmd( client - level.clients, &ent->client->pers.cmd );
3738 	SetClientViewAngle( ent, spawn_angles );
3739 	// don't allow full run speed for a bit
3740 	client->ps.pm_flags |= PMF_TIME_KNOCKBACK;
3741 	client->ps.pm_time = 100;
3742 
3743 	client->respawnTime = level.time;
3744 	client->inactivityTime = level.time + g_inactivity.integer * 1000;
3745 	client->latched_buttons = 0;
3746 
3747 	if (!level.intermissiontime) {
3748 		if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) {
3749 			G_KillBox(ent);
3750 			// force the base weapon up
3751 			//client->ps.weapon = WP_BRYAR_PISTOL;
3752 			//client->ps.weaponstate = FIRST_WEAPON;
3753 			if (client->ps.weapon <= WP_NONE)
3754 			{
3755 				client->ps.weapon = WP_BRYAR_PISTOL;
3756 			}
3757 
3758 			client->ps.torsoTimer = client->ps.legsTimer = 0;
3759 
3760 			if (client->ps.weapon == WP_SABER)
3761 			{
3762 				G_SetAnim(ent, NULL, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0);
3763 			}
3764 			else
3765 			{
3766 				G_SetAnim(ent, NULL, SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0);
3767 				client->ps.legsAnim = WeaponReadyAnim[client->ps.weapon];
3768 			}
3769 			client->ps.weaponstate = WEAPON_RAISING;
3770 			client->ps.weaponTime = client->ps.torsoTimer;
3771 
3772 			if (g_spawnInvulnerability.integer)
3773 			{
3774 				ent->client->ps.eFlags |= EF_INVULNERABLE;
3775 				ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer;
3776 			}
3777 
3778 			// fire the targets of the spawn point
3779 			G_UseTargets(spawnPoint, ent);
3780 
3781 			// positively link the client, even if the command times are weird
3782 			VectorCopy(ent->client->ps.origin, ent->r.currentOrigin);
3783 
3784 			tent = G_TempEntity(ent->client->ps.origin, EV_PLAYER_TELEPORT_IN);
3785 			tent->s.clientNum = ent->s.clientNum;
3786 
3787 			trap->LinkEntity ((sharedEntity_t *)ent);
3788 		}
3789 	} else {
3790 		// move players to intermission
3791 		MoveClientToIntermission(ent);
3792 	}
3793 
3794 	//set teams for NPCs to recognize
3795 	if (level.gametype == GT_SIEGE)
3796 	{ //Imperial (team1) team is allied with "enemy" NPCs in this mode
3797 		if (client->sess.sessionTeam == SIEGETEAM_TEAM1)
3798 		{
3799 			client->playerTeam = ent->s.teamowner = NPCTEAM_ENEMY;
3800 			client->enemyTeam = NPCTEAM_PLAYER;
3801 		}
3802 		else
3803 		{
3804 			client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER;
3805 			client->enemyTeam = NPCTEAM_ENEMY;
3806 		}
3807 	}
3808 	else
3809 	{
3810 		client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER;
3811 		client->enemyTeam = NPCTEAM_ENEMY;
3812 	}
3813 
3814 	/*
3815 	//scaling for the power duel opponent
3816 	if (level.gametype == GT_POWERDUEL &&
3817 		client->sess.duelTeam == DUELTEAM_LONE)
3818 	{
3819 		client->ps.iModelScale = 125;
3820 		VectorSet(ent->modelScale, 1.25f, 1.25f, 1.25f);
3821 	}
3822 	*/
3823 	//Disabled. At least for now. Not sure if I'll want to do it or not eventually.
3824 
3825 	// run a client frame to drop exactly to the floor,
3826 	// initialize animations and other things
3827 	client->ps.commandTime = level.time - 100;
3828 	ent->client->pers.cmd.serverTime = level.time;
3829 	ClientThink( ent-g_entities, NULL );
3830 
3831 	// run the presend to set anything else, follow spectators wait
3832 	// until all clients have been reconnected after map_restart
3833 	if ( ent->client->sess.spectatorState != SPECTATOR_FOLLOW )
3834 		ClientEndFrame( ent );
3835 
3836 	// clear entity state values
3837 	BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue );
3838 
3839 	//rww - make sure client has a valid icarus instance
3840 	trap->ICARUS_FreeEnt( (sharedEntity_t *)ent );
3841 	trap->ICARUS_InitEnt( (sharedEntity_t *)ent );
3842 }
3843 
3844 
3845 /*
3846 ===========
3847 ClientDisconnect
3848 
3849 Called when a player drops from the server.
3850 Will not be called between levels.
3851 
3852 This should NOT be called directly by any game logic,
3853 call trap->DropClient(), which will call this and do
3854 server system housekeeping.
3855 ============
3856 */
3857 extern void G_LeaveVehicle( gentity_t* ent, qboolean ConCheck );
3858 
G_ClearVote(gentity_t * ent)3859 void G_ClearVote( gentity_t *ent ) {
3860 	if ( level.voteTime ) {
3861 		if ( ent->client->mGameFlags & PSG_VOTED ) {
3862 			if ( ent->client->pers.vote == 1 ) {
3863 				level.voteYes--;
3864 				trap->SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) );
3865 			}
3866 			else if ( ent->client->pers.vote == 2 ) {
3867 				level.voteNo--;
3868 				trap->SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) );
3869 			}
3870 		}
3871 		ent->client->mGameFlags &= ~(PSG_VOTED);
3872 		ent->client->pers.vote = 0;
3873 	}
3874 }
G_ClearTeamVote(gentity_t * ent,int team)3875 void G_ClearTeamVote( gentity_t *ent, int team ) {
3876 	int voteteam;
3877 
3878 		 if ( team == TEAM_RED )	voteteam = 0;
3879 	else if ( team == TEAM_BLUE )	voteteam = 1;
3880 	else							return;
3881 
3882 	if ( level.teamVoteTime[voteteam] ) {
3883 		if ( ent->client->mGameFlags & PSG_TEAMVOTED ) {
3884 			if ( ent->client->pers.teamvote == 1 ) {
3885 				level.teamVoteYes[voteteam]--;
3886 				trap->SetConfigstring( CS_TEAMVOTE_YES, va( "%i", level.teamVoteYes[voteteam] ) );
3887 			}
3888 			else if ( ent->client->pers.teamvote == 2 ) {
3889 				level.teamVoteNo[voteteam]--;
3890 				trap->SetConfigstring( CS_TEAMVOTE_NO, va( "%i", level.teamVoteNo[voteteam] ) );
3891 			}
3892 		}
3893 		ent->client->mGameFlags &= ~(PSG_TEAMVOTED);
3894 		ent->client->pers.teamvote = 0;
3895 	}
3896 }
3897 
ClientDisconnect(int clientNum)3898 void ClientDisconnect( int clientNum ) {
3899 	gentity_t	*ent;
3900 	gentity_t	*tent;
3901 	int			i;
3902 
3903 	// cleanup if we are kicking a bot that
3904 	// hasn't spawned yet
3905 	G_RemoveQueuedBotBegin( clientNum );
3906 
3907 	ent = g_entities + clientNum;
3908 	if ( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) {
3909 		return;
3910 	}
3911 
3912 	i = 0;
3913 
3914 	while (i < NUM_FORCE_POWERS)
3915 	{
3916 		if (ent->client->ps.fd.forcePowersActive & (1 << i))
3917 		{
3918 			WP_ForcePowerStop(ent, i);
3919 		}
3920 		i++;
3921 	}
3922 
3923 	i = TRACK_CHANNEL_1;
3924 
3925 	while (i < NUM_TRACK_CHANNELS)
3926 	{
3927 		if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0)
3928 		{
3929 			G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE);
3930 		}
3931 		i++;
3932 	}
3933 	i = 0;
3934 
3935 	G_LeaveVehicle( ent, qtrue );
3936 
3937 	if ( ent->client->ewebIndex )
3938 	{
3939 		gentity_t *eweb = &g_entities[ent->client->ewebIndex];
3940 
3941 		ent->client->ps.emplacedIndex = 0;
3942 		ent->client->ewebIndex = 0;
3943 		ent->client->ewebHealth = 0;
3944 		G_FreeEntity( eweb );
3945 	}
3946 
3947 	// stop any following clients
3948 	for ( i = 0 ; i < level.maxclients ; i++ ) {
3949 		if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR
3950 			&& level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW
3951 			&& level.clients[i].sess.spectatorClient == clientNum ) {
3952 			StopFollowing( &g_entities[i] );
3953 		}
3954 	}
3955 
3956 	// send effect if they were completely connected
3957 	if ( ent->client->pers.connected == CON_CONNECTED
3958 		&& ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {
3959 		tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT );
3960 		tent->s.clientNum = ent->s.clientNum;
3961 
3962 		// They don't get to take powerups with them!
3963 		// Especially important for stuff like CTF flags
3964 		TossClientItems( ent );
3965 	}
3966 
3967 	G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, ent->client->sess.IP, ent->client->pers.guid, ent->client->pers.netname );
3968 
3969 	// if we are playing in tourney mode, give a win to the other player and clear his frags for this round
3970 	if ( level.gametype == GT_DUEL && !level.intermissiontime && !level.warmupTime ) {
3971 		if ( level.sortedClients[1] == clientNum ) {
3972 			level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] = 0;
3973 			level.clients[ level.sortedClients[0] ].sess.wins++;
3974 			ClientUserinfoChanged( level.sortedClients[0] );
3975 		}
3976 		else if ( level.sortedClients[0] == clientNum ) {
3977 			level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] = 0;
3978 			level.clients[ level.sortedClients[1] ].sess.wins++;
3979 			ClientUserinfoChanged( level.sortedClients[1] );
3980 		}
3981 	}
3982 
3983 	if ( level.gametype == GT_DUEL && ent->client->sess.sessionTeam == TEAM_FREE && level.intermissiontime ) {
3984 		trap->SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" );
3985 		level.restarted = qtrue;
3986 		level.changemap = NULL;
3987 		level.intermissiontime = 0;
3988 	}
3989 
3990 	if (ent->ghoul2 && trap->G2API_HaveWeGhoul2Models(ent->ghoul2))
3991 	{
3992 		trap->G2API_CleanGhoul2Models(&ent->ghoul2);
3993 	}
3994 	i = 0;
3995 	while (i < MAX_SABERS)
3996 	{
3997 		if (ent->client->weaponGhoul2[i] && trap->G2API_HaveWeGhoul2Models(ent->client->weaponGhoul2[i]))
3998 		{
3999 			trap->G2API_CleanGhoul2Models(&ent->client->weaponGhoul2[i]);
4000 		}
4001 		i++;
4002 	}
4003 
4004 	G_ClearVote( ent );
4005 	G_ClearTeamVote( ent, ent->client->sess.sessionTeam );
4006 
4007 	trap->UnlinkEntity ((sharedEntity_t *)ent);
4008 	ent->s.modelindex = 0;
4009 	ent->inuse = qfalse;
4010 	ent->classname = "disconnected";
4011 	ent->client->pers.connected = CON_DISCONNECTED;
4012 	ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE;
4013 	ent->client->sess.sessionTeam = TEAM_FREE;
4014 	ent->r.contents = 0;
4015 
4016 	if (ent->client->holdingObjectiveItem > 0)
4017 	{ //carrying a siege objective item - make sure it updates and removes itself from us now in case this is an instant death-respawn situation
4018 		gentity_t *objectiveItem = &g_entities[ent->client->holdingObjectiveItem];
4019 
4020 		if (objectiveItem->inuse && objectiveItem->think)
4021 		{
4022             objectiveItem->think(objectiveItem);
4023 		}
4024 	}
4025 
4026 	trap->SetConfigstring( CS_PLAYERS + clientNum, "");
4027 
4028 	CalculateRanks();
4029 
4030 	if ( ent->r.svFlags & SVF_BOT ) {
4031 		BotAIShutdownClient( clientNum, qfalse );
4032 	}
4033 
4034 	G_ClearClientLog(clientNum);
4035 }
4036 
4037 
4038