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