Quake-III-Arena

Quake III Arena GPL Source Release
Log | Files | Refs

g_active.c (31488B)


      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 Foobar; 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 */
     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 */
     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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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