1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22 //
23
24 #include "g_local.h"
25
26
27 /*
28 ===============
29 G_DamageFeedback
30
31 Called just before a snapshot is sent to the given player.
32 Totals up all damage and generates both the player_state_t
33 damage values to that client for pain blends and kicks, and
34 global pain sound events for all clients.
35 ===============
36 */
P_DamageFeedback(gentity_t * player)37 void P_DamageFeedback( gentity_t *player ) {
38 gclient_t *client;
39 float count;
40 vec3_t angles;
41
42 client = player->client;
43 if ( client->ps.pm_type == PM_DEAD ) {
44 return;
45 }
46
47 // total points of damage shot at the player this frame
48 count = client->damage_blood + client->damage_armor;
49 if ( count == 0 ) {
50 return; // didn't take any damage
51 }
52
53 if ( count > 255 ) {
54 count = 255;
55 }
56
57 // send the information to the client
58
59 // world damage (falling, slime, etc) uses a special code
60 // to make the blend blob centered instead of positional
61 if ( client->damage_fromWorld ) {
62 client->ps.damagePitch = 255;
63 client->ps.damageYaw = 255;
64
65 client->damage_fromWorld = qfalse;
66 } else {
67 vectoangles( client->damage_from, angles );
68 client->ps.damagePitch = angles[PITCH]/360.0 * 256;
69 client->ps.damageYaw = angles[YAW]/360.0 * 256;
70 }
71
72 // play an apropriate pain sound
73 if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) {
74 player->pain_debounce_time = level.time + 700;
75 G_AddEvent( player, EV_PAIN, player->health );
76 client->ps.damageEvent++;
77 }
78
79
80 client->ps.damageCount = count;
81
82 //
83 // clear totals
84 //
85 client->damage_blood = 0;
86 client->damage_armor = 0;
87 client->damage_knockback = 0;
88 }
89
90
91
92 /*
93 =============
94 P_WorldEffects
95
96 Check for lava / slime contents and drowning
97 =============
98 */
P_WorldEffects(gentity_t * ent)99 void P_WorldEffects( gentity_t *ent ) {
100 qboolean envirosuit;
101 int waterlevel;
102
103 if ( ent->client->noclip ) {
104 ent->client->airOutTime = level.time + 12000; // don't need air
105 return;
106 }
107
108 waterlevel = ent->waterlevel;
109
110 envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time;
111
112 //
113 // check for drowning
114 //
115 if ( waterlevel == 3 ) {
116 // envirosuit give air
117 if ( envirosuit ) {
118 ent->client->airOutTime = level.time + 10000;
119 }
120
121 // if out of air, start drowning
122 if ( ent->client->airOutTime < level.time) {
123 // drown!
124 ent->client->airOutTime += 1000;
125 if ( ent->health > 0 ) {
126 // take more damage the longer underwater
127 ent->damage += 2;
128 if (ent->damage > 15)
129 ent->damage = 15;
130
131 // play a gurp sound instead of a normal pain sound
132 if (ent->health <= ent->damage) {
133 G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav"));
134 } else if (rand()&1) {
135 G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav"));
136 } else {
137 G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav"));
138 }
139
140 // don't play a normal pain sound
141 ent->pain_debounce_time = level.time + 200;
142
143 G_Damage (ent, NULL, NULL, NULL, NULL,
144 ent->damage, DAMAGE_NO_ARMOR, MOD_WATER);
145 }
146 }
147 } else {
148 ent->client->airOutTime = level.time + 12000;
149 ent->damage = 2;
150 }
151
152 //
153 // check for sizzle damage (move to pmove?)
154 //
155 if (waterlevel &&
156 (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
157 if (ent->health > 0
158 && ent->pain_debounce_time <= level.time ) {
159
160 if ( envirosuit ) {
161 G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 );
162 } else {
163 if (ent->watertype & CONTENTS_LAVA) {
164 G_Damage (ent, NULL, NULL, NULL, NULL,
165 30*waterlevel, 0, MOD_LAVA);
166 }
167
168 if (ent->watertype & CONTENTS_SLIME) {
169 G_Damage (ent, NULL, NULL, NULL, NULL,
170 10*waterlevel, 0, MOD_SLIME);
171 }
172 }
173 }
174 }
175 }
176
177
178
179 /*
180 ===============
181 G_SetClientSound
182 ===============
183 */
G_SetClientSound(gentity_t * ent)184 void G_SetClientSound( gentity_t *ent ) {
185 #ifdef MISSIONPACK
186 if( ent->s.eFlags & EF_TICKING ) {
187 ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav");
188 }
189 else
190 #endif
191 if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) {
192 ent->client->ps.loopSound = level.snd_fry;
193 } else {
194 ent->client->ps.loopSound = 0;
195 }
196 }
197
198
199
200 //==============================================================
201
202 /*
203 ==============
204 ClientImpacts
205 ==============
206 */
ClientImpacts(gentity_t * ent,pmove_t * pm)207 void ClientImpacts( gentity_t *ent, pmove_t *pm ) {
208 int i, j;
209 trace_t trace;
210 gentity_t *other;
211
212 memset( &trace, 0, sizeof( trace ) );
213 for (i=0 ; i<pm->numtouch ; i++) {
214 for (j=0 ; j<i ; j++) {
215 if (pm->touchents[j] == pm->touchents[i] ) {
216 break;
217 }
218 }
219 if (j != i) {
220 continue; // duplicated
221 }
222 other = &g_entities[ pm->touchents[i] ];
223
224 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
225 ent->touch( ent, other, &trace );
226 }
227
228 if ( !other->touch ) {
229 continue;
230 }
231
232 other->touch( other, ent, &trace );
233 }
234
235 }
236
237 /*
238 ============
239 G_TouchTriggers
240
241 Find all trigger entities that ent's current position touches.
242 Spectators will only interact with teleporters.
243 ============
244 */
G_TouchTriggers(gentity_t * ent)245 void G_TouchTriggers( gentity_t *ent ) {
246 int i, num;
247 int touch[MAX_GENTITIES];
248 gentity_t *hit;
249 trace_t trace;
250 vec3_t mins, maxs;
251 static vec3_t range = { 40, 40, 52 };
252
253 if ( !ent->client ) {
254 return;
255 }
256
257 // dead clients don't activate triggers!
258 if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) {
259 return;
260 }
261
262 VectorSubtract( ent->client->ps.origin, range, mins );
263 VectorAdd( ent->client->ps.origin, range, maxs );
264
265 num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES );
266
267 // can't use ent->absmin, because that has a one unit pad
268 VectorAdd( ent->client->ps.origin, ent->r.mins, mins );
269 VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs );
270
271 for ( i=0 ; i<num ; i++ ) {
272 hit = &g_entities[touch[i]];
273
274 if ( !hit->touch && !ent->touch ) {
275 continue;
276 }
277 if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) {
278 continue;
279 }
280
281 // ignore most entities if a spectator
282 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
283 if ( hit->s.eType != ET_TELEPORT_TRIGGER &&
284 // this is ugly but adding a new ET_? type will
285 // most likely cause network incompatibilities
286 hit->touch != Touch_DoorTrigger) {
287 continue;
288 }
289 }
290
291 // use seperate code for determining if an item is picked up
292 // so you don't have to actually contact its bounding box
293 if ( hit->s.eType == ET_ITEM ) {
294 if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) {
295 continue;
296 }
297 } else {
298 if ( !trap_EntityContact( mins, maxs, hit ) ) {
299 continue;
300 }
301 }
302
303 memset( &trace, 0, sizeof(trace) );
304
305 if ( hit->touch ) {
306 hit->touch (hit, ent, &trace);
307 }
308
309 if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) {
310 ent->touch( ent, hit, &trace );
311 }
312 }
313
314 // if we didn't touch a jump pad this pmove frame
315 if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) {
316 ent->client->ps.jumppad_frame = 0;
317 ent->client->ps.jumppad_ent = 0;
318 }
319 }
320
321 /*
322 =================
323 SpectatorThink
324 =================
325 */
SpectatorThink(gentity_t * ent,usercmd_t * ucmd)326 void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) {
327 pmove_t pm;
328 gclient_t *client;
329
330 client = ent->client;
331
332 if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) {
333 client->ps.pm_type = PM_SPECTATOR;
334 client->ps.speed = 400; // faster than normal
335
336 // set up for pmove
337 memset (&pm, 0, sizeof(pm));
338 pm.ps = &client->ps;
339 pm.cmd = *ucmd;
340 pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies
341 pm.trace = trap_Trace;
342 pm.pointcontents = trap_PointContents;
343
344 // perform a pmove
345 Pmove (&pm);
346 // save results of pmove
347 VectorCopy( client->ps.origin, ent->s.origin );
348
349 G_TouchTriggers( ent );
350 trap_UnlinkEntity( ent );
351 }
352
353 client->oldbuttons = client->buttons;
354 client->buttons = ucmd->buttons;
355
356 // attack button cycles through spectators
357 if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) {
358 Cmd_FollowCycle_f( ent, 1 );
359 }
360 }
361
362
363
364 /*
365 =================
366 ClientInactivityTimer
367
368 Returns qfalse if the client is dropped
369 =================
370 */
ClientInactivityTimer(gclient_t * client)371 qboolean ClientInactivityTimer( gclient_t *client ) {
372 if ( ! g_inactivity.integer ) {
373 // give everyone some time, so if the operator sets g_inactivity during
374 // gameplay, everyone isn't kicked
375 client->inactivityTime = level.time + 60 * 1000;
376 client->inactivityWarning = qfalse;
377 } else if ( client->pers.cmd.forwardmove ||
378 client->pers.cmd.rightmove ||
379 client->pers.cmd.upmove ||
380 (client->pers.cmd.buttons & BUTTON_ATTACK) ) {
381 client->inactivityTime = level.time + g_inactivity.integer * 1000;
382 client->inactivityWarning = qfalse;
383 } else if ( !client->pers.localClient ) {
384 if ( level.time > client->inactivityTime ) {
385 trap_DropClient( client - level.clients, "Dropped due to inactivity" );
386 return qfalse;
387 }
388 if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) {
389 client->inactivityWarning = qtrue;
390 trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" );
391 }
392 }
393 return qtrue;
394 }
395
396 /*
397 ==================
398 ClientTimerActions
399
400 Actions that happen once a second
401 ==================
402 */
ClientTimerActions(gentity_t * ent,int msec)403 void ClientTimerActions( gentity_t *ent, int msec ) {
404 gclient_t *client;
405 #ifdef MISSIONPACK
406 int maxHealth;
407 #endif
408
409 client = ent->client;
410 client->timeResidual += msec;
411
412 while ( client->timeResidual >= 1000 ) {
413 client->timeResidual -= 1000;
414
415 // regenerate
416 #ifdef MISSIONPACK
417 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
418 maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2;
419 }
420 else if ( client->ps.powerups[PW_REGEN] ) {
421 maxHealth = client->ps.stats[STAT_MAX_HEALTH];
422 }
423 else {
424 maxHealth = 0;
425 }
426 if( maxHealth ) {
427 if ( ent->health < maxHealth ) {
428 ent->health += 15;
429 if ( ent->health > maxHealth * 1.1 ) {
430 ent->health = maxHealth * 1.1;
431 }
432 G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
433 } else if ( ent->health < maxHealth * 2) {
434 ent->health += 5;
435 if ( ent->health > maxHealth * 2 ) {
436 ent->health = maxHealth * 2;
437 }
438 G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
439 }
440 #else
441 if ( client->ps.powerups[PW_REGEN] ) {
442 if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) {
443 ent->health += 15;
444 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) {
445 ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1;
446 }
447 G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
448 } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) {
449 ent->health += 5;
450 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) {
451 ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2;
452 }
453 G_AddEvent( ent, EV_POWERUP_REGEN, 0 );
454 }
455 #endif
456 } else {
457 // count down health when over max
458 if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) {
459 ent->health--;
460 }
461 }
462
463 // count down armor when over max
464 if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) {
465 client->ps.stats[STAT_ARMOR]--;
466 }
467 }
468 #ifdef MISSIONPACK
469 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
470 int w, max, inc, t, i;
471 int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN};
472 int weapCount = sizeof(weapList) / sizeof(int);
473 //
474 for (i = 0; i < weapCount; i++) {
475 w = weapList[i];
476
477 switch(w) {
478 case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break;
479 case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break;
480 case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break;
481 case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break;
482 case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break;
483 case WP_RAILGUN: max = 10; inc = 1; t = 1750; break;
484 case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break;
485 case WP_BFG: max = 10; inc = 1; t = 4000; break;
486 case WP_NAILGUN: max = 10; inc = 1; t = 1250; break;
487 case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break;
488 case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break;
489 default: max = 0; inc = 0; t = 1000; break;
490 }
491 client->ammoTimes[w] += msec;
492 if ( client->ps.ammo[w] >= max ) {
493 client->ammoTimes[w] = 0;
494 }
495 if ( client->ammoTimes[w] >= t ) {
496 while ( client->ammoTimes[w] >= t )
497 client->ammoTimes[w] -= t;
498 client->ps.ammo[w] += inc;
499 if ( client->ps.ammo[w] > max ) {
500 client->ps.ammo[w] = max;
501 }
502 }
503 }
504 }
505 #endif
506 }
507
508 /*
509 ====================
510 ClientIntermissionThink
511 ====================
512 */
513 void ClientIntermissionThink( gclient_t *client ) {
514 client->ps.eFlags &= ~EF_TALK;
515 client->ps.eFlags &= ~EF_FIRING;
516
517 // the level will exit when everyone wants to or after timeouts
518
519 // swap and latch button actions
520 client->oldbuttons = client->buttons;
521 client->buttons = client->pers.cmd.buttons;
522 if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) {
523 // this used to be an ^1 but once a player says ready, it should stick
524 client->readyToExit = 1;
525 }
526 }
527
528
529 /*
530 ================
531 ClientEvents
532
533 Events will be passed on to the clients for presentation,
534 but any server game effects are handled here
535 ================
536 */
537 void ClientEvents( gentity_t *ent, int oldEventSequence ) {
538 int i, j;
539 int event;
540 gclient_t *client;
541 int damage;
542 vec3_t dir;
543 vec3_t origin, angles;
544 // qboolean fired;
545 gitem_t *item;
546 gentity_t *drop;
547
548 client = ent->client;
549
550 if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) {
551 oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS;
552 }
553 for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) {
554 event = client->ps.events[ i & (MAX_PS_EVENTS-1) ];
555
556 switch ( event ) {
557 case EV_FALL_MEDIUM:
558 case EV_FALL_FAR:
559 if ( ent->s.eType != ET_PLAYER ) {
560 break; // not in the player model
561 }
562 if ( g_dmflags.integer & DF_NO_FALLING ) {
563 break;
564 }
565 if ( event == EV_FALL_FAR ) {
566 damage = 10;
567 } else {
568 damage = 5;
569 }
570 VectorSet (dir, 0, 0, 1);
571 ent->pain_debounce_time = level.time + 200; // no normal pain sound
572 G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING);
573 break;
574
575 case EV_FIRE_WEAPON:
576 FireWeapon( ent );
577 break;
578
579 case EV_USE_ITEM1: // teleporter
580 // drop flags in CTF
581 item = NULL;
582 j = 0;
583
584 if ( ent->client->ps.powerups[ PW_REDFLAG ] ) {
585 item = BG_FindItemForPowerup( PW_REDFLAG );
586 j = PW_REDFLAG;
587 } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) {
588 item = BG_FindItemForPowerup( PW_BLUEFLAG );
589 j = PW_BLUEFLAG;
590 } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) {
591 item = BG_FindItemForPowerup( PW_NEUTRALFLAG );
592 j = PW_NEUTRALFLAG;
593 }
594
595 if ( item ) {
596 drop = Drop_Item( ent, item, 0 );
597 // decide how many seconds it has left
598 drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000;
599 if ( drop->count < 1 ) {
600 drop->count = 1;
601 }
602
603 ent->client->ps.powerups[ j ] = 0;
604 }
605
606 #ifdef MISSIONPACK
607 if ( g_gametype.integer == GT_HARVESTER ) {
608 if ( ent->client->ps.generic1 > 0 ) {
609 if ( ent->client->sess.sessionTeam == TEAM_RED ) {
610 item = BG_FindItem( "Blue Cube" );
611 } else {
612 item = BG_FindItem( "Red Cube" );
613 }
614 if ( item ) {
615 for ( j = 0; j < ent->client->ps.generic1; j++ ) {
616 drop = Drop_Item( ent, item, 0 );
617 if ( ent->client->sess.sessionTeam == TEAM_RED ) {
618 drop->spawnflags = TEAM_BLUE;
619 } else {
620 drop->spawnflags = TEAM_RED;
621 }
622 }
623 }
624 ent->client->ps.generic1 = 0;
625 }
626 }
627 #endif
628 SelectSpawnPoint( ent->client->ps.origin, origin, angles );
629 TeleportPlayer( ent, origin, angles );
630 break;
631
632 case EV_USE_ITEM2: // medkit
633 ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25;
634
635 break;
636
637 #ifdef MISSIONPACK
638 case EV_USE_ITEM3: // kamikaze
639 // make sure the invulnerability is off
640 ent->client->invulnerabilityTime = 0;
641 // start the kamikze
642 G_StartKamikaze( ent );
643 break;
644
645 case EV_USE_ITEM4: // portal
646 if( ent->client->portalID ) {
647 DropPortalSource( ent );
648 }
649 else {
650 DropPortalDestination( ent );
651 }
652 break;
653 case EV_USE_ITEM5: // invulnerability
654 ent->client->invulnerabilityTime = level.time + 10000;
655 break;
656 #endif
657
658 default:
659 break;
660 }
661 }
662
663 }
664
665 #ifdef MISSIONPACK
666 /*
667 ==============
668 StuckInOtherClient
669 ==============
670 */
671 static int StuckInOtherClient(gentity_t *ent) {
672 int i;
673 gentity_t *ent2;
674
675 ent2 = &g_entities[0];
676 for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) {
677 if ( ent2 == ent ) {
678 continue;
679 }
680 if ( !ent2->inuse ) {
681 continue;
682 }
683 if ( !ent2->client ) {
684 continue;
685 }
686 if ( ent2->health <= 0 ) {
687 continue;
688 }
689 //
690 if (ent2->r.absmin[0] > ent->r.absmax[0])
691 continue;
692 if (ent2->r.absmin[1] > ent->r.absmax[1])
693 continue;
694 if (ent2->r.absmin[2] > ent->r.absmax[2])
695 continue;
696 if (ent2->r.absmax[0] < ent->r.absmin[0])
697 continue;
698 if (ent2->r.absmax[1] < ent->r.absmin[1])
699 continue;
700 if (ent2->r.absmax[2] < ent->r.absmin[2])
701 continue;
702 return qtrue;
703 }
704 return qfalse;
705 }
706 #endif
707
708 void BotTestSolid(vec3_t origin);
709
710 /*
711 ==============
712 SendPendingPredictableEvents
713 ==============
714 */
715 void SendPendingPredictableEvents( playerState_t *ps ) {
716 gentity_t *t;
717 int event, seq;
718 int extEvent, number;
719
720 // if there are still events pending
721 if ( ps->entityEventSequence < ps->eventSequence ) {
722 // create a temporary entity for this event which is sent to everyone
723 // except the client who generated the event
724 seq = ps->entityEventSequence & (MAX_PS_EVENTS-1);
725 event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 );
726 // set external event to zero before calling BG_PlayerStateToEntityState
727 extEvent = ps->externalEvent;
728 ps->externalEvent = 0;
729 // create temporary entity for event
730 t = G_TempEntity( ps->origin, event );
731 number = t->s.number;
732 BG_PlayerStateToEntityState( ps, &t->s, qtrue );
733 t->s.number = number;
734 t->s.eType = ET_EVENTS + event;
735 t->s.eFlags |= EF_PLAYER_EVENT;
736 t->s.otherEntityNum = ps->clientNum;
737 // send to everyone except the client who generated the event
738 t->r.svFlags |= SVF_NOTSINGLECLIENT;
739 t->r.singleClient = ps->clientNum;
740 // set back external event
741 ps->externalEvent = extEvent;
742 }
743 }
744
745 /*
746 ==============
747 ClientThink
748
749 This will be called once for each client frame, which will
750 usually be a couple times for each server frame on fast clients.
751
752 If "g_synchronousClients 1" is set, this will be called exactly
753 once for each server frame, which makes for smooth demo recording.
754 ==============
755 */
756 void ClientThink_real( gentity_t *ent ) {
757 gclient_t *client;
758 pmove_t pm;
759 int oldEventSequence;
760 int msec;
761 usercmd_t *ucmd;
762
763 client = ent->client;
764
765 // don't think if the client is not yet connected (and thus not yet spawned in)
766 if (client->pers.connected != CON_CONNECTED) {
767 return;
768 }
769 // mark the time, so the connection sprite can be removed
770 ucmd = &ent->client->pers.cmd;
771
772 // sanity check the command time to prevent speedup cheating
773 if ( ucmd->serverTime > level.time + 200 ) {
774 ucmd->serverTime = level.time + 200;
775 // G_Printf("serverTime <<<<<\n" );
776 }
777 if ( ucmd->serverTime < level.time - 1000 ) {
778 ucmd->serverTime = level.time - 1000;
779 // G_Printf("serverTime >>>>>\n" );
780 }
781
782 msec = ucmd->serverTime - client->ps.commandTime;
783 // following others may result in bad times, but we still want
784 // to check for follow toggles
785 if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) {
786 return;
787 }
788 if ( msec > 200 ) {
789 msec = 200;
790 }
791
792 if ( pmove_msec.integer < 8 ) {
793 trap_Cvar_Set("pmove_msec", "8");
794 }
795 else if (pmove_msec.integer > 33) {
796 trap_Cvar_Set("pmove_msec", "33");
797 }
798
799 if ( pmove_fixed.integer || client->pers.pmoveFixed ) {
800 ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer;
801 //if (ucmd->serverTime - client->ps.commandTime <= 0)
802 // return;
803 }
804
805 //
806 // check for exiting intermission
807 //
808 if ( level.intermissiontime ) {
809 ClientIntermissionThink( client );
810 return;
811 }
812
813 // spectators don't do much
814 if ( client->sess.sessionTeam == TEAM_SPECTATOR ) {
815 if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
816 return;
817 }
818 SpectatorThink( ent, ucmd );
819 return;
820 }
821
822 // check for inactivity timer, but never drop the local client of a non-dedicated server
823 if ( !ClientInactivityTimer( client ) ) {
824 return;
825 }
826
827 // clear the rewards if time
828 if ( level.time > client->rewardTime ) {
829 client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_ASSIST | EF_AWARD_DEFEND | EF_AWARD_CAP );
830 }
831
832 if ( client->noclip ) {
833 client->ps.pm_type = PM_NOCLIP;
834 } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
835 client->ps.pm_type = PM_DEAD;
836 } else {
837 client->ps.pm_type = PM_NORMAL;
838 }
839
840 client->ps.gravity = g_gravity.value;
841
842 // set speed
843 client->ps.speed = g_speed.value;
844
845 #ifdef MISSIONPACK
846 if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
847 client->ps.speed *= 1.5;
848 }
849 else
850 #endif
851 if ( client->ps.powerups[PW_HASTE] ) {
852 client->ps.speed *= 1.3;
853 }
854
855 // Let go of the hook if we aren't firing
856 if ( client->ps.weapon == WP_GRAPPLING_HOOK &&
857 client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) {
858 Weapon_HookFree(client->hook);
859 }
860
861 // set up for pmove
862 oldEventSequence = client->ps.eventSequence;
863
864 memset (&pm, 0, sizeof(pm));
865
866 // check for the hit-scan gauntlet, don't let the action
867 // go through as an attack unless it actually hits something
868 if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) &&
869 ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) {
870 pm.gauntletHit = CheckGauntletAttack( ent );
871 }
872
873 if ( ent->flags & FL_FORCE_GESTURE ) {
874 ent->flags &= ~FL_FORCE_GESTURE;
875 ent->client->pers.cmd.buttons |= BUTTON_GESTURE;
876 }
877
878 #ifdef MISSIONPACK
879 // check for invulnerability expansion before doing the Pmove
880 if (client->ps.powerups[PW_INVULNERABILITY] ) {
881 if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) {
882 vec3_t mins = { -42, -42, -42 };
883 vec3_t maxs = { 42, 42, 42 };
884 vec3_t oldmins, oldmaxs;
885
886 VectorCopy (ent->r.mins, oldmins);
887 VectorCopy (ent->r.maxs, oldmaxs);
888 // expand
889 VectorCopy (mins, ent->r.mins);
890 VectorCopy (maxs, ent->r.maxs);
891 trap_LinkEntity(ent);
892 // check if this would get anyone stuck in this player
893 if ( !StuckInOtherClient(ent) ) {
894 // set flag so the expanded size will be set in PM_CheckDuck
895 client->ps.pm_flags |= PMF_INVULEXPAND;
896 }
897 // set back
898 VectorCopy (oldmins, ent->r.mins);
899 VectorCopy (oldmaxs, ent->r.maxs);
900 trap_LinkEntity(ent);
901 }
902 }
903 #endif
904
905 pm.ps = &client->ps;
906 pm.cmd = *ucmd;
907 if ( pm.ps->pm_type == PM_DEAD ) {
908 pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY;
909 }
910 else if ( ent->r.svFlags & SVF_BOT ) {
911 pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP;
912 }
913 else {
914 pm.tracemask = MASK_PLAYERSOLID;
915 }
916 pm.trace = trap_Trace;
917 pm.pointcontents = trap_PointContents;
918 pm.debugLevel = g_debugMove.integer;
919 pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0;
920
921 pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed;
922 pm.pmove_msec = pmove_msec.integer;
923
924 VectorCopy( client->ps.origin, client->oldOrigin );
925
926 #ifdef MISSIONPACK
927 if (level.intermissionQueued != 0 && g_singlePlayer.integer) {
928 if ( level.time - level.intermissionQueued >= 1000 ) {
929 pm.cmd.buttons = 0;
930 pm.cmd.forwardmove = 0;
931 pm.cmd.rightmove = 0;
932 pm.cmd.upmove = 0;
933 if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) {
934 trap_SendConsoleCommand( EXEC_APPEND, "centerview\n");
935 }
936 ent->client->ps.pm_type = PM_SPINTERMISSION;
937 }
938 }
939 Pmove (&pm);
940 #else
941 Pmove (&pm);
942 #endif
943
944 // save results of pmove
945 if ( ent->client->ps.eventSequence != oldEventSequence ) {
946 ent->eventTime = level.time;
947 }
948 if (g_smoothClients.integer) {
949 BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
950 }
951 else {
952 BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
953 }
954 SendPendingPredictableEvents( &ent->client->ps );
955
956 if ( !( ent->client->ps.eFlags & EF_FIRING ) ) {
957 client->fireHeld = qfalse; // for grapple
958 }
959
960 // use the snapped origin for linking so it matches client predicted versions
961 VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin );
962
963 VectorCopy (pm.mins, ent->r.mins);
964 VectorCopy (pm.maxs, ent->r.maxs);
965
966 ent->waterlevel = pm.waterlevel;
967 ent->watertype = pm.watertype;
968
969 // execute client events
970 ClientEvents( ent, oldEventSequence );
971
972 // link entity now, after any personal teleporters have been used
973 trap_LinkEntity (ent);
974 if ( !ent->client->noclip ) {
975 G_TouchTriggers( ent );
976 }
977
978 // NOTE: now copy the exact origin over otherwise clients can be snapped into solid
979 VectorCopy( ent->client->ps.origin, ent->r.currentOrigin );
980
981 //test for solid areas in the AAS file
982 BotTestAAS(ent->r.currentOrigin);
983
984 // touch other objects
985 ClientImpacts( ent, &pm );
986
987 // save results of triggers and client events
988 if (ent->client->ps.eventSequence != oldEventSequence) {
989 ent->eventTime = level.time;
990 }
991
992 // swap and latch button actions
993 client->oldbuttons = client->buttons;
994 client->buttons = ucmd->buttons;
995 client->latched_buttons |= client->buttons & ~client->oldbuttons;
996
997 // check for respawning
998 if ( client->ps.stats[STAT_HEALTH] <= 0 ) {
999 // wait for the attack button to be pressed
1000 if ( level.time > client->respawnTime ) {
1001 // forcerespawn is to prevent users from waiting out powerups
1002 if ( g_forcerespawn.integer > 0 &&
1003 ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) {
1004 respawn( ent );
1005 return;
1006 }
1007
1008 // pressing attack or use is the normal respawn method
1009 if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) {
1010 respawn( ent );
1011 }
1012 }
1013 return;
1014 }
1015
1016 // perform once-a-second actions
1017 ClientTimerActions( ent, msec );
1018 }
1019
1020 /*
1021 ==================
1022 ClientThink
1023
1024 A new command has arrived from the client
1025 ==================
1026 */
1027 void ClientThink( int clientNum ) {
1028 gentity_t *ent;
1029
1030 ent = g_entities + clientNum;
1031 trap_GetUsercmd( clientNum, &ent->client->pers.cmd );
1032
1033 // mark the time we got info, so we can display the
1034 // phone jack if they don't get any for a while
1035 ent->client->lastCmdTime = level.time;
1036
1037 if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
1038 ClientThink_real( ent );
1039 }
1040 }
1041
1042
1043 void G_RunClient( gentity_t *ent ) {
1044 if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) {
1045 return;
1046 }
1047 ent->client->pers.cmd.serverTime = level.time;
1048 ClientThink_real( ent );
1049 }
1050
1051
1052 /*
1053 ==================
1054 SpectatorClientEndFrame
1055
1056 ==================
1057 */
1058 void SpectatorClientEndFrame( gentity_t *ent ) {
1059 gclient_t *cl;
1060
1061 // if we are doing a chase cam or a remote view, grab the latest info
1062 if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) {
1063 int clientNum, flags;
1064
1065 clientNum = ent->client->sess.spectatorClient;
1066
1067 // team follow1 and team follow2 go to whatever clients are playing
1068 if ( clientNum == -1 ) {
1069 clientNum = level.follow1;
1070 } else if ( clientNum == -2 ) {
1071 clientNum = level.follow2;
1072 }
1073 if ( clientNum >= 0 ) {
1074 cl = &level.clients[ clientNum ];
1075 if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) {
1076 flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED));
1077 ent->client->ps = cl->ps;
1078 ent->client->ps.pm_flags |= PMF_FOLLOW;
1079 ent->client->ps.eFlags = flags;
1080 return;
1081 } else {
1082 // drop them to free spectators unless they are dedicated camera followers
1083 if ( ent->client->sess.spectatorClient >= 0 ) {
1084 ent->client->sess.spectatorState = SPECTATOR_FREE;
1085 ClientBegin( ent->client - level.clients );
1086 }
1087 }
1088 }
1089 }
1090
1091 if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) {
1092 ent->client->ps.pm_flags |= PMF_SCOREBOARD;
1093 } else {
1094 ent->client->ps.pm_flags &= ~PMF_SCOREBOARD;
1095 }
1096 }
1097
1098 /*
1099 ==============
1100 ClientEndFrame
1101
1102 Called at the end of each server frame for each connected client
1103 A fast client will have multiple ClientThink for each ClientEdFrame,
1104 while a slow client may have multiple ClientEndFrame between ClientThink.
1105 ==============
1106 */
1107 void ClientEndFrame( gentity_t *ent ) {
1108 int i;
1109 clientPersistant_t *pers;
1110
1111 if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) {
1112 SpectatorClientEndFrame( ent );
1113 return;
1114 }
1115
1116 pers = &ent->client->pers;
1117
1118 // turn off any expired powerups
1119 for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
1120 if ( ent->client->ps.powerups[ i ] < level.time ) {
1121 ent->client->ps.powerups[ i ] = 0;
1122 }
1123 }
1124
1125 #ifdef MISSIONPACK
1126 // set powerup for player animation
1127 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) {
1128 ent->client->ps.powerups[PW_GUARD] = level.time;
1129 }
1130 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) {
1131 ent->client->ps.powerups[PW_SCOUT] = level.time;
1132 }
1133 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) {
1134 ent->client->ps.powerups[PW_DOUBLER] = level.time;
1135 }
1136 if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) {
1137 ent->client->ps.powerups[PW_AMMOREGEN] = level.time;
1138 }
1139 if ( ent->client->invulnerabilityTime > level.time ) {
1140 ent->client->ps.powerups[PW_INVULNERABILITY] = level.time;
1141 }
1142 #endif
1143
1144 // save network bandwidth
1145 #if 0
1146 if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) {
1147 // FIXME: this must change eventually for non-sync demo recording
1148 VectorClear( ent->client->ps.viewangles );
1149 }
1150 #endif
1151
1152 //
1153 // If the end of unit layout is displayed, don't give
1154 // the player any normal movement attributes
1155 //
1156 if ( level.intermissiontime ) {
1157 return;
1158 }
1159
1160 // burn from lava, etc
1161 P_WorldEffects (ent);
1162
1163 // apply all the damage taken this frame
1164 P_DamageFeedback (ent);
1165
1166 // add the EF_CONNECTION flag if we haven't gotten commands recently
1167 if ( level.time - ent->client->lastCmdTime > 1000 ) {
1168 ent->s.eFlags |= EF_CONNECTION;
1169 } else {
1170 ent->s.eFlags &= ~EF_CONNECTION;
1171 }
1172
1173 ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health...
1174
1175 G_SetClientSound (ent);
1176
1177 // set the latest infor
1178 if (g_smoothClients.integer) {
1179 BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue );
1180 }
1181 else {
1182 BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue );
1183 }
1184 SendPendingPredictableEvents( &ent->client->ps );
1185
1186 // set the bit for the reachability area the client is currently in
1187 // i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin );
1188 // ent->client->areabits[i >> 3] |= 1 << (i & 7);
1189 }
1190
1191
1192