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