1 /*
2 ===========================================================================
3 
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
6 
7 This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
8 
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13 
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
21 
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
23 
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25 
26 ===========================================================================
27 */
28 
29 #include "sys/platform.h"
30 #include "idlib/LangDict.h"
31 #include "framework/async/NetworkSystem.h"
32 #include "framework/DeclEntityDef.h"
33 #include "renderer/RenderSystem.h"
34 
35 #include "gamesys/SysCvar.h"
36 #include "script/Script_Thread.h"
37 #include "ai/AI.h"
38 #include "WorldSpawn.h"
39 #include "Player.h"
40 #include "Camera.h"
41 #include "Fx.h"
42 #include "Misc.h"
43 
44 const int ASYNC_PLAYER_INV_AMMO_BITS = idMath::BitsForInteger( 999 );	// 9 bits to cover the range [0, 999]
45 const int ASYNC_PLAYER_INV_CLIP_BITS = -7;								// -7 bits to cover the range [-1, 60]
46 
47 /*
48 ===============================================================================
49 
50 	Player control of the Doom Marine.
51 	This object handles all player movement and world interaction.
52 
53 ===============================================================================
54 */
55 
56 // distance between ladder rungs (actually is half that distance, but this sounds better)
57 const int LADDER_RUNG_DISTANCE = 32;
58 
59 // amount of health per dose from the health station
60 const int HEALTH_PER_DOSE = 10;
61 
62 // time before a weapon dropped to the floor disappears
63 const int WEAPON_DROP_TIME = 20 * 1000;
64 
65 // time before a next or prev weapon switch happens
66 const int WEAPON_SWITCH_DELAY = 150;
67 
68 // how many units to raise spectator above default view height so it's in the head of someone
69 const int SPECTATE_RAISE = 25;
70 
71 const int HEALTHPULSE_TIME = 333;
72 
73 // minimum speed to bob and play run/walk animations at
74 const float MIN_BOB_SPEED = 5.0f;
75 
76 const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' );
77 const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' );
78 const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' );
79 const idEventDef EV_Player_StopFxFov( "stopFxFov" );
80 const idEventDef EV_Player_EnableWeapon( "enableWeapon" );
81 const idEventDef EV_Player_DisableWeapon( "disableWeapon" );
82 const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' );
83 const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' );
84 const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" );
85 const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' );
86 const idEventDef EV_Player_OpenPDA( "openPDA" );
87 const idEventDef EV_Player_InPDA( "inPDA", NULL, 'd' );
88 const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" );
89 const idEventDef EV_Player_StopAudioLog( "stopAudioLog" );
90 const idEventDef EV_Player_HideTip( "hideTip" );
91 const idEventDef EV_Player_LevelTrigger( "levelTrigger" );
92 const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" );
93 const idEventDef EV_Player_GetIdealWeapon( "getIdealWeapon", NULL, 's' );
94 
95 CLASS_DECLARATION( idActor, idPlayer )
96 	EVENT( EV_Player_GetButtons,			idPlayer::Event_GetButtons )
97 	EVENT( EV_Player_GetMove,				idPlayer::Event_GetMove )
98 	EVENT( EV_Player_GetViewAngles,			idPlayer::Event_GetViewAngles )
99 	EVENT( EV_Player_StopFxFov,				idPlayer::Event_StopFxFov )
100 	EVENT( EV_Player_EnableWeapon,			idPlayer::Event_EnableWeapon )
101 	EVENT( EV_Player_DisableWeapon,			idPlayer::Event_DisableWeapon )
102 	EVENT( EV_Player_GetCurrentWeapon,		idPlayer::Event_GetCurrentWeapon )
103 	EVENT( EV_Player_GetPreviousWeapon,		idPlayer::Event_GetPreviousWeapon )
104 	EVENT( EV_Player_SelectWeapon,			idPlayer::Event_SelectWeapon )
105 	EVENT( EV_Player_GetWeaponEntity,		idPlayer::Event_GetWeaponEntity )
106 	EVENT( EV_Player_OpenPDA,				idPlayer::Event_OpenPDA )
107 	EVENT( EV_Player_InPDA,					idPlayer::Event_InPDA )
108 	EVENT( EV_Player_ExitTeleporter,		idPlayer::Event_ExitTeleporter )
109 	EVENT( EV_Player_StopAudioLog,			idPlayer::Event_StopAudioLog )
110 	EVENT( EV_Player_HideTip,				idPlayer::Event_HideTip )
111 	EVENT( EV_Player_LevelTrigger,			idPlayer::Event_LevelTrigger )
112 	EVENT( EV_Gibbed,						idPlayer::Event_Gibbed )
113 	EVENT( EV_Player_GetIdealWeapon,		idPlayer::Event_GetIdealWeapon )
114 END_CLASS
115 
116 const int MAX_RESPAWN_TIME = 10000;
117 const int RAGDOLL_DEATH_TIME = 3000;
118 const int MAX_PDAS = 64;
119 const int MAX_PDA_ITEMS = 128;
120 const int STEPUP_TIME = 200;
121 const int MAX_INVENTORY_ITEMS = 20;
122 
123 idVec3 idPlayer::colorBarTable[ 5 ] = {
124 	idVec3( 0.25f, 0.25f, 0.25f ),
125 	idVec3( 1.00f, 0.00f, 0.00f ),
126 	idVec3( 0.00f, 0.80f, 0.10f ),
127 	idVec3( 0.20f, 0.50f, 0.80f ),
128 	idVec3( 1.00f, 0.80f, 0.10f )
129 };
130 
131 /*
132 ==============
133 idInventory::Clear
134 ==============
135 */
Clear(void)136 void idInventory::Clear( void ) {
137 	maxHealth		= 0;
138 	weapons			= 0;
139 	powerups		= 0;
140 	armor			= 0;
141 	maxarmor		= 0;
142 	deplete_armor	= 0;
143 	deplete_rate	= 0.0f;
144 	deplete_ammount	= 0;
145 	nextArmorDepleteTime = 0;
146 
147 	memset( ammo, 0, sizeof( ammo ) );
148 
149 	ClearPowerUps();
150 
151 	// set to -1 so that the gun knows to have a full clip the first time we get it and at the start of the level
152 	memset( clip, -1, sizeof( clip ) );
153 
154 	items.DeleteContents( true );
155 	memset(pdasViewed, 0, 4 * sizeof( pdasViewed[0] ) );
156 	pdas.Clear();
157 	videos.Clear();
158 	emails.Clear();
159 	selVideo = 0;
160 	selEMail = 0;
161 	selPDA = 0;
162 	selAudio = 0;
163 	pdaOpened = false;
164 	turkeyScore = false;
165 
166 	levelTriggers.Clear();
167 
168 	nextItemPickup = 0;
169 	nextItemNum = 1;
170 	onePickupTime = 0;
171 	pickupItemNames.Clear();
172 	objectiveNames.Clear();
173 
174 	ammoPredictTime = 0;
175 
176 	lastGiveTime = 0;
177 
178 	ammoPulse	= false;
179 	weaponPulse	= false;
180 	armorPulse	= false;
181 }
182 
183 /*
184 ==============
185 idInventory::GivePowerUp
186 ==============
187 */
GivePowerUp(idPlayer * player,int powerup,int msec)188 void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) {
189 	if ( !msec ) {
190 		// get the duration from the .def files
191 		const idDeclEntityDef *def = NULL;
192 		switch ( powerup ) {
193 			case BERSERK:
194 				def = gameLocal.FindEntityDef( "powerup_berserk", false );
195 				break;
196 			case INVISIBILITY:
197 				def = gameLocal.FindEntityDef( "powerup_invisibility", false );
198 				break;
199 			case MEGAHEALTH:
200 				def = gameLocal.FindEntityDef( "powerup_megahealth", false );
201 				break;
202 			case ADRENALINE:
203 				def = gameLocal.FindEntityDef( "powerup_adrenaline", false );
204 				break;
205 		}
206 		assert( def );
207 		msec = def->dict.GetInt( "time" ) * 1000;
208 	}
209 	powerups |= 1 << powerup;
210 	powerupEndTime[ powerup ] = gameLocal.time + msec;
211 }
212 
213 /*
214 ==============
215 idInventory::ClearPowerUps
216 ==============
217 */
ClearPowerUps(void)218 void idInventory::ClearPowerUps( void ) {
219 	int i;
220 	for ( i = 0; i < MAX_POWERUPS; i++ ) {
221 		powerupEndTime[ i ] = 0;
222 	}
223 	powerups = 0;
224 }
225 
226 /*
227 ==============
228 idInventory::GetPersistantData
229 ==============
230 */
GetPersistantData(idDict & dict)231 void idInventory::GetPersistantData( idDict &dict ) {
232 	int		i;
233 	int		num;
234 	idDict	*item;
235 	idStr	key;
236 	const idKeyValue *kv;
237 	const char *name;
238 
239 	// armor
240 	dict.SetInt( "armor", armor );
241 
242 	// don't bother with powerups, maxhealth, maxarmor, or the clip
243 
244 	// ammo
245 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
246 		name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
247 		if ( name ) {
248 			dict.SetInt( name, ammo[ i ] );
249 		}
250 	}
251 
252 	// items
253 	num = 0;
254 	for( i = 0; i < items.Num(); i++ ) {
255 		item = items[ i ];
256 
257 		// copy all keys with "inv_"
258 		kv = item->MatchPrefix( "inv_" );
259 		if ( kv ) {
260 			while( kv ) {
261 				sprintf( key, "item_%i %s", num, kv->GetKey().c_str() );
262 				dict.Set( key, kv->GetValue() );
263 				kv = item->MatchPrefix( "inv_", kv );
264 			}
265 			num++;
266 		}
267 	}
268 	dict.SetInt( "items", num );
269 
270 	// pdas viewed
271 	for ( i = 0; i < 4; i++ ) {
272 		dict.SetInt( va("pdasViewed_%i", i), pdasViewed[i] );
273 	}
274 
275 	dict.SetInt( "selPDA", selPDA );
276 	dict.SetInt( "selVideo", selVideo );
277 	dict.SetInt( "selEmail", selEMail );
278 	dict.SetInt( "selAudio", selAudio );
279 	dict.SetInt( "pdaOpened", pdaOpened );
280 	dict.SetInt( "turkeyScore", turkeyScore );
281 
282 	// pdas
283 	for ( i = 0; i < pdas.Num(); i++ ) {
284 		sprintf( key, "pda_%i", i );
285 		dict.Set( key, pdas[ i ] );
286 	}
287 	dict.SetInt( "pdas", pdas.Num() );
288 
289 	// video cds
290 	for ( i = 0; i < videos.Num(); i++ ) {
291 		sprintf( key, "video_%i", i );
292 		dict.Set( key, videos[ i ].c_str() );
293 	}
294 	dict.SetInt( "videos", videos.Num() );
295 
296 	// emails
297 	for ( i = 0; i < emails.Num(); i++ ) {
298 		sprintf( key, "email_%i", i );
299 		dict.Set( key, emails[ i ].c_str() );
300 	}
301 	dict.SetInt( "emails", emails.Num() );
302 
303 	// weapons
304 	dict.SetInt( "weapon_bits", weapons );
305 
306 	dict.SetInt( "levelTriggers", levelTriggers.Num() );
307 	for ( i = 0; i < levelTriggers.Num(); i++ ) {
308 		sprintf( key, "levelTrigger_Level_%i", i );
309 		dict.Set( key, levelTriggers[i].levelName );
310 		sprintf( key, "levelTrigger_Trigger_%i", i );
311 		dict.Set( key, levelTriggers[i].triggerName );
312 	}
313 }
314 
315 /*
316 ==============
317 idInventory::RestoreInventory
318 ==============
319 */
RestoreInventory(idPlayer * owner,const idDict & dict)320 void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) {
321 	int			i;
322 	int			num;
323 	idDict		*item;
324 	idStr		key;
325 	idStr		itemname;
326 	const idKeyValue *kv;
327 	const char	*name;
328 
329 	Clear();
330 
331 	// health/armor
332 	maxHealth		= dict.GetInt( "maxhealth", "100" );
333 	armor			= dict.GetInt( "armor", "50" );
334 	maxarmor		= dict.GetInt( "maxarmor", "100" );
335 	deplete_armor	= dict.GetInt( "deplete_armor", "0" );
336 	deplete_rate	= dict.GetFloat( "deplete_rate", "2.0" );
337 	deplete_ammount	= dict.GetInt( "deplete_ammount", "1" );
338 
339 	// the clip and powerups aren't restored
340 
341 	// ammo
342 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
343 		name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
344 		if ( name ) {
345 			ammo[ i ] = dict.GetInt( name );
346 		}
347 	}
348 
349 	// items
350 	num = dict.GetInt( "items" );
351 	items.SetNum( num );
352 	for( i = 0; i < num; i++ ) {
353 		item = new idDict();
354 		items[ i ] = item;
355 		sprintf( itemname, "item_%i ", i );
356 		kv = dict.MatchPrefix( itemname );
357 		while( kv ) {
358 			key = kv->GetKey();
359 			key.Strip( itemname );
360 			item->Set( key, kv->GetValue() );
361 			kv = dict.MatchPrefix( itemname, kv );
362 		}
363 	}
364 
365 	// pdas viewed
366 	for ( i = 0; i < 4; i++ ) {
367 		pdasViewed[i] = dict.GetInt(va("pdasViewed_%i", i));
368 	}
369 
370 	selPDA = dict.GetInt( "selPDA" );
371 	selEMail = dict.GetInt( "selEmail" );
372 	selVideo = dict.GetInt( "selVideo" );
373 	selAudio = dict.GetInt( "selAudio" );
374 	pdaOpened = dict.GetBool( "pdaOpened" );
375 	turkeyScore = dict.GetBool( "turkeyScore" );
376 
377 	// pdas
378 	num = dict.GetInt( "pdas" );
379 	pdas.SetNum( num );
380 	for ( i = 0; i < num; i++ ) {
381 		sprintf( itemname, "pda_%i", i );
382 		pdas[i] = dict.GetString( itemname, "default" );
383 	}
384 
385 	// videos
386 	num = dict.GetInt( "videos" );
387 	videos.SetNum( num );
388 	for ( i = 0; i < num; i++ ) {
389 		sprintf( itemname, "video_%i", i );
390 		videos[i] = dict.GetString( itemname, "default" );
391 	}
392 
393 	// emails
394 	num = dict.GetInt( "emails" );
395 	emails.SetNum( num );
396 	for ( i = 0; i < num; i++ ) {
397 		sprintf( itemname, "email_%i", i );
398 		emails[i] = dict.GetString( itemname, "default" );
399 	}
400 
401 	// weapons are stored as a number for persistant data, but as strings in the entityDef
402 	weapons	= dict.GetInt( "weapon_bits", "0" );
403 
404 	if ( g_skill.GetInteger() >= 3 ) {
405 		Give( owner, dict, "weapon", dict.GetString( "weapon_nightmare" ), NULL, false );
406 	} else {
407 		Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false );
408 	}
409 
410 	num = dict.GetInt( "levelTriggers" );
411 	for ( i = 0; i < num; i++ ) {
412 		sprintf( itemname, "levelTrigger_Level_%i", i );
413 		idLevelTriggerInfo lti;
414 		lti.levelName = dict.GetString( itemname );
415 		sprintf( itemname, "levelTrigger_Trigger_%i", i );
416 		lti.triggerName = dict.GetString( itemname );
417 		levelTriggers.Append( lti );
418 	}
419 
420 }
421 
422 /*
423 ==============
424 idInventory::Save
425 ==============
426 */
Save(idSaveGame * savefile) const427 void idInventory::Save( idSaveGame *savefile ) const {
428 	int i;
429 
430 	savefile->WriteInt( maxHealth );
431 	savefile->WriteInt( weapons );
432 	savefile->WriteInt( powerups );
433 	savefile->WriteInt( armor );
434 	savefile->WriteInt( maxarmor );
435 	savefile->WriteInt( ammoPredictTime );
436 	savefile->WriteInt( deplete_armor );
437 	savefile->WriteFloat( deplete_rate );
438 	savefile->WriteInt( deplete_ammount );
439 	savefile->WriteInt( nextArmorDepleteTime );
440 
441 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
442 		savefile->WriteInt( ammo[ i ] );
443 	}
444 	for( i = 0; i < MAX_WEAPONS; i++ ) {
445 		savefile->WriteInt( clip[ i ] );
446 	}
447 	for( i = 0; i < MAX_POWERUPS; i++ ) {
448 		savefile->WriteInt( powerupEndTime[ i ] );
449 	}
450 
451 	savefile->WriteInt( items.Num() );
452 	for( i = 0; i < items.Num(); i++ ) {
453 		savefile->WriteDict( items[ i ] );
454 	}
455 
456 	savefile->WriteInt( pdasViewed[0] );
457 	savefile->WriteInt( pdasViewed[1] );
458 	savefile->WriteInt( pdasViewed[2] );
459 	savefile->WriteInt( pdasViewed[3] );
460 
461 	savefile->WriteInt( selPDA );
462 	savefile->WriteInt( selVideo );
463 	savefile->WriteInt( selEMail );
464 	savefile->WriteInt( selAudio );
465 	savefile->WriteBool( pdaOpened );
466 	savefile->WriteBool( turkeyScore );
467 
468 	savefile->WriteInt( pdas.Num() );
469 	for( i = 0; i < pdas.Num(); i++ ) {
470 		savefile->WriteString( pdas[ i ] );
471 	}
472 
473 	savefile->WriteInt( pdaSecurity.Num() );
474 	for( i=0; i < pdaSecurity.Num(); i++ ) {
475 		savefile->WriteString( pdaSecurity[ i ] );
476 	}
477 
478 	savefile->WriteInt( videos.Num() );
479 	for( i = 0; i < videos.Num(); i++ ) {
480 		savefile->WriteString( videos[ i ] );
481 	}
482 
483 	savefile->WriteInt( emails.Num() );
484 	for ( i = 0; i < emails.Num(); i++ ) {
485 		savefile->WriteString( emails[ i ] );
486 	}
487 
488 	savefile->WriteInt( nextItemPickup );
489 	savefile->WriteInt( nextItemNum );
490 	savefile->WriteInt( onePickupTime );
491 
492 	savefile->WriteInt( pickupItemNames.Num() );
493 	for( i = 0; i < pickupItemNames.Num(); i++ ) {
494 		savefile->WriteString( pickupItemNames[i].icon );
495 		savefile->WriteString( pickupItemNames[i].name );
496 	}
497 
498 	savefile->WriteInt( objectiveNames.Num() );
499 	for( i = 0; i < objectiveNames.Num(); i++ ) {
500 		savefile->WriteString( objectiveNames[i].screenshot );
501 		savefile->WriteString( objectiveNames[i].text );
502 		savefile->WriteString( objectiveNames[i].title );
503 	}
504 
505 	savefile->WriteInt( levelTriggers.Num() );
506 	for ( i = 0; i < levelTriggers.Num(); i++ ) {
507 		savefile->WriteString( levelTriggers[i].levelName );
508 		savefile->WriteString( levelTriggers[i].triggerName );
509 	}
510 
511 	savefile->WriteBool( ammoPulse );
512 	savefile->WriteBool( weaponPulse );
513 	savefile->WriteBool( armorPulse );
514 
515 	savefile->WriteInt( lastGiveTime );
516 }
517 
518 /*
519 ==============
520 idInventory::Restore
521 ==============
522 */
Restore(idRestoreGame * savefile)523 void idInventory::Restore( idRestoreGame *savefile ) {
524 	int i, num;
525 
526 	savefile->ReadInt( maxHealth );
527 	savefile->ReadInt( weapons );
528 	savefile->ReadInt( powerups );
529 	savefile->ReadInt( armor );
530 	savefile->ReadInt( maxarmor );
531 	savefile->ReadInt( ammoPredictTime );
532 	savefile->ReadInt( deplete_armor );
533 	savefile->ReadFloat( deplete_rate );
534 	savefile->ReadInt( deplete_ammount );
535 	savefile->ReadInt( nextArmorDepleteTime );
536 
537 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
538 		savefile->ReadInt( ammo[ i ] );
539 	}
540 	for( i = 0; i < MAX_WEAPONS; i++ ) {
541 		savefile->ReadInt( clip[ i ] );
542 	}
543 	for( i = 0; i < MAX_POWERUPS; i++ ) {
544 		savefile->ReadInt( powerupEndTime[ i ] );
545 	}
546 
547 	savefile->ReadInt( num );
548 	for( i = 0; i < num; i++ ) {
549 		idDict *itemdict = new idDict;
550 
551 		savefile->ReadDict( itemdict );
552 		items.Append( itemdict );
553 	}
554 
555 	// pdas
556 	savefile->ReadInt( pdasViewed[0] );
557 	savefile->ReadInt( pdasViewed[1] );
558 	savefile->ReadInt( pdasViewed[2] );
559 	savefile->ReadInt( pdasViewed[3] );
560 
561 	savefile->ReadInt( selPDA );
562 	savefile->ReadInt( selVideo );
563 	savefile->ReadInt( selEMail );
564 	savefile->ReadInt( selAudio );
565 	savefile->ReadBool( pdaOpened );
566 	savefile->ReadBool( turkeyScore );
567 
568 	savefile->ReadInt( num );
569 	for( i = 0; i < num; i++ ) {
570 		idStr strPda;
571 		savefile->ReadString( strPda );
572 		pdas.Append( strPda );
573 	}
574 
575 	// pda security clearances
576 	savefile->ReadInt( num );
577 	for ( i = 0; i < num; i++ ) {
578 		idStr invName;
579 		savefile->ReadString( invName );
580 		pdaSecurity.Append( invName );
581 	}
582 
583 	// videos
584 	savefile->ReadInt( num );
585 	for( i = 0; i < num; i++ ) {
586 		idStr strVideo;
587 		savefile->ReadString( strVideo );
588 		videos.Append( strVideo );
589 	}
590 
591 	// email
592 	savefile->ReadInt( num );
593 	for( i = 0; i < num; i++ ) {
594 		idStr strEmail;
595 		savefile->ReadString( strEmail );
596 		emails.Append( strEmail );
597 	}
598 
599 	savefile->ReadInt( nextItemPickup );
600 	savefile->ReadInt( nextItemNum );
601 	savefile->ReadInt( onePickupTime );
602 	savefile->ReadInt( num );
603 	for( i = 0; i < num; i++ ) {
604 		idItemInfo info;
605 
606 		savefile->ReadString( info.icon );
607 		savefile->ReadString( info.name );
608 
609 		pickupItemNames.Append( info );
610 	}
611 
612 	savefile->ReadInt( num );
613 	for( i = 0; i < num; i++ ) {
614 		idObjectiveInfo obj;
615 
616 		savefile->ReadString( obj.screenshot );
617 		savefile->ReadString( obj.text );
618 		savefile->ReadString( obj.title );
619 
620 		objectiveNames.Append( obj );
621 	}
622 
623 	savefile->ReadInt( num );
624 	for ( i = 0; i < num; i++ ) {
625 		idLevelTriggerInfo lti;
626 		savefile->ReadString( lti.levelName );
627 		savefile->ReadString( lti.triggerName );
628 		levelTriggers.Append( lti );
629 	}
630 
631 	savefile->ReadBool( ammoPulse );
632 	savefile->ReadBool( weaponPulse );
633 	savefile->ReadBool( armorPulse );
634 
635 	savefile->ReadInt( lastGiveTime );
636 }
637 
638 /*
639 ==============
640 idInventory::AmmoIndexForAmmoClass
641 ==============
642 */
AmmoIndexForAmmoClass(const char * ammo_classname) const643 ammo_t idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const {
644 	return idWeapon::GetAmmoNumForName( ammo_classname );
645 }
646 
647 /*
648 ==============
649 idInventory::AmmoIndexForAmmoClass
650 ==============
651 */
MaxAmmoForAmmoClass(idPlayer * owner,const char * ammo_classname) const652 int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const {
653 	return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" );
654 }
655 
656 /*
657 ==============
658 idInventory::AmmoPickupNameForIndex
659 ==============
660 */
AmmoPickupNameForIndex(ammo_t ammonum) const661 const char *idInventory::AmmoPickupNameForIndex( ammo_t ammonum ) const {
662 	return idWeapon::GetAmmoPickupNameForNum( ammonum );
663 }
664 
665 /*
666 ==============
667 idInventory::WeaponIndexForAmmoClass
668 mapping could be prepared in the constructor
669 ==============
670 */
WeaponIndexForAmmoClass(const idDict & spawnArgs,const char * ammo_classname) const671 int idInventory::WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const {
672 	int i;
673 	const char *weapon_classname;
674 	for( i = 0; i < MAX_WEAPONS; i++ ) {
675 		weapon_classname = spawnArgs.GetString( va( "def_weapon%d", i ) );
676 		if ( !weapon_classname ) {
677 			continue;
678 		}
679 		const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
680 		if ( !decl ) {
681 			continue;
682 		}
683 		if ( !idStr::Icmp( ammo_classname, decl->dict.GetString( "ammoType" ) ) ) {
684 			return i;
685 		}
686 	}
687 	return -1;
688 }
689 
690 /*
691 ==============
692 idInventory::AmmoIndexForWeaponClass
693 ==============
694 */
AmmoIndexForWeaponClass(const char * weapon_classname,int * ammoRequired)695 ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) {
696 	const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
697 	if ( !decl ) {
698 		gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname );
699 	}
700 	if ( ammoRequired ) {
701 		*ammoRequired = decl->dict.GetInt( "ammoRequired" );
702 	}
703 	ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) );
704 	return ammo_i;
705 }
706 
707 /*
708 ==============
709 idInventory::AddPickupName
710 ==============
711 */
AddPickupName(const char * name,const char * icon)712 void idInventory::AddPickupName( const char *name, const char *icon ) {
713 	int num;
714 
715 	num = pickupItemNames.Num();
716 	if ( ( num == 0 ) || ( pickupItemNames[ num - 1 ].name.Icmp( name ) != 0 ) ) {
717 		idItemInfo &info = pickupItemNames.Alloc();
718 
719 		if ( idStr::Cmpn( name, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
720 			info.name = common->GetLanguageDict()->GetString( name );
721 		} else {
722 			info.name = name;
723 		}
724 		info.icon = icon;
725 	}
726 }
727 
728 /*
729 ==============
730 idInventory::Give
731 ==============
732 */
Give(idPlayer * owner,const idDict & spawnArgs,const char * statname,const char * value,int * idealWeapon,bool updateHud)733 bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ) {
734 	int						i;
735 	const char				*pos;
736 	const char				*end;
737 	int						len;
738 	idStr					weaponString;
739 	int						max;
740 	const idDeclEntityDef	*weaponDecl;
741 	bool					tookWeapon;
742 	int						amount;
743 	idItemInfo				info;
744 	const char				*name;
745 
746 	if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) {
747 		i = AmmoIndexForAmmoClass( statname );
748 		max = MaxAmmoForAmmoClass( owner, statname );
749 		if ( ammo[ i ] >= max ) {
750 			return false;
751 		}
752 		amount = atoi( value );
753 		if ( amount ) {
754 			ammo[ i ] += amount;
755 			if ( ( max > 0 ) && ( ammo[ i ] > max ) ) {
756 				ammo[ i ] = max;
757 			}
758 			ammoPulse = true;
759 
760 			name = AmmoPickupNameForIndex( i );
761 			if ( idStr::Length( name ) ) {
762 				AddPickupName( name, "" );
763 			}
764 		}
765 	} else if ( !idStr::Icmp( statname, "armor" ) ) {
766 		if ( armor >= maxarmor ) {
767 			return false;	// can't hold any more, so leave the item
768 		}
769 		amount = atoi( value );
770 		if ( amount ) {
771 			armor += amount;
772 			if ( armor > maxarmor ) {
773 				armor = maxarmor;
774 			}
775 			nextArmorDepleteTime = 0;
776 			armorPulse = true;
777 		}
778 	} else if ( idStr::FindText( statname, "inclip_" ) == 0 ) {
779 		i = WeaponIndexForAmmoClass( spawnArgs, statname + 7 );
780 		if ( i != -1 ) {
781 			// set, don't add. not going over the clip size limit.
782 			clip[ i ] = atoi( value );
783 		}
784 	} else if ( !idStr::Icmp( statname, "berserk" ) ) {
785 		GivePowerUp( owner, BERSERK, SEC2MS( atof( value ) ) );
786 	} else if ( !idStr::Icmp( statname, "mega" ) ) {
787 		GivePowerUp( owner, MEGAHEALTH, SEC2MS( atof( value ) ) );
788 	} else if ( !idStr::Icmp( statname, "weapon" ) ) {
789 		tookWeapon = false;
790 		for( pos = value; pos != NULL; pos = end ) {
791 			end = strchr( pos, ',' );
792 			if ( end ) {
793 				len = end - pos;
794 				end++;
795 			} else {
796 				len = strlen( pos );
797 			}
798 
799 			idStr weaponName( pos, 0, len );
800 
801 			// find the number of the matching weapon name
802 			for( i = 0; i < MAX_WEAPONS; i++ ) {
803 				if ( weaponName == spawnArgs.GetString( va( "def_weapon%d", i ) ) ) {
804 					break;
805 				}
806 			}
807 
808 			if ( i >= MAX_WEAPONS ) {
809 				gameLocal.Error( "Unknown weapon '%s'", weaponName.c_str() );
810 			}
811 
812 			// cache the media for this weapon
813 			weaponDecl = gameLocal.FindEntityDef( weaponName, false );
814 
815 			// don't pickup "no ammo" weapon types twice
816 			// not for D3 SP .. there is only one case in the game where you can get a no ammo
817 			// weapon when you might already have it, in that case it is more conistent to pick it up
818 			if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) {
819 				continue;
820 			}
821 
822 			if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) || ( weaponName == "weapon_soulcube" ) ) {
823 				if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) {
824 					if ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) && idealWeapon ) {
825 						assert( !gameLocal.isClient );
826 						*idealWeapon = i;
827 					}
828 					if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time ) {
829 						owner->hud->SetStateInt( "newWeapon", i );
830 						owner->hud->HandleNamedEvent( "newWeapon" );
831 						lastGiveTime = gameLocal.time;
832 					}
833 					weaponPulse = true;
834 					weapons |= ( 1 << i );
835 					tookWeapon = true;
836 				}
837 			}
838 		}
839 		return tookWeapon;
840 	} else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) {
841 		// ignore these as they're handled elsewhere
842 		return false;
843 	} else {
844 		// unknown item
845 		gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname );
846 		return false;
847 	}
848 
849 	return true;
850 }
851 
852 /*
853 ===============
854 idInventoy::Drop
855 ===============
856 */
Drop(const idDict & spawnArgs,const char * weapon_classname,int weapon_index)857 void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) {
858 	// remove the weapon bit
859 	// also remove the ammo associated with the weapon as we pushed it in the item
860 	assert( weapon_index != -1 || weapon_classname );
861 	if ( weapon_index == -1 ) {
862 		for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) {
863 			if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) {
864 				break;
865 			}
866 		}
867 		if ( weapon_index >= MAX_WEAPONS ) {
868 			gameLocal.Error( "Unknown weapon '%s'", weapon_classname );
869 		}
870 	} else if ( !weapon_classname ) {
871 		weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) );
872 	}
873 	weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) );
874 	ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL );
875 	if ( ammo_i ) {
876 		clip[ weapon_index ] = -1;
877 		ammo[ ammo_i ] = 0;
878 	}
879 }
880 
881 /*
882 ===============
883 idInventory::HasAmmo
884 ===============
885 */
HasAmmo(ammo_t type,int amount)886 int idInventory::HasAmmo( ammo_t type, int amount ) {
887 	if ( ( type == 0 ) || !amount ) {
888 		// always allow weapons that don't use ammo to fire
889 		return -1;
890 	}
891 
892 	// check if we have infinite ammo
893 	if ( ammo[ type ] < 0 ) {
894 		return -1;
895 	}
896 
897 	// return how many shots we can fire
898 	return ammo[ type ] / amount;
899 }
900 
901 /*
902 ===============
903 idInventory::HasAmmo
904 ===============
905 */
HasAmmo(const char * weapon_classname)906 int idInventory::HasAmmo( const char *weapon_classname ) {
907 	int ammoRequired;
908 	ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired );
909 	return HasAmmo( ammo_i, ammoRequired );
910 }
911 
912 /*
913 ===============
914 idInventory::UseAmmo
915 ===============
916 */
UseAmmo(ammo_t type,int amount)917 bool idInventory::UseAmmo( ammo_t type, int amount ) {
918 	if ( !HasAmmo( type, amount ) ) {
919 		return false;
920 	}
921 
922 	// take an ammo away if not infinite
923 	if ( ammo[ type ] >= 0 ) {
924 		ammo[ type ] -= amount;
925 		ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots
926 	}
927 
928 	return true;
929 }
930 
931 /*
932 ===============
933 idInventory::UpdateArmor
934 ===============
935 */
UpdateArmor(void)936 void idInventory::UpdateArmor( void ) {
937 	if ( deplete_armor != 0.0f && deplete_armor < armor ) {
938 		if ( !nextArmorDepleteTime ) {
939 			nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
940 		} else if ( gameLocal.time > nextArmorDepleteTime ) {
941 			armor -= deplete_ammount;
942 			if ( armor < deplete_armor ) {
943 				armor = deplete_armor;
944 			}
945 			nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
946 		}
947 	}
948 }
949 
950 /*
951 ==============
952 idPlayer::idPlayer
953 ==============
954 */
idPlayer()955 idPlayer::idPlayer() {
956 	memset( &usercmd, 0, sizeof( usercmd ) );
957 
958 	noclip					= false;
959 	godmode					= false;
960 
961 	spawnAnglesSet			= false;
962 	spawnAngles				= ang_zero;
963 	viewAngles				= ang_zero;
964 	cmdAngles				= ang_zero;
965 
966 	oldButtons				= 0;
967 	buttonMask				= 0;
968 	oldFlags				= 0;
969 
970 	lastHitTime				= 0;
971 	lastSndHitTime			= 0;
972 	lastSavingThrowTime		= 0;
973 
974 	weapon					= NULL;
975 
976 	hud						= NULL;
977 	objectiveSystem			= NULL;
978 	objectiveSystemOpen		= false;
979 
980 	heartRate				= BASE_HEARTRATE;
981 	heartInfo.Init( 0, 0, 0, 0 );
982 	lastHeartAdjust			= 0;
983 	lastHeartBeat			= 0;
984 	lastDmgTime				= 0;
985 	deathClearContentsTime	= 0;
986 	lastArmorPulse			= -10000;
987 	stamina					= 0.0f;
988 	healthPool				= 0.0f;
989 	nextHealthPulse			= 0;
990 	healthPulse				= false;
991 	nextHealthTake			= 0;
992 	healthTake				= false;
993 
994 	scoreBoardOpen			= false;
995 	forceScoreBoard			= false;
996 	forceRespawn			= false;
997 	spectating				= false;
998 	spectator				= 0;
999 	colorBar				= vec3_zero;
1000 	colorBarIndex			= 0;
1001 	forcedReady				= false;
1002 	wantSpectate			= false;
1003 
1004 	lastHitToggle			= false;
1005 
1006 	minRespawnTime			= 0;
1007 	maxRespawnTime			= 0;
1008 
1009 	firstPersonViewOrigin	= vec3_zero;
1010 	firstPersonViewAxis		= mat3_identity;
1011 
1012 	hipJoint				= INVALID_JOINT;
1013 	chestJoint				= INVALID_JOINT;
1014 	headJoint				= INVALID_JOINT;
1015 
1016 	bobFoot					= 0;
1017 	bobFrac					= 0.0f;
1018 	bobfracsin				= 0.0f;
1019 	bobCycle				= 0;
1020 	xyspeed					= 0.0f;
1021 	stepUpTime				= 0;
1022 	stepUpDelta				= 0.0f;
1023 	idealLegsYaw			= 0.0f;
1024 	legsYaw					= 0.0f;
1025 	legsForward				= true;
1026 	oldViewYaw				= 0.0f;
1027 	viewBobAngles			= ang_zero;
1028 	viewBob					= vec3_zero;
1029 	landChange				= 0;
1030 	landTime				= 0;
1031 
1032 	currentWeapon			= -1;
1033 	idealWeapon				= -1;
1034 	previousWeapon			= -1;
1035 	weaponSwitchTime		=  0;
1036 	weaponEnabled			= true;
1037 	weapon_soulcube			= -1;
1038 	weapon_pda				= -1;
1039 	weapon_fists			= -1;
1040 	showWeaponViewModel		= true;
1041 
1042 	skin					= NULL;
1043 	powerUpSkin				= NULL;
1044 	baseSkinName			= "";
1045 
1046 	numProjectilesFired		= 0;
1047 	numProjectileHits		= 0;
1048 
1049 	airless					= false;
1050 	airTics					= 0;
1051 	lastAirDamage			= 0;
1052 
1053 	gibDeath				= false;
1054 	gibsLaunched			= false;
1055 	gibsDir					= vec3_zero;
1056 
1057 	zoomFov.Init( 0, 0, 0, 0 );
1058 	centerView.Init( 0, 0, 0, 0 );
1059 	fxFov					= false;
1060 
1061 	influenceFov			= 0;
1062 	influenceActive			= 0;
1063 	influenceRadius			= 0.0f;
1064 	influenceEntity			= NULL;
1065 	influenceMaterial		= NULL;
1066 	influenceSkin			= NULL;
1067 
1068 	privateCameraView		= NULL;
1069 
1070 	memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) );
1071 	memset( loggedAccel, 0, sizeof( loggedAccel ) );
1072 	currentLoggedAccel	= 0;
1073 
1074 	focusTime				= 0;
1075 	focusGUIent				= NULL;
1076 	focusUI					= NULL;
1077 	focusCharacter			= NULL;
1078 	talkCursor				= 0;
1079 	focusVehicle			= NULL;
1080 	cursor					= NULL;
1081 
1082 	oldMouseX				= 0;
1083 	oldMouseY				= 0;
1084 
1085 	pdaAudio				= "";
1086 	pdaVideo				= "";
1087 	pdaVideoWave			= "";
1088 
1089 	lastDamageDef			= 0;
1090 	lastDamageDir			= vec3_zero;
1091 	lastDamageLocation		= 0;
1092 	smoothedFrame			= 0;
1093 	smoothedOriginUpdated	= false;
1094 	smoothedOrigin			= vec3_zero;
1095 	smoothedAngles			= ang_zero;
1096 
1097 	fl.networkSync			= true;
1098 
1099 	latchedTeam				= -1;
1100 	doingDeathSkin			= false;
1101 	weaponGone				= false;
1102 	useInitialSpawns		= false;
1103 	tourneyRank				= 0;
1104 	lastSpectateTeleport	= 0;
1105 	tourneyLine				= 0;
1106 	hiddenWeapon			= false;
1107 	tipUp					= false;
1108 	objectiveUp				= false;
1109 	teleportEntity			= NULL;
1110 	teleportKiller			= -1;
1111 	respawning				= false;
1112 	ready					= false;
1113 	leader					= false;
1114 	lastSpectateChange		= 0;
1115 	lastTeleFX				= -9999;
1116 	weaponCatchup			= false;
1117 	lastSnapshotSequence	= 0;
1118 
1119 	MPAim					= -1;
1120 	lastMPAim				= -1;
1121 	lastMPAimTime			= 0;
1122 	MPAimFadeTime			= 0;
1123 	MPAimHighlight			= false;
1124 
1125 	spawnedTime				= 0;
1126 	lastManOver				= false;
1127 	lastManPlayAgain		= false;
1128 	lastManPresent			= false;
1129 
1130 	isTelefragged			= false;
1131 
1132 	isLagged				= false;
1133 	isChatting				= false;
1134 
1135 	selfSmooth				= false;
1136 }
1137 
1138 /*
1139 ==============
1140 idPlayer::LinkScriptVariables
1141 
1142 set up conditions for animation
1143 ==============
1144 */
LinkScriptVariables(void)1145 void idPlayer::LinkScriptVariables( void ) {
1146 	AI_FORWARD.LinkTo(			scriptObject, "AI_FORWARD" );
1147 	AI_BACKWARD.LinkTo(			scriptObject, "AI_BACKWARD" );
1148 	AI_STRAFE_LEFT.LinkTo(		scriptObject, "AI_STRAFE_LEFT" );
1149 	AI_STRAFE_RIGHT.LinkTo(		scriptObject, "AI_STRAFE_RIGHT" );
1150 	AI_ATTACK_HELD.LinkTo(		scriptObject, "AI_ATTACK_HELD" );
1151 	AI_WEAPON_FIRED.LinkTo(		scriptObject, "AI_WEAPON_FIRED" );
1152 	AI_JUMP.LinkTo(				scriptObject, "AI_JUMP" );
1153 	AI_DEAD.LinkTo(				scriptObject, "AI_DEAD" );
1154 	AI_CROUCH.LinkTo(			scriptObject, "AI_CROUCH" );
1155 	AI_ONGROUND.LinkTo(			scriptObject, "AI_ONGROUND" );
1156 	AI_ONLADDER.LinkTo(			scriptObject, "AI_ONLADDER" );
1157 	AI_HARDLANDING.LinkTo(		scriptObject, "AI_HARDLANDING" );
1158 	AI_SOFTLANDING.LinkTo(		scriptObject, "AI_SOFTLANDING" );
1159 	AI_RUN.LinkTo(				scriptObject, "AI_RUN" );
1160 	AI_PAIN.LinkTo(				scriptObject, "AI_PAIN" );
1161 	AI_RELOAD.LinkTo(			scriptObject, "AI_RELOAD" );
1162 	AI_TELEPORT.LinkTo(			scriptObject, "AI_TELEPORT" );
1163 	AI_TURN_LEFT.LinkTo(		scriptObject, "AI_TURN_LEFT" );
1164 	AI_TURN_RIGHT.LinkTo(		scriptObject, "AI_TURN_RIGHT" );
1165 }
1166 
1167 /*
1168 ==============
1169 idPlayer::SetupWeaponEntity
1170 ==============
1171 */
SetupWeaponEntity(void)1172 void idPlayer::SetupWeaponEntity( void ) {
1173 	int w;
1174 	const char *weap;
1175 
1176 	if ( weapon.GetEntity() ) {
1177 		// get rid of old weapon
1178 		weapon.GetEntity()->Clear();
1179 		currentWeapon = -1;
1180 	} else if ( !gameLocal.isClient ) {
1181 		weapon = static_cast<idWeapon *>( gameLocal.SpawnEntityType( idWeapon::Type, NULL ) );
1182 		weapon.GetEntity()->SetOwner( this );
1183 		currentWeapon = -1;
1184 	}
1185 
1186 	for( w = 0; w < MAX_WEAPONS; w++ ) {
1187 		weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
1188 		if ( weap && *weap ) {
1189 			idWeapon::CacheWeapon( weap );
1190 		}
1191 	}
1192 }
1193 
1194 /*
1195 ==============
1196 idPlayer::Init
1197 ==============
1198 */
Init(void)1199 void idPlayer::Init( void ) {
1200 	const char			*value;
1201 	const idKeyValue	*kv;
1202 
1203 	noclip					= false;
1204 	godmode					= false;
1205 
1206 	oldButtons				= 0;
1207 	oldFlags				= 0;
1208 
1209 	currentWeapon			= -1;
1210 	idealWeapon				= -1;
1211 	previousWeapon			= -1;
1212 	weaponSwitchTime		= 0;
1213 	weaponEnabled			= true;
1214 	weapon_soulcube			= SlotForWeapon( "weapon_soulcube" );
1215 	weapon_pda				= SlotForWeapon( "weapon_pda" );
1216 	weapon_fists			= SlotForWeapon( "weapon_fists" );
1217 	showWeaponViewModel		= GetUserInfo()->GetBool( "ui_showGun" );
1218 
1219 
1220 	lastDmgTime				= 0;
1221 	lastArmorPulse			= -10000;
1222 	lastHeartAdjust			= 0;
1223 	lastHeartBeat			= 0;
1224 	heartInfo.Init( 0, 0, 0, 0 );
1225 
1226 	bobCycle				= 0;
1227 	bobFrac					= 0.0f;
1228 	landChange				= 0;
1229 	landTime				= 0;
1230 	zoomFov.Init( 0, 0, 0, 0 );
1231 	centerView.Init( 0, 0, 0, 0 );
1232 	fxFov					= false;
1233 
1234 	influenceFov			= 0;
1235 	influenceActive			= 0;
1236 	influenceRadius			= 0.0f;
1237 	influenceEntity			= NULL;
1238 	influenceMaterial		= NULL;
1239 	influenceSkin			= NULL;
1240 
1241 	currentLoggedAccel		= 0;
1242 
1243 	focusTime				= 0;
1244 	focusGUIent				= NULL;
1245 	focusUI					= NULL;
1246 	focusCharacter			= NULL;
1247 	talkCursor				= 0;
1248 	focusVehicle			= NULL;
1249 
1250 	// remove any damage effects
1251 	playerView.ClearEffects();
1252 
1253 	// damage values
1254 	fl.takedamage			= true;
1255 	ClearPain();
1256 
1257 	// restore persistent data
1258 	RestorePersistantInfo();
1259 
1260 	bobCycle		= 0;
1261 	stamina			= 0.0f;
1262 	healthPool		= 0.0f;
1263 	nextHealthPulse = 0;
1264 	healthPulse		= false;
1265 	nextHealthTake	= 0;
1266 	healthTake		= false;
1267 
1268 	SetupWeaponEntity();
1269 	currentWeapon = -1;
1270 	previousWeapon = -1;
1271 
1272 	heartRate = BASE_HEARTRATE;
1273 	AdjustHeartRate( BASE_HEARTRATE, 0.0f, 0.0f, true );
1274 
1275 	idealLegsYaw = 0.0f;
1276 	legsYaw = 0.0f;
1277 	legsForward	= true;
1278 	oldViewYaw = 0.0f;
1279 
1280 	// set the pm_ cvars
1281 	if ( !gameLocal.isMultiplayer || gameLocal.isServer ) {
1282 		kv = spawnArgs.MatchPrefix( "pm_", NULL );
1283 		while( kv ) {
1284 			cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
1285 			kv = spawnArgs.MatchPrefix( "pm_", kv );
1286 		}
1287 	}
1288 
1289 	// disable stamina on hell levels
1290 	if ( gameLocal.world && gameLocal.world->spawnArgs.GetBool( "no_stamina" ) ) {
1291 		pm_stamina.SetFloat( 0.0f );
1292 	}
1293 
1294 	// stamina always initialized to maximum
1295 	stamina = pm_stamina.GetFloat();
1296 
1297 	// air always initialized to maximum too
1298 	airTics = pm_airTics.GetFloat();
1299 	airless = false;
1300 
1301 	gibDeath = false;
1302 	gibsLaunched = false;
1303 	gibsDir.Zero();
1304 
1305 	// set the gravity
1306 	physicsObj.SetGravity( gameLocal.GetGravity() );
1307 
1308 	// start out standing
1309 	SetEyeHeight( pm_normalviewheight.GetFloat() );
1310 
1311 	stepUpTime = 0;
1312 	stepUpDelta = 0.0f;
1313 	viewBobAngles.Zero();
1314 	viewBob.Zero();
1315 
1316 	value = spawnArgs.GetString( "model" );
1317 	if ( value && ( *value != 0 ) ) {
1318 		SetModel( value );
1319 	}
1320 
1321 	if ( cursor ) {
1322 		cursor->SetStateInt( "talkcursor", 0 );
1323 		cursor->SetStateString( "combatcursor", "1" );
1324 		cursor->SetStateString( "itemcursor", "0" );
1325 		cursor->SetStateString( "guicursor", "0" );
1326 	}
1327 
1328 	if ( ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) && skin ) {
1329 		SetSkin( skin );
1330 		renderEntity.shaderParms[6] = 0.0f;
1331 	} else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) {
1332 		skin = declManager->FindSkin( value );
1333 		SetSkin( skin );
1334 		renderEntity.shaderParms[6] = 0.0f;
1335 	}
1336 
1337 	value = spawnArgs.GetString( "bone_hips", "" );
1338 	hipJoint = animator.GetJointHandle( value );
1339 	if ( hipJoint == INVALID_JOINT ) {
1340 		gameLocal.Error( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() );
1341 	}
1342 
1343 	value = spawnArgs.GetString( "bone_chest", "" );
1344 	chestJoint = animator.GetJointHandle( value );
1345 	if ( chestJoint == INVALID_JOINT ) {
1346 		gameLocal.Error( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() );
1347 	}
1348 
1349 	value = spawnArgs.GetString( "bone_head", "" );
1350 	headJoint = animator.GetJointHandle( value );
1351 	if ( headJoint == INVALID_JOINT ) {
1352 		gameLocal.Error( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() );
1353 	}
1354 
1355 	// initialize the script variables
1356 	AI_FORWARD		= false;
1357 	AI_BACKWARD		= false;
1358 	AI_STRAFE_LEFT	= false;
1359 	AI_STRAFE_RIGHT	= false;
1360 	AI_ATTACK_HELD	= false;
1361 	AI_WEAPON_FIRED	= false;
1362 	AI_JUMP			= false;
1363 	AI_DEAD			= false;
1364 	AI_CROUCH		= false;
1365 	AI_ONGROUND		= true;
1366 	AI_ONLADDER		= false;
1367 	AI_HARDLANDING	= false;
1368 	AI_SOFTLANDING	= false;
1369 	AI_RUN			= false;
1370 	AI_PAIN			= false;
1371 	AI_RELOAD		= false;
1372 	AI_TELEPORT		= false;
1373 	AI_TURN_LEFT	= false;
1374 	AI_TURN_RIGHT	= false;
1375 
1376 	// reset the script object
1377 	ConstructScriptObject();
1378 
1379 	// execute the script so the script object's constructor takes effect immediately
1380 	scriptThread->Execute();
1381 
1382 	forceScoreBoard		= false;
1383 	forcedReady			= false;
1384 
1385 	privateCameraView	= NULL;
1386 
1387 	lastSpectateChange	= 0;
1388 	lastTeleFX			= -9999;
1389 
1390 	hiddenWeapon		= false;
1391 	tipUp				= false;
1392 	objectiveUp			= false;
1393 	teleportEntity		= NULL;
1394 	teleportKiller		= -1;
1395 	leader				= false;
1396 
1397 	SetPrivateCameraView( NULL );
1398 
1399 	lastSnapshotSequence	= 0;
1400 
1401 	MPAim				= -1;
1402 	lastMPAim			= -1;
1403 	lastMPAimTime		= 0;
1404 	MPAimFadeTime		= 0;
1405 	MPAimHighlight		= false;
1406 
1407 	if ( hud ) {
1408 		hud->HandleNamedEvent( "aim_clear" );
1409 	}
1410 
1411 	cvarSystem->SetCVarBool( "ui_chat", false );
1412 }
1413 
1414 /*
1415 ==============
1416 idPlayer::Spawn
1417 
1418 Prepare any resources used by the player.
1419 ==============
1420 */
Spawn(void)1421 void idPlayer::Spawn( void ) {
1422 	idStr		temp;
1423 	idBounds	bounds;
1424 
1425 	if ( entityNumber >= MAX_CLIENTS ) {
1426 		gameLocal.Error( "entityNum > MAX_CLIENTS for player.  Player may only be spawned with a client." );
1427 	}
1428 
1429 	// allow thinking during cinematics
1430 	cinematic = true;
1431 
1432 	if ( gameLocal.isMultiplayer ) {
1433 		// always start in spectating state waiting to be spawned in
1434 		// do this before SetClipModel to get the right bounding box
1435 		spectating = true;
1436 	}
1437 
1438 	// set our collision model
1439 	physicsObj.SetSelf( this );
1440 	SetClipModel();
1441 	physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
1442 	physicsObj.SetContents( CONTENTS_BODY );
1443 	physicsObj.SetClipMask( MASK_PLAYERSOLID );
1444 	SetPhysics( &physicsObj );
1445 	InitAASLocation();
1446 
1447 	skin = renderEntity.customSkin;
1448 
1449 	// only the local player needs guis
1450 	if ( !gameLocal.isMultiplayer || entityNumber == gameLocal.localClientNum ) {
1451 
1452 		// load HUD
1453 		if ( gameLocal.isMultiplayer ) {
1454 			hud = uiManager->FindGui( "guis/mphud.gui", true, false, true );
1455 		} else if ( spawnArgs.GetString( "hud", "", temp ) ) {
1456 			hud = uiManager->FindGui( temp, true, false, true );
1457 		}
1458 		if ( hud ) {
1459 			hud->Activate( true, gameLocal.time );
1460 		}
1461 
1462 		// load cursor
1463 		if ( spawnArgs.GetString( "cursor", "", temp ) ) {
1464 			cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer );
1465 		}
1466 		if ( cursor ) {
1467 			// DG: make it scale to 4:3 so crosshair looks properly round
1468 			//     yes, like so many scaling-related things this is a bit hacky
1469 			//     and note that this is special cased in StateChanged and you
1470 			//     can *not* generally set windowDef properties like this.
1471 			cursor->SetStateBool("scaleto43", true);
1472 			cursor->StateChanged(gameLocal.time); // DG end
1473 
1474 			cursor->Activate( true, gameLocal.time );
1475 		}
1476 
1477 		objectiveSystem = uiManager->FindGui( "guis/pda.gui", true, false, true );
1478 		objectiveSystemOpen = false;
1479 	}
1480 
1481 	SetLastHitTime( 0 );
1482 
1483 	// load the armor sound feedback
1484 	declManager->FindSound( "player_sounds_hitArmor" );
1485 
1486 	// set up conditions for animation
1487 	LinkScriptVariables();
1488 
1489 	animator.RemoveOriginOffset( true );
1490 
1491 	// initialize user info related settings
1492 	// on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game
1493 	if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
1494 		UserInfoChanged( false );
1495 	}
1496 
1497 	// create combat collision hull for exact collision detection
1498 	SetCombatModel();
1499 
1500 	// init the damage effects
1501 	playerView.SetPlayerEntity( this );
1502 
1503 	// supress model in non-player views, but allow it in mirrors and remote views
1504 	renderEntity.suppressSurfaceInViewID = entityNumber+1;
1505 
1506 	// don't project shadow on self or weapon
1507 	renderEntity.noSelfShadow = true;
1508 
1509 	idAFAttachment *headEnt = head.GetEntity();
1510 	if ( headEnt ) {
1511 		headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1;
1512 		headEnt->GetRenderEntity()->noSelfShadow = true;
1513 	}
1514 
1515 	if ( gameLocal.isMultiplayer ) {
1516 		Init();
1517 		Hide();	// properly hidden if starting as a spectator
1518 		if ( !gameLocal.isClient ) {
1519 			// set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot
1520 			SetupWeaponEntity();
1521 			SpawnFromSpawnSpot();
1522 			forceRespawn = true;
1523 			assert( spectating );
1524 		}
1525 	} else {
1526 		SetupWeaponEntity();
1527 		SpawnFromSpawnSpot();
1528 	}
1529 
1530 	// trigger playtesting item gives, if we didn't get here from a previous level
1531 	// the devmap key will be set on the first devmap, but cleared on any level
1532 	// transitions
1533 	if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) {
1534 		// fire a trigger with the name "devmap"
1535 		idEntity *ent = gameLocal.FindEntity( "devmap" );
1536 		if ( ent ) {
1537 			ent->ActivateTargets( this );
1538 		}
1539 	}
1540 	if ( hud ) {
1541 		// We can spawn with a full soul cube, so we need to make sure the hud knows this
1542 		if ( weapon_soulcube > 0 && ( inventory.weapons & ( 1 << weapon_soulcube ) ) ) {
1543 			int max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
1544 			if ( inventory.ammo[ idWeapon::GetAmmoNumForName( "ammo_souls" ) ] >= max_souls ) {
1545 				hud->HandleNamedEvent( "soulCubeReady" );
1546 			}
1547 		}
1548 		hud->HandleNamedEvent( "itemPickup" );
1549 	}
1550 
1551 	if ( GetPDA() ) {
1552 		// Add any emails from the inventory
1553 		for ( int i = 0; i < inventory.emails.Num(); i++ ) {
1554 			GetPDA()->AddEmail( inventory.emails[i] );
1555 		}
1556 		GetPDA()->SetSecurity( common->GetLanguageDict()->GetString( "#str_00066" ) );
1557 	}
1558 
1559 	if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
1560 		hiddenWeapon = true;
1561 		if ( weapon.GetEntity() ) {
1562 			weapon.GetEntity()->LowerWeapon();
1563 		}
1564 		idealWeapon = 0;
1565 	} else {
1566 		hiddenWeapon = false;
1567 	}
1568 
1569 	if ( hud ) {
1570 		UpdateHudWeapon();
1571 		hud->StateChanged( gameLocal.time );
1572 	}
1573 
1574 	tipUp = false;
1575 	objectiveUp = false;
1576 
1577 	if ( inventory.levelTriggers.Num() ) {
1578 		PostEventMS( &EV_Player_LevelTrigger, 0 );
1579 	}
1580 
1581 	inventory.pdaOpened = false;
1582 	inventory.selPDA = 0;
1583 
1584 	if ( !gameLocal.isMultiplayer ) {
1585 		if ( g_skill.GetInteger() < 2 ) {
1586 			if ( health < 25 ) {
1587 				health = 25;
1588 			}
1589 			if ( g_useDynamicProtection.GetBool() ) {
1590 				g_damageScale.SetFloat( 1.0f );
1591 			}
1592 		} else {
1593 			g_damageScale.SetFloat( 1.0f );
1594 			g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f );
1595 
1596 			if ( g_skill.GetInteger() == 3 ) {
1597 				healthTake = true;
1598 				nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
1599 			}
1600 		}
1601 	}
1602 }
1603 
1604 /*
1605 ==============
1606 idPlayer::~idPlayer()
1607 
1608 Release any resources used by the player.
1609 ==============
1610 */
~idPlayer()1611 idPlayer::~idPlayer() {
1612 	delete weapon.GetEntity();
1613 	weapon = NULL;
1614 }
1615 
1616 /*
1617 ===========
1618 idPlayer::Save
1619 ===========
1620 */
Save(idSaveGame * savefile) const1621 void idPlayer::Save( idSaveGame *savefile ) const {
1622 	int i;
1623 
1624 	savefile->WriteUsercmd( usercmd );
1625 	playerView.Save( savefile );
1626 
1627 	savefile->WriteBool( noclip );
1628 	savefile->WriteBool( godmode );
1629 
1630 	// don't save spawnAnglesSet, since we'll have to reset them after loading the savegame
1631 	savefile->WriteAngles( spawnAngles );
1632 	savefile->WriteAngles( viewAngles );
1633 	savefile->WriteAngles( cmdAngles );
1634 
1635 	savefile->WriteInt( buttonMask );
1636 	savefile->WriteInt( oldButtons );
1637 	savefile->WriteInt( oldFlags );
1638 
1639 	savefile->WriteInt( lastHitTime );
1640 	savefile->WriteInt( lastSndHitTime );
1641 	savefile->WriteInt( lastSavingThrowTime );
1642 
1643 	// idBoolFields don't need to be saved, just re-linked in Restore
1644 
1645 	inventory.Save( savefile );
1646 	weapon.Save( savefile );
1647 
1648 	savefile->WriteUserInterface( hud, false );
1649 	savefile->WriteUserInterface( objectiveSystem, false );
1650 	savefile->WriteBool( objectiveSystemOpen );
1651 
1652 	savefile->WriteInt( weapon_soulcube );
1653 	savefile->WriteInt( weapon_pda );
1654 	savefile->WriteInt( weapon_fists );
1655 
1656 	savefile->WriteInt( heartRate );
1657 
1658 	savefile->WriteFloat( heartInfo.GetStartTime() );
1659 	savefile->WriteFloat( heartInfo.GetDuration() );
1660 	savefile->WriteFloat( heartInfo.GetStartValue() );
1661 	savefile->WriteFloat( heartInfo.GetEndValue() );
1662 
1663 	savefile->WriteInt( lastHeartAdjust );
1664 	savefile->WriteInt( lastHeartBeat );
1665 	savefile->WriteInt( lastDmgTime );
1666 	savefile->WriteInt( deathClearContentsTime );
1667 	savefile->WriteBool( doingDeathSkin );
1668 	savefile->WriteInt( lastArmorPulse );
1669 	savefile->WriteFloat( stamina );
1670 	savefile->WriteFloat( healthPool );
1671 	savefile->WriteInt( nextHealthPulse );
1672 	savefile->WriteBool( healthPulse );
1673 	savefile->WriteInt( nextHealthTake );
1674 	savefile->WriteBool( healthTake );
1675 
1676 	savefile->WriteBool( hiddenWeapon );
1677 	soulCubeProjectile.Save( savefile );
1678 
1679 	savefile->WriteInt( spectator );
1680 	savefile->WriteVec3( colorBar );
1681 	savefile->WriteInt( colorBarIndex );
1682 	savefile->WriteBool( scoreBoardOpen );
1683 	savefile->WriteBool( forceScoreBoard );
1684 	savefile->WriteBool( forceRespawn );
1685 	savefile->WriteBool( spectating );
1686 	savefile->WriteInt( lastSpectateTeleport );
1687 	savefile->WriteBool( lastHitToggle );
1688 	savefile->WriteBool( forcedReady );
1689 	savefile->WriteBool( wantSpectate );
1690 	savefile->WriteBool( weaponGone );
1691 	savefile->WriteBool( useInitialSpawns );
1692 	savefile->WriteInt( latchedTeam );
1693 	savefile->WriteInt( tourneyRank );
1694 	savefile->WriteInt( tourneyLine );
1695 
1696 	teleportEntity.Save( savefile );
1697 	savefile->WriteInt( teleportKiller );
1698 
1699 	savefile->WriteInt( minRespawnTime );
1700 	savefile->WriteInt( maxRespawnTime );
1701 
1702 	savefile->WriteVec3( firstPersonViewOrigin );
1703 	savefile->WriteMat3( firstPersonViewAxis );
1704 
1705 	// don't bother saving dragEntity since it's a dev tool
1706 
1707 	savefile->WriteJoint( hipJoint );
1708 	savefile->WriteJoint( chestJoint );
1709 	savefile->WriteJoint( headJoint );
1710 
1711 	savefile->WriteStaticObject( physicsObj );
1712 
1713 	savefile->WriteInt( aasLocation.Num() );
1714 	for( i = 0; i < aasLocation.Num(); i++ ) {
1715 		savefile->WriteInt( aasLocation[ i ].areaNum );
1716 		savefile->WriteVec3( aasLocation[ i ].pos );
1717 	}
1718 
1719 	savefile->WriteInt( bobFoot );
1720 	savefile->WriteFloat( bobFrac );
1721 	savefile->WriteFloat( bobfracsin );
1722 	savefile->WriteInt( bobCycle );
1723 	savefile->WriteFloat( xyspeed );
1724 	savefile->WriteInt( stepUpTime );
1725 	savefile->WriteFloat( stepUpDelta );
1726 	savefile->WriteFloat( idealLegsYaw );
1727 	savefile->WriteFloat( legsYaw );
1728 	savefile->WriteBool( legsForward );
1729 	savefile->WriteFloat( oldViewYaw );
1730 	savefile->WriteAngles( viewBobAngles );
1731 	savefile->WriteVec3( viewBob );
1732 	savefile->WriteInt( landChange );
1733 	savefile->WriteInt( landTime );
1734 
1735 	savefile->WriteInt( currentWeapon );
1736 	savefile->WriteInt( idealWeapon );
1737 	savefile->WriteInt( previousWeapon );
1738 	savefile->WriteInt( weaponSwitchTime );
1739 	savefile->WriteBool( weaponEnabled );
1740 	savefile->WriteBool( showWeaponViewModel );
1741 
1742 	savefile->WriteSkin( skin );
1743 	savefile->WriteSkin( powerUpSkin );
1744 	savefile->WriteString( baseSkinName );
1745 
1746 	savefile->WriteInt( numProjectilesFired );
1747 	savefile->WriteInt( numProjectileHits );
1748 
1749 	savefile->WriteBool( airless );
1750 	savefile->WriteInt( airTics );
1751 	savefile->WriteInt( lastAirDamage );
1752 
1753 	savefile->WriteBool( gibDeath );
1754 	savefile->WriteBool( gibsLaunched );
1755 	savefile->WriteVec3( gibsDir );
1756 
1757 	savefile->WriteFloat( zoomFov.GetStartTime() );
1758 	savefile->WriteFloat( zoomFov.GetDuration() );
1759 	savefile->WriteFloat( zoomFov.GetStartValue() );
1760 	savefile->WriteFloat( zoomFov.GetEndValue() );
1761 
1762 	savefile->WriteFloat( centerView.GetStartTime() );
1763 	savefile->WriteFloat( centerView.GetDuration() );
1764 	savefile->WriteFloat( centerView.GetStartValue() );
1765 	savefile->WriteFloat( centerView.GetEndValue() );
1766 
1767 	savefile->WriteBool( fxFov );
1768 
1769 	savefile->WriteFloat( influenceFov );
1770 	savefile->WriteInt( influenceActive );
1771 	savefile->WriteFloat( influenceRadius );
1772 	savefile->WriteObject( influenceEntity );
1773 	savefile->WriteMaterial( influenceMaterial );
1774 	savefile->WriteSkin( influenceSkin );
1775 
1776 	savefile->WriteObject( privateCameraView );
1777 
1778 	for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
1779 		savefile->WriteAngles( loggedViewAngles[ i ] );
1780 	}
1781 	for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
1782 		savefile->WriteInt( loggedAccel[ i ].time );
1783 		savefile->WriteVec3( loggedAccel[ i ].dir );
1784 	}
1785 	savefile->WriteInt( currentLoggedAccel );
1786 
1787 	savefile->WriteObject( focusGUIent );
1788 	// can't save focusUI
1789 	savefile->WriteObject( focusCharacter );
1790 	savefile->WriteInt( talkCursor );
1791 	savefile->WriteInt( focusTime );
1792 	savefile->WriteObject( focusVehicle );
1793 	savefile->WriteUserInterface( cursor, false );
1794 
1795 	savefile->WriteInt( oldMouseX );
1796 	savefile->WriteInt( oldMouseY );
1797 
1798 	savefile->WriteString( pdaAudio );
1799 	savefile->WriteString( pdaVideo );
1800 	savefile->WriteString( pdaVideoWave );
1801 
1802 	savefile->WriteBool( tipUp );
1803 	savefile->WriteBool( objectiveUp );
1804 
1805 	savefile->WriteInt( lastDamageDef );
1806 	savefile->WriteVec3( lastDamageDir );
1807 	savefile->WriteInt( lastDamageLocation );
1808 	savefile->WriteInt( smoothedFrame );
1809 	savefile->WriteBool( smoothedOriginUpdated );
1810 	savefile->WriteVec3( smoothedOrigin );
1811 	savefile->WriteAngles( smoothedAngles );
1812 
1813 	savefile->WriteBool( ready );
1814 	savefile->WriteBool( respawning );
1815 	savefile->WriteBool( leader );
1816 	savefile->WriteInt( lastSpectateChange );
1817 	savefile->WriteInt( lastTeleFX );
1818 
1819 	savefile->WriteFloat( pm_stamina.GetFloat() );
1820 
1821 	if ( hud ) {
1822 		hud->SetStateString( "message", common->GetLanguageDict()->GetString( "#str_02916" ) );
1823 		hud->HandleNamedEvent( "Message" );
1824 	}
1825 }
1826 
1827 /*
1828 ===========
1829 idPlayer::Restore
1830 ===========
1831 */
Restore(idRestoreGame * savefile)1832 void idPlayer::Restore( idRestoreGame *savefile ) {
1833 	int	  i;
1834 	int	  num;
1835 	float set;
1836 
1837 	savefile->ReadUsercmd( usercmd );
1838 	playerView.Restore( savefile );
1839 
1840 	savefile->ReadBool( noclip );
1841 	savefile->ReadBool( godmode );
1842 
1843 	savefile->ReadAngles( spawnAngles );
1844 	savefile->ReadAngles( viewAngles );
1845 	savefile->ReadAngles( cmdAngles );
1846 
1847 	memset( usercmd.angles, 0, sizeof( usercmd.angles ) );
1848 	SetViewAngles( viewAngles );
1849 	spawnAnglesSet = true;
1850 
1851 	savefile->ReadInt( buttonMask );
1852 	savefile->ReadInt( oldButtons );
1853 	savefile->ReadInt( oldFlags );
1854 
1855 	usercmd.flags = 0;
1856 	oldFlags = 0;
1857 
1858 	savefile->ReadInt( lastHitTime );
1859 	savefile->ReadInt( lastSndHitTime );
1860 	savefile->ReadInt( lastSavingThrowTime );
1861 
1862 	// Re-link idBoolFields to the scriptObject, values will be restored in scriptObject's restore
1863 	LinkScriptVariables();
1864 
1865 	inventory.Restore( savefile );
1866 	weapon.Restore( savefile );
1867 
1868 	for ( i = 0; i < inventory.emails.Num(); i++ ) {
1869 		GetPDA()->AddEmail( inventory.emails[i] );
1870 	}
1871 
1872 	savefile->ReadUserInterface( hud );
1873 	savefile->ReadUserInterface( objectiveSystem );
1874 	savefile->ReadBool( objectiveSystemOpen );
1875 
1876 	savefile->ReadInt( weapon_soulcube );
1877 	savefile->ReadInt( weapon_pda );
1878 	savefile->ReadInt( weapon_fists );
1879 
1880 	savefile->ReadInt( heartRate );
1881 
1882 	savefile->ReadFloat( set );
1883 	heartInfo.SetStartTime( set );
1884 	savefile->ReadFloat( set );
1885 	heartInfo.SetDuration( set );
1886 	savefile->ReadFloat( set );
1887 	heartInfo.SetStartValue( set );
1888 	savefile->ReadFloat( set );
1889 	heartInfo.SetEndValue( set );
1890 
1891 	savefile->ReadInt( lastHeartAdjust );
1892 	savefile->ReadInt( lastHeartBeat );
1893 	savefile->ReadInt( lastDmgTime );
1894 	savefile->ReadInt( deathClearContentsTime );
1895 	savefile->ReadBool( doingDeathSkin );
1896 	savefile->ReadInt( lastArmorPulse );
1897 	savefile->ReadFloat( stamina );
1898 	savefile->ReadFloat( healthPool );
1899 	savefile->ReadInt( nextHealthPulse );
1900 	savefile->ReadBool( healthPulse );
1901 	savefile->ReadInt( nextHealthTake );
1902 	savefile->ReadBool( healthTake );
1903 
1904 	savefile->ReadBool( hiddenWeapon );
1905 	soulCubeProjectile.Restore( savefile );
1906 
1907 	savefile->ReadInt( spectator );
1908 	savefile->ReadVec3( colorBar );
1909 	savefile->ReadInt( colorBarIndex );
1910 	savefile->ReadBool( scoreBoardOpen );
1911 	savefile->ReadBool( forceScoreBoard );
1912 	savefile->ReadBool( forceRespawn );
1913 	savefile->ReadBool( spectating );
1914 	savefile->ReadInt( lastSpectateTeleport );
1915 	savefile->ReadBool( lastHitToggle );
1916 	savefile->ReadBool( forcedReady );
1917 	savefile->ReadBool( wantSpectate );
1918 	savefile->ReadBool( weaponGone );
1919 	savefile->ReadBool( useInitialSpawns );
1920 	savefile->ReadInt( latchedTeam );
1921 	savefile->ReadInt( tourneyRank );
1922 	savefile->ReadInt( tourneyLine );
1923 
1924 	teleportEntity.Restore( savefile );
1925 	savefile->ReadInt( teleportKiller );
1926 
1927 	savefile->ReadInt( minRespawnTime );
1928 	savefile->ReadInt( maxRespawnTime );
1929 
1930 	savefile->ReadVec3( firstPersonViewOrigin );
1931 	savefile->ReadMat3( firstPersonViewAxis );
1932 
1933 	// don't bother saving dragEntity since it's a dev tool
1934 	dragEntity.Clear();
1935 
1936 	savefile->ReadJoint( hipJoint );
1937 	savefile->ReadJoint( chestJoint );
1938 	savefile->ReadJoint( headJoint );
1939 
1940 	savefile->ReadStaticObject( physicsObj );
1941 	RestorePhysics( &physicsObj );
1942 
1943 	savefile->ReadInt( num );
1944 	aasLocation.SetGranularity( 1 );
1945 	aasLocation.SetNum( num );
1946 	for( i = 0; i < num; i++ ) {
1947 		savefile->ReadInt( aasLocation[ i ].areaNum );
1948 		savefile->ReadVec3( aasLocation[ i ].pos );
1949 	}
1950 
1951 	savefile->ReadInt( bobFoot );
1952 	savefile->ReadFloat( bobFrac );
1953 	savefile->ReadFloat( bobfracsin );
1954 	savefile->ReadInt( bobCycle );
1955 	savefile->ReadFloat( xyspeed );
1956 	savefile->ReadInt( stepUpTime );
1957 	savefile->ReadFloat( stepUpDelta );
1958 	savefile->ReadFloat( idealLegsYaw );
1959 	savefile->ReadFloat( legsYaw );
1960 	savefile->ReadBool( legsForward );
1961 	savefile->ReadFloat( oldViewYaw );
1962 	savefile->ReadAngles( viewBobAngles );
1963 	savefile->ReadVec3( viewBob );
1964 	savefile->ReadInt( landChange );
1965 	savefile->ReadInt( landTime );
1966 
1967 	savefile->ReadInt( currentWeapon );
1968 	savefile->ReadInt( idealWeapon );
1969 	savefile->ReadInt( previousWeapon );
1970 	savefile->ReadInt( weaponSwitchTime );
1971 	savefile->ReadBool( weaponEnabled );
1972 	savefile->ReadBool( showWeaponViewModel );
1973 
1974 	savefile->ReadSkin( skin );
1975 	savefile->ReadSkin( powerUpSkin );
1976 	savefile->ReadString( baseSkinName );
1977 
1978 	savefile->ReadInt( numProjectilesFired );
1979 	savefile->ReadInt( numProjectileHits );
1980 
1981 	savefile->ReadBool( airless );
1982 	savefile->ReadInt( airTics );
1983 	savefile->ReadInt( lastAirDamage );
1984 
1985 	savefile->ReadBool( gibDeath );
1986 	savefile->ReadBool( gibsLaunched );
1987 	savefile->ReadVec3( gibsDir );
1988 
1989 	savefile->ReadFloat( set );
1990 	zoomFov.SetStartTime( set );
1991 	savefile->ReadFloat( set );
1992 	zoomFov.SetDuration( set );
1993 	savefile->ReadFloat( set );
1994 	zoomFov.SetStartValue( set );
1995 	savefile->ReadFloat( set );
1996 	zoomFov.SetEndValue( set );
1997 
1998 	savefile->ReadFloat( set );
1999 	centerView.SetStartTime( set );
2000 	savefile->ReadFloat( set );
2001 	centerView.SetDuration( set );
2002 	savefile->ReadFloat( set );
2003 	centerView.SetStartValue( set );
2004 	savefile->ReadFloat( set );
2005 	centerView.SetEndValue( set );
2006 
2007 	savefile->ReadBool( fxFov );
2008 
2009 	savefile->ReadFloat( influenceFov );
2010 	savefile->ReadInt( influenceActive );
2011 	savefile->ReadFloat( influenceRadius );
2012 	savefile->ReadObject( reinterpret_cast<idClass *&>( influenceEntity ) );
2013 	savefile->ReadMaterial( influenceMaterial );
2014 	savefile->ReadSkin( influenceSkin );
2015 
2016 	savefile->ReadObject( reinterpret_cast<idClass *&>( privateCameraView ) );
2017 
2018 	for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
2019 		savefile->ReadAngles( loggedViewAngles[ i ] );
2020 	}
2021 	for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
2022 		savefile->ReadInt( loggedAccel[ i ].time );
2023 		savefile->ReadVec3( loggedAccel[ i ].dir );
2024 	}
2025 	savefile->ReadInt( currentLoggedAccel );
2026 
2027 	savefile->ReadObject( reinterpret_cast<idClass *&>( focusGUIent ) );
2028 	// can't save focusUI
2029 	focusUI = NULL;
2030 	savefile->ReadObject( reinterpret_cast<idClass *&>( focusCharacter ) );
2031 	savefile->ReadInt( talkCursor );
2032 	savefile->ReadInt( focusTime );
2033 	savefile->ReadObject( reinterpret_cast<idClass *&>( focusVehicle ) );
2034 	savefile->ReadUserInterface( cursor );
2035 
2036 	// DG: make it scale to 4:3 so crosshair looks properly round
2037 	//     yes, like so many scaling-related things this is a bit hacky
2038 	//     and note that this is special cased in StateChanged and you
2039 	//     can *not* generally set windowDef properties like this.
2040 	cursor->SetStateBool("scaleto43", true);
2041 	cursor->StateChanged(gameLocal.time); // DG end
2042 
2043 	savefile->ReadInt( oldMouseX );
2044 	savefile->ReadInt( oldMouseY );
2045 
2046 	savefile->ReadString( pdaAudio );
2047 	savefile->ReadString( pdaVideo );
2048 	savefile->ReadString( pdaVideoWave );
2049 
2050 	savefile->ReadBool( tipUp );
2051 	savefile->ReadBool( objectiveUp );
2052 
2053 	savefile->ReadInt( lastDamageDef );
2054 	savefile->ReadVec3( lastDamageDir );
2055 	savefile->ReadInt( lastDamageLocation );
2056 	savefile->ReadInt( smoothedFrame );
2057 	savefile->ReadBool( smoothedOriginUpdated );
2058 	savefile->ReadVec3( smoothedOrigin );
2059 	savefile->ReadAngles( smoothedAngles );
2060 
2061 	savefile->ReadBool( ready );
2062 	savefile->ReadBool( respawning );
2063 	savefile->ReadBool( leader );
2064 	savefile->ReadInt( lastSpectateChange );
2065 	savefile->ReadInt( lastTeleFX );
2066 
2067 	// set the pm_ cvars
2068 	const idKeyValue	*kv;
2069 	kv = spawnArgs.MatchPrefix( "pm_", NULL );
2070 	while( kv ) {
2071 		cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
2072 		kv = spawnArgs.MatchPrefix( "pm_", kv );
2073 	}
2074 
2075 	savefile->ReadFloat( set );
2076 	pm_stamina.SetFloat( set );
2077 
2078 	// create combat collision hull for exact collision detection
2079 	SetCombatModel();
2080 
2081 	// DG: workaround for lingering messages that are shown forever after loading a savegame
2082 	//     (one way to get them is saving again, while the message from first save is still
2083 	//      shown, and then load)
2084 	if ( hud ) {
2085 		hud->SetStateString( "message", "" );
2086 	}
2087 }
2088 
2089 /*
2090 ===============
2091 idPlayer::PrepareForRestart
2092 ================
2093 */
PrepareForRestart(void)2094 void idPlayer::PrepareForRestart( void ) {
2095 	ClearPowerUps();
2096 	Spectate( true );
2097 	forceRespawn = true;
2098 
2099 	// we will be restarting program, clear the client entities from program-related things first
2100 	ShutdownThreads();
2101 
2102 	// the sound world is going to be cleared, don't keep references to emitters
2103 	FreeSoundEmitter( false );
2104 }
2105 
2106 /*
2107 ===============
2108 idPlayer::Restart
2109 ================
2110 */
Restart(void)2111 void idPlayer::Restart( void ) {
2112 	idActor::Restart();
2113 
2114 	// client needs to setup the animation script object again
2115 	if ( gameLocal.isClient ) {
2116 		Init();
2117 	} else {
2118 		// choose a random spot and prepare the point of view in case player is left spectating
2119 		assert( spectating );
2120 		SpawnFromSpawnSpot();
2121 	}
2122 
2123 	useInitialSpawns = true;
2124 	UpdateSkinSetup( true );
2125 }
2126 
2127 /*
2128 ===============
2129 idPlayer::ServerSpectate
2130 ================
2131 */
ServerSpectate(bool spectate)2132 void idPlayer::ServerSpectate( bool spectate ) {
2133 	assert( !gameLocal.isClient );
2134 
2135 	if ( spectating != spectate ) {
2136 		Spectate( spectate );
2137 		if ( spectate ) {
2138 			SetSpectateOrigin();
2139 		} else {
2140 			if ( gameLocal.gameType == GAME_DM ) {
2141 				// make sure the scores are reset so you can't exploit by spectating and entering the game back
2142 				// other game types don't matter, as you either can't join back, or it's team scores
2143 				gameLocal.mpGame.ClearFrags( entityNumber );
2144 			}
2145 		}
2146 	}
2147 	if ( !spectate ) {
2148 		SpawnFromSpawnSpot();
2149 	}
2150 }
2151 
2152 /*
2153 ===========
2154 idPlayer::SelectInitialSpawnPoint
2155 
2156 Try to find a spawn point marked 'initial', otherwise
2157 use normal spawn selection.
2158 ============
2159 */
SelectInitialSpawnPoint(idVec3 & origin,idAngles & angles)2160 void idPlayer::SelectInitialSpawnPoint( idVec3 &origin, idAngles &angles ) {
2161 	idEntity *spot;
2162 	idStr skin;
2163 
2164 	spot = gameLocal.SelectInitialSpawnPoint( this );
2165 
2166 	// set the player skin from the spawn location
2167 	if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) {
2168 		spawnArgs.Set( "spawn_skin", skin );
2169 	}
2170 
2171 	// activate the spawn locations targets
2172 	spot->PostEventMS( &EV_ActivateTargets, 0, this );
2173 
2174 	origin = spot->GetPhysics()->GetOrigin();
2175 	origin[2] += 4.0f + CM_BOX_EPSILON;		// move up to make sure the player is at least an epsilon above the floor
2176 	angles = spot->GetPhysics()->GetAxis().ToAngles();
2177 }
2178 
2179 /*
2180 ===========
2181 idPlayer::SpawnFromSpawnSpot
2182 
2183 Chooses a spawn location and spawns the player
2184 ============
2185 */
SpawnFromSpawnSpot(void)2186 void idPlayer::SpawnFromSpawnSpot( void ) {
2187 	idVec3		spawn_origin;
2188 	idAngles	spawn_angles;
2189 
2190 	SelectInitialSpawnPoint( spawn_origin, spawn_angles );
2191 	SpawnToPoint( spawn_origin, spawn_angles );
2192 }
2193 
2194 /*
2195 ===========
2196 idPlayer::SpawnToPoint
2197 
2198 Called every time a client is placed fresh in the world:
2199 after the first ClientBegin, and after each respawn
2200 Initializes all non-persistant parts of playerState
2201 
2202 when called here with spectating set to true, just place yourself and init
2203 ============
2204 */
SpawnToPoint(const idVec3 & spawn_origin,const idAngles & spawn_angles)2205 void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) {
2206 	idVec3 spec_origin;
2207 
2208 	assert( !gameLocal.isClient );
2209 
2210 	respawning = true;
2211 
2212 	Init();
2213 
2214 	fl.noknockback = false;
2215 
2216 	// stop any ragdolls being used
2217 	StopRagdoll();
2218 
2219 	// set back the player physics
2220 	SetPhysics( &physicsObj );
2221 
2222 	physicsObj.SetClipModelAxis();
2223 	physicsObj.EnableClip();
2224 
2225 	if ( !spectating ) {
2226 		SetCombatContents( true );
2227 	}
2228 
2229 	physicsObj.SetLinearVelocity( vec3_origin );
2230 
2231 	// setup our initial view
2232 	if ( !spectating ) {
2233 		SetOrigin( spawn_origin );
2234 	} else {
2235 		spec_origin = spawn_origin;
2236 		spec_origin[ 2 ] += pm_normalheight.GetFloat();
2237 		spec_origin[ 2 ] += SPECTATE_RAISE;
2238 		SetOrigin( spec_origin );
2239 	}
2240 
2241 	// if this is the first spawn of the map, we don't have a usercmd yet,
2242 	// so the delta angles won't be correct.  This will be fixed on the first think.
2243 	viewAngles = ang_zero;
2244 	SetDeltaViewAngles( ang_zero );
2245 	SetViewAngles( spawn_angles );
2246 	spawnAngles = spawn_angles;
2247 	spawnAnglesSet = false;
2248 
2249 	legsForward = true;
2250 	legsYaw = 0.0f;
2251 	idealLegsYaw = 0.0f;
2252 	oldViewYaw = viewAngles.yaw;
2253 
2254 	if ( spectating ) {
2255 		Hide();
2256 	} else {
2257 		Show();
2258 	}
2259 
2260 	if ( gameLocal.isMultiplayer ) {
2261 		if ( !spectating ) {
2262 			// we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof'
2263 			if ( lastTeleFX < gameLocal.time - 1000 ) {
2264 				idEntityFx::StartFx( spawnArgs.GetString( "fx_spawn" ), &spawn_origin, NULL, this, true );
2265 				lastTeleFX = gameLocal.time;
2266 			}
2267 		}
2268 		AI_TELEPORT = true;
2269 	} else {
2270 		AI_TELEPORT = false;
2271 	}
2272 
2273 	// kill anything at the new position
2274 	if ( !spectating ) {
2275 		physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it
2276 		gameLocal.KillBox( this );
2277 	}
2278 
2279 	// don't allow full run speed for a bit
2280 	physicsObj.SetKnockBack( 100 );
2281 
2282 	// set our respawn time and buttons so that if we're killed we don't respawn immediately
2283 	minRespawnTime = gameLocal.time;
2284 	maxRespawnTime = gameLocal.time;
2285 	if ( !spectating ) {
2286 		forceRespawn = false;
2287 	}
2288 
2289 	privateCameraView = NULL;
2290 
2291 	BecomeActive( TH_THINK );
2292 
2293 	// run a client frame to drop exactly to the floor,
2294 	// initialize animations and other things
2295 	Think();
2296 
2297 	respawning			= false;
2298 	lastManOver			= false;
2299 	lastManPlayAgain	= false;
2300 	isTelefragged		= false;
2301 }
2302 
2303 /*
2304 ===============
2305 idPlayer::SavePersistantInfo
2306 
2307 Saves any inventory and player stats when changing levels.
2308 ===============
2309 */
SavePersistantInfo(void)2310 void idPlayer::SavePersistantInfo( void ) {
2311 	idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber];
2312 
2313 	playerInfo.Clear();
2314 	inventory.GetPersistantData( playerInfo );
2315 	playerInfo.SetInt( "health", health );
2316 	playerInfo.SetInt( "current_weapon", currentWeapon );
2317 }
2318 
2319 /*
2320 ===============
2321 idPlayer::RestorePersistantInfo
2322 
2323 Restores any inventory and player stats when changing levels.
2324 ===============
2325 */
RestorePersistantInfo(void)2326 void idPlayer::RestorePersistantInfo( void ) {
2327 	if ( gameLocal.isMultiplayer ) {
2328 		gameLocal.persistentPlayerInfo[entityNumber].Clear();
2329 	}
2330 
2331 	spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] );
2332 
2333 	inventory.RestoreInventory( this, spawnArgs );
2334 	health = spawnArgs.GetInt( "health", "100" );
2335 	if ( !gameLocal.isClient ) {
2336 		idealWeapon = spawnArgs.GetInt( "current_weapon", "1" );
2337 	}
2338 }
2339 
2340 /*
2341 ================
2342 idPlayer::GetUserInfo
2343 ================
2344 */
GetUserInfo(void)2345 idDict *idPlayer::GetUserInfo( void ) {
2346 	return &gameLocal.userInfo[ entityNumber ];
2347 }
2348 
2349 /*
2350 ==============
2351 idPlayer::UpdateSkinSetup
2352 ==============
2353 */
UpdateSkinSetup(bool restart)2354 void idPlayer::UpdateSkinSetup( bool restart ) {
2355 	if ( restart ) {
2356 		team = ( idStr::Icmp( GetUserInfo()->GetString( "ui_team" ), "Blue" ) == 0 );
2357 	}
2358 	if ( gameLocal.gameType == GAME_TDM ) {
2359 		if ( team ) {
2360 			baseSkinName = "skins/characters/player/marine_mp_blue";
2361 		} else {
2362 			baseSkinName = "skins/characters/player/marine_mp_red";
2363 		}
2364 		if ( !gameLocal.isClient && team != latchedTeam ) {
2365 			gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team );
2366 		}
2367 		latchedTeam = team;
2368 	} else {
2369 		baseSkinName = GetUserInfo()->GetString( "ui_skin" );
2370 	}
2371 	if ( !baseSkinName.Length() ) {
2372 		baseSkinName = "skins/characters/player/marine_mp";
2373 	}
2374 	skin = declManager->FindSkin( baseSkinName, false );
2375 	assert( skin );
2376 	// match the skin to a color band for scoreboard
2377 	if ( baseSkinName.Find( "red" ) != -1 ) {
2378 		colorBarIndex = 1;
2379 	} else if ( baseSkinName.Find( "green" ) != -1 ) {
2380 		colorBarIndex = 2;
2381 	} else if ( baseSkinName.Find( "blue" ) != -1 ) {
2382 		colorBarIndex = 3;
2383 	} else if ( baseSkinName.Find( "yellow" ) != -1 ) {
2384 		colorBarIndex = 4;
2385 	} else {
2386 		colorBarIndex = 0;
2387 	}
2388 	colorBar = colorBarTable[ colorBarIndex ];
2389 	if ( PowerUpActive( BERSERK ) ) {
2390 		powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
2391 	}
2392 }
2393 
2394 /*
2395 ==============
2396 idPlayer::BalanceTDM
2397 ==============
2398 */
BalanceTDM(void)2399 bool idPlayer::BalanceTDM( void ) {
2400 	int			i, balanceTeam, teamCount[2];
2401 	idEntity	*ent;
2402 
2403 	teamCount[ 0 ] = teamCount[ 1 ] = 0;
2404 	for( i = 0; i < gameLocal.numClients; i++ ) {
2405 		ent = gameLocal.entities[ i ];
2406 		if ( ent && ent->IsType( idPlayer::Type ) ) {
2407 			teamCount[ static_cast< idPlayer * >( ent )->team ]++;
2408 		}
2409 	}
2410 	balanceTeam = -1;
2411 	if ( teamCount[ 0 ] < teamCount[ 1 ] ) {
2412 		balanceTeam = 0;
2413 	} else if ( teamCount[ 0 ] > teamCount[ 1 ] ) {
2414 		balanceTeam = 1;
2415 	}
2416 	if ( balanceTeam != -1 && team != balanceTeam ) {
2417 		common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "blue" : "red" );
2418 		team = balanceTeam;
2419 		GetUserInfo()->Set( "ui_team", team ? "Blue" : "Red" );
2420 		return true;
2421 	}
2422 	return false;
2423 }
2424 
2425 /*
2426 ==============
2427 idPlayer::UserInfoChanged
2428 ==============
2429 */
UserInfoChanged(bool canModify)2430 bool idPlayer::UserInfoChanged( bool canModify ) {
2431 	idDict	*userInfo;
2432 	bool	modifiedInfo;
2433 	bool	spec;
2434 	bool	newready;
2435 
2436 	userInfo = GetUserInfo();
2437 	showWeaponViewModel = userInfo->GetBool( "ui_showGun" );
2438 
2439 	if ( !gameLocal.isMultiplayer ) {
2440 		return false;
2441 	}
2442 
2443 	modifiedInfo = false;
2444 
2445 	spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 );
2446 	if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
2447 		// never let spectators go back to game while sudden death is on
2448 		if ( canModify && gameLocal.mpGame.GetGameState() == idMultiplayerGame::SUDDENDEATH && !spec && wantSpectate == true ) {
2449 			userInfo->Set( "ui_spectate", "Spectate" );
2450 			modifiedInfo |= true;
2451 		} else {
2452 			if ( spec != wantSpectate && !spec ) {
2453 				// returning from spectate, set forceRespawn so we don't get stuck in spectate forever
2454 				forceRespawn = true;
2455 			}
2456 			wantSpectate = spec;
2457 		}
2458 	} else {
2459 		if ( canModify && spec ) {
2460 			userInfo->Set( "ui_spectate", "Play" );
2461 			modifiedInfo |= true;
2462 		} else if ( spectating ) {
2463 			// allow player to leaving spectator mode if they were in it when si_spectators got turned off
2464 			forceRespawn = true;
2465 		}
2466 		wantSpectate = false;
2467 	}
2468 
2469 	newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 );
2470 	if ( ready != newready && gameLocal.mpGame.GetGameState() == idMultiplayerGame::WARMUP && !wantSpectate ) {
2471 		gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_07180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLanguageDict()->GetString( "#str_04300" ) : common->GetLanguageDict()->GetString( "#str_04301" ) );
2472 	}
2473 	ready = newready;
2474 	team = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Blue" ) == 0 );
2475 	// server maintains TDM balance
2476 	if ( canModify && gameLocal.gameType == GAME_TDM && !gameLocal.mpGame.IsInGame( entityNumber ) && g_balanceTDM.GetBool() ) {
2477 		modifiedInfo |= BalanceTDM( );
2478 	}
2479 	UpdateSkinSetup( false );
2480 
2481 	isChatting = userInfo->GetBool( "ui_chat", "0" );
2482 	if ( canModify && isChatting && AI_DEAD ) {
2483 		// if dead, always force chat icon off.
2484 		isChatting = false;
2485 		userInfo->SetBool( "ui_chat", false );
2486 		modifiedInfo |= true;
2487 	}
2488 
2489 	return modifiedInfo;
2490 }
2491 
2492 /*
2493 ===============
2494 idPlayer::UpdateHudAmmo
2495 ===============
2496 */
UpdateHudAmmo(idUserInterface * _hud)2497 void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) {
2498 	int inclip;
2499 	int ammoamount;
2500 
2501 	assert( weapon.GetEntity() );
2502 	assert( _hud );
2503 
2504 	inclip		= weapon.GetEntity()->AmmoInClip();
2505 	ammoamount	= weapon.GetEntity()->AmmoAvailable();
2506 	if ( ammoamount < 0 || !weapon.GetEntity()->IsReady() ) {
2507 		// show infinite ammo
2508 		_hud->SetStateString( "player_ammo", "" );
2509 		_hud->SetStateString( "player_totalammo", "" );
2510 	} else {
2511 		// show remaining ammo
2512 		_hud->SetStateString( "player_totalammo", va( "%i", ammoamount - inclip ) );
2513 		_hud->SetStateString( "player_ammo", weapon.GetEntity()->ClipSize() ? va( "%i", inclip ) : "--" );		// how much in the current clip
2514 		_hud->SetStateString( "player_clips", weapon.GetEntity()->ClipSize() ? va( "%i", ammoamount / weapon.GetEntity()->ClipSize() ) : "--" );
2515 		_hud->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount - inclip ) );
2516 	}
2517 
2518 	_hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) );
2519 	_hud->SetStateBool( "player_clip_empty", ( weapon.GetEntity()->ClipSize() ? inclip == 0 : false ) );
2520 	_hud->SetStateBool( "player_clip_low", ( weapon.GetEntity()->ClipSize() ? inclip <= weapon.GetEntity()->LowAmmo() : false ) );
2521 
2522 	_hud->HandleNamedEvent( "updateAmmo" );
2523 }
2524 
2525 /*
2526 ===============
2527 idPlayer::UpdateHudStats
2528 ===============
2529 */
UpdateHudStats(idUserInterface * _hud)2530 void idPlayer::UpdateHudStats( idUserInterface *_hud ) {
2531 	int staminapercentage;
2532 	float max_stamina;
2533 
2534 	assert( _hud );
2535 
2536 	max_stamina = pm_stamina.GetFloat();
2537 	if ( !max_stamina ) {
2538 		// stamina disabled, so show full stamina bar
2539 		staminapercentage = 100.0f;
2540 	} else {
2541 		staminapercentage = idMath::FtoiFast( 100.0f * stamina / max_stamina );
2542 	}
2543 
2544 	_hud->SetStateInt( "player_health", health );
2545 	_hud->SetStateInt( "player_stamina", staminapercentage );
2546 	_hud->SetStateInt( "player_armor", inventory.armor );
2547 	_hud->SetStateInt( "player_hr", heartRate );
2548 	_hud->SetStateInt( "player_nostamina", ( max_stamina == 0 ) ? 1 : 0 );
2549 
2550 	_hud->HandleNamedEvent( "updateArmorHealthAir" );
2551 
2552 	if ( healthPulse ) {
2553 		_hud->HandleNamedEvent( "healthPulse" );
2554 		StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL );
2555 		healthPulse = false;
2556 	}
2557 
2558 	if ( healthTake ) {
2559 		_hud->HandleNamedEvent( "healthPulse" );
2560 		StartSound( "snd_healthtake", SND_CHANNEL_ITEM, 0, false, NULL );
2561 		healthTake = false;
2562 	}
2563 
2564 	if ( inventory.ammoPulse ) {
2565 		_hud->HandleNamedEvent( "ammoPulse" );
2566 		inventory.ammoPulse = false;
2567 	}
2568 	if ( inventory.weaponPulse ) {
2569 		// We need to update the weapon hud manually, but not
2570 		// the armor/ammo/health because they are updated every
2571 		// frame no matter what
2572 		UpdateHudWeapon();
2573 		_hud->HandleNamedEvent( "weaponPulse" );
2574 		inventory.weaponPulse = false;
2575 	}
2576 	if ( inventory.armorPulse ) {
2577 		_hud->HandleNamedEvent( "armorPulse" );
2578 		inventory.armorPulse = false;
2579 	}
2580 
2581 	UpdateHudAmmo( _hud );
2582 }
2583 
2584 /*
2585 ===============
2586 idPlayer::UpdateHudWeapon
2587 ===============
2588 */
UpdateHudWeapon(bool flashWeapon)2589 void idPlayer::UpdateHudWeapon( bool flashWeapon ) {
2590 	idUserInterface *hud = idPlayer::hud;
2591 
2592 	// if updating the hud of a followed client
2593 	if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) {
2594 		idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] );
2595 		if ( p->spectating && p->spectator == entityNumber ) {
2596 			assert( p->hud );
2597 			hud = p->hud;
2598 		}
2599 	}
2600 
2601 	if ( !hud ) {
2602 		return;
2603 	}
2604 
2605 	for ( int i = 0; i < MAX_WEAPONS; i++ ) {
2606 		const char *weapnum = va( "def_weapon%d", i );
2607 		const char *hudWeap = va( "weapon%d", i );
2608 		int weapstate = 0;
2609 		if ( inventory.weapons & ( 1 << i ) ) {
2610 			const char *weap = spawnArgs.GetString( weapnum );
2611 			if ( weap && *weap ) {
2612 				weapstate++;
2613 			}
2614 			if ( idealWeapon == i ) {
2615 				weapstate++;
2616 			}
2617 		}
2618 		hud->SetStateInt( hudWeap, weapstate );
2619 	}
2620 	if ( flashWeapon ) {
2621 		hud->HandleNamedEvent( "weaponChange" );
2622 	}
2623 }
2624 
2625 /*
2626 ===============
2627 idPlayer::DrawHUD
2628 ===============
2629 */
DrawHUD(idUserInterface * _hud)2630 void idPlayer::DrawHUD( idUserInterface *_hud ) {
2631 
2632 	if ( !weapon.GetEntity() || influenceActive != INFLUENCE_NONE || privateCameraView || gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() ) {
2633 		return;
2634 	}
2635 
2636 	UpdateHudStats( _hud );
2637 
2638 	_hud->SetStateString( "weapicon", weapon.GetEntity()->Icon() );
2639 
2640 	// FIXME: this is temp to allow the sound meter to show up in the hud
2641 	// it should be commented out before shipping but the code can remain
2642 	// for mod developers to enable for the same functionality
2643 	_hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) );
2644 
2645 	weapon.GetEntity()->UpdateGUI();
2646 
2647 	_hud->Redraw( gameLocal.realClientTime );
2648 
2649 	// weapon targeting crosshair
2650 	if ( !GuiActive() ) {
2651 		if ( cursor && weapon.GetEntity()->ShowCrosshair() ) {
2652 			cursor->Redraw( gameLocal.realClientTime );
2653 		}
2654 	}
2655 }
2656 
2657 /*
2658 ===============
2659 idPlayer::EnterCinematic
2660 ===============
2661 */
EnterCinematic(void)2662 void idPlayer::EnterCinematic( void ) {
2663 	Hide();
2664 	StopAudioLog();
2665 	StopSound( SND_CHANNEL_PDA, false );
2666 	if ( hud ) {
2667 		hud->HandleNamedEvent( "radioChatterDown" );
2668 	}
2669 
2670 	physicsObj.SetLinearVelocity( vec3_origin );
2671 
2672 	SetState( "EnterCinematic" );
2673 	UpdateScript();
2674 
2675 	if ( weaponEnabled && weapon.GetEntity() ) {
2676 		weapon.GetEntity()->EnterCinematic();
2677 	}
2678 
2679 	AI_FORWARD		= false;
2680 	AI_BACKWARD		= false;
2681 	AI_STRAFE_LEFT	= false;
2682 	AI_STRAFE_RIGHT	= false;
2683 	AI_RUN			= false;
2684 	AI_ATTACK_HELD	= false;
2685 	AI_WEAPON_FIRED	= false;
2686 	AI_JUMP			= false;
2687 	AI_CROUCH		= false;
2688 	AI_ONGROUND		= true;
2689 	AI_ONLADDER		= false;
2690 	AI_DEAD			= ( health <= 0 );
2691 	AI_RUN			= false;
2692 	AI_PAIN			= false;
2693 	AI_HARDLANDING	= false;
2694 	AI_SOFTLANDING	= false;
2695 	AI_RELOAD		= false;
2696 	AI_TELEPORT		= false;
2697 	AI_TURN_LEFT	= false;
2698 	AI_TURN_RIGHT	= false;
2699 }
2700 
2701 /*
2702 ===============
2703 idPlayer::ExitCinematic
2704 ===============
2705 */
ExitCinematic(void)2706 void idPlayer::ExitCinematic( void ) {
2707 	Show();
2708 
2709 	if ( weaponEnabled && weapon.GetEntity() ) {
2710 		weapon.GetEntity()->ExitCinematic();
2711 	}
2712 
2713 	SetState( "ExitCinematic" );
2714 	UpdateScript();
2715 }
2716 
2717 /*
2718 =====================
2719 idPlayer::UpdateConditions
2720 =====================
2721 */
UpdateConditions(void)2722 void idPlayer::UpdateConditions( void ) {
2723 	idVec3	velocity;
2724 	float	forwardspeed;
2725 	float	sidespeed;
2726 
2727 	// minus the push velocity to avoid playing the walking animation and sounds when riding a mover
2728 	velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity();
2729 
2730 	if ( influenceActive ) {
2731 		AI_FORWARD		= false;
2732 		AI_BACKWARD		= false;
2733 		AI_STRAFE_LEFT	= false;
2734 		AI_STRAFE_RIGHT	= false;
2735 	} else if ( gameLocal.time - lastDmgTime < 500 ) {
2736 		forwardspeed = velocity * viewAxis[ 0 ];
2737 		sidespeed = velocity * viewAxis[ 1 ];
2738 		AI_FORWARD		= AI_ONGROUND && ( forwardspeed > 20.01f );
2739 		AI_BACKWARD		= AI_ONGROUND && ( forwardspeed < -20.01f );
2740 		AI_STRAFE_LEFT	= AI_ONGROUND && ( sidespeed > 20.01f );
2741 		AI_STRAFE_RIGHT	= AI_ONGROUND && ( sidespeed < -20.01f );
2742 	} else if ( xyspeed > MIN_BOB_SPEED ) {
2743 		AI_FORWARD		= AI_ONGROUND && ( usercmd.forwardmove > 0 );
2744 		AI_BACKWARD		= AI_ONGROUND && ( usercmd.forwardmove < 0 );
2745 		AI_STRAFE_LEFT	= AI_ONGROUND && ( usercmd.rightmove < 0 );
2746 		AI_STRAFE_RIGHT	= AI_ONGROUND && ( usercmd.rightmove > 0 );
2747 	} else {
2748 		AI_FORWARD		= false;
2749 		AI_BACKWARD		= false;
2750 		AI_STRAFE_LEFT	= false;
2751 		AI_STRAFE_RIGHT	= false;
2752 	}
2753 
2754 	AI_RUN			= ( usercmd.buttons & BUTTON_RUN ) && ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) );
2755 	AI_DEAD			= ( health <= 0 );
2756 }
2757 
2758 /*
2759 ==================
2760 WeaponFireFeedback
2761 
2762 Called when a weapon fires, generates head twitches, etc
2763 ==================
2764 */
WeaponFireFeedback(const idDict * weaponDef)2765 void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) {
2766 	// force a blink
2767 	blink_time = 0;
2768 
2769 	// play the fire animation
2770 	AI_WEAPON_FIRED = true;
2771 
2772 	// update view feedback
2773 	playerView.WeaponFireFeedback( weaponDef );
2774 }
2775 
2776 /*
2777 ===============
2778 idPlayer::StopFiring
2779 ===============
2780 */
StopFiring(void)2781 void idPlayer::StopFiring( void ) {
2782 	AI_ATTACK_HELD	= false;
2783 	AI_WEAPON_FIRED = false;
2784 	AI_RELOAD		= false;
2785 	if ( weapon.GetEntity() ) {
2786 		weapon.GetEntity()->EndAttack();
2787 	}
2788 }
2789 
2790 /*
2791 ===============
2792 idPlayer::FireWeapon
2793 ===============
2794 */
FireWeapon(void)2795 void idPlayer::FireWeapon( void ) {
2796 	idMat3 axis;
2797 	idVec3 muzzle;
2798 
2799 	if ( privateCameraView ) {
2800 		return;
2801 	}
2802 
2803 	if ( g_editEntityMode.GetInteger() ) {
2804 		GetViewPos( muzzle, axis );
2805 		if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) {
2806 			return;
2807 		}
2808 	}
2809 
2810 	if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) {
2811 		if ( weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) {
2812 			AI_ATTACK_HELD = true;
2813 			weapon.GetEntity()->BeginAttack();
2814 			if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) {
2815 				if ( hud ) {
2816 					hud->HandleNamedEvent( "soulCubeNotReady" );
2817 				}
2818 				SelectWeapon( previousWeapon, false );
2819 			}
2820 		} else {
2821 			NextBestWeapon();
2822 		}
2823 	}
2824 
2825 	if ( hud ) {
2826 		if ( tipUp ) {
2827 			HideTip();
2828 		}
2829 		// may want to track with with a bool as well
2830 		// keep from looking up named events so often
2831 		if ( objectiveUp ) {
2832 			HideObjective();
2833 		}
2834 	}
2835 }
2836 
2837 /*
2838 ===============
2839 idPlayer::CacheWeapons
2840 ===============
2841 */
CacheWeapons(void)2842 void idPlayer::CacheWeapons( void ) {
2843 	idStr	weap;
2844 	int		w;
2845 
2846 	// check if we have any weapons
2847 	if ( !inventory.weapons ) {
2848 		return;
2849 	}
2850 
2851 	for( w = 0; w < MAX_WEAPONS; w++ ) {
2852 		if ( inventory.weapons & ( 1 << w ) ) {
2853 			weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
2854 			if ( weap != "" ) {
2855 				idWeapon::CacheWeapon( weap );
2856 			} else {
2857 				inventory.weapons &= ~( 1 << w );
2858 			}
2859 		}
2860 	}
2861 }
2862 
2863 /*
2864 ===============
2865 idPlayer::Give
2866 ===============
2867 */
Give(const char * statname,const char * value)2868 bool idPlayer::Give( const char *statname, const char *value ) {
2869 	int amount;
2870 
2871 	if ( AI_DEAD ) {
2872 		return false;
2873 	}
2874 
2875 	if ( !idStr::Icmp( statname, "health" ) ) {
2876 		if ( health >= inventory.maxHealth ) {
2877 			return false;
2878 		}
2879 		amount = atoi( value );
2880 		if ( amount ) {
2881 			health += amount;
2882 			if ( health > inventory.maxHealth ) {
2883 				health = inventory.maxHealth;
2884 			}
2885 			if ( hud ) {
2886 				hud->HandleNamedEvent( "healthPulse" );
2887 			}
2888 		}
2889 
2890 	} else if ( !idStr::Icmp( statname, "stamina" ) ) {
2891 		if ( stamina >= 100 ) {
2892 			return false;
2893 		}
2894 		stamina += atof( value );
2895 		if ( stamina > 100 ) {
2896 			stamina = 100;
2897 		}
2898 
2899 	} else if ( !idStr::Icmp( statname, "heartRate" ) ) {
2900 		heartRate += atoi( value );
2901 		if ( heartRate > MAX_HEARTRATE ) {
2902 			heartRate = MAX_HEARTRATE;
2903 		}
2904 
2905 	} else if ( !idStr::Icmp( statname, "air" ) ) {
2906 		if ( airTics >= pm_airTics.GetInteger() ) {
2907 			return false;
2908 		}
2909 		airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger();
2910 		if ( airTics > pm_airTics.GetInteger() ) {
2911 			airTics = pm_airTics.GetInteger();
2912 		}
2913 	} else {
2914 		return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true );
2915 	}
2916 	return true;
2917 }
2918 
2919 
2920 /*
2921 ===============
2922 idPlayer::GiveHealthPool
2923 
2924 adds health to the player health pool
2925 ===============
2926 */
GiveHealthPool(float amt)2927 void idPlayer::GiveHealthPool( float amt ) {
2928 
2929 	if ( AI_DEAD ) {
2930 		return;
2931 	}
2932 
2933 	if ( health > 0 ) {
2934 		healthPool += amt;
2935 		if ( healthPool > inventory.maxHealth - health ) {
2936 			healthPool = inventory.maxHealth - health;
2937 		}
2938 		nextHealthPulse = gameLocal.time;
2939 	}
2940 }
2941 
2942 /*
2943 ===============
2944 idPlayer::GiveItem
2945 
2946 Returns false if the item shouldn't be picked up
2947 ===============
2948 */
GiveItem(idItem * item)2949 bool idPlayer::GiveItem( idItem *item ) {
2950 	int					i;
2951 	const idKeyValue	*arg;
2952 	idDict				attr;
2953 	bool				gave;
2954 	int					numPickup;
2955 
2956 	if ( gameLocal.isMultiplayer && spectating ) {
2957 		return false;
2958 	}
2959 
2960 	item->GetAttributes( attr );
2961 
2962 	gave = false;
2963 	numPickup = inventory.pickupItemNames.Num();
2964 	for( i = 0; i < attr.GetNumKeyVals(); i++ ) {
2965 		arg = attr.GetKeyVal( i );
2966 		if ( Give( arg->GetKey(), arg->GetValue() ) ) {
2967 			gave = true;
2968 		}
2969 	}
2970 
2971 	arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL );
2972 	if ( arg && hud ) {
2973 		// We need to update the weapon hud manually, but not
2974 		// the armor/ammo/health because they are updated every
2975 		// frame no matter what
2976 		UpdateHudWeapon( false );
2977 		hud->HandleNamedEvent( "weaponPulse" );
2978 	}
2979 
2980 	// display the pickup feedback on the hud
2981 	if ( gave && ( numPickup == inventory.pickupItemNames.Num() ) ) {
2982 		inventory.AddPickupName( item->spawnArgs.GetString( "inv_name" ), item->spawnArgs.GetString( "inv_icon" ) );
2983 	}
2984 
2985 	return gave;
2986 }
2987 
2988 /*
2989 ===============
2990 idPlayer::PowerUpModifier
2991 ===============
2992 */
PowerUpModifier(int type)2993 float idPlayer::PowerUpModifier( int type ) {
2994 	float mod = 1.0f;
2995 
2996 	if ( PowerUpActive( BERSERK ) ) {
2997 		switch( type ) {
2998 			case SPEED: {
2999 				mod *= 1.7f;
3000 				break;
3001 			}
3002 			case PROJECTILE_DAMAGE: {
3003 				mod *= 2.0f;
3004 				break;
3005 			}
3006 			case MELEE_DAMAGE: {
3007 				mod *= 30.0f;
3008 				break;
3009 			}
3010 			case MELEE_DISTANCE: {
3011 				mod *= 2.0f;
3012 				break;
3013 			}
3014 		}
3015 	}
3016 
3017 	if ( gameLocal.isMultiplayer && !gameLocal.isClient ) {
3018 		if ( PowerUpActive( MEGAHEALTH ) ) {
3019 			if ( healthPool <= 0 ) {
3020 				GiveHealthPool( 100 );
3021 			}
3022 		} else {
3023 			healthPool = 0;
3024 		}
3025 	}
3026 
3027 	return mod;
3028 }
3029 
3030 /*
3031 ===============
3032 idPlayer::PowerUpActive
3033 ===============
3034 */
PowerUpActive(int powerup) const3035 bool idPlayer::PowerUpActive( int powerup ) const {
3036 	return ( inventory.powerups & ( 1 << powerup ) ) != 0;
3037 }
3038 
3039 /*
3040 ===============
3041 idPlayer::GivePowerUp
3042 ===============
3043 */
GivePowerUp(int powerup,int time)3044 bool idPlayer::GivePowerUp( int powerup, int time ) {
3045 	const char *sound;
3046 	const char *skin;
3047 
3048 	if ( powerup >= 0 && powerup < MAX_POWERUPS ) {
3049 
3050 		if ( gameLocal.isServer ) {
3051 			idBitMsg	msg;
3052 			byte		msgBuf[MAX_EVENT_PARAM_SIZE];
3053 
3054 			msg.Init( msgBuf, sizeof( msgBuf ) );
3055 			msg.WriteShort( powerup );
3056 			msg.WriteBits( 1, 1 );
3057 			ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3058 		}
3059 
3060 		if ( powerup != MEGAHEALTH ) {
3061 			inventory.GivePowerUp( this, powerup, time );
3062 		}
3063 
3064 		const idDeclEntityDef *def = NULL;
3065 
3066 		switch( powerup ) {
3067 			case BERSERK: {
3068 				if ( spawnArgs.GetString( "snd_berserk_third", "", &sound ) ) {
3069 					StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_DEMONIC, 0, false, NULL );
3070 				}
3071 				if ( baseSkinName.Length() ) {
3072 					powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
3073 				}
3074 				if ( !gameLocal.isClient ) {
3075 					idealWeapon = 0;
3076 				}
3077 				break;
3078 			}
3079 			case INVISIBILITY: {
3080 				spawnArgs.GetString( "skin_invisibility", "", &skin );
3081 				powerUpSkin = declManager->FindSkin( skin );
3082 				// remove any decals from the model
3083 				if ( modelDefHandle != -1 ) {
3084 					gameRenderWorld->RemoveDecals( modelDefHandle );
3085 				}
3086 				if ( weapon.GetEntity() ) {
3087 					weapon.GetEntity()->UpdateSkin();
3088 				}
3089 				if ( spawnArgs.GetString( "snd_invisibility", "", &sound ) ) {
3090 					StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3091 				}
3092 				break;
3093 			}
3094 			case ADRENALINE: {
3095 				stamina = 100.0f;
3096 				break;
3097 			 }
3098 			case MEGAHEALTH: {
3099 				if ( spawnArgs.GetString( "snd_megahealth", "", &sound ) ) {
3100 					StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3101 				}
3102 				def = gameLocal.FindEntityDef( "powerup_megahealth", false );
3103 				if ( def ) {
3104 					health = def->dict.GetInt( "inv_health" );
3105 				}
3106 				break;
3107 			 }
3108 		}
3109 
3110 		if ( hud ) {
3111 			hud->HandleNamedEvent( "itemPickup" );
3112 		}
3113 
3114 		return true;
3115 	} else {
3116 		gameLocal.Warning( "Player given power up %i\n which is out of range", powerup );
3117 	}
3118 	return false;
3119 }
3120 
3121 /*
3122 ==============
3123 idPlayer::ClearPowerup
3124 ==============
3125 */
ClearPowerup(int i)3126 void idPlayer::ClearPowerup( int i ) {
3127 
3128 	if ( gameLocal.isServer ) {
3129 		idBitMsg	msg;
3130 		byte		msgBuf[MAX_EVENT_PARAM_SIZE];
3131 
3132 		msg.Init( msgBuf, sizeof( msgBuf ) );
3133 		msg.WriteShort( i );
3134 		msg.WriteBits( 0, 1 );
3135 		ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3136 	}
3137 
3138 	powerUpSkin = NULL;
3139 	inventory.powerups &= ~( 1 << i );
3140 	inventory.powerupEndTime[ i ] = 0;
3141 	switch( i ) {
3142 		case BERSERK: {
3143 			StopSound( SND_CHANNEL_DEMONIC, false );
3144 			break;
3145 		}
3146 		case INVISIBILITY: {
3147 			if ( weapon.GetEntity() ) {
3148 				weapon.GetEntity()->UpdateSkin();
3149 			}
3150 			break;
3151 		}
3152 	}
3153 }
3154 
3155 /*
3156 ==============
3157 idPlayer::UpdatePowerUps
3158 ==============
3159 */
UpdatePowerUps(void)3160 void idPlayer::UpdatePowerUps( void ) {
3161 	int i;
3162 
3163 	if ( !gameLocal.isClient ) {
3164 		for ( i = 0; i < MAX_POWERUPS; i++ ) {
3165 			if ( PowerUpActive( i ) && inventory.powerupEndTime[i] <= gameLocal.time ) {
3166 				ClearPowerup( i );
3167 			}
3168 		}
3169 	}
3170 
3171 	if ( health > 0 ) {
3172 		if ( powerUpSkin ) {
3173 			renderEntity.customSkin = powerUpSkin;
3174 		} else {
3175 			renderEntity.customSkin = skin;
3176 		}
3177 	}
3178 
3179 	if ( healthPool && gameLocal.time > nextHealthPulse && !AI_DEAD && health > 0 ) {
3180 		assert( !gameLocal.isClient );	// healthPool never be set on client
3181 		int amt = ( healthPool > 5 ) ? 5 : healthPool;
3182 		health += amt;
3183 		if ( health > inventory.maxHealth ) {
3184 			health = inventory.maxHealth;
3185 			healthPool = 0;
3186 		} else {
3187 			healthPool -= amt;
3188 		}
3189 		nextHealthPulse = gameLocal.time + HEALTHPULSE_TIME;
3190 		healthPulse = true;
3191 	}
3192 
3193 	if ( !gameLocal.inCinematic && influenceActive == 0 && g_skill.GetInteger() == 3 && gameLocal.time > nextHealthTake && !AI_DEAD && health > g_healthTakeLimit.GetInteger() ) {
3194 		assert( !gameLocal.isClient );	// healthPool never be set on client
3195 		health -= g_healthTakeAmt.GetInteger();
3196 		if ( health < g_healthTakeLimit.GetInteger() ) {
3197 			health = g_healthTakeLimit.GetInteger();
3198 		}
3199 		nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
3200 		healthTake = true;
3201 	}
3202 }
3203 
3204 /*
3205 ===============
3206 idPlayer::ClearPowerUps
3207 ===============
3208 */
ClearPowerUps(void)3209 void idPlayer::ClearPowerUps( void ) {
3210 	int i;
3211 	for ( i = 0; i < MAX_POWERUPS; i++ ) {
3212 		if ( PowerUpActive( i ) ) {
3213 			ClearPowerup( i );
3214 		}
3215 	}
3216 	inventory.ClearPowerUps();
3217 }
3218 
3219 /*
3220 ===============
3221 idPlayer::GiveInventoryItem
3222 ===============
3223 */
GiveInventoryItem(idDict * item)3224 bool idPlayer::GiveInventoryItem( idDict *item ) {
3225 	if ( gameLocal.isMultiplayer && spectating ) {
3226 		return false;
3227 	}
3228 	inventory.items.Append( new idDict( *item ) );
3229 	idItemInfo info;
3230 	const char* itemName = item->GetString( "inv_name" );
3231 	if ( idStr::Cmpn( itemName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
3232 		info.name = common->GetLanguageDict()->GetString( itemName );
3233 	} else {
3234 		info.name = itemName;
3235 	}
3236 	info.icon = item->GetString( "inv_icon" );
3237 	inventory.pickupItemNames.Append( info );
3238 	if ( hud ) {
3239 		hud->SetStateString( "itemicon", info.icon );
3240 		hud->HandleNamedEvent( "invPickup" );
3241 	}
3242 	return true;
3243 }
3244 
3245 /*
3246 ==============
3247 idPlayer::UpdateObjectiveInfo
3248 ==============
3249  */
UpdateObjectiveInfo(void)3250 void idPlayer::UpdateObjectiveInfo( void ) {
3251 	if ( objectiveSystem == NULL ) {
3252 		return;
3253 	}
3254 	objectiveSystem->SetStateString( "objective1", "" );
3255 	objectiveSystem->SetStateString( "objective2", "" );
3256 	objectiveSystem->SetStateString( "objective3", "" );
3257 	for ( int i = 0; i < inventory.objectiveNames.Num(); i++ ) {
3258 		objectiveSystem->SetStateString( va( "objective%i", i+1 ), "1" );
3259 		objectiveSystem->SetStateString( va( "objectivetitle%i", i+1 ), inventory.objectiveNames[i].title.c_str() );
3260 		objectiveSystem->SetStateString( va( "objectivetext%i", i+1 ), inventory.objectiveNames[i].text.c_str() );
3261 		objectiveSystem->SetStateString( va( "objectiveshot%i", i+1 ), inventory.objectiveNames[i].screenshot.c_str() );
3262 	}
3263 	objectiveSystem->StateChanged( gameLocal.time );
3264 }
3265 
3266 /*
3267 ===============
3268 idPlayer::GiveObjective
3269 ===============
3270 */
GiveObjective(const char * title,const char * text,const char * screenshot)3271 void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) {
3272 	idObjectiveInfo info;
3273 	info.title = title;
3274 	info.text = text;
3275 	info.screenshot = screenshot;
3276 	inventory.objectiveNames.Append( info );
3277 	ShowObjective( "newObjective" );
3278 	if ( hud ) {
3279 		hud->HandleNamedEvent( "newObjective" );
3280 	}
3281 }
3282 
3283 /*
3284 ===============
3285 idPlayer::CompleteObjective
3286 ===============
3287 */
CompleteObjective(const char * title)3288 void idPlayer::CompleteObjective( const char *title ) {
3289 	int c = inventory.objectiveNames.Num();
3290 	for ( int i = 0;  i < c; i++ ) {
3291 		if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) {
3292 			inventory.objectiveNames.RemoveIndex( i );
3293 			break;
3294 		}
3295 	}
3296 	ShowObjective( "newObjectiveComplete" );
3297 
3298 	if ( hud ) {
3299 		hud->HandleNamedEvent( "newObjectiveComplete" );
3300 	}
3301 }
3302 
3303 /*
3304 ===============
3305 idPlayer::GiveVideo
3306 ===============
3307 */
GiveVideo(const char * videoName,idDict * item)3308 void idPlayer::GiveVideo( const char *videoName, idDict *item ) {
3309 
3310 	if ( videoName == NULL || *videoName == 0 ) {
3311 		return;
3312 	}
3313 
3314 	inventory.videos.AddUnique( videoName );
3315 
3316 	if ( item ) {
3317 		idItemInfo info;
3318 		info.name = item->GetString( "inv_name" );
3319 		info.icon = item->GetString( "inv_icon" );
3320 		inventory.pickupItemNames.Append( info );
3321 	}
3322 	if ( hud ) {
3323 		hud->HandleNamedEvent( "videoPickup" );
3324 	}
3325 }
3326 
3327 /*
3328 ===============
3329 idPlayer::GiveSecurity
3330 ===============
3331 */
GiveSecurity(const char * security)3332 void idPlayer::GiveSecurity( const char *security ) {
3333 	GetPDA()->SetSecurity( security );
3334 	if ( hud ) {
3335 		hud->SetStateString( "pda_security", "1" );
3336 		hud->HandleNamedEvent( "securityPickup" );
3337 	}
3338 }
3339 
3340 /*
3341 ===============
3342 idPlayer::GiveEmail
3343 ===============
3344 */
GiveEmail(const char * emailName)3345 void idPlayer::GiveEmail( const char *emailName ) {
3346 
3347 	if ( emailName == NULL || *emailName == 0 ) {
3348 		return;
3349 	}
3350 
3351 	inventory.emails.AddUnique( emailName );
3352 	GetPDA()->AddEmail( emailName );
3353 
3354 	if ( hud ) {
3355 		hud->HandleNamedEvent( "emailPickup" );
3356 	}
3357 }
3358 
3359 /*
3360 ===============
3361 idPlayer::GivePDA
3362 ===============
3363 */
GivePDA(const char * pdaName,idDict * item)3364 void idPlayer::GivePDA( const char *pdaName, idDict *item )
3365 {
3366 	if ( gameLocal.isMultiplayer && spectating ) {
3367 		return;
3368 	}
3369 
3370 	if ( item ) {
3371 		inventory.pdaSecurity.AddUnique( item->GetString( "inv_name" ) );
3372 	}
3373 
3374 	if ( pdaName == NULL || *pdaName == 0 ) {
3375 		pdaName = "personal";
3376 	}
3377 
3378 	const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, pdaName ) );
3379 
3380 	inventory.pdas.AddUnique( pdaName );
3381 
3382 	// Copy any videos over
3383 	for ( int i = 0; i < pda->GetNumVideos(); i++ ) {
3384 		const idDeclVideo *video = pda->GetVideoByIndex( i );
3385 		if ( video ) {
3386 			inventory.videos.AddUnique( video->GetName() );
3387 		}
3388 	}
3389 
3390 	// This is kind of a hack, but it works nicely
3391 	// We don't want to display the 'you got a new pda' message during a map load
3392 	if ( gameLocal.GetFrameNum() > 10 ) {
3393 		if ( pda && hud ) {
3394 			idStr pdaName = pda->GetPdaName();
3395 			pdaName.RemoveColors();
3396 			hud->SetStateString( "pda", "1" );
3397 			hud->SetStateString( "pda_text", pdaName );
3398 			const char *sec = pda->GetSecurity();
3399 			hud->SetStateString( "pda_security", ( sec && *sec ) ? "1" : "0" );
3400 			hud->HandleNamedEvent( "pdaPickup" );
3401 		}
3402 
3403 		if ( inventory.pdas.Num() == 1 ) {
3404 			GetPDA()->RemoveAddedEmailsAndVideos();
3405 			if ( !objectiveSystemOpen ) {
3406 				TogglePDA();
3407 			}
3408 			objectiveSystem->HandleNamedEvent( "showPDATip" );
3409 			//ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_firstPDA" ), true );
3410 		}
3411 
3412 		if ( inventory.pdas.Num() > 1 && pda->GetNumVideos() > 0 && hud ) {
3413 			hud->HandleNamedEvent( "videoPickup" );
3414 		}
3415 	}
3416 }
3417 
3418 /*
3419 ===============
3420 idPlayer::FindInventoryItem
3421 ===============
3422 */
FindInventoryItem(const char * name)3423 idDict *idPlayer::FindInventoryItem( const char *name ) {
3424 	for ( int i = 0; i < inventory.items.Num(); i++ ) {
3425 		const char *iname = inventory.items[i]->GetString( "inv_name" );
3426 		if ( iname && *iname ) {
3427 			if ( idStr::Icmp( name, iname ) == 0 ) {
3428 				return inventory.items[i];
3429 			}
3430 		}
3431 	}
3432 	return NULL;
3433 }
3434 
3435 /*
3436 ===============
3437 idPlayer::RemoveInventoryItem
3438 ===============
3439 */
RemoveInventoryItem(const char * name)3440 void idPlayer::RemoveInventoryItem( const char *name ) {
3441 	idDict *item = FindInventoryItem(name);
3442 	if ( item ) {
3443 		RemoveInventoryItem( item );
3444 	}
3445 }
3446 
3447 /*
3448 ===============
3449 idPlayer::RemoveInventoryItem
3450 ===============
3451 */
RemoveInventoryItem(idDict * item)3452 void idPlayer::RemoveInventoryItem( idDict *item ) {
3453 	inventory.items.Remove( item );
3454 	delete item;
3455 }
3456 
3457 /*
3458 ===============
3459 idPlayer::GiveItem
3460 ===============
3461 */
GiveItem(const char * itemname)3462 void idPlayer::GiveItem( const char *itemname ) {
3463 	idDict args;
3464 
3465 	args.Set( "classname", itemname );
3466 	args.Set( "owner", name.c_str() );
3467 	gameLocal.SpawnEntityDef( args );
3468 	if ( hud ) {
3469 		hud->HandleNamedEvent( "itemPickup" );
3470 	}
3471 }
3472 
3473 /*
3474 ==================
3475 idPlayer::SlotForWeapon
3476 ==================
3477 */
SlotForWeapon(const char * weaponName)3478 int idPlayer::SlotForWeapon( const char *weaponName ) {
3479 	int i;
3480 
3481 	for( i = 0; i < MAX_WEAPONS; i++ ) {
3482 		const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
3483 		if ( !idStr::Cmp( weap, weaponName ) ) {
3484 			return i;
3485 		}
3486 	}
3487 
3488 	// not found
3489 	return -1;
3490 }
3491 
3492 /*
3493 ===============
3494 idPlayer::Reload
3495 ===============
3496 */
Reload(void)3497 void idPlayer::Reload( void ) {
3498 	if ( gameLocal.isClient ) {
3499 		return;
3500 	}
3501 
3502 	if ( spectating || gameLocal.inCinematic || influenceActive ) {
3503 		return;
3504 	}
3505 
3506 	if ( weapon.GetEntity() && weapon.GetEntity()->IsLinked() ) {
3507 		weapon.GetEntity()->Reload();
3508 	}
3509 }
3510 
3511 /*
3512 ===============
3513 idPlayer::NextBestWeapon
3514 ===============
3515 */
NextBestWeapon(void)3516 void idPlayer::NextBestWeapon( void ) {
3517 	const char *weap;
3518 	int w = MAX_WEAPONS;
3519 
3520 	if ( gameLocal.isClient || !weaponEnabled ) {
3521 		return;
3522 	}
3523 
3524 	while ( w > 0 ) {
3525 		w--;
3526 		weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3527 		if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) {
3528 			continue;
3529 		}
3530 		if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) {
3531 			continue;
3532 		}
3533 		break;
3534 	}
3535 	idealWeapon = w;
3536 	weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3537 	UpdateHudWeapon();
3538 }
3539 
3540 /*
3541 ===============
3542 idPlayer::NextWeapon
3543 ===============
3544 */
NextWeapon(void)3545 void idPlayer::NextWeapon( void ) {
3546 	const char *weap;
3547 	int w;
3548 
3549 	if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3550 		return;
3551 	}
3552 
3553 	if ( gameLocal.isClient ) {
3554 		return;
3555 	}
3556 
3557 	// check if we have any weapons
3558 	if ( !inventory.weapons ) {
3559 		return;
3560 	}
3561 
3562 	w = idealWeapon;
3563 	while( 1 ) {
3564 		w++;
3565 		if ( w >= MAX_WEAPONS ) {
3566 			w = 0;
3567 		}
3568 		weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3569 		if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3570 			continue;
3571 		}
3572 		if ( !weap[ 0 ] ) {
3573 			continue;
3574 		}
3575 		if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3576 			continue;
3577 		}
3578 		if ( inventory.HasAmmo( weap ) ) {
3579 			break;
3580 		}
3581 	}
3582 
3583 	if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3584 		idealWeapon = w;
3585 		weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3586 		UpdateHudWeapon();
3587 	}
3588 }
3589 
3590 /*
3591 ===============
3592 idPlayer::PrevWeapon
3593 ===============
3594 */
PrevWeapon(void)3595 void idPlayer::PrevWeapon( void ) {
3596 	const char *weap;
3597 	int w;
3598 
3599 	if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3600 		return;
3601 	}
3602 
3603 	if ( gameLocal.isClient ) {
3604 		return;
3605 	}
3606 
3607 	// check if we have any weapons
3608 	if ( !inventory.weapons ) {
3609 		return;
3610 	}
3611 
3612 	w = idealWeapon;
3613 	while( 1 ) {
3614 		w--;
3615 		if ( w < 0 ) {
3616 			w = MAX_WEAPONS - 1;
3617 		}
3618 		weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3619 		if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3620 			continue;
3621 		}
3622 		if ( !weap[ 0 ] ) {
3623 			continue;
3624 		}
3625 		if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3626 			continue;
3627 		}
3628 		if ( inventory.HasAmmo( weap ) ) {
3629 			break;
3630 		}
3631 	}
3632 
3633 	if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3634 		idealWeapon = w;
3635 		weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3636 		UpdateHudWeapon();
3637 	}
3638 }
3639 
3640 /*
3641 ===============
3642 idPlayer::SelectWeapon
3643 ===============
3644 */
SelectWeapon(int num,bool force)3645 void idPlayer::SelectWeapon( int num, bool force ) {
3646 	const char *weap;
3647 
3648 	if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) {
3649 		return;
3650 	}
3651 
3652 	if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) {
3653 		return;
3654 	}
3655 
3656 	if ( gameLocal.isClient ) {
3657 		return;
3658 	}
3659 
3660 	if ( ( num != weapon_pda ) && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
3661 		num = weapon_fists;
3662 		hiddenWeapon ^= 1;
3663 		if ( hiddenWeapon && weapon.GetEntity() ) {
3664 			weapon.GetEntity()->LowerWeapon();
3665 		} else {
3666 			weapon.GetEntity()->RaiseWeapon();
3667 		}
3668 	}
3669 
3670 	weap = spawnArgs.GetString( va( "def_weapon%d", num ) );
3671 	if ( !weap[ 0 ] ) {
3672 		gameLocal.Printf( "Invalid weapon\n" );
3673 		return;
3674 	}
3675 
3676 	if ( force || ( inventory.weapons & ( 1 << num ) ) ) {
3677 		if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) {
3678 			return;
3679 		}
3680 		if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) {
3681 			weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) );
3682 			if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) {
3683 				return;
3684 			}
3685 			idealWeapon = previousWeapon;
3686 		} else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) {
3687 			ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
3688 			return;
3689 		} else {
3690 			idealWeapon = num;
3691 		}
3692 		UpdateHudWeapon();
3693 	}
3694 }
3695 
3696 /*
3697 =================
3698 idPlayer::DropWeapon
3699 =================
3700 */
DropWeapon(bool died)3701 void idPlayer::DropWeapon( bool died ) {
3702 	idVec3 forward, up;
3703 	int inclip, ammoavailable;
3704 
3705 	assert( !gameLocal.isClient );
3706 
3707 	if ( spectating || weaponGone || weapon.GetEntity() == NULL ) {
3708 		return;
3709 	}
3710 
3711 	if ( ( !died && !weapon.GetEntity()->IsReady() ) || weapon.GetEntity()->IsReloading() ) {
3712 		return;
3713 	}
3714 	// ammoavailable is how many shots we can fire
3715 	// inclip is which amount is in clip right now
3716 	ammoavailable = weapon.GetEntity()->AmmoAvailable();
3717 	inclip = weapon.GetEntity()->AmmoInClip();
3718 
3719 	// don't drop a grenade if we have none left
3720 	if ( !idStr::Icmp( idWeapon::GetAmmoNameForNum( weapon.GetEntity()->GetAmmoType() ), "ammo_grenades" ) && ( ammoavailable - inclip <= 0 ) ) {
3721 		return;
3722 	}
3723 
3724 	// expect an ammo setup that makes sense before doing any dropping
3725 	// ammoavailable is -1 for infinite ammo, and weapons like chainsaw
3726 	// a bad ammo config usually indicates a bad weapon state, so we should not drop
3727 	// used to be an assertion check, but it still happens in edge cases
3728 	if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3729 		common->DPrintf( "idPlayer::DropWeapon: bad ammo setup\n" );
3730 		return;
3731 	}
3732 	idEntity *item = NULL;
3733 	if ( died ) {
3734 		// ain't gonna throw you no weapon if I'm dead
3735 		item = weapon.GetEntity()->DropItem( vec3_origin, 0, WEAPON_DROP_TIME, died );
3736 	} else {
3737 		viewAngles.ToVectors( &forward, NULL, &up );
3738 		item = weapon.GetEntity()->DropItem( 250.0f * forward + 150.0f * up, 500, WEAPON_DROP_TIME, died );
3739 	}
3740 	if ( !item ) {
3741 		return;
3742 	}
3743 	// set the appropriate ammo in the dropped object
3744 	const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" );
3745 	if ( keyval ) {
3746 		item->spawnArgs.SetInt( keyval->GetKey(), ammoavailable );
3747 		idStr inclipKey = keyval->GetKey();
3748 		inclipKey.Insert( "inclip_", 4 );
3749 		item->spawnArgs.SetInt( inclipKey, inclip );
3750 	}
3751 	if ( !died ) {
3752 		// remove from our local inventory completely
3753 		inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 );
3754 		weapon.GetEntity()->ResetAmmoClip();
3755 		NextWeapon();
3756 		weapon.GetEntity()->WeaponStolen();
3757 		weaponGone = true;
3758 	}
3759 }
3760 
3761 /*
3762 =================
3763 idPlayer::StealWeapon
3764 steal the target player's current weapon
3765 =================
3766 */
StealWeapon(idPlayer * player)3767 void idPlayer::StealWeapon( idPlayer *player ) {
3768 	assert( !gameLocal.isClient );
3769 
3770 	// make sure there's something to steal
3771 	idWeapon *player_weapon = static_cast< idWeapon * >( player->weapon.GetEntity() );
3772 	if ( !player_weapon || !player_weapon->CanDrop() || weaponGone ) {
3773 		return;
3774 	}
3775 	// steal - we need to effectively force the other player to abandon his weapon
3776 	int newweap = player->currentWeapon;
3777 	if ( newweap == -1 ) {
3778 		return;
3779 	}
3780 	// might be just dropped - check inventory
3781 	if ( ! ( player->inventory.weapons & ( 1 << newweap ) ) ) {
3782 		return;
3783 	}
3784 	const char *weapon_classname = spawnArgs.GetString( va( "def_weapon%d", newweap ) );
3785 	assert( weapon_classname );
3786 	int ammoavailable = player->weapon.GetEntity()->AmmoAvailable();
3787 	int inclip = player->weapon.GetEntity()->AmmoInClip();
3788 	if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3789 		// see DropWeapon
3790 		common->DPrintf( "idPlayer::StealWeapon: bad ammo setup\n" );
3791 		// we still steal the weapon, so let's use the default ammo levels
3792 		inclip = -1;
3793 		const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname );
3794 		assert( decl );
3795 		const idKeyValue *keypair = decl->dict.MatchPrefix( "inv_ammo_" );
3796 		assert( keypair );
3797 		ammoavailable = atoi( keypair->GetValue() );
3798 	}
3799 
3800 	player->weapon.GetEntity()->WeaponStolen();
3801 	player->inventory.Drop( player->spawnArgs, NULL, newweap );
3802 	player->SelectWeapon( weapon_fists, false );
3803 	// in case the robbed player is firing rounds with a continuous fire weapon like the chaingun/plasma etc.
3804 	// this will ensure the firing actually stops
3805 	player->weaponGone = true;
3806 
3807 	// give weapon, setup the ammo count
3808 	Give( "weapon", weapon_classname );
3809 	ammo_t ammo_i = player->inventory.AmmoIndexForWeaponClass( weapon_classname, NULL );
3810 	idealWeapon = newweap;
3811 	inventory.ammo[ ammo_i ] += ammoavailable;
3812 	inventory.clip[ newweap ] = inclip;
3813 }
3814 
3815 /*
3816 ===============
3817 idPlayer::ActiveGui
3818 ===============
3819 */
ActiveGui(void)3820 idUserInterface *idPlayer::ActiveGui( void ) {
3821 	if ( objectiveSystemOpen ) {
3822 		return objectiveSystem;
3823 	}
3824 
3825 	return focusUI;
3826 }
3827 
3828 /*
3829 ===============
3830 idPlayer::Weapon_Combat
3831 ===============
3832 */
Weapon_Combat(void)3833 void idPlayer::Weapon_Combat( void ) {
3834 	if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) {
3835 		return;
3836 	}
3837 
3838 	weapon.GetEntity()->RaiseWeapon();
3839 	if ( weapon.GetEntity()->IsReloading() ) {
3840 		if ( !AI_RELOAD ) {
3841 			AI_RELOAD = true;
3842 			SetState( "ReloadWeapon" );
3843 			UpdateScript();
3844 		}
3845 	} else {
3846 		AI_RELOAD = false;
3847 	}
3848 
3849 	if ( idealWeapon == weapon_soulcube && soulCubeProjectile.GetEntity() != NULL ) {
3850 		idealWeapon = currentWeapon;
3851 	}
3852 
3853 	if ( idealWeapon != currentWeapon ) {
3854 		if ( weaponCatchup ) {
3855 			assert( gameLocal.isClient );
3856 
3857 			currentWeapon = idealWeapon;
3858 			weaponGone = false;
3859 			animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3860 			weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3861 			animPrefix.Strip( "weapon_" );
3862 
3863 			weapon.GetEntity()->NetCatchup();
3864 			const function_t *newstate = GetScriptFunction( "NetCatchup" );
3865 			if ( newstate ) {
3866 				SetState( newstate );
3867 				UpdateScript();
3868 			}
3869 			weaponCatchup = false;
3870 		} else {
3871 			if ( weapon.GetEntity()->IsReady() ) {
3872 				weapon.GetEntity()->PutAway();
3873 			}
3874 
3875 			if ( weapon.GetEntity()->IsHolstered() ) {
3876 				assert( idealWeapon >= 0 );
3877 				assert( idealWeapon < MAX_WEAPONS );
3878 
3879 				if ( currentWeapon != weapon_pda && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) {
3880 					previousWeapon = currentWeapon;
3881 				}
3882 				currentWeapon = idealWeapon;
3883 				weaponGone = false;
3884 				animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3885 				weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3886 				animPrefix.Strip( "weapon_" );
3887 
3888 				weapon.GetEntity()->Raise();
3889 			}
3890 		}
3891 	} else {
3892 		weaponGone = false;	// if you drop and re-get weap, you may miss the = false above
3893 		if ( weapon.GetEntity()->IsHolstered() ) {
3894 			if ( !weapon.GetEntity()->AmmoAvailable() ) {
3895 				// weapons can switch automatically if they have no more ammo
3896 				NextBestWeapon();
3897 			} else {
3898 				weapon.GetEntity()->Raise();
3899 				state = GetScriptFunction( "RaiseWeapon" );
3900 				if ( state ) {
3901 					SetState( state );
3902 				}
3903 			}
3904 		}
3905 	}
3906 
3907 	// check for attack
3908 	AI_WEAPON_FIRED = false;
3909 	if ( !influenceActive ) {
3910 		if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) {
3911 			FireWeapon();
3912 		} else if ( oldButtons & BUTTON_ATTACK ) {
3913 			AI_ATTACK_HELD = false;
3914 			weapon.GetEntity()->EndAttack();
3915 		}
3916 	}
3917 
3918 	// update our ammo clip in our inventory
3919 	if ( ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) {
3920 		inventory.clip[ currentWeapon ] = weapon.GetEntity()->AmmoInClip();
3921 		if ( hud && ( currentWeapon == idealWeapon ) ) {
3922 			UpdateHudAmmo( hud );
3923 		}
3924 	}
3925 }
3926 
3927 /*
3928 ===============
3929 idPlayer::Weapon_NPC
3930 ===============
3931 */
Weapon_NPC(void)3932 void idPlayer::Weapon_NPC( void ) {
3933 	if ( idealWeapon != currentWeapon ) {
3934 		Weapon_Combat();
3935 	}
3936 	StopFiring();
3937 	weapon.GetEntity()->LowerWeapon();
3938 
3939 	if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
3940 		buttonMask |= BUTTON_ATTACK;
3941 		focusCharacter->TalkTo( this );
3942 	}
3943 }
3944 
3945 /*
3946 ===============
3947 idPlayer::LowerWeapon
3948 ===============
3949 */
LowerWeapon(void)3950 void idPlayer::LowerWeapon( void ) {
3951 	if ( weapon.GetEntity() && !weapon.GetEntity()->IsHidden() ) {
3952 		weapon.GetEntity()->LowerWeapon();
3953 	}
3954 }
3955 
3956 /*
3957 ===============
3958 idPlayer::RaiseWeapon
3959 ===============
3960 */
RaiseWeapon(void)3961 void idPlayer::RaiseWeapon( void ) {
3962 	if ( weapon.GetEntity() && weapon.GetEntity()->IsHidden() ) {
3963 		weapon.GetEntity()->RaiseWeapon();
3964 	}
3965 }
3966 
3967 /*
3968 ===============
3969 idPlayer::WeaponLoweringCallback
3970 ===============
3971 */
WeaponLoweringCallback(void)3972 void idPlayer::WeaponLoweringCallback( void ) {
3973 	SetState( "LowerWeapon" );
3974 	UpdateScript();
3975 }
3976 
3977 /*
3978 ===============
3979 idPlayer::WeaponRisingCallback
3980 ===============
3981 */
WeaponRisingCallback(void)3982 void idPlayer::WeaponRisingCallback( void ) {
3983 	SetState( "RaiseWeapon" );
3984 	UpdateScript();
3985 }
3986 
3987 /*
3988 ===============
3989 idPlayer::Weapon_GUI
3990 ===============
3991 */
Weapon_GUI(void)3992 void idPlayer::Weapon_GUI( void ) {
3993 
3994 	if ( !objectiveSystemOpen ) {
3995 		if ( idealWeapon != currentWeapon ) {
3996 			Weapon_Combat();
3997 		}
3998 		StopFiring();
3999 		weapon.GetEntity()->LowerWeapon();
4000 	}
4001 
4002 	// disable click prediction for the GUIs. handy to check the state sync does the right thing
4003 	if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) {
4004 		return;
4005 	}
4006 
4007 	if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) {
4008 		sysEvent_t ev;
4009 		const char *command = NULL;
4010 		bool updateVisuals = false;
4011 
4012 		idUserInterface *ui = ActiveGui();
4013 		if ( ui ) {
4014 			ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 );
4015 			command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals );
4016 			if ( updateVisuals && focusGUIent && ui == focusUI ) {
4017 				focusGUIent->UpdateVisuals();
4018 			}
4019 		}
4020 		if ( gameLocal.isClient ) {
4021 			// we predict enough, but don't want to execute commands
4022 			return;
4023 		}
4024 		if ( focusGUIent ) {
4025 			HandleGuiCommands( focusGUIent, command );
4026 		} else {
4027 			HandleGuiCommands( this, command );
4028 		}
4029 	}
4030 }
4031 
4032 /*
4033 ===============
4034 idPlayer::UpdateWeapon
4035 ===============
4036 */
UpdateWeapon(void)4037 void idPlayer::UpdateWeapon( void ) {
4038 	if ( health <= 0 ) {
4039 		return;
4040 	}
4041 
4042 	assert( !spectating );
4043 
4044 	if ( gameLocal.isClient ) {
4045 		// clients need to wait till the weapon and it's world model entity
4046 		// are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity )
4047 		if ( !weapon.GetEntity()->IsWorldModelReady() ) {
4048 			return;
4049 		}
4050 	}
4051 
4052 	// always make sure the weapon is correctly setup before accessing it
4053 	if ( !weapon.GetEntity()->IsLinked() ) {
4054 		if ( idealWeapon != -1 ) {
4055 			animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
4056 			weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] );
4057 			assert( weapon.GetEntity()->IsLinked() );
4058 		} else {
4059 			return;
4060 		}
4061 	}
4062 
4063 	if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) {
4064 		HideTip();
4065 	}
4066 
4067 	if ( g_dragEntity.GetBool() ) {
4068 		StopFiring();
4069 		weapon.GetEntity()->LowerWeapon();
4070 		dragEntity.Update( this );
4071 	} else if ( ActiveGui() ) {
4072 		// gui handling overrides weapon use
4073 		Weapon_GUI();
4074 	} else	if ( focusCharacter && ( focusCharacter->health > 0 ) ) {
4075 		Weapon_NPC();
4076 	} else {
4077 		Weapon_Combat();
4078 	}
4079 
4080 	if ( hiddenWeapon ) {
4081 		weapon.GetEntity()->LowerWeapon();
4082 	}
4083 
4084 	// update weapon state, particles, dlights, etc
4085 	weapon.GetEntity()->PresentWeapon( showWeaponViewModel );
4086 }
4087 
4088 /*
4089 ===============
4090 idPlayer::SpectateFreeFly
4091 ===============
4092 */
SpectateFreeFly(bool force)4093 void idPlayer::SpectateFreeFly( bool force ) {
4094 	idPlayer	*player;
4095 	idVec3		newOrig;
4096 	idVec3		spawn_origin;
4097 	idAngles	spawn_angles;
4098 
4099 	player = gameLocal.GetClientByNum( spectator );
4100 	if ( force || gameLocal.time > lastSpectateChange ) {
4101 		spectator = entityNumber;
4102 		if ( player && player != this && !player->spectating && !player->IsInTeleport() ) {
4103 			newOrig = player->GetPhysics()->GetOrigin();
4104 			if ( player->physicsObj.IsCrouching() ) {
4105 				newOrig[ 2 ] += pm_crouchviewheight.GetFloat();
4106 			} else {
4107 				newOrig[ 2 ] += pm_normalviewheight.GetFloat();
4108 			}
4109 			newOrig[ 2 ] += SPECTATE_RAISE;
4110 			idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
4111 			idVec3 start = player->GetPhysics()->GetOrigin();
4112 			start[2] += pm_spectatebbox.GetFloat() * 0.5f;
4113 			trace_t t;
4114 			// assuming spectate bbox is inside stand or crouch box
4115 			gameLocal.clip.TraceBounds( t, start, newOrig, b, MASK_PLAYERSOLID, player );
4116 			newOrig.Lerp( start, newOrig, t.fraction );
4117 			SetOrigin( newOrig );
4118 			idAngles angle = player->viewAngles;
4119 			angle[ 2 ] = 0;
4120 			SetViewAngles( angle );
4121 		} else {
4122 			SelectInitialSpawnPoint( spawn_origin, spawn_angles );
4123 			spawn_origin[ 2 ] += pm_normalviewheight.GetFloat();
4124 			spawn_origin[ 2 ] += SPECTATE_RAISE;
4125 			SetOrigin( spawn_origin );
4126 			SetViewAngles( spawn_angles );
4127 		}
4128 		lastSpectateChange = gameLocal.time + 500;
4129 	}
4130 }
4131 
4132 /*
4133 ===============
4134 idPlayer::SpectateCycle
4135 ===============
4136 */
SpectateCycle(void)4137 void idPlayer::SpectateCycle( void ) {
4138 	idPlayer *player;
4139 
4140 	if ( gameLocal.time > lastSpectateChange ) {
4141 		int latchedSpectator = spectator;
4142 		spectator = gameLocal.GetNextClientNum( spectator );
4143 		player = gameLocal.GetClientByNum( spectator );
4144 		assert( player ); // never call here when the current spectator is wrong
4145 		// ignore other spectators
4146 		while ( latchedSpectator != spectator && player->spectating ) {
4147 			spectator = gameLocal.GetNextClientNum( spectator );
4148 			player = gameLocal.GetClientByNum( spectator );
4149 		}
4150 		lastSpectateChange = gameLocal.time + 500;
4151 	}
4152 }
4153 
4154 /*
4155 ===============
4156 idPlayer::UpdateSpectating
4157 ===============
4158 */
UpdateSpectating(void)4159 void idPlayer::UpdateSpectating( void ) {
4160 	assert( spectating );
4161 	assert( !gameLocal.isClient );
4162 	assert( IsHidden() );
4163 	idPlayer *player;
4164 	if ( !gameLocal.isMultiplayer ) {
4165 		return;
4166 	}
4167 	player = gameLocal.GetClientByNum( spectator );
4168 	if ( !player || ( player->spectating && player != this ) ) {
4169 		SpectateFreeFly( true );
4170 	} else if ( usercmd.upmove > 0 ) {
4171 		SpectateFreeFly( false );
4172 	} else if ( usercmd.buttons & BUTTON_ATTACK ) {
4173 		SpectateCycle();
4174 	}
4175 }
4176 
4177 /*
4178 ===============
4179 idPlayer::HandleSingleGuiCommand
4180 ===============
4181 */
HandleSingleGuiCommand(idEntity * entityGui,idLexer * src)4182 bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) {
4183 	idToken token;
4184 
4185 	if ( !src->ReadToken( &token ) ) {
4186 		return false;
4187 	}
4188 
4189 	if ( token == ";" ) {
4190 		return false;
4191 	}
4192 
4193 	if ( token.Icmp( "addhealth" ) == 0 ) {
4194 		if ( entityGui && health < 100 ) {
4195 			int _health = entityGui->spawnArgs.GetInt( "gui_parm1" );
4196 			int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health;
4197 			_health -= amt;
4198 			entityGui->spawnArgs.SetInt( "gui_parm1", _health );
4199 			if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) {
4200 				entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health );
4201 			}
4202 			health += amt;
4203 			if ( health > 100 ) {
4204 				health = 100;
4205 			}
4206 		}
4207 		return true;
4208 	}
4209 
4210 	if ( token.Icmp( "ready" ) == 0 ) {
4211 		PerformImpulse( IMPULSE_17 );
4212 		return true;
4213 	}
4214 
4215 	if ( token.Icmp( "updatepda" ) == 0 ) {
4216 		UpdatePDAInfo( true );
4217 		return true;
4218 	}
4219 
4220 	if ( token.Icmp( "updatepda2" ) == 0 ) {
4221 		UpdatePDAInfo( false );
4222 		return true;
4223 	}
4224 
4225 	if ( token.Icmp( "stoppdavideo" ) == 0 ) {
4226 		if ( objectiveSystem && objectiveSystemOpen && pdaVideoWave.Length() > 0 ) {
4227 			StopSound( SND_CHANNEL_PDA, false );
4228 		}
4229 		return true;
4230 	}
4231 
4232 	if ( token.Icmp( "close" ) == 0 ) {
4233 		if ( objectiveSystem && objectiveSystemOpen ) {
4234 			TogglePDA();
4235 		}
4236 	}
4237 
4238 	if ( token.Icmp( "playpdavideo" ) == 0 ) {
4239 		if ( objectiveSystem && objectiveSystemOpen && pdaVideo.Length() > 0 ) {
4240 			const idMaterial *mat = declManager->FindMaterial( pdaVideo );
4241 			if ( mat ) {
4242 				int c = mat->GetNumStages();
4243 				for ( int i = 0; i < c; i++ ) {
4244 					const shaderStage_t *stage = mat->GetStage(i);
4245 					if ( stage && stage->texture.cinematic ) {
4246 						stage->texture.cinematic->ResetTime( gameLocal.time );
4247 					}
4248 				}
4249 				if ( pdaVideoWave.Length() ) {
4250 					const idSoundShader *shader = declManager->FindSound( pdaVideoWave );
4251 					StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, NULL );
4252 				}
4253 			}
4254 		}
4255 	}
4256 
4257 	if ( token.Icmp( "playpdaaudio" ) == 0 ) {
4258 		if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4259 			const idSoundShader *shader = declManager->FindSound( pdaAudio );
4260 			int ms;
4261 			StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, &ms );
4262 			StartAudioLog();
4263 			CancelEvents( &EV_Player_StopAudioLog );
4264 			PostEventMS( &EV_Player_StopAudioLog, ms + 150 );
4265 		}
4266 		return true;
4267 	}
4268 
4269 	if ( token.Icmp( "stoppdaaudio" ) == 0 ) {
4270 		if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4271 			// idSoundShader *shader = declManager->FindSound( pdaAudio );
4272 			StopAudioLog();
4273 			StopSound( SND_CHANNEL_PDA, false );
4274 		}
4275 		return true;
4276 	}
4277 
4278 	src->UnreadToken( &token );
4279 	return false;
4280 }
4281 
4282 /*
4283 ==============
4284 idPlayer::Collide
4285 ==============
4286 */
Collide(const trace_t & collision,const idVec3 & velocity)4287 bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) {
4288 	idEntity *other;
4289 
4290 	if ( gameLocal.isClient ) {
4291 		return false;
4292 	}
4293 
4294 	other = gameLocal.entities[ collision.c.entityNum ];
4295 	if ( other ) {
4296 		other->Signal( SIG_TOUCH );
4297 		if ( !spectating ) {
4298 			if ( other->RespondsTo( EV_Touch ) ) {
4299 				other->ProcessEvent( &EV_Touch, this, &collision );
4300 			}
4301 		} else {
4302 			if ( other->RespondsTo( EV_SpectatorTouch ) ) {
4303 				other->ProcessEvent( &EV_SpectatorTouch, this, &collision );
4304 			}
4305 		}
4306 	}
4307 	return false;
4308 }
4309 
4310 
4311 /*
4312 ================
4313 idPlayer::UpdateLocation
4314 
4315 Searches nearby locations
4316 ================
4317 */
UpdateLocation(void)4318 void idPlayer::UpdateLocation( void ) {
4319 	if ( hud ) {
4320 		idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() );
4321 		if ( locationEntity ) {
4322 			hud->SetStateString( "location", locationEntity->GetLocation() );
4323 		} else {
4324 			hud->SetStateString( "location", common->GetLanguageDict()->GetString( "#str_02911" ) );
4325 		}
4326 	}
4327 }
4328 
4329 /*
4330 ================
4331 idPlayer::ClearFocus
4332 
4333 Clears the focus cursor
4334 ================
4335 */
ClearFocus(void)4336 void idPlayer::ClearFocus( void ) {
4337 	focusCharacter	= NULL;
4338 	focusGUIent		= NULL;
4339 	focusUI			= NULL;
4340 	focusVehicle	= NULL;
4341 	talkCursor		= 0;
4342 }
4343 
4344 /*
4345 ================
4346 idPlayer::UpdateFocus
4347 
4348 Searches nearby entities for interactive guis, possibly making one of them
4349 the focus and sending it a mouse move event
4350 ================
4351 */
UpdateFocus(void)4352 void idPlayer::UpdateFocus( void ) {
4353 	idClipModel *clipModelList[ MAX_GENTITIES ];
4354 	idClipModel *clip;
4355 	int			listedClipModels;
4356 	idEntity	*oldFocus;
4357 	idEntity	*ent;
4358 	idUserInterface *oldUI;
4359 	idAI		*oldChar;
4360 	int			oldTalkCursor;
4361 	int			i, j;
4362 	idVec3		start, end;
4363 	bool		allowFocus;
4364 	const char *command;
4365 	trace_t		trace;
4366 	guiPoint_t	pt;
4367 	const idKeyValue *kv;
4368 	sysEvent_t	ev;
4369 	idUserInterface *ui;
4370 
4371 	if ( gameLocal.inCinematic ) {
4372 		return;
4373 	}
4374 
4375 	// only update the focus character when attack button isn't pressed so players
4376 	// can still chainsaw NPC's
4377 	if ( gameLocal.isMultiplayer || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK ) ) ) {
4378 		allowFocus = false;
4379 	} else {
4380 		allowFocus = true;
4381 	}
4382 
4383 	oldFocus		= focusGUIent;
4384 	oldUI			= focusUI;
4385 	oldChar			= focusCharacter;
4386 	oldTalkCursor	= talkCursor;
4387 
4388 	if ( focusTime <= gameLocal.time ) {
4389 		ClearFocus();
4390 	}
4391 
4392 	// don't let spectators interact with GUIs
4393 	if ( spectating ) {
4394 		return;
4395 	}
4396 
4397 	start = GetEyePosition();
4398 	end = start + viewAngles.ToForward() * 80.0f;
4399 
4400 	// player identification -> names to the hud
4401 	if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) {
4402 		idVec3 end = start + viewAngles.ToForward() * 768.0f;
4403 		gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4404 		int iclient = -1;
4405 		if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) {
4406 			iclient = trace.c.entityNum;
4407 		}
4408 		if ( MPAim != iclient ) {
4409 			lastMPAim = MPAim;
4410 			MPAim = iclient;
4411 			lastMPAimTime = gameLocal.realClientTime;
4412 		}
4413 	}
4414 
4415 	idBounds bounds( start );
4416 	bounds.AddPoint( end );
4417 
4418 	listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
4419 
4420 	// no pretense at sorting here, just assume that there will only be one active
4421 	// gui within range along the trace
4422 	for ( i = 0; i < listedClipModels; i++ ) {
4423 		clip = clipModelList[ i ];
4424 		ent = clip->GetEntity();
4425 
4426 		if ( ent->IsHidden() ) {
4427 			continue;
4428 		}
4429 
4430 		if ( allowFocus ) {
4431 			if ( ent->IsType( idAFAttachment::Type ) ) {
4432 				idEntity *body = static_cast<idAFAttachment *>( ent )->GetBody();
4433 				if ( body && body->IsType( idAI::Type ) && ( static_cast<idAI *>( body )->GetTalkState() >= TALK_OK ) ) {
4434 					gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4435 					if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4436 						ClearFocus();
4437 						focusCharacter = static_cast<idAI *>( body );
4438 						talkCursor = 1;
4439 						focusTime = gameLocal.time + FOCUS_TIME;
4440 						break;
4441 					}
4442 				}
4443 				continue;
4444 			}
4445 
4446 			if ( ent->IsType( idAI::Type ) ) {
4447 				if ( static_cast<idAI *>( ent )->GetTalkState() >= TALK_OK ) {
4448 					gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4449 					if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4450 						ClearFocus();
4451 						focusCharacter = static_cast<idAI *>( ent );
4452 						talkCursor = 1;
4453 						focusTime = gameLocal.time + FOCUS_TIME;
4454 						break;
4455 					}
4456 				}
4457 				continue;
4458 			}
4459 
4460 			if ( ent->IsType( idAFEntity_Vehicle::Type ) ) {
4461 				gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4462 				if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4463 					ClearFocus();
4464 					focusVehicle = static_cast<idAFEntity_Vehicle *>( ent );
4465 					focusTime = gameLocal.time + FOCUS_TIME;
4466 					break;
4467 				}
4468 				continue;
4469 			}
4470 		}
4471 
4472 		if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) {
4473 			continue;
4474 		}
4475 
4476 		if ( ent->spawnArgs.GetBool( "inv_item" ) ) {
4477 			// don't allow guis on pickup items focus
4478 			continue;
4479 		}
4480 
4481 		pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end );
4482 		if ( pt.x != -1 ) {
4483 			// we have a hit
4484 			renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity();
4485 			if ( !focusGUIrenderEntity ) {
4486 				continue;
4487 			}
4488 
4489 			if ( pt.guiId == 1 ) {
4490 				ui = focusGUIrenderEntity->gui[ 0 ];
4491 			} else if ( pt.guiId == 2 ) {
4492 				ui = focusGUIrenderEntity->gui[ 1 ];
4493 			} else {
4494 				ui = focusGUIrenderEntity->gui[ 2 ];
4495 			}
4496 
4497 			if ( ui == NULL ) {
4498 				continue;
4499 			}
4500 
4501 			ClearFocus();
4502 			focusGUIent = ent;
4503 			focusUI = ui;
4504 
4505 			if ( oldFocus != ent ) {
4506 				// new activation
4507 				// going to see if we have anything in inventory a gui might be interested in
4508 				// need to enumerate inventory items
4509 				focusUI->SetStateInt( "inv_count", inventory.items.Num() );
4510 				for ( j = 0; j < inventory.items.Num(); j++ ) {
4511 					idDict *item = inventory.items[ j ];
4512 					const char *iname = item->GetString( "inv_name" );
4513 					const char *iicon = item->GetString( "inv_icon" );
4514 					const char *itext = item->GetString( "inv_text" );
4515 
4516 					focusUI->SetStateString( va( "inv_name_%i", j), iname );
4517 					focusUI->SetStateString( va( "inv_icon_%i", j), iicon );
4518 					focusUI->SetStateString( va( "inv_text_%i", j), itext );
4519 					kv = item->MatchPrefix("inv_id", NULL);
4520 					if ( kv ) {
4521 						focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
4522 					}
4523 					focusUI->SetStateInt( iname, 1 );
4524 				}
4525 
4526 
4527 				for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) {
4528 					const char *p = inventory.pdaSecurity[ j ];
4529 					if ( p && *p ) {
4530 						focusUI->SetStateInt( p, 1 );
4531 					}
4532 				}
4533 
4534 				int staminapercentage = ( int )( 100.0f * stamina / pm_stamina.GetFloat() );
4535 				focusUI->SetStateString( "player_health", va("%i", health ) );
4536 				focusUI->SetStateString( "player_stamina", va( "%i%%", staminapercentage ) );
4537 				focusUI->SetStateString( "player_armor", va( "%i%%", inventory.armor ) );
4538 
4539 				kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL );
4540 				while ( kv ) {
4541 					focusUI->SetStateString( kv->GetKey(), kv->GetValue() );
4542 					kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv );
4543 				}
4544 			}
4545 
4546 			// clamp the mouse to the corner
4547 			ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
4548 			command = focusUI->HandleEvent( &ev, gameLocal.time );
4549 			HandleGuiCommands( focusGUIent, command );
4550 
4551 			// move to an absolute position
4552 			ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT );
4553 			command = focusUI->HandleEvent( &ev, gameLocal.time );
4554 			HandleGuiCommands( focusGUIent, command );
4555 			focusTime = gameLocal.time + FOCUS_GUI_TIME;
4556 			break;
4557 		}
4558 	}
4559 
4560 	if ( focusGUIent && focusUI ) {
4561 		if ( !oldFocus || oldFocus != focusGUIent ) {
4562 			command = focusUI->Activate( true, gameLocal.time );
4563 			HandleGuiCommands( focusGUIent, command );
4564 			StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL );
4565 			// HideTip();
4566 			// HideObjective();
4567 		}
4568 	} else if ( oldFocus && oldUI ) {
4569 		command = oldUI->Activate( false, gameLocal.time );
4570 		HandleGuiCommands( oldFocus, command );
4571 		StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL );
4572 	}
4573 
4574 	if ( cursor && ( oldTalkCursor != talkCursor ) ) {
4575 		cursor->SetStateInt( "talkcursor", talkCursor );
4576 	}
4577 
4578 	if ( oldChar != focusCharacter && hud ) {
4579 		if ( focusCharacter ) {
4580 			hud->SetStateString( "npc", focusCharacter->spawnArgs.GetString( "npc_name", "Joe" ) );
4581 			hud->HandleNamedEvent( "showNPC" );
4582 			// HideTip();
4583 			// HideObjective();
4584 		} else {
4585 			hud->SetStateString( "npc", "" );
4586 			hud->HandleNamedEvent( "hideNPC" );
4587 		}
4588 	}
4589 }
4590 
4591 /*
4592 =================
4593 idPlayer::CrashLand
4594 
4595 Check for hard landings that generate sound events
4596 =================
4597 */
CrashLand(const idVec3 & oldOrigin,const idVec3 & oldVelocity)4598 void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) {
4599 	idVec3		origin, velocity;
4600 	idVec3		gravityVector, gravityNormal;
4601 	float		delta;
4602 	float		hardDelta, fatalDelta;
4603 	float		dist;
4604 	float		vel, acc;
4605 	float		t;
4606 	float		a, b, c, den;
4607 	waterLevel_t waterLevel;
4608 	bool		noDamage;
4609 
4610 	AI_SOFTLANDING = false;
4611 	AI_HARDLANDING = false;
4612 
4613 	// if the player is not on the ground
4614 	if ( !physicsObj.HasGroundContacts() ) {
4615 		return;
4616 	}
4617 
4618 	gravityNormal = physicsObj.GetGravityNormal();
4619 
4620 	// if the player wasn't going down
4621 	if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) {
4622 		return;
4623 	}
4624 
4625 	waterLevel = physicsObj.GetWaterLevel();
4626 
4627 	// never take falling damage if completely underwater
4628 	if ( waterLevel == WATERLEVEL_HEAD ) {
4629 		return;
4630 	}
4631 
4632 	// no falling damage if touching a nodamage surface
4633 	noDamage = false;
4634 	for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) {
4635 		const contactInfo_t &contact = physicsObj.GetContact( i );
4636 		if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) {
4637 			noDamage = true;
4638 			StartSound( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL );
4639 			break;
4640 		}
4641 	}
4642 
4643 	origin = GetPhysics()->GetOrigin();
4644 	gravityVector = physicsObj.GetGravity();
4645 
4646 	// calculate the exact velocity on landing
4647 	dist = ( origin - oldOrigin ) * -gravityNormal;
4648 	vel = oldVelocity * -gravityNormal;
4649 	acc = -gravityVector.Length();
4650 
4651 	a = acc / 2.0f;
4652 	b = vel;
4653 	c = -dist;
4654 
4655 	den = b * b - 4.0f * a * c;
4656 	if ( den < 0 ) {
4657 		return;
4658 	}
4659 	t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a );
4660 
4661 	delta = vel + t * acc;
4662 	delta = delta * delta * 0.0001;
4663 
4664 	// reduce falling damage if there is standing water
4665 	if ( waterLevel == WATERLEVEL_WAIST ) {
4666 		delta *= 0.25f;
4667 	}
4668 	if ( waterLevel == WATERLEVEL_FEET ) {
4669 		delta *= 0.5f;
4670 	}
4671 
4672 	if ( delta < 1.0f ) {
4673 		return;
4674 	}
4675 
4676 	// allow falling a bit further for multiplayer
4677 	if ( gameLocal.isMultiplayer ) {
4678 		fatalDelta	= 75.0f;
4679 		hardDelta	= 50.0f;
4680 	} else {
4681 		fatalDelta	= 65.0f;
4682 		hardDelta	= 45.0f;
4683 	}
4684 
4685 	if ( delta > fatalDelta ) {
4686 		AI_HARDLANDING = true;
4687 		landChange = -32;
4688 		landTime = gameLocal.time;
4689 		if ( !noDamage ) {
4690 			pain_debounce_time = gameLocal.time + pain_delay + 1;  // ignore pain since we'll play our landing anim
4691 			Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 );
4692 		}
4693 	} else if ( delta > hardDelta ) {
4694 		AI_HARDLANDING = true;
4695 		landChange	= -24;
4696 		landTime	= gameLocal.time;
4697 		if ( !noDamage ) {
4698 			pain_debounce_time = gameLocal.time + pain_delay + 1;  // ignore pain since we'll play our landing anim
4699 			Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 );
4700 		}
4701 	} else if ( delta > 30 ) {
4702 		AI_HARDLANDING = true;
4703 		landChange	= -16;
4704 		landTime	= gameLocal.time;
4705 		if ( !noDamage ) {
4706 			pain_debounce_time = gameLocal.time + pain_delay + 1;  // ignore pain since we'll play our landing anim
4707 			Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 );
4708 		}
4709 	} else if ( delta > 7 ) {
4710 		AI_SOFTLANDING = true;
4711 		landChange	= -8;
4712 		landTime	= gameLocal.time;
4713 	} else if ( delta > 3 ) {
4714 		// just walk on
4715 	}
4716 }
4717 
4718 /*
4719 ===============
4720 idPlayer::BobCycle
4721 ===============
4722 */
BobCycle(const idVec3 & pushVelocity)4723 void idPlayer::BobCycle( const idVec3 &pushVelocity ) {
4724 	float		bobmove;
4725 	int			old, deltaTime;
4726 	idVec3		vel, gravityDir, velocity;
4727 	idMat3		viewaxis;
4728 	float		bob;
4729 	float		delta;
4730 	float		speed;
4731 	float		f;
4732 
4733 	//
4734 	// calculate speed and cycle to be used for
4735 	// all cyclic walking effects
4736 	//
4737 	velocity = physicsObj.GetLinearVelocity() - pushVelocity;
4738 
4739 	gravityDir = physicsObj.GetGravityNormal();
4740 	vel = velocity - ( velocity * gravityDir ) * gravityDir;
4741 	xyspeed = vel.LengthFast();
4742 
4743 	// do not evaluate the bob for other clients
4744 	// when doing a spectate follow, don't do any weapon bobbing
4745 	if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
4746 		viewBobAngles.Zero();
4747 		viewBob.Zero();
4748 		return;
4749 	}
4750 
4751 	if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) {
4752 		// airborne
4753 		bobCycle = 0;
4754 		bobFoot = 0;
4755 		bobfracsin = 0;
4756 	} else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) {
4757 		// start at beginning of cycle again
4758 		bobCycle = 0;
4759 		bobFoot = 0;
4760 		bobfracsin = 0;
4761 	} else {
4762 		if ( physicsObj.IsCrouching() ) {
4763 			bobmove = pm_crouchbob.GetFloat();
4764 			// ducked characters never play footsteps
4765 		} else {
4766 			// vary the bobbing based on the speed of the player
4767 			bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac;
4768 		}
4769 
4770 		// check for footstep / splash sounds
4771 		old = bobCycle;
4772 		bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255;
4773 		bobFoot = ( bobCycle & 128 ) >> 7;
4774 		bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) );
4775 	}
4776 
4777 	// calculate angles for view bobbing
4778 	viewBobAngles.Zero();
4779 
4780 	viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis();
4781 
4782 	// add angles based on velocity
4783 	delta = velocity * viewaxis[0];
4784 	viewBobAngles.pitch += delta * pm_runpitch.GetFloat();
4785 
4786 	delta = velocity * viewaxis[1];
4787 	viewBobAngles.roll -= delta * pm_runroll.GetFloat();
4788 
4789 	// add angles based on bob
4790 	// make sure the bob is visible even at low speeds
4791 	speed = xyspeed > 200 ? xyspeed : 200;
4792 
4793 	delta = bobfracsin * pm_bobpitch.GetFloat() * speed;
4794 	if ( physicsObj.IsCrouching() ) {
4795 		delta *= 3;		// crouching
4796 	}
4797 	viewBobAngles.pitch += delta;
4798 	delta = bobfracsin * pm_bobroll.GetFloat() * speed;
4799 	if ( physicsObj.IsCrouching() ) {
4800 		delta *= 3;		// crouching accentuates roll
4801 	}
4802 	if ( bobFoot & 1 ) {
4803 		delta = -delta;
4804 	}
4805 	viewBobAngles.roll += delta;
4806 
4807 	// calculate position for view bobbing
4808 	viewBob.Zero();
4809 
4810 	if ( physicsObj.HasSteppedUp() ) {
4811 
4812 		// check for stepping up before a previous step is completed
4813 		deltaTime = gameLocal.time - stepUpTime;
4814 		if ( deltaTime < STEPUP_TIME ) {
4815 			stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp();
4816 		} else {
4817 			stepUpDelta = physicsObj.GetStepUp();
4818 		}
4819 		if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) {
4820 			stepUpDelta = 2.0f * pm_stepsize.GetFloat();
4821 		}
4822 		stepUpTime = gameLocal.time;
4823 	}
4824 
4825 	idVec3 gravity = physicsObj.GetGravityNormal();
4826 
4827 	// if the player stepped up recently
4828 	deltaTime = gameLocal.time - stepUpTime;
4829 	if ( deltaTime < STEPUP_TIME ) {
4830 		viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME );
4831 	}
4832 
4833 	// add bob height after any movement smoothing
4834 	bob = bobfracsin * xyspeed * pm_bobup.GetFloat();
4835 	if ( bob > 6 ) {
4836 		bob = 6;
4837 	}
4838 	viewBob[2] += bob;
4839 
4840 	// add fall height
4841 	delta = gameLocal.time - landTime;
4842 	if ( delta < LAND_DEFLECT_TIME ) {
4843 		f = delta / LAND_DEFLECT_TIME;
4844 		viewBob -= gravity * ( landChange * f );
4845 	} else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
4846 		delta -= LAND_DEFLECT_TIME;
4847 		f = 1.0 - ( delta / LAND_RETURN_TIME );
4848 		viewBob -= gravity * ( landChange * f );
4849 	}
4850 }
4851 
4852 /*
4853 ================
4854 idPlayer::UpdateDeltaViewAngles
4855 ================
4856 */
UpdateDeltaViewAngles(const idAngles & angles)4857 void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) {
4858 	// set the delta angle
4859 	idAngles delta;
4860 	for( int i = 0; i < 3; i++ ) {
4861 		delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] );
4862 	}
4863 	SetDeltaViewAngles( delta );
4864 }
4865 
4866 /*
4867 ================
4868 idPlayer::SetViewAngles
4869 ================
4870 */
SetViewAngles(const idAngles & angles)4871 void idPlayer::SetViewAngles( const idAngles &angles ) {
4872 	UpdateDeltaViewAngles( angles );
4873 	viewAngles = angles;
4874 }
4875 
4876 /*
4877 ================
4878 idPlayer::UpdateViewAngles
4879 ================
4880 */
UpdateViewAngles(void)4881 void idPlayer::UpdateViewAngles( void ) {
4882 	int i;
4883 	idAngles delta;
4884 
4885 	if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen ) ) {
4886 		// no view changes at all, but we still want to update the deltas or else when
4887 		// we get out of this mode, our view will snap to a kind of random angle
4888 		UpdateDeltaViewAngles( viewAngles );
4889 		return;
4890 	}
4891 
4892 	// if dead
4893 	if ( health <= 0 ) {
4894 		if ( pm_thirdPersonDeath.GetBool() ) {
4895 			viewAngles.roll = 0.0f;
4896 			viewAngles.pitch = 30.0f;
4897 		} else {
4898 			viewAngles.roll = 40.0f;
4899 			viewAngles.pitch = -15.0f;
4900 		}
4901 		return;
4902 	}
4903 
4904 	// circularly clamp the angles with deltas
4905 	for ( i = 0; i < 3; i++ ) {
4906 		cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] );
4907 		if ( influenceActive == INFLUENCE_LEVEL3 ) {
4908 			viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) );
4909 		} else {
4910 			viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] );
4911 		}
4912 	}
4913 	if ( !centerView.IsDone( gameLocal.time ) ) {
4914 		viewAngles.pitch = centerView.GetCurrentValue(gameLocal.time);
4915 	}
4916 
4917 	// clamp the pitch
4918 	if ( noclip ) {
4919 		if ( viewAngles.pitch > 89.0f ) {
4920 			// don't let the player look down more than 89 degrees while noclipping
4921 			viewAngles.pitch = 89.0f;
4922 		} else if ( viewAngles.pitch < -89.0f ) {
4923 			// don't let the player look up more than 89 degrees while noclipping
4924 			viewAngles.pitch = -89.0f;
4925 		}
4926 	} else {
4927 		if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) {
4928 			// don't let the player look down enough to see the shadow of his (non-existant) feet
4929 			viewAngles.pitch = pm_maxviewpitch.GetFloat();
4930 		} else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) {
4931 			// don't let the player look up more than 89 degrees
4932 			viewAngles.pitch = pm_minviewpitch.GetFloat();
4933 		}
4934 	}
4935 
4936 	UpdateDeltaViewAngles( viewAngles );
4937 
4938 	// orient the model towards the direction we're looking
4939 	SetAngles( idAngles( 0, viewAngles.yaw, 0 ) );
4940 
4941 	// save in the log for analyzing weapon angle offsets
4942 	loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles;
4943 }
4944 
4945 /*
4946 ==============
4947 idPlayer::AdjustHeartRate
4948 
4949 Player heartrate works as follows
4950 
4951 DEF_HEARTRATE is resting heartrate
4952 
4953 Taking damage when health is above 75 adjusts heart rate by 1 beat per second
4954 Taking damage when health is below 75 adjusts heart rate by 5 beats per second
4955 Maximum heartrate from damage is MAX_HEARTRATE
4956 
4957 Firing a weapon adds 1 beat per second up to a maximum of COMBAT_HEARTRATE
4958 
4959 Being at less than 25% stamina adds 5 beats per second up to ZEROSTAMINA_HEARTRATE
4960 
4961 All heartrates are target rates.. the heart rate will start falling as soon as there have been no adjustments for 5 seconds
4962 Once it starts falling it always tries to get to DEF_HEARTRATE
4963 
4964 The exception to the above rule is upon death at which point the rate is set to DYING_HEARTRATE and starts falling
4965 immediately to zero
4966 
4967 Heart rate volumes go from zero ( -40 db for DEF_HEARTRATE to 5 db for MAX_HEARTRATE ) the volume is
4968 scaled linearly based on the actual rate
4969 
4970 Exception to the above rule is once the player is dead, the dying heart rate starts at either the current volume if
4971 it is audible or -10db and scales to 8db on the last few beats
4972 ==============
4973 */
AdjustHeartRate(int target,float timeInSecs,float delay,bool force)4974 void idPlayer::AdjustHeartRate( int target, float timeInSecs, float delay, bool force ) {
4975 
4976 	if ( heartInfo.GetEndValue() == target ) {
4977 		return;
4978 	}
4979 
4980 	if ( AI_DEAD && !force ) {
4981 		return;
4982 	}
4983 
4984 	lastHeartAdjust = gameLocal.time;
4985 
4986 	heartInfo.Init( gameLocal.time + delay * 1000, timeInSecs * 1000, heartRate, target );
4987 }
4988 
4989 /*
4990 ==============
4991 idPlayer::GetBaseHeartRate
4992 ==============
4993 */
GetBaseHeartRate(void)4994 int idPlayer::GetBaseHeartRate( void ) {
4995 	int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float)health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
4996 	int rate = idMath::FtoiFast( base + ( ZEROSTAMINA_HEARTRATE - base ) * ( 1.0f - stamina / pm_stamina.GetFloat() ) );
4997 	int diff = ( lastDmgTime ) ? gameLocal.time - lastDmgTime : 99999;
4998 	rate += ( diff < 5000 ) ? ( diff < 2500 ) ? ( diff < 1000 ) ? 15 : 10 : 5 : 0;
4999 	return rate;
5000 }
5001 
5002 /*
5003 ==============
5004 idPlayer::SetCurrentHeartRate
5005 ==============
5006 */
SetCurrentHeartRate(void)5007 void idPlayer::SetCurrentHeartRate( void ) {
5008 
5009 	int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float) health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
5010 
5011 	if ( PowerUpActive( ADRENALINE )) {
5012 		heartRate = 135;
5013 	} else {
5014 		heartRate = idMath::FtoiFast( heartInfo.GetCurrentValue( gameLocal.time ) );
5015 		int currentRate = GetBaseHeartRate();
5016 		if ( health >= 0 && gameLocal.time > lastHeartAdjust + 2500 ) {
5017 			AdjustHeartRate( currentRate, 2.5f, 0.0f, false );
5018 		}
5019 	}
5020 
5021 	int bps = idMath::FtoiFast( 60.0f / heartRate * 1000.0f );
5022 	if ( gameLocal.time - lastHeartBeat > bps ) {
5023 		int dmgVol = DMG_VOLUME;
5024 		int deathVol = DEATH_VOLUME;
5025 		int zeroVol = ZERO_VOLUME;
5026 		float pct = 0.0;
5027 		if ( heartRate > BASE_HEARTRATE && health > 0 ) {
5028 			pct = (float)(heartRate - base) / (MAX_HEARTRATE - base);
5029 			pct *= ((float)dmgVol - (float)zeroVol);
5030 		} else if ( health <= 0 ) {
5031 			pct = (float)(heartRate - DYING_HEARTRATE) / (BASE_HEARTRATE - DYING_HEARTRATE);
5032 			if ( pct > 1.0f ) {
5033 				pct = 1.0f;
5034 			} else if (pct < 0.0f) {
5035 				pct = 0.0f;
5036 			}
5037 			pct *= ((float)deathVol - (float)zeroVol);
5038 		}
5039 
5040 		pct += (float)zeroVol;
5041 
5042 		if ( pct != zeroVol ) {
5043 			StartSound( "snd_heartbeat", SND_CHANNEL_HEART, SSF_PRIVATE_SOUND, false, NULL );
5044 			// modify just this channel to a custom volume
5045 			soundShaderParms_t	parms;
5046 			memset( &parms, 0, sizeof( parms ) );
5047 			parms.volume = pct;
5048 			refSound.referenceSound->ModifySound( SND_CHANNEL_HEART, &parms );
5049 		}
5050 
5051 		lastHeartBeat = gameLocal.time;
5052 	}
5053 }
5054 
5055 /*
5056 ==============
5057 idPlayer::UpdateAir
5058 ==============
5059 */
UpdateAir(void)5060 void idPlayer::UpdateAir( void ) {
5061 	if ( health <= 0 ) {
5062 		return;
5063 	}
5064 
5065 	// see if the player is connected to the info_vacuum
5066 	bool	newAirless = false;
5067 
5068 	if ( gameLocal.vacuumAreaNum != -1 ) {
5069 		int	num = GetNumPVSAreas();
5070 		if ( num > 0 ) {
5071 			int		areaNum;
5072 
5073 			// if the player box spans multiple areas, get the area from the origin point instead,
5074 			// otherwise a rotating player box may poke into an outside area
5075 			if ( num == 1 ) {
5076 				const int	*pvsAreas = GetPVSAreas();
5077 				areaNum = pvsAreas[0];
5078 			} else {
5079 				areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() );
5080 			}
5081 			newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR );
5082 		}
5083 	}
5084 
5085 	if ( newAirless ) {
5086 		if ( !airless ) {
5087 			StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5088 			StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL );
5089 			if ( hud ) {
5090 				hud->HandleNamedEvent( "noAir" );
5091 			}
5092 		}
5093 		airTics--;
5094 		if ( airTics < 0 ) {
5095 			airTics = 0;
5096 			// check for damage
5097 			const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false );
5098 			int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f );
5099 			if ( gameLocal.time > lastAirDamage + dmgTiming ) {
5100 				Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 );
5101 				lastAirDamage = gameLocal.time;
5102 			}
5103 		}
5104 
5105 	} else {
5106 		if ( airless ) {
5107 			StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5108 			StopSound( SND_CHANNEL_BODY2, false );
5109 			if ( hud ) {
5110 				hud->HandleNamedEvent( "Air" );
5111 			}
5112 		}
5113 		airTics+=2;	// regain twice as fast as lose
5114 		if ( airTics > pm_airTics.GetInteger() ) {
5115 			airTics = pm_airTics.GetInteger();
5116 		}
5117 	}
5118 
5119 	airless = newAirless;
5120 
5121 	if ( hud ) {
5122 		hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() );
5123 	}
5124 }
5125 
5126 /*
5127 ==============
5128 idPlayer::AddGuiPDAData
5129 ==============
5130  */
AddGuiPDAData(const declType_t dataType,const char * listName,const idDeclPDA * src,idUserInterface * gui)5131 int idPlayer::AddGuiPDAData( const declType_t dataType, const char *listName, const idDeclPDA *src, idUserInterface *gui ) {
5132 	int c, i;
5133 	idStr work;
5134 	if ( dataType == DECL_EMAIL ) {
5135 		c = src->GetNumEmails();
5136 		for ( i = 0; i < c; i++ ) {
5137 			const idDeclEmail *email = src->GetEmailByIndex( i );
5138 			if ( email == NULL ) {
5139 				work = va( "-\tEmail %d not found\t-", i );
5140 			} else {
5141 				work = email->GetFrom();
5142 				work += "\t";
5143 				work += email->GetSubject();
5144 				work += "\t";
5145 				work += email->GetDate();
5146 			}
5147 			gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5148 		}
5149 		return c;
5150 	} else if ( dataType == DECL_AUDIO ) {
5151 		c = src->GetNumAudios();
5152 		for ( i = 0; i < c; i++ ) {
5153 			const idDeclAudio *audio = src->GetAudioByIndex( i );
5154 			if ( audio == NULL ) {
5155 				work = va( "Audio Log %d not found", i );
5156 			} else {
5157 				work = audio->GetAudioName();
5158 			}
5159 			gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5160 		}
5161 		return c;
5162 	} else if ( dataType == DECL_VIDEO ) {
5163 		c = inventory.videos.Num();
5164 		for ( i = 0; i < c; i++ ) {
5165 			const idDeclVideo *video = GetVideo( i );
5166 			if ( video == NULL ) {
5167 				work = va( "Video CD %s not found", inventory.videos[i].c_str() );
5168 			} else {
5169 				work = video->GetVideoName();
5170 			}
5171 			gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5172 		}
5173 		return c;
5174 	}
5175 	return 0;
5176 }
5177 
5178 /*
5179 ==============
5180 idPlayer::GetPDA
5181 ==============
5182  */
GetPDA(void) const5183 const idDeclPDA *idPlayer::GetPDA( void ) const {
5184 	if ( inventory.pdas.Num() ) {
5185 		return static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[ 0 ] ) );
5186 	} else {
5187 		return NULL;
5188 	}
5189 }
5190 
5191 
5192 /*
5193 ==============
5194 idPlayer::GetVideo
5195 ==============
5196 */
GetVideo(int index)5197 const idDeclVideo *idPlayer::GetVideo( int index ) {
5198 	if ( index >= 0 && index < inventory.videos.Num() ) {
5199 		return static_cast< const idDeclVideo* >( declManager->FindType( DECL_VIDEO, inventory.videos[index], false ) );
5200 	}
5201 	return NULL;
5202 }
5203 
5204 
5205 /*
5206 ==============
5207 idPlayer::UpdatePDAInfo
5208 ==============
5209 */
UpdatePDAInfo(bool updatePDASel)5210 void idPlayer::UpdatePDAInfo( bool updatePDASel ) {
5211 	int j, sel;
5212 
5213 	if ( objectiveSystem == NULL ) {
5214 		return;
5215 	}
5216 
5217 	assert( hud );
5218 
5219 	int currentPDA = objectiveSystem->State().GetInt( "listPDA_sel_0", "0" );
5220 	if ( currentPDA == -1 ) {
5221 		currentPDA = 0;
5222 	}
5223 
5224 	if ( updatePDASel ) {
5225 		objectiveSystem->SetStateInt( "listPDAVideo_sel_0", 0 );
5226 		objectiveSystem->SetStateInt( "listPDAEmail_sel_0", 0 );
5227 		objectiveSystem->SetStateInt( "listPDAAudio_sel_0", 0 );
5228 	}
5229 
5230 	if ( currentPDA > 0 ) {
5231 		currentPDA = inventory.pdas.Num() - currentPDA;
5232 	}
5233 
5234 	// Mark in the bit array that this pda has been read
5235 	if ( currentPDA < 128 ) {
5236 		inventory.pdasViewed[currentPDA >> 5] |= 1 << (currentPDA & 31);
5237 	}
5238 
5239 	pdaAudio = "";
5240 	pdaVideo = "";
5241 	pdaVideoWave = "";
5242 	idStr name, data, preview, info, wave;
5243 	for ( j = 0; j < MAX_PDAS; j++ ) {
5244 		objectiveSystem->SetStateString( va( "listPDA_item_%i", j ), "" );
5245 	}
5246 	for ( j = 0; j < MAX_PDA_ITEMS; j++ ) {
5247 		objectiveSystem->SetStateString( va( "listPDAVideo_item_%i", j ), "" );
5248 		objectiveSystem->SetStateString( va( "listPDAAudio_item_%i", j ), "" );
5249 		objectiveSystem->SetStateString( va( "listPDAEmail_item_%i", j ), "" );
5250 		objectiveSystem->SetStateString( va( "listPDASecurity_item_%i", j ), "" );
5251 	}
5252 	for ( j = 0; j < inventory.pdas.Num(); j++ ) {
5253 
5254 		const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[j], false ) );
5255 
5256 		if ( pda == NULL ) {
5257 			continue;
5258 		}
5259 
5260 		int index = inventory.pdas.Num() - j;
5261 		if ( j == 0 ) {
5262 			// Special case for the first PDA
5263 			index = 0;
5264 		}
5265 
5266 		if ( j != currentPDA && j < 128 && inventory.pdasViewed[j >> 5] & (1 << (j & 31)) ) {
5267 			// This pda has been read already, mark in gray
5268 			objectiveSystem->SetStateString( va( "listPDA_item_%i", index), va(S_COLOR_GRAY "%s", pda->GetPdaName()) );
5269 		} else {
5270 			// This pda has not been read yet
5271 		objectiveSystem->SetStateString( va( "listPDA_item_%i", index), pda->GetPdaName() );
5272 		}
5273 
5274 		const char *security = pda->GetSecurity();
5275 		if ( j == currentPDA || (currentPDA == 0 && security && *security ) ) {
5276 			if ( *security == 0 ) {
5277 				security = common->GetLanguageDict()->GetString( "#str_00066" );
5278 			}
5279 			objectiveSystem->SetStateString( "PDASecurityClearance", security );
5280 		}
5281 
5282 		if ( j == currentPDA ) {
5283 
5284 			objectiveSystem->SetStateString( "pda_icon", pda->GetIcon() );
5285 			objectiveSystem->SetStateString( "pda_id", pda->GetID() );
5286 			objectiveSystem->SetStateString( "pda_title", pda->GetTitle() );
5287 
5288 			if ( j == 0 ) {
5289 				// Selected, personal pda
5290 				// Add videos
5291 				if ( updatePDASel || !inventory.pdaOpened ) {
5292 				objectiveSystem->HandleNamedEvent( "playerPDAActive" );
5293 				objectiveSystem->SetStateString( "pda_personal", "1" );
5294 					inventory.pdaOpened = true;
5295 				}
5296 				objectiveSystem->SetStateString( "pda_location", hud->State().GetString("location") );
5297 				objectiveSystem->SetStateString( "pda_name", cvarSystem->GetCVarString( "ui_name") );
5298 				AddGuiPDAData( DECL_VIDEO, "listPDAVideo", pda, objectiveSystem );
5299 				sel = objectiveSystem->State().GetInt( "listPDAVideo_sel_0", "0" );
5300 				const idDeclVideo *vid = NULL;
5301 				if ( sel >= 0 && sel < inventory.videos.Num() ) {
5302 					vid = static_cast< const idDeclVideo * >( declManager->FindType( DECL_VIDEO, inventory.videos[ sel ], false ) );
5303 				}
5304 				if ( vid ) {
5305 					pdaVideo = vid->GetRoq();
5306 					pdaVideoWave = vid->GetWave();
5307 					objectiveSystem->SetStateString( "PDAVideoTitle", vid->GetVideoName() );
5308 					objectiveSystem->SetStateString( "PDAVideoVid", vid->GetRoq() );
5309 					objectiveSystem->SetStateString( "PDAVideoIcon", vid->GetPreview() );
5310 					objectiveSystem->SetStateString( "PDAVideoInfo", vid->GetInfo() );
5311 				} else {
5312 					//FIXME: need to precache these in the player def
5313 					objectiveSystem->SetStateString( "PDAVideoVid", "sound/vo/video/welcome.tga" );
5314 					objectiveSystem->SetStateString( "PDAVideoIcon", "sound/vo/video/welcome.tga" );
5315 					objectiveSystem->SetStateString( "PDAVideoTitle", "" );
5316 					objectiveSystem->SetStateString( "PDAVideoInfo", "" );
5317 				}
5318 			} else {
5319 				// Selected, non-personal pda
5320 				// Add audio logs
5321 				if ( updatePDASel ) {
5322 				objectiveSystem->HandleNamedEvent( "playerPDANotActive" );
5323 				objectiveSystem->SetStateString( "pda_personal", "0" );
5324 					inventory.pdaOpened = true;
5325 				}
5326 				objectiveSystem->SetStateString( "pda_location", pda->GetPost() );
5327 				objectiveSystem->SetStateString( "pda_name", pda->GetFullName() );
5328 				int audioCount = AddGuiPDAData( DECL_AUDIO, "listPDAAudio", pda, objectiveSystem );
5329 				objectiveSystem->SetStateInt( "audioLogCount", audioCount );
5330 				sel = objectiveSystem->State().GetInt( "listPDAAudio_sel_0", "0" );
5331 				const idDeclAudio *aud = NULL;
5332 				if ( sel >= 0 ) {
5333 					aud = pda->GetAudioByIndex( sel );
5334 				}
5335 				if ( aud ) {
5336 					pdaAudio = aud->GetWave();
5337 					objectiveSystem->SetStateString( "PDAAudioTitle", aud->GetAudioName() );
5338 					objectiveSystem->SetStateString( "PDAAudioIcon", aud->GetPreview() );
5339 					objectiveSystem->SetStateString( "PDAAudioInfo", aud->GetInfo() );
5340 				} else {
5341 					objectiveSystem->SetStateString( "PDAAudioIcon", "sound/vo/video/welcome.tga" );
5342 					objectiveSystem->SetStateString( "PDAAutioTitle", "" );
5343 					objectiveSystem->SetStateString( "PDAAudioInfo", "" );
5344 				}
5345 			}
5346 			// add emails
5347 			name = "";
5348 			data = "";
5349 			int numEmails = pda->GetNumEmails();
5350 			if ( numEmails > 0 ) {
5351 				AddGuiPDAData( DECL_EMAIL, "listPDAEmail", pda, objectiveSystem );
5352 				sel = objectiveSystem->State().GetInt( "listPDAEmail_sel_0", "-1" );
5353 				if ( sel >= 0 && sel < numEmails ) {
5354 					const idDeclEmail *email = pda->GetEmailByIndex( sel );
5355 					name = email->GetSubject();
5356 					data = email->GetBody();
5357 				}
5358 			}
5359 			objectiveSystem->SetStateString( "PDAEmailTitle", name );
5360 			objectiveSystem->SetStateString( "PDAEmailText", data );
5361 		}
5362 	}
5363 	if ( objectiveSystem->State().GetInt( "listPDA_sel_0", "-1" ) == -1 ) {
5364 		objectiveSystem->SetStateInt( "listPDA_sel_0", 0 );
5365 	}
5366 	objectiveSystem->StateChanged( gameLocal.time );
5367 }
5368 
5369 /*
5370 ==============
5371 idPlayer::TogglePDA
5372 ==============
5373 */
TogglePDA(void)5374 void idPlayer::TogglePDA( void ) {
5375 	if ( objectiveSystem == NULL ) {
5376 		return;
5377 	}
5378 
5379 	if ( inventory.pdas.Num() == 0 ) {
5380 		ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
5381 		return;
5382 	}
5383 
5384 	assert( hud );
5385 
5386 	if ( !objectiveSystemOpen ) {
5387 		int j, c = inventory.items.Num();
5388 		objectiveSystem->SetStateInt( "inv_count", c );
5389 		for ( j = 0; j < MAX_INVENTORY_ITEMS; j++ ) {
5390 			objectiveSystem->SetStateString( va( "inv_name_%i", j ), "" );
5391 			objectiveSystem->SetStateString( va( "inv_icon_%i", j ), "" );
5392 			objectiveSystem->SetStateString( va( "inv_text_%i", j ), "" );
5393 		}
5394 		for ( j = 0; j < c; j++ ) {
5395 			idDict *item = inventory.items[j];
5396 			if ( !item->GetBool( "inv_pda" ) ) {
5397 				const char *iname = item->GetString( "inv_name" );
5398 				const char *iicon = item->GetString( "inv_icon" );
5399 				const char *itext = item->GetString( "inv_text" );
5400 				objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname );
5401 				objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon );
5402 				objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext );
5403 				const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL );
5404 				if ( kv ) {
5405 					objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
5406 				}
5407 			}
5408 		}
5409 
5410 		for ( j = 0; j < MAX_WEAPONS; j++ ) {
5411 			const char *weapnum = va( "def_weapon%d", j );
5412 			const char *hudWeap = va( "weapon%d", j );
5413 			int weapstate = 0;
5414 			if ( inventory.weapons & ( 1 << j ) ) {
5415 				const char *weap = spawnArgs.GetString( weapnum );
5416 				if ( weap && *weap ) {
5417 					weapstate++;
5418 				}
5419 			}
5420 			objectiveSystem->SetStateInt( hudWeap, weapstate );
5421 		}
5422 
5423 		objectiveSystem->SetStateInt( "listPDA_sel_0", inventory.selPDA );
5424 		objectiveSystem->SetStateInt( "listPDAVideo_sel_0", inventory.selVideo );
5425 		objectiveSystem->SetStateInt( "listPDAAudio_sel_0", inventory.selAudio );
5426 		objectiveSystem->SetStateInt( "listPDAEmail_sel_0", inventory.selEMail );
5427 		UpdatePDAInfo( false );
5428 		UpdateObjectiveInfo();
5429 		objectiveSystem->Activate( true, gameLocal.time );
5430 		hud->HandleNamedEvent( "pdaPickupHide" );
5431 		hud->HandleNamedEvent( "videoPickupHide" );
5432 	} else {
5433 		inventory.selPDA = objectiveSystem->State().GetInt( "listPDA_sel_0" );
5434 		inventory.selVideo = objectiveSystem->State().GetInt( "listPDAVideo_sel_0" );
5435 		inventory.selAudio = objectiveSystem->State().GetInt( "listPDAAudio_sel_0" );
5436 		inventory.selEMail = objectiveSystem->State().GetInt( "listPDAEmail_sel_0" );
5437 		objectiveSystem->Activate( false, gameLocal.time );
5438 	}
5439 	objectiveSystemOpen ^= 1;
5440 }
5441 
5442 /*
5443 ==============
5444 idPlayer::ToggleScoreboard
5445 ==============
5446 */
ToggleScoreboard(void)5447 void idPlayer::ToggleScoreboard( void ) {
5448 	scoreBoardOpen ^= 1;
5449 }
5450 
5451 /*
5452 ==============
5453 idPlayer::Spectate
5454 ==============
5455 */
Spectate(bool spectate)5456 void idPlayer::Spectate( bool spectate ) {
5457 	idBitMsg	msg;
5458 	byte		msgBuf[MAX_EVENT_PARAM_SIZE];
5459 
5460 	// track invisible player bug
5461 	// all hiding and showing should be performed through Spectate calls
5462 	// except for the private camera view, which is used for teleports
5463 	assert( ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() == spectating ) );
5464 
5465 	if ( spectating == spectate ) {
5466 		return;
5467 	}
5468 
5469 	spectating = spectate;
5470 
5471 	if ( gameLocal.isServer ) {
5472 		msg.Init( msgBuf, sizeof( msgBuf ) );
5473 		msg.WriteBits( spectating, 1 );
5474 		ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 );
5475 	}
5476 
5477 	if ( spectating ) {
5478 		// join the spectators
5479 		ClearPowerUps();
5480 		spectator = this->entityNumber;
5481 		Init();
5482 		StopRagdoll();
5483 		SetPhysics( &physicsObj );
5484 		physicsObj.DisableClip();
5485 		Hide();
5486 		Event_DisableWeapon();
5487 		if ( hud ) {
5488 			hud->HandleNamedEvent( "aim_clear" );
5489 			MPAimFadeTime = 0;
5490 		}
5491 	} else {
5492 		// put everything back together again
5493 		currentWeapon = -1;	// to make sure the def will be loaded if necessary
5494 		Show();
5495 		Event_EnableWeapon();
5496 	}
5497 	SetClipModel();
5498 }
5499 
5500 /*
5501 ==============
5502 idPlayer::SetClipModel
5503 ==============
5504 */
SetClipModel(void)5505 void idPlayer::SetClipModel( void ) {
5506 	idBounds bounds;
5507 
5508 	if ( spectating ) {
5509 		bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
5510 	} else {
5511 		bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 );
5512 		bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() );
5513 	}
5514 	// the origin of the clip model needs to be set before calling SetClipModel
5515 	// otherwise our physics object's current origin value gets reset to 0
5516 	idClipModel *newClip;
5517 	if ( pm_usecylinder.GetBool() ) {
5518 		newClip = new idClipModel( idTraceModel( bounds, 8 ) );
5519 		newClip->Translate( physicsObj.PlayerGetOrigin() );
5520 		physicsObj.SetClipModel( newClip, 1.0f );
5521 	} else {
5522 		newClip = new idClipModel( idTraceModel( bounds ) );
5523 		newClip->Translate( physicsObj.PlayerGetOrigin() );
5524 		physicsObj.SetClipModel( newClip, 1.0f );
5525 	}
5526 }
5527 
5528 /*
5529 ==============
5530 idPlayer::UseVehicle
5531 ==============
5532 */
UseVehicle(void)5533 void idPlayer::UseVehicle( void ) {
5534 	trace_t	trace;
5535 	idVec3 start, end;
5536 	idEntity *ent;
5537 
5538 	if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5539 		Show();
5540 		static_cast<idAFEntity_Vehicle*>(GetBindMaster())->Use( this );
5541 	} else {
5542 		start = GetEyePosition();
5543 		end = start + viewAngles.ToForward() * 80.0f;
5544 		gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
5545 		if ( trace.fraction < 1.0f ) {
5546 			ent = gameLocal.entities[ trace.c.entityNum ];
5547 			if ( ent && ent->IsType( idAFEntity_Vehicle::Type ) ) {
5548 				Hide();
5549 				static_cast<idAFEntity_Vehicle*>(ent)->Use( this );
5550 			}
5551 		}
5552 	}
5553 }
5554 
5555 /*
5556 ==============
5557 idPlayer::PerformImpulse
5558 ==============
5559 */
PerformImpulse(int impulse)5560 void idPlayer::PerformImpulse( int impulse ) {
5561 
5562 	if ( gameLocal.isClient ) {
5563 		idBitMsg	msg;
5564 		byte		msgBuf[MAX_EVENT_PARAM_SIZE];
5565 
5566 		assert( entityNumber == gameLocal.localClientNum );
5567 		msg.Init( msgBuf, sizeof( msgBuf ) );
5568 		msg.BeginWriting();
5569 		msg.WriteBits( impulse, 6 );
5570 		ClientSendEvent( EVENT_IMPULSE, &msg );
5571 	}
5572 
5573 	if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) {
5574 		SelectWeapon( impulse, false );
5575 		return;
5576 	}
5577 
5578 	switch( impulse ) {
5579 		case IMPULSE_13: {
5580 			Reload();
5581 			break;
5582 		}
5583 		case IMPULSE_14: {
5584 			NextWeapon();
5585 			break;
5586 		}
5587 		case IMPULSE_15: {
5588 			PrevWeapon();
5589 			break;
5590 		}
5591 		case IMPULSE_17: {
5592 			if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5593 				gameLocal.mpGame.ToggleReady();
5594 			}
5595 			break;
5596 		}
5597 		case IMPULSE_18: {
5598 			centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0);
5599 			break;
5600 		}
5601 		case IMPULSE_19: {
5602 			// when we're not in single player, IMPULSE_19 is used for showScores
5603 			// otherwise it opens the pda
5604 			if ( !gameLocal.isMultiplayer ) {
5605 				if ( objectiveSystemOpen ) {
5606 					TogglePDA();
5607 				} else if ( weapon_pda >= 0 ) {
5608 					SelectWeapon( weapon_pda, true );
5609 				}
5610 			}
5611 			break;
5612 		}
5613 		case IMPULSE_20: {
5614 			if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5615 				gameLocal.mpGame.ToggleTeam();
5616 			}
5617 			break;
5618 		}
5619 		case IMPULSE_22: {
5620 			if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5621 				gameLocal.mpGame.ToggleSpectate();
5622 			}
5623 			break;
5624 		}
5625 		case IMPULSE_28: {
5626 			if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5627 				gameLocal.mpGame.CastVote( gameLocal.localClientNum, true );
5628 			}
5629 			break;
5630 		}
5631 		case IMPULSE_29: {
5632 			if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5633 				gameLocal.mpGame.CastVote( gameLocal.localClientNum, false );
5634 			}
5635 			break;
5636 		}
5637 		case IMPULSE_40: {
5638 			UseVehicle();
5639 			break;
5640 		}
5641 	}
5642 }
5643 
HandleESC(void)5644 bool idPlayer::HandleESC( void ) {
5645 	if ( gameLocal.inCinematic ) {
5646 		return SkipCinematic();
5647 	}
5648 
5649 	if ( objectiveSystemOpen ) {
5650 		TogglePDA();
5651 		return true;
5652 	}
5653 
5654 	return false;
5655 }
5656 
SkipCinematic(void)5657 bool idPlayer::SkipCinematic( void ) {
5658 	StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL );
5659 	return gameLocal.SkipCinematic();
5660 }
5661 
5662 /*
5663 ==============
5664 idPlayer::EvaluateControls
5665 ==============
5666 */
EvaluateControls(void)5667 void idPlayer::EvaluateControls( void ) {
5668 	// check for respawning
5669 	if ( health <= 0 ) {
5670 		if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) {
5671 			forceRespawn = true;
5672 		} else if ( gameLocal.time > maxRespawnTime ) {
5673 			forceRespawn = true;
5674 		}
5675 	}
5676 
5677 	// in MP, idMultiplayerGame decides spawns
5678 	if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) {
5679 		// in single player, we let the session handle restarting the level or loading a game
5680 		gameLocal.sessionCommand = "died";
5681 	}
5682 
5683 	if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
5684 		PerformImpulse( usercmd.impulse );
5685 	}
5686 
5687 	scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
5688 
5689 	oldFlags = usercmd.flags;
5690 
5691 	AdjustSpeed();
5692 
5693 	// update the viewangles
5694 	UpdateViewAngles();
5695 }
5696 
5697 /*
5698 ==============
5699 idPlayer::AdjustSpeed
5700 ==============
5701 */
AdjustSpeed(void)5702 void idPlayer::AdjustSpeed( void ) {
5703 	float speed;
5704 	float rate;
5705 
5706 	if ( spectating ) {
5707 		speed = pm_spectatespeed.GetFloat();
5708 		bobFrac = 0.0f;
5709 	} else if ( noclip ) {
5710 		speed = pm_noclipspeed.GetFloat();
5711 		bobFrac = 0.0f;
5712 	} else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) {
5713 		if ( !gameLocal.isMultiplayer && !physicsObj.IsCrouching() && !PowerUpActive( ADRENALINE ) ) {
5714 			stamina -= MS2SEC( gameLocal.msec );
5715 		}
5716 		if ( stamina < 0 ) {
5717 			stamina = 0;
5718 		}
5719 		if ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ) {
5720 			bobFrac = 1.0f;
5721 		} else if ( pm_staminathreshold.GetFloat() <= 0.0001f ) {
5722 			bobFrac = 0.0f;
5723 		} else {
5724 			bobFrac = stamina / pm_staminathreshold.GetFloat();
5725 		}
5726 		speed = pm_walkspeed.GetFloat() * ( 1.0f - bobFrac ) + pm_runspeed.GetFloat() * bobFrac;
5727 	} else {
5728 		rate = pm_staminarate.GetFloat();
5729 
5730 		// increase 25% faster when not moving
5731 		if ( ( usercmd.forwardmove == 0 ) && ( usercmd.rightmove == 0 ) && ( !physicsObj.OnLadder() || ( usercmd.upmove == 0 ) ) ) {
5732 			 rate *= 1.25f;
5733 		}
5734 
5735 		stamina += rate * MS2SEC( gameLocal.msec );
5736 		if ( stamina > pm_stamina.GetFloat() ) {
5737 			stamina = pm_stamina.GetFloat();
5738 		}
5739 		speed = pm_walkspeed.GetFloat();
5740 		bobFrac = 0.0f;
5741 	}
5742 
5743 	speed *= PowerUpModifier(SPEED);
5744 
5745 	if ( influenceActive == INFLUENCE_LEVEL3 ) {
5746 		speed *= 0.33f;
5747 	}
5748 
5749 	physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() );
5750 }
5751 
5752 /*
5753 ==============
5754 idPlayer::AdjustBodyAngles
5755 ==============
5756 */
AdjustBodyAngles(void)5757 void idPlayer::AdjustBodyAngles( void ) {
5758 	idMat3	lookAxis;
5759 	idMat3	legsAxis;
5760 	bool	blend;
5761 	float	diff;
5762 	float	frac;
5763 	float	upBlend;
5764 	float	forwardBlend;
5765 	float	downBlend;
5766 
5767 	if ( health < 0 ) {
5768 		return;
5769 	}
5770 
5771 	blend = true;
5772 
5773 	if ( !physicsObj.HasGroundContacts() ) {
5774 		idealLegsYaw = 0.0f;
5775 		legsForward = true;
5776 	} else if ( usercmd.forwardmove < 0 ) {
5777 		idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() );
5778 		legsForward = false;
5779 	} else if ( usercmd.forwardmove > 0 ) {
5780 		idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() );
5781 		legsForward = true;
5782 	} else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) {
5783 		if ( !legsForward ) {
5784 			idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() );
5785 		} else {
5786 			idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() );
5787 		}
5788 	} else if ( usercmd.rightmove != 0 ) {
5789 		idealLegsYaw = 0.0f;
5790 		legsForward = true;
5791 	} else {
5792 		legsForward = true;
5793 		diff = idMath::Fabs( idealLegsYaw - legsYaw );
5794 		idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw );
5795 		if ( diff < 0.1f ) {
5796 			legsYaw = idealLegsYaw;
5797 			blend = false;
5798 		}
5799 	}
5800 
5801 	if ( !physicsObj.IsCrouching() ) {
5802 		legsForward = true;
5803 	}
5804 
5805 	oldViewYaw = viewAngles.yaw;
5806 
5807 	AI_TURN_LEFT = false;
5808 	AI_TURN_RIGHT = false;
5809 	if ( idealLegsYaw < -45.0f ) {
5810 		idealLegsYaw = 0;
5811 		AI_TURN_RIGHT = true;
5812 		blend = true;
5813 	} else if ( idealLegsYaw > 45.0f ) {
5814 		idealLegsYaw = 0;
5815 		AI_TURN_LEFT = true;
5816 		blend = true;
5817 	}
5818 
5819 	if ( blend ) {
5820 		legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f;
5821 	}
5822 	legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3();
5823 	animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis );
5824 
5825 	// calculate the blending between down, straight, and up
5826 	frac = viewAngles.pitch / 90.0f;
5827 	if ( frac > 0.0f ) {
5828 		downBlend		= frac;
5829 		forwardBlend	= 1.0f - frac;
5830 		upBlend			= 0.0f;
5831 	} else {
5832 		downBlend		= 0.0f;
5833 		forwardBlend	= 1.0f + frac;
5834 		upBlend			= -frac;
5835 	}
5836 
5837 	animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend );
5838 	animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend );
5839 	animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend );
5840 
5841 	animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend );
5842 	animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend );
5843 	animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend );
5844 }
5845 
5846 /*
5847 ==============
5848 idPlayer::InitAASLocation
5849 ==============
5850 */
InitAASLocation(void)5851 void idPlayer::InitAASLocation( void ) {
5852 	int		i;
5853 	int		num;
5854 	idVec3	size;
5855 	idBounds bounds;
5856 	idAAS	*aas;
5857 	idVec3	origin;
5858 
5859 	GetFloorPos( 64.0f, origin );
5860 
5861 	num = gameLocal.NumAAS();
5862 	aasLocation.SetGranularity( 1 );
5863 	aasLocation.SetNum( num );
5864 	for( i = 0; i < aasLocation.Num(); i++ ) {
5865 		aasLocation[ i ].areaNum = 0;
5866 		aasLocation[ i ].pos = origin;
5867 		aas = gameLocal.GetAAS( i );
5868 		if ( aas && aas->GetSettings() ) {
5869 			size = aas->GetSettings()->boundingBoxes[0][1];
5870 			bounds[0] = -size;
5871 			size.z = 32.0f;
5872 			bounds[1] = size;
5873 
5874 			aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5875 		}
5876 	}
5877 }
5878 
5879 /*
5880 ==============
5881 idPlayer::SetAASLocation
5882 ==============
5883 */
SetAASLocation(void)5884 void idPlayer::SetAASLocation( void ) {
5885 	int		i;
5886 	int		areaNum;
5887 	idVec3	size;
5888 	idBounds bounds;
5889 	idAAS	*aas;
5890 	idVec3	origin;
5891 
5892 	if ( !GetFloorPos( 64.0f, origin ) ) {
5893 		return;
5894 	}
5895 
5896 	for( i = 0; i < aasLocation.Num(); i++ ) {
5897 		aas = gameLocal.GetAAS( i );
5898 		if ( !aas ) {
5899 			continue;
5900 		}
5901 
5902 		size = aas->GetSettings()->boundingBoxes[0][1];
5903 		bounds[0] = -size;
5904 		size.z = 32.0f;
5905 		bounds[1] = size;
5906 
5907 		areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5908 		if ( areaNum ) {
5909 			aasLocation[ i ].pos = origin;
5910 			aasLocation[ i ].areaNum = areaNum;
5911 		}
5912 	}
5913 }
5914 
5915 /*
5916 ==============
5917 idPlayer::GetAASLocation
5918 ==============
5919 */
GetAASLocation(idAAS * aas,idVec3 & pos,int & areaNum) const5920 void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const {
5921 	int i;
5922 
5923 	if ( aas != NULL ) {
5924 		for( i = 0; i < aasLocation.Num(); i++ ) {
5925 			if ( aas == gameLocal.GetAAS( i ) ) {
5926 				areaNum = aasLocation[ i ].areaNum;
5927 				pos = aasLocation[ i ].pos;
5928 				return;
5929 			}
5930 		}
5931 	}
5932 
5933 	areaNum = 0;
5934 	pos = physicsObj.GetOrigin();
5935 }
5936 
5937 /*
5938 ==============
5939 idPlayer::Move
5940 ==============
5941 */
Move(void)5942 void idPlayer::Move( void ) {
5943 	float newEyeOffset;
5944 	idVec3 oldOrigin;
5945 	idVec3 oldVelocity;
5946 	idVec3 pushVelocity;
5947 
5948 	// save old origin and velocity for crashlanding
5949 	oldOrigin = physicsObj.GetOrigin();
5950 	oldVelocity = physicsObj.GetLinearVelocity();
5951 	pushVelocity = physicsObj.GetPushedLinearVelocity();
5952 
5953 	// set physics variables
5954 	physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() );
5955 	physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() );
5956 
5957 	if ( noclip ) {
5958 		physicsObj.SetContents( 0 );
5959 		physicsObj.SetMovementType( PM_NOCLIP );
5960 	} else if ( spectating ) {
5961 		physicsObj.SetContents( 0 );
5962 		physicsObj.SetMovementType( PM_SPECTATOR );
5963 	} else if ( health <= 0 ) {
5964 		physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
5965 		physicsObj.SetMovementType( PM_DEAD );
5966 	} else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
5967 		physicsObj.SetContents( CONTENTS_BODY );
5968 		physicsObj.SetMovementType( PM_FREEZE );
5969 	} else {
5970 		physicsObj.SetContents( CONTENTS_BODY );
5971 		physicsObj.SetMovementType( PM_NORMAL );
5972 	}
5973 
5974 	if ( spectating ) {
5975 		physicsObj.SetClipMask( MASK_DEADSOLID );
5976 	} else if ( health <= 0 ) {
5977 		physicsObj.SetClipMask( MASK_DEADSOLID );
5978 	} else {
5979 		physicsObj.SetClipMask( MASK_PLAYERSOLID );
5980 	}
5981 
5982 	physicsObj.SetDebugLevel( g_debugMove.GetBool() );
5983 	physicsObj.SetPlayerInput( usercmd, viewAngles );
5984 
5985 	// FIXME: physics gets disabled somehow
5986 	BecomeActive( TH_PHYSICS );
5987 	RunPhysics();
5988 
5989 	// update our last valid AAS location for the AI
5990 	SetAASLocation();
5991 
5992 	if ( spectating ) {
5993 		newEyeOffset = 0.0f;
5994 	} else if ( health <= 0 ) {
5995 		newEyeOffset = pm_deadviewheight.GetFloat();
5996 	} else if ( physicsObj.IsCrouching() ) {
5997 		newEyeOffset = pm_crouchviewheight.GetFloat();
5998 	} else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5999 		newEyeOffset = 0.0f;
6000 	} else {
6001 		newEyeOffset = pm_normalviewheight.GetFloat();
6002 	}
6003 
6004 	if ( EyeHeight() != newEyeOffset ) {
6005 		if ( spectating ) {
6006 			SetEyeHeight( newEyeOffset );
6007 		} else {
6008 			// smooth out duck height changes
6009 			SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) );
6010 		}
6011 	}
6012 
6013 	if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
6014 		AI_CROUCH	= false;
6015 		AI_ONGROUND	= ( influenceActive == INFLUENCE_LEVEL2 );
6016 		AI_ONLADDER	= false;
6017 		AI_JUMP		= false;
6018 	} else {
6019 		AI_CROUCH	= physicsObj.IsCrouching();
6020 		AI_ONGROUND	= physicsObj.HasGroundContacts();
6021 		AI_ONLADDER	= physicsObj.OnLadder();
6022 		AI_JUMP		= physicsObj.HasJumped();
6023 
6024 		// check if we're standing on top of a monster and give a push if we are
6025 		idEntity *groundEnt = physicsObj.GetGroundEntity();
6026 		if ( groundEnt && groundEnt->IsType( idAI::Type ) ) {
6027 			idVec3 vel = physicsObj.GetLinearVelocity();
6028 			if ( vel.ToVec2().LengthSqr() < 0.1f ) {
6029 				vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2();
6030 				vel.ToVec2().NormalizeFast();
6031 				vel.ToVec2() *= pm_walkspeed.GetFloat();
6032 			} else {
6033 				// give em a push in the direction they're going
6034 				vel *= 1.1f;
6035 			}
6036 			physicsObj.SetLinearVelocity( vel );
6037 		}
6038 	}
6039 
6040 	if ( AI_JUMP ) {
6041 		// bounce the view weapon
6042 		loggedAccel_t	*acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6043 		currentLoggedAccel++;
6044 		acc->time = gameLocal.time;
6045 		acc->dir[2] = 200;
6046 		acc->dir[0] = acc->dir[1] = 0;
6047 	}
6048 
6049 	if ( AI_ONLADDER ) {
6050 		int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE;
6051 		int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE;
6052 
6053 		if ( old_rung != new_rung ) {
6054 			StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL );
6055 		}
6056 	}
6057 
6058 	BobCycle( pushVelocity );
6059 	CrashLand( oldOrigin, oldVelocity );
6060 }
6061 
6062 /*
6063 ==============
6064 idPlayer::UpdateHud
6065 ==============
6066 */
UpdateHud(void)6067 void idPlayer::UpdateHud( void ) {
6068 	idPlayer *aimed;
6069 
6070 	if ( !hud ) {
6071 		return;
6072 	}
6073 
6074 	if ( entityNumber != gameLocal.localClientNum ) {
6075 		return;
6076 	}
6077 
6078 	int c = inventory.pickupItemNames.Num();
6079 	if ( c > 0 ) {
6080 		if ( gameLocal.time > inventory.nextItemPickup ) {
6081 			if ( inventory.nextItemPickup && gameLocal.time - inventory.nextItemPickup > 2000 ) {
6082 				inventory.nextItemNum = 1;
6083 			}
6084 			int i;
6085 			for ( i = 0; i < 5 && i < c; i++ ) {
6086 				hud->SetStateString( va( "itemtext%i", inventory.nextItemNum ), inventory.pickupItemNames[0].name );
6087 				hud->SetStateString( va( "itemicon%i", inventory.nextItemNum ), inventory.pickupItemNames[0].icon );
6088 				hud->HandleNamedEvent( va( "itemPickup%i", inventory.nextItemNum++ ) );
6089 				inventory.pickupItemNames.RemoveIndex( 0 );
6090 				if (inventory.nextItemNum == 1 ) {
6091 					inventory.onePickupTime = gameLocal.time;
6092 				} else	if ( inventory.nextItemNum > 5 ) {
6093 					inventory.nextItemNum = 1;
6094 					inventory.nextItemPickup = inventory.onePickupTime + 2000;
6095 				} else {
6096 					inventory.nextItemPickup = gameLocal.time + 400;
6097 				}
6098 			}
6099 		}
6100 	}
6101 
6102 	if ( gameLocal.realClientTime == lastMPAimTime ) {
6103 		if ( MPAim != -1 && gameLocal.gameType == GAME_TDM
6104 			&& gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type )
6105 			&& static_cast< idPlayer * >( gameLocal.entities[ MPAim ] )->team == team ) {
6106 				aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
6107 				hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
6108 				hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
6109 				hud->HandleNamedEvent( "aim_flash" );
6110 				MPAimHighlight = true;
6111 				MPAimFadeTime = 0;	// no fade till loosing focus
6112 		} else if ( MPAimHighlight ) {
6113 			hud->HandleNamedEvent( "aim_fade" );
6114 			MPAimFadeTime = gameLocal.realClientTime;
6115 			MPAimHighlight = false;
6116 		}
6117 	}
6118 	if ( MPAimFadeTime ) {
6119 		assert( !MPAimHighlight );
6120 		if ( gameLocal.realClientTime - MPAimFadeTime > 2000 ) {
6121 			MPAimFadeTime = 0;
6122 		}
6123 	}
6124 
6125 	hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() );
6126 	if ( numProjectilesFired ) {
6127 		hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) );
6128 	} else {
6129 		hud->SetStateString( "projectilepct", "Hit % 0.0" );
6130 	}
6131 
6132 	if ( isLagged && gameLocal.isMultiplayer && gameLocal.localClientNum == entityNumber ) {
6133 		hud->SetStateString( "hudLag", "1" );
6134 	} else {
6135 		hud->SetStateString( "hudLag", "0" );
6136 	}
6137 }
6138 
6139 /*
6140 ==============
6141 idPlayer::UpdateDeathSkin
6142 ==============
6143 */
UpdateDeathSkin(bool state_hitch)6144 void idPlayer::UpdateDeathSkin( bool state_hitch ) {
6145 	if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) {
6146 		return;
6147 	}
6148 	if ( health <= 0 ) {
6149 		if ( !doingDeathSkin ) {
6150 			deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" );
6151 			doingDeathSkin = true;
6152 			renderEntity.noShadow = true;
6153 			if ( state_hitch ) {
6154 				renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f;
6155 			} else {
6156 				renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
6157 			}
6158 			UpdateVisuals();
6159 		}
6160 
6161 		// wait a bit before switching off the content
6162 		if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) {
6163 			SetCombatContents( false );
6164 			deathClearContentsTime = 0;
6165 		}
6166 	} else {
6167 		renderEntity.noShadow = false;
6168 		renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f;
6169 		UpdateVisuals();
6170 		doingDeathSkin = false;
6171 	}
6172 }
6173 
6174 /*
6175 ==============
6176 idPlayer::StartFxOnBone
6177 ==============
6178 */
StartFxOnBone(const char * fx,const char * bone)6179 void idPlayer::StartFxOnBone( const char *fx, const char *bone ) {
6180 	idVec3 offset;
6181 	idMat3 axis;
6182 	jointHandle_t jointHandle = GetAnimator()->GetJointHandle( bone );
6183 
6184 	if ( jointHandle == INVALID_JOINT ) {
6185 		gameLocal.Printf( "Cannot find bone %s\n", bone );
6186 		return;
6187 	}
6188 
6189 	if ( GetAnimator()->GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) {
6190 		offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis();
6191 		axis = axis * GetPhysics()->GetAxis();
6192 	}
6193 
6194 	idEntityFx::StartFx( fx, &offset, &axis, this, true );
6195 }
6196 
6197 /*
6198 ==============
6199 idPlayer::Think
6200 
6201 Called every tic for each player
6202 ==============
6203 */
Think(void)6204 void idPlayer::Think( void ) {
6205 	renderEntity_t *headRenderEnt;
6206 
6207 	UpdatePlayerIcons();
6208 
6209 	// latch button actions
6210 	oldButtons = usercmd.buttons;
6211 
6212 	// grab out usercmd
6213 	usercmd_t oldCmd = usercmd;
6214 	usercmd = gameLocal.usercmds[ entityNumber ];
6215 	buttonMask &= usercmd.buttons;
6216 	usercmd.buttons &= ~buttonMask;
6217 
6218 	if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
6219 		return;
6220 	}
6221 
6222 	// clear the ik before we do anything else so the skeleton doesn't get updated twice
6223 	walkIK.ClearJointMods();
6224 
6225 	// if this is the very first frame of the map, set the delta view angles
6226 	// based on the usercmd angles
6227 	if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) {
6228 		spawnAnglesSet = true;
6229 		SetViewAngles( spawnAngles );
6230 		oldFlags = usercmd.flags;
6231 	}
6232 
6233 	if ( objectiveSystemOpen || gameLocal.inCinematic || influenceActive ) {
6234 		if ( objectiveSystemOpen && AI_PAIN ) {
6235 			TogglePDA();
6236 		}
6237 		usercmd.forwardmove = 0;
6238 		usercmd.rightmove = 0;
6239 		usercmd.upmove = 0;
6240 	}
6241 
6242 	// log movement changes for weapon bobbing effects
6243 	if ( usercmd.forwardmove != oldCmd.forwardmove ) {
6244 		loggedAccel_t	*acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6245 		currentLoggedAccel++;
6246 		acc->time = gameLocal.time;
6247 		acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove;
6248 		acc->dir[1] = acc->dir[2] = 0;
6249 	}
6250 
6251 	if ( usercmd.rightmove != oldCmd.rightmove ) {
6252 		loggedAccel_t	*acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6253 		currentLoggedAccel++;
6254 		acc->time = gameLocal.time;
6255 		acc->dir[1] = usercmd.rightmove - oldCmd.rightmove;
6256 		acc->dir[0] = acc->dir[2] = 0;
6257 	}
6258 
6259 	// freelook centering
6260 	if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) {
6261 		centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 );
6262 	}
6263 
6264 	// zooming
6265 	if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) {
6266 		if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) {
6267 			zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() );
6268 		} else {
6269 			zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() );
6270 		}
6271 	}
6272 
6273 	// if we have an active gui, we will unrotate the view angles as
6274 	// we turn the mouse movements into gui events
6275 	idUserInterface *gui = ActiveGui();
6276 	if ( gui && gui != focusUI ) {
6277 		RouteGuiMouse( gui );
6278 	}
6279 
6280 	// set the push velocity on the weapon before running the physics
6281 	if ( weapon.GetEntity() ) {
6282 		weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() );
6283 	}
6284 
6285 	EvaluateControls();
6286 
6287 	if ( !af.IsActive() ) {
6288 		AdjustBodyAngles();
6289 		CopyJointsFromBodyToHead();
6290 	}
6291 
6292 	Move();
6293 
6294 	if ( !g_stopTime.GetBool() ) {
6295 
6296 		if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) {
6297 			TouchTriggers();
6298 		}
6299 
6300 		// not done on clients for various reasons. don't do it on server and save the sound channel for other things
6301 		if ( !gameLocal.isMultiplayer ) {
6302 			SetCurrentHeartRate();
6303 			float scale = g_damageScale.GetFloat();
6304 			if ( g_useDynamicProtection.GetBool() && scale < 1.0f && gameLocal.time - lastDmgTime > 500 ) {
6305 				if ( scale < 1.0f ) {
6306 					scale += 0.05f;
6307 				}
6308 				if ( scale > 1.0f ) {
6309 					scale = 1.0f;
6310 				}
6311 				g_damageScale.SetFloat( scale );
6312 			}
6313 		}
6314 
6315 		// update GUIs, Items, and character interactions
6316 		UpdateFocus();
6317 
6318 		UpdateLocation();
6319 
6320 		// update player script
6321 		UpdateScript();
6322 
6323 		// service animations
6324 		if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) {
6325 			UpdateConditions();
6326 			UpdateAnimState();
6327 			CheckBlink();
6328 		}
6329 
6330 		// clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
6331 		AI_PAIN = false;
6332 	}
6333 
6334 	// calculate the exact bobbed view position, which is used to
6335 	// position the view weapon, among other things
6336 	CalculateFirstPersonView();
6337 
6338 	// this may use firstPersonView, or a thirdPeroson / camera view
6339 	CalculateRenderView();
6340 
6341 	inventory.UpdateArmor();
6342 
6343 	if ( spectating ) {
6344 		UpdateSpectating();
6345 	} else if ( health > 0 ) {
6346 		UpdateWeapon();
6347 	}
6348 
6349 	UpdateAir();
6350 
6351 	UpdateHud();
6352 
6353 	UpdatePowerUps();
6354 
6355 	UpdateDeathSkin( false );
6356 
6357 	if ( gameLocal.isMultiplayer ) {
6358 		DrawPlayerIcons();
6359 	}
6360 
6361 	if ( head.GetEntity() ) {
6362 		headRenderEnt = head.GetEntity()->GetRenderEntity();
6363 	} else {
6364 		headRenderEnt = NULL;
6365 	}
6366 
6367 	if ( headRenderEnt ) {
6368 		if ( influenceSkin ) {
6369 			headRenderEnt->customSkin = influenceSkin;
6370 		} else {
6371 			headRenderEnt->customSkin = NULL;
6372 		}
6373 	}
6374 
6375 	if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
6376 		renderEntity.suppressShadowInViewID	= 0;
6377 		if ( headRenderEnt ) {
6378 			headRenderEnt->suppressShadowInViewID = 0;
6379 		}
6380 	} else {
6381 		renderEntity.suppressShadowInViewID	= entityNumber+1;
6382 		if ( headRenderEnt ) {
6383 			headRenderEnt->suppressShadowInViewID = entityNumber+1;
6384 		}
6385 	}
6386 	// never cast shadows from our first-person muzzle flashes
6387 	renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6388 	if ( headRenderEnt ) {
6389 		headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6390 	}
6391 
6392 	if ( !g_stopTime.GetBool() ) {
6393 		UpdateAnimation();
6394 
6395 		Present();
6396 
6397 		UpdateDamageEffects();
6398 
6399 		LinkCombat();
6400 
6401 		playerView.CalculateShake();
6402 	}
6403 
6404 	if ( !( thinkFlags & TH_THINK ) ) {
6405 		gameLocal.Printf( "player %d not thinking?\n", entityNumber );
6406 	}
6407 
6408 	if ( g_showEnemies.GetBool() ) {
6409 		idActor *ent;
6410 		int num = 0;
6411 		for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
6412 			gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() );
6413 			gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() );
6414 			num++;
6415 		}
6416 		gameLocal.Printf( "%d: enemies\n", num );
6417 	}
6418 }
6419 
6420 /*
6421 =================
6422 idPlayer::RouteGuiMouse
6423 =================
6424 */
RouteGuiMouse(idUserInterface * gui)6425 void idPlayer::RouteGuiMouse( idUserInterface *gui ) {
6426 	sysEvent_t ev;
6427 
6428 	if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) {
6429 		ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY );
6430 		gui->HandleEvent( &ev, gameLocal.time );
6431 		oldMouseX = usercmd.mx;
6432 		oldMouseY = usercmd.my;
6433 	}
6434 }
6435 
6436 /*
6437 ==================
6438 idPlayer::LookAtKiller
6439 ==================
6440 */
LookAtKiller(idEntity * inflictor,idEntity * attacker)6441 void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) {
6442 	idVec3 dir;
6443 
6444 	if ( attacker && attacker != this ) {
6445 		dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6446 	} else if ( inflictor && inflictor != this ) {
6447 		dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6448 	} else {
6449 		dir = viewAxis[ 0 ];
6450 	}
6451 
6452 	idAngles ang( 0, dir.ToYaw(), 0 );
6453 	SetViewAngles( ang );
6454 }
6455 
6456 /*
6457 ==============
6458 idPlayer::Kill
6459 ==============
6460 */
Kill(bool delayRespawn,bool nodamage)6461 void idPlayer::Kill( bool delayRespawn, bool nodamage ) {
6462 	if ( spectating ) {
6463 		SpectateFreeFly( false );
6464 	} else if ( health > 0 ) {
6465 		godmode = false;
6466 		if ( nodamage ) {
6467 			ServerSpectate( true );
6468 			forceRespawn = true;
6469 		} else {
6470 			Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
6471 			if ( delayRespawn ) {
6472 				forceRespawn = false;
6473 				int delay = spawnArgs.GetFloat( "respawn_delay" );
6474 				minRespawnTime = gameLocal.time + SEC2MS( delay );
6475 				maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6476 			}
6477 		}
6478 	}
6479 }
6480 
6481 /*
6482 ==================
6483 idPlayer::Killed
6484 ==================
6485 */
Killed(idEntity * inflictor,idEntity * attacker,int damage,const idVec3 & dir,int location)6486 void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
6487 	float delay;
6488 
6489 	assert( !gameLocal.isClient );
6490 
6491 	// stop taking knockback once dead
6492 	fl.noknockback = true;
6493 	if ( health < -999 ) {
6494 		health = -999;
6495 	}
6496 
6497 	if ( AI_DEAD ) {
6498 		AI_PAIN = true;
6499 		return;
6500 	}
6501 
6502 	heartInfo.Init( 0, 0, 0, BASE_HEARTRATE );
6503 	AdjustHeartRate( DEAD_HEARTRATE, 10.0f, 0.0f, true );
6504 
6505 	if ( !g_testDeath.GetBool() ) {
6506 		playerView.Fade( colorBlack, 12000 );
6507 	}
6508 
6509 	AI_DEAD = true;
6510 	SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
6511 	SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
6512 	SetWaitState( "" );
6513 
6514 	animator.ClearAllJoints();
6515 
6516 	if ( StartRagdoll() ) {
6517 		pm_modelView.SetInteger( 0 );
6518 		minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME;
6519 		maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6520 	} else {
6521 		// don't allow respawn until the death anim is done
6522 		// g_forcerespawn may force spawning at some later time
6523 		delay = spawnArgs.GetFloat( "respawn_delay" );
6524 		minRespawnTime = gameLocal.time + SEC2MS( delay );
6525 		maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6526 	}
6527 
6528 	physicsObj.SetMovementType( PM_DEAD );
6529 	StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
6530 	StopSound( SND_CHANNEL_BODY2, false );
6531 
6532 	fl.takedamage = true;		// can still be gibbed
6533 
6534 	// get rid of weapon
6535 	weapon.GetEntity()->OwnerDied();
6536 
6537 	// drop the weapon as an item
6538 	DropWeapon( true );
6539 
6540 	if ( !g_testDeath.GetBool() ) {
6541 		LookAtKiller( inflictor, attacker );
6542 	}
6543 
6544 	if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) {
6545 		idPlayer *killer = NULL;
6546 		// no gibbing in MP. Event_Gib will early out in MP
6547 		if ( attacker->IsType( idPlayer::Type ) ) {
6548 			killer = static_cast<idPlayer*>(attacker);
6549 			if ( health < -20 || killer->PowerUpActive( BERSERK ) ) {
6550 				gibDeath = true;
6551 				gibsDir = dir;
6552 				gibsLaunched = false;
6553 			}
6554 		}
6555 		gameLocal.mpGame.PlayerDeath( this, killer, isTelefragged );
6556 	} else {
6557 		physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
6558 	}
6559 
6560 	ClearPowerUps();
6561 
6562 	UpdateVisuals();
6563 
6564 	isChatting = false;
6565 }
6566 
6567 /*
6568 =====================
6569 idPlayer::GetAIAimTargets
6570 
6571 Returns positions for the AI to aim at.
6572 =====================
6573 */
GetAIAimTargets(const idVec3 & lastSightPos,idVec3 & headPos,idVec3 & chestPos)6574 void idPlayer::GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ) {
6575 	idVec3 offset;
6576 	idMat3 axis;
6577 	idVec3 origin;
6578 
6579 	origin = lastSightPos - physicsObj.GetOrigin();
6580 
6581 	GetJointWorldTransform( chestJoint, gameLocal.time, offset, axis );
6582 	headPos = offset + origin;
6583 
6584 	GetJointWorldTransform( headJoint, gameLocal.time, offset, axis );
6585 	chestPos = offset + origin;
6586 }
6587 
6588 /*
6589 ================
6590 idPlayer::DamageFeedback
6591 
6592 callback function for when another entity received damage from this entity.  damage can be adjusted and returned to the caller.
6593 ================
6594 */
DamageFeedback(idEntity * victim,idEntity * inflictor,int & damage)6595 void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
6596 	assert( !gameLocal.isClient );
6597 	damage *= PowerUpModifier( BERSERK );
6598 	if ( damage && ( victim != this ) && victim->IsType( idActor::Type ) ) {
6599 		SetLastHitTime( gameLocal.time );
6600 	}
6601 }
6602 
6603 /*
6604 =================
6605 idPlayer::CalcDamagePoints
6606 
6607 Calculates how many health and armor points will be inflicted, but
6608 doesn't actually do anything with them.  This is used to tell when an attack
6609 would have killed the player, possibly allowing a "saving throw"
6610 =================
6611 */
CalcDamagePoints(idEntity * inflictor,idEntity * attacker,const idDict * damageDef,const float damageScale,const int location,int * health,int * armor)6612 void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef,
6613 							   const float damageScale, const int location, int *health, int *armor ) {
6614 	int		damage;
6615 	int		armorSave;
6616 
6617 	damageDef->GetInt( "damage", "20", damage );
6618 	damage = GetDamageForLocation( damage, location );
6619 
6620 	idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast<idPlayer*>(attacker) : NULL;
6621 	if ( !gameLocal.isMultiplayer ) {
6622 		if ( inflictor != gameLocal.world ) {
6623 			switch ( g_skill.GetInteger() ) {
6624 				case 0:
6625 					damage *= 0.80f;
6626 					if ( damage < 1 ) {
6627 						damage = 1;
6628 					}
6629 					break;
6630 				case 2:
6631 					damage *= 1.70f;
6632 					break;
6633 				case 3:
6634 					damage *= 3.5f;
6635 					break;
6636 				default:
6637 					break;
6638 			}
6639 		}
6640 	}
6641 
6642 	damage *= damageScale;
6643 
6644 	// always give half damage if hurting self
6645 	if ( attacker == this ) {
6646 		if ( gameLocal.isMultiplayer ) {
6647 			// only do this in mp so single player plasma and rocket splash is very dangerous in close quarters
6648 			damage *= damageDef->GetFloat( "selfDamageScale", "0.5" );
6649 		} else {
6650 			damage *= damageDef->GetFloat( "selfDamageScale", "1" );
6651 		}
6652 	}
6653 
6654 	// check for completely getting out of the damage
6655 	if ( !damageDef->GetBool( "noGod" ) ) {
6656 		// check for godmode
6657 		if ( godmode ) {
6658 			damage = 0;
6659 		}
6660 	}
6661 
6662 	// inform the attacker that they hit someone
6663 	attacker->DamageFeedback( this, inflictor, damage );
6664 
6665 	// save some from armor
6666 	if ( !damageDef->GetBool( "noArmor" ) ) {
6667 		float armor_protection;
6668 
6669 		armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat();
6670 
6671 		armorSave = ceil( damage * armor_protection );
6672 		if ( armorSave >= inventory.armor ) {
6673 			armorSave = inventory.armor;
6674 		}
6675 
6676 		if ( !damage ) {
6677 			armorSave = 0;
6678 		} else if ( armorSave >= damage ) {
6679 			armorSave = damage - 1;
6680 			damage = 1;
6681 		} else {
6682 			damage -= armorSave;
6683 		}
6684 	} else {
6685 		armorSave = 0;
6686 	}
6687 
6688 	// check for team damage
6689 	if ( gameLocal.gameType == GAME_TDM
6690 		&& !gameLocal.serverInfo.GetBool( "si_teamDamage" )
6691 		&& !damageDef->GetBool( "noTeam" )
6692 		&& player
6693 		&& player != this		// you get self damage no matter what
6694 		&& player->team == team ) {
6695 			damage = 0;
6696 	}
6697 
6698 	*health = damage;
6699 	*armor = armorSave;
6700 }
6701 
6702 /*
6703 ============
6704 Damage
6705 
6706 this		entity that is being damaged
6707 inflictor	entity that is causing the damage
6708 attacker	entity that caused the inflictor to damage targ
6709 	example: this=monster, inflictor=rocket, attacker=player
6710 
6711 dir			direction of the attack for knockback in global space
6712 
6713 damageDef	an idDict with all the options for damage effects
6714 
6715 inflictor, attacker, dir, and point can be NULL for environmental effects
6716 ============
6717 */
Damage(idEntity * inflictor,idEntity * attacker,const idVec3 & dir,const char * damageDefName,const float damageScale,const int location)6718 void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
6719 					   const char *damageDefName, const float damageScale, const int location ) {
6720 	idVec3		kick;
6721 	int			damage;
6722 	int			armorSave;
6723 	int			knockback;
6724 	idVec3		damage_from;
6725 	idVec3		localDamageVector;
6726 	float		attackerPushScale;
6727 
6728 	// damage is only processed on server
6729 	if ( gameLocal.isClient ) {
6730 		return;
6731 	}
6732 
6733 	if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) {
6734 		return;
6735 	}
6736 
6737 	if ( !inflictor ) {
6738 		inflictor = gameLocal.world;
6739 	}
6740 	if ( !attacker ) {
6741 		attacker = gameLocal.world;
6742 	}
6743 
6744 	if ( attacker->IsType( idAI::Type ) ) {
6745 		if ( PowerUpActive( BERSERK ) ) {
6746 			return;
6747 		}
6748 		// don't take damage from monsters during influences
6749 		if ( influenceActive != 0 ) {
6750 			return;
6751 		}
6752 	}
6753 
6754 	const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false );
6755 	if ( !damageDef ) {
6756 		gameLocal.Warning( "Unknown damageDef '%s'", damageDefName );
6757 		return;
6758 	}
6759 
6760 	if ( damageDef->dict.GetBool( "ignore_player" ) ) {
6761 		return;
6762 	}
6763 
6764 	CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave );
6765 
6766 	// determine knockback
6767 	damageDef->dict.GetInt( "knockback", "20", knockback );
6768 
6769 	if ( knockback != 0 && !fl.noknockback ) {
6770 		if ( attacker == this ) {
6771 			damageDef->dict.GetFloat( "attackerPushScale", "0", attackerPushScale );
6772 		} else {
6773 			attackerPushScale = 1.0f;
6774 		}
6775 
6776 		kick = dir;
6777 		kick.Normalize();
6778 		kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f;
6779 		physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick );
6780 
6781 		// set the timer so that the player can't cancel out the movement immediately
6782 		physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) );
6783 	}
6784 
6785 	// give feedback on the player view and audibly when armor is helping
6786 	if ( armorSave ) {
6787 		inventory.armor -= armorSave;
6788 
6789 		if ( gameLocal.time > lastArmorPulse + 200 ) {
6790 			StartSound( "snd_hitArmor", SND_CHANNEL_ITEM, 0, false, NULL );
6791 		}
6792 		lastArmorPulse = gameLocal.time;
6793 	}
6794 
6795 	if ( damageDef->dict.GetBool( "burn" ) ) {
6796 		StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL );
6797 	} else if ( damageDef->dict.GetBool( "no_air" ) ) {
6798 		if ( !armorSave && health > 0 ) {
6799 			StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL );
6800 		}
6801 	}
6802 
6803 	if ( g_debugDamage.GetInteger() ) {
6804 		gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n",
6805 			entityNumber, health, damage, armorSave );
6806 	}
6807 
6808 	// move the world direction vector to local coordinates
6809 	damage_from = dir;
6810 	damage_from.Normalize();
6811 
6812 	viewAxis.ProjectVector( damage_from, localDamageVector );
6813 
6814 	// add to the damage inflicted on a player this frame
6815 	// the total will be turned into screen blends and view angle kicks
6816 	// at the end of the frame
6817 	if ( health > 0 ) {
6818 		playerView.DamageImpulse( localDamageVector, &damageDef->dict );
6819 	}
6820 
6821 	// do the damage
6822 	if ( damage > 0 ) {
6823 
6824 		if ( !gameLocal.isMultiplayer ) {
6825 			float scale = g_damageScale.GetFloat();
6826 			if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) {
6827 				if ( gameLocal.time > lastDmgTime + 500 && scale > 0.25f ) {
6828 					scale -= 0.05f;
6829 					g_damageScale.SetFloat( scale );
6830 				}
6831 			}
6832 
6833 			if ( scale > 0.0f ) {
6834 				damage *= scale;
6835 			}
6836 		}
6837 
6838 		if ( damage < 1 ) {
6839 			damage = 1;
6840 		}
6841 
6842 		health -= damage;
6843 
6844 		if ( health <= 0 ) {
6845 
6846 			if ( health < -999 ) {
6847 				health = -999;
6848 			}
6849 
6850 			isTelefragged = damageDef->dict.GetBool( "telefrag" );
6851 
6852 			lastDmgTime = gameLocal.time;
6853 			Killed( inflictor, attacker, damage, dir, location );
6854 
6855 		} else {
6856 			// force a blink
6857 			blink_time = 0;
6858 
6859 			// let the anim script know we took damage
6860 			AI_PAIN = Pain( inflictor, attacker, damage, dir, location );
6861 			if ( !g_testDeath.GetBool() ) {
6862 				lastDmgTime = gameLocal.time;
6863 			}
6864 		}
6865 	} else {
6866 		// don't accumulate impulses
6867 		if ( af.IsLoaded() ) {
6868 			// clear impacts
6869 			af.Rest();
6870 
6871 			// physics is turned off by calling af.Rest()
6872 			BecomeActive( TH_PHYSICS );
6873 		}
6874 	}
6875 
6876 	lastDamageDef = damageDef->Index();
6877 	lastDamageDir = damage_from;
6878 	lastDamageLocation = location;
6879 }
6880 
6881 /*
6882 ===========
6883 idPlayer::Teleport
6884 ============
6885 */
Teleport(const idVec3 & origin,const idAngles & angles,idEntity * destination)6886 void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) {
6887 	idVec3 org;
6888 
6889 	if ( weapon.GetEntity() ) {
6890 		weapon.GetEntity()->LowerWeapon();
6891 	}
6892 
6893 	SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) );
6894 	if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) {
6895 		SetOrigin( org );
6896 	}
6897 
6898 	// clear the ik heights so model doesn't appear in the wrong place
6899 	walkIK.EnableAll();
6900 
6901 	GetPhysics()->SetLinearVelocity( vec3_origin );
6902 
6903 	SetViewAngles( angles );
6904 
6905 	legsYaw = 0.0f;
6906 	idealLegsYaw = 0.0f;
6907 	oldViewYaw = viewAngles.yaw;
6908 
6909 	if ( gameLocal.isMultiplayer ) {
6910 		playerView.Flash( colorWhite, 140 );
6911 	}
6912 
6913 	UpdateVisuals();
6914 
6915 	teleportEntity = destination;
6916 
6917 	if ( !gameLocal.isClient && !noclip ) {
6918 		if ( gameLocal.isMultiplayer ) {
6919 			// kill anything at the new position or mark for kill depending on immediate or delayed teleport
6920 			gameLocal.KillBox( this, destination != NULL );
6921 		} else {
6922 			// kill anything at the new position
6923 			gameLocal.KillBox( this, true );
6924 		}
6925 	}
6926 }
6927 
6928 /*
6929 ====================
6930 idPlayer::SetPrivateCameraView
6931 ====================
6932 */
SetPrivateCameraView(idCamera * camView)6933 void idPlayer::SetPrivateCameraView( idCamera *camView ) {
6934 	privateCameraView = camView;
6935 	if ( camView ) {
6936 		StopFiring();
6937 		Hide();
6938 	} else {
6939 		if ( !spectating ) {
6940 			Show();
6941 		}
6942 	}
6943 }
6944 
6945 /*
6946 ====================
6947 idPlayer::DefaultFov
6948 
6949 Returns the base FOV
6950 ====================
6951 */
DefaultFov(void) const6952 float idPlayer::DefaultFov( void ) const {
6953 	float fov;
6954 
6955 	fov = g_fov.GetFloat();
6956 	if ( gameLocal.isMultiplayer ) {
6957 		if ( fov < 90.0f ) {
6958 			return 90.0f;
6959 		} else if ( fov > 110.0f ) {
6960 			return 110.0f;
6961 		}
6962 	}
6963 
6964 	return fov;
6965 }
6966 
6967 /*
6968 ====================
6969 idPlayer::CalcFov
6970 
6971 Fixed fov at intermissions, otherwise account for fov variable and zooms.
6972 ====================
6973 */
CalcFov(bool honorZoom)6974 float idPlayer::CalcFov( bool honorZoom ) {
6975 	float fov;
6976 
6977 	if ( fxFov ) {
6978 		return DefaultFov() + 10.0f + cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f;
6979 	}
6980 
6981 	if ( influenceFov ) {
6982 		return influenceFov;
6983 	}
6984 
6985 	if ( zoomFov.IsDone( gameLocal.time ) ) {
6986 		fov = ( honorZoom && usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ? weapon.GetEntity()->GetZoomFov() : DefaultFov();
6987 	} else {
6988 		fov = zoomFov.GetCurrentValue( gameLocal.time );
6989 	}
6990 
6991 	// bound normal viewsize
6992 	if ( fov < 1 ) {
6993 		fov = 1;
6994 	} else if ( fov > 179 ) {
6995 		fov = 179;
6996 	}
6997 
6998 	return fov;
6999 }
7000 
7001 /*
7002 ==============
7003 idPlayer::GunTurningOffset
7004 
7005 generate a rotational offset for the gun based on the view angle
7006 history in loggedViewAngles
7007 ==============
7008 */
GunTurningOffset(void)7009 idAngles idPlayer::GunTurningOffset( void ) {
7010 	idAngles	a;
7011 
7012 	a.Zero();
7013 
7014 	if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) {
7015 		return a;
7016 	}
7017 
7018 	idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ];
7019 
7020 	idAngles	av, base;
7021 	int weaponAngleOffsetAverages;
7022 	float weaponAngleOffsetScale, weaponAngleOffsetMax;
7023 
7024 	weapon.GetEntity()->GetWeaponAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax );
7025 
7026 	av = current;
7027 
7028 	// calcualte this so the wrap arounds work properly
7029 	for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) {
7030 		idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ];
7031 
7032 		idAngles delta = a2 - current;
7033 
7034 		if ( delta[1] > 180 ) {
7035 			delta[1] -= 360;
7036 		} else if ( delta[1] < -180 ) {
7037 			delta[1] += 360;
7038 		}
7039 
7040 		av += delta * ( 1.0f / weaponAngleOffsetAverages );
7041 	}
7042 
7043 	a = ( av - current ) * weaponAngleOffsetScale;
7044 
7045 	for ( int i = 0 ; i < 3 ; i++ ) {
7046 		if ( a[i] < -weaponAngleOffsetMax ) {
7047 			a[i] = -weaponAngleOffsetMax;
7048 		} else if ( a[i] > weaponAngleOffsetMax ) {
7049 			a[i] = weaponAngleOffsetMax;
7050 		}
7051 	}
7052 
7053 	return a;
7054 }
7055 
7056 /*
7057 ==============
7058 idPlayer::GunAcceleratingOffset
7059 
7060 generate a positional offset for the gun based on the movement
7061 history in loggedAccelerations
7062 ==============
7063 */
GunAcceleratingOffset(void)7064 idVec3	idPlayer::GunAcceleratingOffset( void ) {
7065 	idVec3	ofs;
7066 
7067 	float weaponOffsetTime, weaponOffsetScale;
7068 
7069 	ofs.Zero();
7070 
7071 	weapon.GetEntity()->GetWeaponTimeOffsets( &weaponOffsetTime, &weaponOffsetScale );
7072 
7073 	int stop = currentLoggedAccel - NUM_LOGGED_ACCELS;
7074 	if ( stop < 0 ) {
7075 		stop = 0;
7076 	}
7077 	for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) {
7078 		loggedAccel_t	*acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)];
7079 
7080 		float	f;
7081 		float	t = gameLocal.time - acc->time;
7082 		if ( t >= weaponOffsetTime ) {
7083 			break;	// remainder are too old to care about
7084 		}
7085 
7086 		f = t / weaponOffsetTime;
7087 		f = ( cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f;
7088 		ofs += f * weaponOffsetScale * acc->dir;
7089 	}
7090 
7091 	return ofs;
7092 }
7093 
7094 /*
7095 ==============
7096 idPlayer::CalculateViewWeaponPos
7097 
7098 Calculate the bobbing position of the view weapon
7099 ==============
7100 */
CalculateViewWeaponPos(idVec3 & origin,idMat3 & axis)7101 void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) {
7102 	float		scale;
7103 	float		fracsin;
7104 	idAngles	angles;
7105 	int			delta;
7106 
7107 	// CalculateRenderView must have been called first
7108 	const idVec3 &viewOrigin = firstPersonViewOrigin;
7109 	const idMat3 &viewAxis = firstPersonViewAxis;
7110 
7111 	// these cvars are just for hand tweaking before moving a value to the weapon def
7112 	idVec3	gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() );
7113 
7114 	// as the player changes direction, the gun will take a small lag
7115 	idVec3	gunOfs = GunAcceleratingOffset();
7116 	origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis;
7117 
7118 	// on odd legs, invert some angles
7119 	if ( bobCycle & 128 ) {
7120 		scale = -xyspeed;
7121 	} else {
7122 		scale = xyspeed;
7123 	}
7124 
7125 	// gun angles from bobbing
7126 	angles.roll		= scale * bobfracsin * 0.005f;
7127 	angles.yaw		= scale * bobfracsin * 0.01f;
7128 	angles.pitch	= xyspeed * bobfracsin * 0.005f;
7129 
7130 	// gun angles from turning
7131 	if ( gameLocal.isMultiplayer ) {
7132 		idAngles offset = GunTurningOffset();
7133 		offset *= g_mpWeaponAngleScale.GetFloat();
7134 		angles += offset;
7135 	} else {
7136 		angles += GunTurningOffset();
7137 	}
7138 
7139 	idVec3 gravity = physicsObj.GetGravityNormal();
7140 
7141 	// drop the weapon when landing after a jump / fall
7142 	delta = gameLocal.time - landTime;
7143 	if ( delta < LAND_DEFLECT_TIME ) {
7144 		origin -= gravity * ( landChange*0.25f * delta / LAND_DEFLECT_TIME );
7145 	} else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
7146 		origin -= gravity * ( landChange*0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME );
7147 	}
7148 
7149 	// speed sensitive idle drift
7150 	scale = xyspeed + 40.0f;
7151 	fracsin = scale * sin( MS2SEC( gameLocal.time ) ) * 0.01f;
7152 	angles.roll		+= fracsin;
7153 	angles.yaw		+= fracsin;
7154 	angles.pitch	+= fracsin;
7155 
7156 	axis = angles.ToMat3() * viewAxis;
7157 }
7158 
7159 /*
7160 ===============
7161 idPlayer::OffsetThirdPersonView
7162 ===============
7163 */
OffsetThirdPersonView(float angle,float range,float height,bool clip)7164 void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) {
7165 	idVec3			view;
7166 	idVec3			focusAngles;
7167 	trace_t			trace;
7168 	idVec3			focusPoint;
7169 	float			focusDist;
7170 	float			forwardScale, sideScale;
7171 	idVec3			origin;
7172 	idAngles		angles;
7173 	idMat3			axis;
7174 	idBounds		bounds;
7175 
7176 	angles = viewAngles;
7177 	GetViewPos( origin, axis );
7178 
7179 	if ( angle ) {
7180 		angles.pitch = 0.0f;
7181 	}
7182 
7183 	if ( angles.pitch > 45.0f ) {
7184 		angles.pitch = 45.0f;		// don't go too far overhead
7185 	}
7186 
7187 	focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE;
7188 	focusPoint.z += height;
7189 	view = origin;
7190 	view.z += 8 + height;
7191 
7192 	angles.pitch *= 0.5f;
7193 	renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7194 
7195 	idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale );
7196 	view -= range * forwardScale * renderView->viewaxis[ 0 ];
7197 	view += range * sideScale * renderView->viewaxis[ 1 ];
7198 
7199 	if ( clip ) {
7200 		// trace a ray from the origin to the viewpoint to make sure the view isn't
7201 		// in a solid block.  Use an 8 by 8 block to prevent the view from near clipping anything
7202 		bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) );
7203 		gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7204 		if ( trace.fraction != 1.0f ) {
7205 			view = trace.endpos;
7206 			view.z += ( 1.0f - trace.fraction ) * 32.0f;
7207 
7208 			// try another trace to this position, because a tunnel may have the ceiling
7209 			// close enough that this is poking out
7210 			gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7211 			view = trace.endpos;
7212 		}
7213 	}
7214 
7215 	// select pitch to look at focus point from vieword
7216 	focusPoint -= view;
7217 	focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
7218 	if ( focusDist < 1.0f ) {
7219 		focusDist = 1.0f;	// should never happen
7220 	}
7221 
7222 	angles.pitch = - RAD2DEG( atan2( focusPoint.z, focusDist ) );
7223 	angles.yaw -= angle;
7224 
7225 	renderView->vieworg = view;
7226 	renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7227 	renderView->viewID = 0;
7228 }
7229 
7230 /*
7231 ===============
7232 idPlayer::GetEyePosition
7233 ===============
7234 */
GetEyePosition(void) const7235 idVec3 idPlayer::GetEyePosition( void ) const {
7236 	idVec3 org;
7237 
7238 	// use the smoothed origin if spectating another player in multiplayer
7239 	if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
7240 		org = smoothedOrigin;
7241 	} else {
7242 		org = GetPhysics()->GetOrigin();
7243 	}
7244 	return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z );
7245 }
7246 
7247 /*
7248 ===============
7249 idPlayer::GetViewPos
7250 ===============
7251 */
GetViewPos(idVec3 & origin,idMat3 & axis) const7252 void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const {
7253 	idAngles angles;
7254 
7255 	// if dead, fix the angle and don't add any kick
7256 	if ( health <= 0 ) {
7257 		angles.yaw = viewAngles.yaw;
7258 		angles.roll = 40;
7259 		angles.pitch = -15;
7260 		axis = angles.ToMat3();
7261 		origin = GetEyePosition();
7262 	} else {
7263 		origin = GetEyePosition() + viewBob;
7264 		angles = viewAngles + viewBobAngles + playerView.AngleOffset();
7265 
7266 		axis = angles.ToMat3() * physicsObj.GetGravityAxis();
7267 
7268 		// adjust the origin based on the camera nodal distance (eye distance from neck)
7269 		origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat();
7270 		origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat();
7271 	}
7272 }
7273 
7274 /*
7275 ===============
7276 idPlayer::CalculateFirstPersonView
7277 ===============
7278 */
CalculateFirstPersonView(void)7279 void idPlayer::CalculateFirstPersonView( void ) {
7280 	if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) {
7281 		//	Displays the view from the point of view of the "camera" joint in the player model
7282 
7283 		idMat3 axis;
7284 		idVec3 origin;
7285 		idAngles ang;
7286 
7287 		ang = viewBobAngles + playerView.AngleOffset();
7288 		ang.yaw += viewAxis[ 0 ].ToYaw();
7289 
7290 		jointHandle_t joint = animator.GetJointHandle( "camera" );
7291 		animator.GetJointTransform( joint, gameLocal.time, origin, axis );
7292 		firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob;
7293 		firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis();
7294 	} else {
7295 		// offset for local bobbing and kicks
7296 		GetViewPos( firstPersonViewOrigin, firstPersonViewAxis );
7297 #if 0
7298 		// shakefrom sound stuff only happens in first person
7299 		firstPersonViewAxis = firstPersonViewAxis * playerView.ShakeAxis();
7300 #endif
7301 	}
7302 }
7303 
7304 /*
7305 ==================
7306 idPlayer::GetRenderView
7307 
7308 Returns the renderView that was calculated for this tic
7309 ==================
7310 */
GetRenderView(void)7311 renderView_t *idPlayer::GetRenderView( void ) {
7312 	return renderView;
7313 }
7314 
7315 /*
7316 ==================
7317 idPlayer::CalculateRenderView
7318 
7319 create the renderView for the current tic
7320 ==================
7321 */
CalculateRenderView(void)7322 void idPlayer::CalculateRenderView( void ) {
7323 	int i;
7324 	float range;
7325 
7326 	if ( !renderView ) {
7327 		renderView = new renderView_t;
7328 	}
7329 	memset( renderView, 0, sizeof( *renderView ) );
7330 
7331 	// copy global shader parms
7332 	for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
7333 		renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ];
7334 	}
7335 	renderView->globalMaterial = gameLocal.GetGlobalMaterial();
7336 	renderView->time = gameLocal.time;
7337 
7338 	// calculate size of 3D view
7339 	renderView->x = 0;
7340 	renderView->y = 0;
7341 	renderView->width = SCREEN_WIDTH;
7342 	renderView->height = SCREEN_HEIGHT;
7343 	renderView->viewID = 0;
7344 
7345 	// check if we should be drawing from a camera's POV
7346 	if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) {
7347 		// get origin, axis, and fov
7348 		if ( privateCameraView ) {
7349 			privateCameraView->GetViewParms( renderView );
7350 		} else {
7351 			gameLocal.GetCamera()->GetViewParms( renderView );
7352 		}
7353 	} else {
7354 		if ( g_stopTime.GetBool() ) {
7355 			renderView->vieworg = firstPersonViewOrigin;
7356 			renderView->viewaxis = firstPersonViewAxis;
7357 
7358 			if ( !pm_thirdPerson.GetBool() ) {
7359 				// set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7360 				// allow the right player view weapons
7361 				renderView->viewID = entityNumber + 1;
7362 			}
7363 		} else if ( pm_thirdPerson.GetBool() ) {
7364 			OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() );
7365 		} else if ( pm_thirdPersonDeath.GetBool() ) {
7366 			range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f;
7367 			OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false );
7368 		} else {
7369 			renderView->vieworg = firstPersonViewOrigin;
7370 			renderView->viewaxis = firstPersonViewAxis;
7371 
7372 			// set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7373 			// allow the right player view weapons
7374 			renderView->viewID = entityNumber + 1;
7375 		}
7376 
7377 		// field of view
7378 		gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y );
7379 	}
7380 
7381 	if ( renderView->fov_y == 0 ) {
7382 		common->Error( "renderView->fov_y == 0" );
7383 	}
7384 
7385 	if ( g_showviewpos.GetBool() ) {
7386 		gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() );
7387 	}
7388 }
7389 
7390 /*
7391 =============
7392 idPlayer::AddAIKill
7393 =============
7394 */
AddAIKill(void)7395 void idPlayer::AddAIKill( void ) {
7396 	int max_souls;
7397 	int ammo_souls;
7398 
7399 	if ( ( weapon_soulcube < 0 ) || ( inventory.weapons & ( 1 << weapon_soulcube ) ) == 0 ) {
7400 		return;
7401 	}
7402 
7403 	assert( hud );
7404 
7405 	ammo_souls = idWeapon::GetAmmoNumForName( "ammo_souls" );
7406 	max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
7407 	if ( inventory.ammo[ ammo_souls ] < max_souls ) {
7408 		inventory.ammo[ ammo_souls ]++;
7409 		if ( inventory.ammo[ ammo_souls ] >= max_souls ) {
7410 			hud->HandleNamedEvent( "soulCubeReady" );
7411 			StartSound( "snd_soulcube_ready", SND_CHANNEL_ANY, 0, false, NULL );
7412 		}
7413 	}
7414 }
7415 
7416 /*
7417 =============
7418 idPlayer::SetSoulCubeProjectile
7419 =============
7420 */
SetSoulCubeProjectile(idProjectile * projectile)7421 void idPlayer::SetSoulCubeProjectile( idProjectile *projectile ) {
7422 	soulCubeProjectile = projectile;
7423 }
7424 
7425 /*
7426 =============
7427 idPlayer::AddProjectilesFired
7428 =============
7429 */
AddProjectilesFired(int count)7430 void idPlayer::AddProjectilesFired( int count ) {
7431 	numProjectilesFired += count;
7432 }
7433 
7434 /*
7435 =============
7436 idPlayer::AddProjectileHites
7437 =============
7438 */
AddProjectileHits(int count)7439 void idPlayer::AddProjectileHits( int count ) {
7440 	numProjectileHits += count;
7441 }
7442 
7443 /*
7444 =============
7445 idPlayer::SetLastHitTime
7446 =============
7447 */
SetLastHitTime(int time)7448 void idPlayer::SetLastHitTime( int time ) {
7449 	idPlayer *aimed = NULL;
7450 
7451 	if ( time && lastHitTime != time ) {
7452 		lastHitToggle ^= 1;
7453 	}
7454 	lastHitTime = time;
7455 	if ( !time ) {
7456 		// level start and inits
7457 		return;
7458 	}
7459 	if ( gameLocal.isMultiplayer && ( time - lastSndHitTime ) > 10 ) {
7460 		lastSndHitTime = time;
7461 		StartSound( "snd_hit_feedback", SND_CHANNEL_ANY, SSF_PRIVATE_SOUND, false, NULL );
7462 	}
7463 	if ( cursor ) {
7464 		cursor->HandleNamedEvent( "hitTime" );
7465 	}
7466 	if ( hud ) {
7467 		if ( MPAim != -1 ) {
7468 			if ( gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) {
7469 				aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
7470 			}
7471 			assert( aimed );
7472 			// full highlight, no fade till loosing aim
7473 			hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
7474 			if ( aimed ) {
7475 				hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7476 			}
7477 			hud->HandleNamedEvent( "aim_flash" );
7478 			MPAimHighlight = true;
7479 			MPAimFadeTime = 0;
7480 		} else if ( lastMPAim != -1 ) {
7481 			if ( gameLocal.entities[ lastMPAim ] && gameLocal.entities[ lastMPAim ]->IsType( idPlayer::Type ) ) {
7482 				aimed = static_cast< idPlayer * >( gameLocal.entities[ lastMPAim ] );
7483 			}
7484 			assert( aimed );
7485 			// start fading right away
7486 			hud->SetStateString( "aim_text", gameLocal.userInfo[ lastMPAim ].GetString( "ui_name" ) );
7487 			if ( aimed ) {
7488 				hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7489 			}
7490 			hud->HandleNamedEvent( "aim_flash" );
7491 			hud->HandleNamedEvent( "aim_fade" );
7492 			MPAimHighlight = false;
7493 			MPAimFadeTime = gameLocal.realClientTime;
7494 		}
7495 	}
7496 }
7497 
7498 /*
7499 =============
7500 idPlayer::SetInfluenceLevel
7501 =============
7502 */
SetInfluenceLevel(int level)7503 void idPlayer::SetInfluenceLevel( int level ) {
7504 	if ( level != influenceActive ) {
7505 		if ( level ) {
7506 			for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
7507 				if ( ent->IsType( idProjectile::Type ) ) {
7508 					// remove all projectiles
7509 					ent->PostEventMS( &EV_Remove, 0 );
7510 				}
7511 			}
7512 			if ( weaponEnabled && weapon.GetEntity() ) {
7513 				weapon.GetEntity()->EnterCinematic();
7514 			}
7515 		} else {
7516 			physicsObj.SetLinearVelocity( vec3_origin );
7517 			if ( weaponEnabled && weapon.GetEntity() ) {
7518 				weapon.GetEntity()->ExitCinematic();
7519 			}
7520 		}
7521 		influenceActive = level;
7522 	}
7523 }
7524 
7525 /*
7526 =============
7527 idPlayer::SetInfluenceView
7528 =============
7529 */
SetInfluenceView(const char * mtr,const char * skinname,float radius,idEntity * ent)7530 void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) {
7531 	influenceMaterial = NULL;
7532 	influenceEntity = NULL;
7533 	influenceSkin = NULL;
7534 	if ( mtr && *mtr ) {
7535 		influenceMaterial = declManager->FindMaterial( mtr );
7536 	}
7537 	if ( skinname && *skinname ) {
7538 		influenceSkin = declManager->FindSkin( skinname );
7539 		if ( head.GetEntity() ) {
7540 			head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
7541 		}
7542 		UpdateVisuals();
7543 	}
7544 	influenceRadius = radius;
7545 	if ( radius > 0.0f ) {
7546 		influenceEntity = ent;
7547 	}
7548 }
7549 
7550 /*
7551 =============
7552 idPlayer::SetInfluenceFov
7553 =============
7554 */
SetInfluenceFov(float fov)7555 void idPlayer::SetInfluenceFov( float fov ) {
7556 	influenceFov = fov;
7557 }
7558 
7559 /*
7560 ================
7561 idPlayer::OnLadder
7562 ================
7563 */
OnLadder(void) const7564 bool idPlayer::OnLadder( void ) const {
7565 	return physicsObj.OnLadder();
7566 }
7567 
7568 /*
7569 ==================
7570 idPlayer::Event_GetButtons
7571 ==================
7572 */
Event_GetButtons(void)7573 void idPlayer::Event_GetButtons( void ) {
7574 	idThread::ReturnInt( usercmd.buttons );
7575 }
7576 
7577 /*
7578 ==================
7579 idPlayer::Event_GetMove
7580 ==================
7581 */
Event_GetMove(void)7582 void idPlayer::Event_GetMove( void ) {
7583 	idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove );
7584 	idThread::ReturnVector( move );
7585 }
7586 
7587 /*
7588 ================
7589 idPlayer::Event_GetViewAngles
7590 ================
7591 */
Event_GetViewAngles(void)7592 void idPlayer::Event_GetViewAngles( void ) {
7593 	idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) );
7594 }
7595 
7596 /*
7597 ==================
7598 idPlayer::Event_StopFxFov
7599 ==================
7600 */
Event_StopFxFov(void)7601 void idPlayer::Event_StopFxFov( void ) {
7602 	fxFov = false;
7603 }
7604 
7605 /*
7606 ==================
7607 idPlayer::StartFxFov
7608 ==================
7609 */
StartFxFov(float duration)7610 void idPlayer::StartFxFov( float duration ) {
7611 	fxFov = true;
7612 	PostEventSec( &EV_Player_StopFxFov, duration );
7613 }
7614 
7615 /*
7616 ==================
7617 idPlayer::Event_EnableWeapon
7618 ==================
7619 */
Event_EnableWeapon(void)7620 void idPlayer::Event_EnableWeapon( void ) {
7621 	hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7622 	weaponEnabled = true;
7623 	if ( weapon.GetEntity() ) {
7624 		weapon.GetEntity()->ExitCinematic();
7625 	}
7626 }
7627 
7628 /*
7629 ==================
7630 idPlayer::Event_DisableWeapon
7631 ==================
7632 */
Event_DisableWeapon(void)7633 void idPlayer::Event_DisableWeapon( void ) {
7634 	hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7635 	weaponEnabled = false;
7636 	if ( weapon.GetEntity() ) {
7637 		weapon.GetEntity()->EnterCinematic();
7638 	}
7639 }
7640 
7641 /*
7642 ==================
7643 idPlayer::Event_GetCurrentWeapon
7644 ==================
7645 */
Event_GetCurrentWeapon(void)7646 void idPlayer::Event_GetCurrentWeapon( void ) {
7647 	const char *weapon;
7648 
7649 	if ( currentWeapon >= 0 ) {
7650 		weapon = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
7651 		idThread::ReturnString( weapon );
7652 	} else {
7653 		idThread::ReturnString( "" );
7654 	}
7655 }
7656 
7657 /*
7658 ==================
7659 idPlayer::Event_GetPreviousWeapon
7660 ==================
7661 */
Event_GetPreviousWeapon(void)7662 void idPlayer::Event_GetPreviousWeapon( void ) {
7663 	const char *weapon;
7664 
7665 	if ( previousWeapon >= 0 ) {
7666 		int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon;
7667 		weapon = spawnArgs.GetString( va( "def_weapon%d", pw) );
7668 		idThread::ReturnString( weapon );
7669 	} else {
7670 		idThread::ReturnString( spawnArgs.GetString( "def_weapon0" ) );
7671 	}
7672 }
7673 
7674 /*
7675 ==================
7676 idPlayer::Event_SelectWeapon
7677 ==================
7678 */
Event_SelectWeapon(const char * weaponName)7679 void idPlayer::Event_SelectWeapon( const char *weaponName ) {
7680 	int i;
7681 	int weaponNum;
7682 
7683 	if ( gameLocal.isClient ) {
7684 		gameLocal.Warning( "Cannot switch weapons from script in multiplayer" );
7685 		return;
7686 	}
7687 
7688 	if ( hiddenWeapon && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
7689 		idealWeapon = weapon_fists;
7690 		weapon.GetEntity()->HideWeapon();
7691 		return;
7692 	}
7693 
7694 	weaponNum = -1;
7695 	for( i = 0; i < MAX_WEAPONS; i++ ) {
7696 		if ( inventory.weapons & ( 1 << i ) ) {
7697 			const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
7698 			if ( !idStr::Cmp( weap, weaponName ) ) {
7699 				weaponNum = i;
7700 				break;
7701 			}
7702 		}
7703 	}
7704 
7705 	if ( weaponNum < 0 ) {
7706 		gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName );
7707 		return;
7708 	}
7709 
7710 	hiddenWeapon = false;
7711 	idealWeapon = weaponNum;
7712 
7713 	UpdateHudWeapon();
7714 }
7715 
7716 /*
7717 ==================
7718 idPlayer::Event_GetWeaponEntity
7719 ==================
7720 */
Event_GetWeaponEntity(void)7721 void idPlayer::Event_GetWeaponEntity( void ) {
7722 	idThread::ReturnEntity( weapon.GetEntity() );
7723 }
7724 
7725 /*
7726 ==================
7727 idPlayer::Event_OpenPDA
7728 ==================
7729 */
Event_OpenPDA(void)7730 void idPlayer::Event_OpenPDA( void ) {
7731 	if ( !gameLocal.isMultiplayer ) {
7732 		TogglePDA();
7733 	}
7734 }
7735 
7736 /*
7737 ==================
7738 idPlayer::Event_InPDA
7739 ==================
7740 */
Event_InPDA(void)7741 void idPlayer::Event_InPDA( void ) {
7742 	idThread::ReturnInt( objectiveSystemOpen );
7743 }
7744 
7745 /*
7746 ==================
7747 idPlayer::TeleportDeath
7748 ==================
7749 */
TeleportDeath(int killer)7750 void idPlayer::TeleportDeath( int killer ) {
7751 	teleportKiller = killer;
7752 }
7753 
7754 /*
7755 ==================
7756 idPlayer::Event_ExitTeleporter
7757 ==================
7758 */
Event_ExitTeleporter(void)7759 void idPlayer::Event_ExitTeleporter( void ) {
7760 	idEntity	*exitEnt;
7761 	float		pushVel;
7762 
7763 	// verify and setup
7764 	exitEnt = teleportEntity.GetEntity();
7765 	if ( !exitEnt ) {
7766 		common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber );
7767 		return;
7768 	}
7769 
7770 	pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" );
7771 
7772 	if ( gameLocal.isServer ) {
7773 		ServerSendEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 );
7774 	}
7775 
7776 	SetPrivateCameraView( NULL );
7777 	// setup origin and push according to the exit target
7778 	SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
7779 	SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() );
7780 	physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel );
7781 	physicsObj.ClearPushedVelocity();
7782 	// teleport fx
7783 	playerView.Flash( colorWhite, 120 );
7784 
7785 	// clear the ik heights so model doesn't appear in the wrong place
7786 	walkIK.EnableAll();
7787 
7788 	UpdateVisuals();
7789 
7790 	StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL );
7791 
7792 	if ( teleportKiller != -1 ) {
7793 		// we got killed while being teleported
7794 		Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT );
7795 		teleportKiller = -1;
7796 	} else {
7797 		// kill anything that would have waited at teleport exit
7798 		gameLocal.KillBox( this );
7799 	}
7800 	teleportEntity = NULL;
7801 }
7802 
7803 /*
7804 ================
7805 idPlayer::ClientPredictionThink
7806 ================
7807 */
ClientPredictionThink(void)7808 void idPlayer::ClientPredictionThink( void ) {
7809 	renderEntity_t *headRenderEnt;
7810 
7811 	oldFlags = usercmd.flags;
7812 	oldButtons = usercmd.buttons;
7813 
7814 	usercmd = gameLocal.usercmds[ entityNumber ];
7815 
7816 	if ( entityNumber != gameLocal.localClientNum ) {
7817 		// ignore attack button of other clients. that's no good for predictions
7818 		usercmd.buttons &= ~BUTTON_ATTACK;
7819 	}
7820 
7821 	buttonMask &= usercmd.buttons;
7822 	usercmd.buttons &= ~buttonMask;
7823 
7824 	if ( objectiveSystemOpen ) {
7825 		usercmd.forwardmove = 0;
7826 		usercmd.rightmove = 0;
7827 		usercmd.upmove = 0;
7828 	}
7829 
7830 	// clear the ik before we do anything else so the skeleton doesn't get updated twice
7831 	walkIK.ClearJointMods();
7832 
7833 	if ( gameLocal.isNewFrame ) {
7834 		if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
7835 			PerformImpulse( usercmd.impulse );
7836 		}
7837 	}
7838 
7839 	scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
7840 
7841 	AdjustSpeed();
7842 
7843 	UpdateViewAngles();
7844 
7845 	// update the smoothed view angles
7846 	if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) {
7847 		idAngles anglesDiff = viewAngles - smoothedAngles;
7848 		anglesDiff.Normalize180();
7849 		if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) {
7850 			// smoothen by pushing back to the previous angles
7851 			viewAngles -= gameLocal.clientSmoothing * anglesDiff;
7852 			viewAngles.Normalize180();
7853 		}
7854 		smoothedAngles = viewAngles;
7855 	}
7856 	smoothedOriginUpdated = false;
7857 
7858 	if ( !af.IsActive() ) {
7859 		AdjustBodyAngles();
7860 	}
7861 
7862 	if ( !isLagged ) {
7863 		// don't allow client to move when lagged
7864 		Move();
7865 	}
7866 
7867 	// update GUIs, Items, and character interactions
7868 	UpdateFocus();
7869 
7870 	// service animations
7871 	if ( !spectating && !af.IsActive() ) {
7872 		UpdateConditions();
7873 		UpdateAnimState();
7874 		CheckBlink();
7875 	}
7876 
7877 	// clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
7878 	AI_PAIN = false;
7879 
7880 	// calculate the exact bobbed view position, which is used to
7881 	// position the view weapon, among other things
7882 	CalculateFirstPersonView();
7883 
7884 	// this may use firstPersonView, or a thirdPerson / camera view
7885 	CalculateRenderView();
7886 
7887 	if ( !gameLocal.inCinematic && weapon.GetEntity() && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) {
7888 		UpdateWeapon();
7889 	}
7890 
7891 	UpdateHud();
7892 
7893 	if ( gameLocal.isNewFrame ) {
7894 		UpdatePowerUps();
7895 	}
7896 
7897 	UpdateDeathSkin( false );
7898 
7899 	if ( head.GetEntity() ) {
7900 		headRenderEnt = head.GetEntity()->GetRenderEntity();
7901 	} else {
7902 		headRenderEnt = NULL;
7903 	}
7904 
7905 	if ( headRenderEnt ) {
7906 		if ( influenceSkin ) {
7907 			headRenderEnt->customSkin = influenceSkin;
7908 		} else {
7909 			headRenderEnt->customSkin = NULL;
7910 		}
7911 	}
7912 
7913 	if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
7914 		renderEntity.suppressShadowInViewID	= 0;
7915 		if ( headRenderEnt ) {
7916 			headRenderEnt->suppressShadowInViewID = 0;
7917 		}
7918 	} else {
7919 		renderEntity.suppressShadowInViewID	= entityNumber+1;
7920 		if ( headRenderEnt ) {
7921 			headRenderEnt->suppressShadowInViewID = entityNumber+1;
7922 		}
7923 	}
7924 	// never cast shadows from our first-person muzzle flashes
7925 	renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7926 	if ( headRenderEnt ) {
7927 		headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7928 	}
7929 
7930 	if ( !gameLocal.inCinematic ) {
7931 		UpdateAnimation();
7932 	}
7933 
7934 	if ( gameLocal.isMultiplayer ) {
7935 		DrawPlayerIcons();
7936 	}
7937 
7938 	Present();
7939 
7940 	UpdateDamageEffects();
7941 
7942 	LinkCombat();
7943 
7944 	if ( gameLocal.isNewFrame && entityNumber == gameLocal.localClientNum ) {
7945 		playerView.CalculateShake();
7946 	}
7947 }
7948 
7949 /*
7950 ================
7951 idPlayer::GetPhysicsToVisualTransform
7952 ================
7953 */
GetPhysicsToVisualTransform(idVec3 & origin,idMat3 & axis)7954 bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
7955 	if ( af.IsActive() ) {
7956 		af.GetPhysicsToVisualTransform( origin, axis );
7957 		return true;
7958 	}
7959 
7960 	// smoothen the rendered origin and angles of other clients
7961 	// smooth self origin if snapshots are telling us prediction is off
7962 	if ( gameLocal.isClient && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) {
7963 		// render origin and axis
7964 		idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis();
7965 		idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis;
7966 
7967 		// update the smoothed origin
7968 		if ( !smoothedOriginUpdated ) {
7969 			idVec2 originDiff = renderOrigin.ToVec2() - smoothedOrigin.ToVec2();
7970 			if ( originDiff.LengthSqr() < Square( 100.0f ) ) {
7971 				// smoothen by pushing back to the previous position
7972 				if ( selfSmooth ) {
7973 					assert( entityNumber == gameLocal.localClientNum );
7974 					renderOrigin.ToVec2() -= net_clientSelfSmoothing.GetFloat() * originDiff;
7975 				} else {
7976 					renderOrigin.ToVec2() -= gameLocal.clientSmoothing * originDiff;
7977 				}
7978 			}
7979 			smoothedOrigin = renderOrigin;
7980 
7981 			smoothedFrame = gameLocal.framenum;
7982 			smoothedOriginUpdated = true;
7983 		}
7984 
7985 		axis = idAngles( 0.0f, smoothedAngles.yaw, 0.0f ).ToMat3();
7986 		origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose();
7987 
7988 	} else {
7989 
7990 		axis = viewAxis;
7991 		origin = modelOffset;
7992 	}
7993 	return true;
7994 }
7995 
7996 /*
7997 ================
7998 idPlayer::GetPhysicsToSoundTransform
7999 ================
8000 */
GetPhysicsToSoundTransform(idVec3 & origin,idMat3 & axis)8001 bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
8002 	idCamera *camera;
8003 
8004 	if ( privateCameraView ) {
8005 		camera = privateCameraView;
8006 	} else {
8007 		camera = gameLocal.GetCamera();
8008 	}
8009 
8010 	if ( camera ) {
8011 		renderView_t view;
8012 
8013 		memset( &view, 0, sizeof( view ) );
8014 		camera->GetViewParms( &view );
8015 		origin = view.vieworg;
8016 		axis = view.viewaxis;
8017 		return true;
8018 	} else {
8019 		return idActor::GetPhysicsToSoundTransform( origin, axis );
8020 	}
8021 }
8022 
8023 /*
8024 ================
8025 idPlayer::WriteToSnapshot
8026 ================
8027 */
WriteToSnapshot(idBitMsgDelta & msg) const8028 void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const {
8029 	physicsObj.WriteToSnapshot( msg );
8030 	WriteBindToSnapshot( msg );
8031 	msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] );
8032 	msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] );
8033 	msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] );
8034 	msg.WriteShort( health );
8035 	msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits );
8036 	msg.WriteDir( lastDamageDir, 9 );
8037 	msg.WriteShort( lastDamageLocation );
8038 	msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) );
8039 	msg.WriteBits( inventory.weapons, MAX_WEAPONS );
8040 	msg.WriteBits( weapon.GetSpawnId(), 32 );
8041 	msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) );
8042 	msg.WriteBits( lastHitToggle, 1 );
8043 	msg.WriteBits( weaponGone, 1 );
8044 	msg.WriteBits( isLagged, 1 );
8045 	msg.WriteBits( isChatting, 1 );
8046 }
8047 
8048 /*
8049 ================
8050 idPlayer::ReadFromSnapshot
8051 ================
8052 */
ReadFromSnapshot(const idBitMsgDelta & msg)8053 void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) {
8054 	int		i, oldHealth, newIdealWeapon, weaponSpawnId;
8055 	bool	newHitToggle, stateHitch;
8056 
8057 	if ( snapshotSequence - lastSnapshotSequence > 1 ) {
8058 		stateHitch = true;
8059 	} else {
8060 		stateHitch = false;
8061 	}
8062 	lastSnapshotSequence = snapshotSequence;
8063 
8064 	oldHealth = health;
8065 
8066 	physicsObj.ReadFromSnapshot( msg );
8067 	ReadBindFromSnapshot( msg );
8068 	deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f );
8069 	deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f );
8070 	deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f );
8071 	health = msg.ReadShort();
8072 	lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) );
8073 	lastDamageDir = msg.ReadDir( 9 );
8074 	lastDamageLocation = msg.ReadShort();
8075 	newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) );
8076 	inventory.weapons = msg.ReadBits( MAX_WEAPONS );
8077 	weaponSpawnId = msg.ReadBits( 32 );
8078 	spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) );
8079 	newHitToggle = msg.ReadBits( 1 ) != 0;
8080 	weaponGone = msg.ReadBits( 1 ) != 0;
8081 	isLagged = msg.ReadBits( 1 ) != 0;
8082 	isChatting = msg.ReadBits( 1 ) != 0;
8083 
8084 	// no msg reading below this
8085 
8086 	if ( weapon.SetSpawnId( weaponSpawnId ) ) {
8087 		if ( weapon.GetEntity() ) {
8088 			// maintain ownership locally
8089 			weapon.GetEntity()->SetOwner( this );
8090 		}
8091 		currentWeapon = -1;
8092 	}
8093 	// if not a local client assume the client has all ammo types
8094 	if ( entityNumber != gameLocal.localClientNum ) {
8095 		for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8096 			inventory.ammo[ i ] = 999;
8097 		}
8098 	}
8099 
8100 	if ( oldHealth > 0 && health <= 0 ) {
8101 		if ( stateHitch ) {
8102 			// so we just hide and don't show a death skin
8103 			UpdateDeathSkin( true );
8104 		}
8105 		// die
8106 		AI_DEAD = true;
8107 		ClearPowerUps();
8108 		SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
8109 		SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
8110 		SetWaitState( "" );
8111 		animator.ClearAllJoints();
8112 		if ( entityNumber == gameLocal.localClientNum ) {
8113 			playerView.Fade( colorBlack, 12000 );
8114 		}
8115 		StartRagdoll();
8116 		physicsObj.SetMovementType( PM_DEAD );
8117 		if ( !stateHitch ) {
8118 			StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
8119 		}
8120 		if ( weapon.GetEntity() ) {
8121 			weapon.GetEntity()->OwnerDied();
8122 		}
8123 	} else if ( oldHealth <= 0 && health > 0 ) {
8124 		// respawn
8125 		Init();
8126 		StopRagdoll();
8127 		SetPhysics( &physicsObj );
8128 		physicsObj.EnableClip();
8129 		SetCombatContents( true );
8130 	} else if ( health < oldHealth && health > 0 ) {
8131 		if ( stateHitch ) {
8132 			lastDmgTime = gameLocal.time;
8133 		} else {
8134 			// damage feedback
8135 			const idDeclEntityDef *def = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) );
8136 			if ( def ) {
8137 				playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict );
8138 				AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation );
8139 				lastDmgTime = gameLocal.time;
8140 			} else {
8141 				common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef );
8142 			}
8143 		}
8144 	} else if ( health > oldHealth && PowerUpActive( MEGAHEALTH ) && !stateHitch ) {
8145 		// just pulse, for any health raise
8146 		healthPulse = true;
8147 	}
8148 
8149 	// If the player is alive, restore proper physics object
8150 	if ( health > 0 && IsActiveAF() ) {
8151 		StopRagdoll();
8152 		SetPhysics( &physicsObj );
8153 		physicsObj.EnableClip();
8154 		SetCombatContents( true );
8155 	}
8156 
8157 	if ( idealWeapon != newIdealWeapon ) {
8158 		if ( stateHitch ) {
8159 			weaponCatchup = true;
8160 		}
8161 		idealWeapon = newIdealWeapon;
8162 		UpdateHudWeapon();
8163 	}
8164 
8165 	if ( lastHitToggle != newHitToggle ) {
8166 		SetLastHitTime( gameLocal.realClientTime );
8167 	}
8168 
8169 	if ( msg.HasChanged() ) {
8170 		UpdateVisuals();
8171 	}
8172 }
8173 
8174 /*
8175 ================
8176 idPlayer::WritePlayerStateToSnapshot
8177 ================
8178 */
WritePlayerStateToSnapshot(idBitMsgDelta & msg) const8179 void idPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const {
8180 	int i;
8181 
8182 	msg.WriteByte( bobCycle );
8183 	msg.WriteInt( stepUpTime );
8184 	msg.WriteFloat( stepUpDelta );
8185 	msg.WriteShort( inventory.weapons );
8186 	msg.WriteByte( inventory.armor );
8187 
8188 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8189 		msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS );
8190 	}
8191 	for( i = 0; i < MAX_WEAPONS; i++ ) {
8192 		msg.WriteBits( inventory.clip[i], ASYNC_PLAYER_INV_CLIP_BITS );
8193 	}
8194 }
8195 
8196 /*
8197 ================
8198 idPlayer::ReadPlayerStateFromSnapshot
8199 ================
8200 */
ReadPlayerStateFromSnapshot(const idBitMsgDelta & msg)8201 void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) {
8202 	int i, ammo;
8203 
8204 	bobCycle = msg.ReadByte();
8205 	stepUpTime = msg.ReadInt();
8206 	stepUpDelta = msg.ReadFloat();
8207 	inventory.weapons = msg.ReadShort();
8208 	inventory.armor = msg.ReadByte();
8209 
8210 	for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8211 		ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS );
8212 		if ( gameLocal.time >= inventory.ammoPredictTime ) {
8213 			inventory.ammo[ i ] = ammo;
8214 		}
8215 	}
8216 	for( i = 0; i < MAX_WEAPONS; i++ ) {
8217 		inventory.clip[i] = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS );
8218 	}
8219 }
8220 
8221 /*
8222 ================
8223 idPlayer::ServerReceiveEvent
8224 ================
8225 */
ServerReceiveEvent(int event,int time,const idBitMsg & msg)8226 bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) {
8227 
8228 	if ( idEntity::ServerReceiveEvent( event, time, msg ) ) {
8229 		return true;
8230 	}
8231 
8232 	// client->server events
8233 	switch( event ) {
8234 		case EVENT_IMPULSE: {
8235 			PerformImpulse( msg.ReadBits( 6 ) );
8236 			return true;
8237 		}
8238 		default: {
8239 			return false;
8240 		}
8241 	}
8242 }
8243 
8244 /*
8245 ================
8246 idPlayer::ClientReceiveEvent
8247 ================
8248 */
ClientReceiveEvent(int event,int time,const idBitMsg & msg)8249 bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
8250 	int powerup;
8251 	bool start;
8252 
8253 	switch ( event ) {
8254 		case EVENT_EXIT_TELEPORTER:
8255 			Event_ExitTeleporter();
8256 			return true;
8257 		case EVENT_ABORT_TELEPORTER:
8258 			SetPrivateCameraView( NULL );
8259 			return true;
8260 		case EVENT_POWERUP: {
8261 			powerup = msg.ReadShort();
8262 			start = msg.ReadBits( 1 ) != 0;
8263 			if ( start ) {
8264 				GivePowerUp( powerup, 0 );
8265 			} else {
8266 				ClearPowerup( powerup );
8267 			}
8268 			return true;
8269 		}
8270 		case EVENT_SPECTATE: {
8271 			bool spectate = ( msg.ReadBits( 1 ) != 0 );
8272 			Spectate( spectate );
8273 			return true;
8274 		}
8275 		case EVENT_ADD_DAMAGE_EFFECT: {
8276 			if ( spectating ) {
8277 				// if we're spectating, ignore
8278 				// happens if the event and the spectate change are written on the server during the same frame (fraglimit)
8279 				return true;
8280 			}
8281 			break;
8282 		}
8283 		default:
8284 			break;
8285 	}
8286 
8287 	return idActor::ClientReceiveEvent( event, time, msg );
8288 }
8289 
8290 /*
8291 ================
8292 idPlayer::Hide
8293 ================
8294 */
Hide(void)8295 void idPlayer::Hide( void ) {
8296 	idWeapon *weap;
8297 
8298 	idActor::Hide();
8299 	weap = weapon.GetEntity();
8300 	if ( weap ) {
8301 		weap->HideWorldModel();
8302 	}
8303 }
8304 
8305 /*
8306 ================
8307 idPlayer::Show
8308 ================
8309 */
Show(void)8310 void idPlayer::Show( void ) {
8311 	idWeapon *weap;
8312 
8313 	idActor::Show();
8314 	weap = weapon.GetEntity();
8315 	if ( weap ) {
8316 		weap->ShowWorldModel();
8317 	}
8318 }
8319 
8320 /*
8321 ===============
8322 idPlayer::StartAudioLog
8323 ===============
8324 */
StartAudioLog(void)8325 void idPlayer::StartAudioLog( void ) {
8326 	if ( hud ) {
8327 		hud->HandleNamedEvent( "audioLogUp" );
8328 	}
8329 }
8330 
8331 /*
8332 ===============
8333 idPlayer::StopAudioLog
8334 ===============
8335 */
StopAudioLog(void)8336 void idPlayer::StopAudioLog( void ) {
8337 	if ( hud ) {
8338 		hud->HandleNamedEvent( "audioLogDown" );
8339 	}
8340 }
8341 
8342 /*
8343 ===============
8344 idPlayer::ShowTip
8345 ===============
8346 */
ShowTip(const char * title,const char * tip,bool autoHide)8347 void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) {
8348 	if ( tipUp ) {
8349 		return;
8350 	}
8351 	hud->SetStateString( "tip", tip );
8352 	hud->SetStateString( "tiptitle", title );
8353 	hud->HandleNamedEvent( "tipWindowUp" );
8354 	if ( autoHide ) {
8355 		PostEventSec( &EV_Player_HideTip, 5.0f );
8356 	}
8357 	tipUp = true;
8358 }
8359 
8360 /*
8361 ===============
8362 idPlayer::HideTip
8363 ===============
8364 */
HideTip(void)8365 void idPlayer::HideTip( void ) {
8366 	hud->HandleNamedEvent( "tipWindowDown" );
8367 	tipUp = false;
8368 }
8369 
8370 /*
8371 ===============
8372 idPlayer::Event_HideTip
8373 ===============
8374 */
Event_HideTip(void)8375 void idPlayer::Event_HideTip( void ) {
8376 	HideTip();
8377 }
8378 
8379 /*
8380 ===============
8381 idPlayer::ShowObjective
8382 ===============
8383 */
ShowObjective(const char * obj)8384 void idPlayer::ShowObjective( const char *obj ) {
8385 	hud->HandleNamedEvent( obj );
8386 	objectiveUp = true;
8387 }
8388 
8389 /*
8390 ===============
8391 idPlayer::HideObjective
8392 ===============
8393 */
HideObjective(void)8394 void idPlayer::HideObjective( void ) {
8395 	hud->HandleNamedEvent( "closeObjective" );
8396 	objectiveUp = false;
8397 }
8398 
8399 /*
8400 ===============
8401 idPlayer::Event_StopAudioLog
8402 ===============
8403 */
Event_StopAudioLog(void)8404 void idPlayer::Event_StopAudioLog( void ) {
8405 	StopAudioLog();
8406 }
8407 
8408 /*
8409 ===============
8410 idPlayer::SetSpectateOrigin
8411 ===============
8412 */
SetSpectateOrigin(void)8413 void idPlayer::SetSpectateOrigin( void ) {
8414 	idVec3 neworig;
8415 
8416 	neworig = GetPhysics()->GetOrigin();
8417 	neworig[ 2 ] += EyeHeight();
8418 	neworig[ 2 ] += 25;
8419 	SetOrigin( neworig );
8420 }
8421 
8422 /*
8423 ===============
8424 idPlayer::RemoveWeapon
8425 ===============
8426 */
RemoveWeapon(const char * weap)8427 void idPlayer::RemoveWeapon( const char *weap ) {
8428 	if ( weap && *weap ) {
8429 		inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 );
8430 	}
8431 }
8432 
8433 /*
8434 ===============
8435 idPlayer::CanShowWeaponViewmodel
8436 ===============
8437 */
CanShowWeaponViewmodel(void) const8438 bool idPlayer::CanShowWeaponViewmodel( void ) const {
8439 	return showWeaponViewModel;
8440 }
8441 
8442 /*
8443 ===============
8444 idPlayer::SetLevelTrigger
8445 ===============
8446 */
SetLevelTrigger(const char * levelName,const char * triggerName)8447 void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) {
8448 	if ( levelName && *levelName && triggerName && *triggerName ) {
8449 		idLevelTriggerInfo lti;
8450 		lti.levelName = levelName;
8451 		lti.triggerName = triggerName;
8452 		inventory.levelTriggers.Append( lti );
8453 	}
8454 }
8455 
8456 /*
8457 ===============
8458 idPlayer::Event_LevelTrigger
8459 ===============
8460 */
Event_LevelTrigger(void)8461 void idPlayer::Event_LevelTrigger( void ) {
8462 	idStr mapName = gameLocal.GetMapName();
8463 	mapName.StripPath();
8464 	mapName.StripFileExtension();
8465 	for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) {
8466 		if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){
8467 			idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName );
8468 			if ( ent ) {
8469 				ent->PostEventMS( &EV_Activate, 1, this );
8470 			}
8471 		}
8472 	}
8473 }
8474 
8475 /*
8476 ===============
8477 idPlayer::Event_Gibbed
8478 ===============
8479 */
Event_Gibbed(void)8480 void idPlayer::Event_Gibbed( void ) {
8481 }
8482 
8483 /*
8484 ==================
8485 idPlayer::Event_GetIdealWeapon
8486 ==================
8487 */
Event_GetIdealWeapon(void)8488 void idPlayer::Event_GetIdealWeapon( void ) {
8489 	const char *weapon;
8490 
8491 	if ( idealWeapon >= 0 ) {
8492 		weapon = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
8493 		idThread::ReturnString( weapon );
8494 	} else {
8495 		idThread::ReturnString( "" );
8496 	}
8497 }
8498 
8499 /*
8500 ===============
8501 idPlayer::UpdatePlayerIcons
8502 ===============
8503 */
UpdatePlayerIcons(void)8504 void idPlayer::UpdatePlayerIcons( void ) {
8505 	int time = networkSystem->ServerGetClientTimeSinceLastPacket( entityNumber );
8506 	if ( time > cvarSystem->GetCVarInteger( "net_clientMaxPrediction" ) ) {
8507 		isLagged = true;
8508 	} else {
8509 		isLagged = false;
8510 	}
8511 }
8512 
8513 /*
8514 ===============
8515 idPlayer::DrawPlayerIcons
8516 ===============
8517 */
DrawPlayerIcons(void)8518 void idPlayer::DrawPlayerIcons( void ) {
8519 	if ( !NeedsIcon() ) {
8520 		playerIcon.FreeIcon();
8521 		return;
8522 	}
8523 	playerIcon.Draw( this, headJoint );
8524 }
8525 
8526 /*
8527 ===============
8528 idPlayer::HidePlayerIcons
8529 ===============
8530 */
HidePlayerIcons(void)8531 void idPlayer::HidePlayerIcons( void ) {
8532 	playerIcon.FreeIcon();
8533 }
8534 
8535 /*
8536 ===============
8537 idPlayer::NeedsIcon
8538 ==============
8539 */
NeedsIcon(void)8540 bool idPlayer::NeedsIcon( void ) {
8541 	// local clients don't render their own icons... they're only info for other clients
8542 	return entityNumber != gameLocal.localClientNum && ( isLagged || isChatting );
8543 }
8544