2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
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.
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.
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/>.
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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
35 ===============================================================================
37 Player control of the Doom Marine.
38 This object handles all player movement and world interaction.
40 ===============================================================================
43 // distance between ladder rungs (actually is half that distance, but this sounds better)
44 const int LADDER_RUNG_DISTANCE = 32;
46 // amount of health per dose from the health station
47 const int HEALTH_PER_DOSE = 10;
49 // time before a weapon dropped to the floor disappears
50 const int WEAPON_DROP_TIME = 20 * 1000;
52 // time before a next or prev weapon switch happens
53 const int WEAPON_SWITCH_DELAY = 150;
55 // how many units to raise spectator above default view height so it's in the head of someone
56 const int SPECTATE_RAISE = 25;
58 const int HEALTHPULSE_TIME = 333;
60 // minimum speed to bob and play run/walk animations at
61 const float MIN_BOB_SPEED = 5.0f;
63 const idEventDef EV_Player_GetButtons( "getButtons", NULL, 'd' );
64 const idEventDef EV_Player_GetMove( "getMove", NULL, 'v' );
65 const idEventDef EV_Player_GetViewAngles( "getViewAngles", NULL, 'v' );
66 const idEventDef EV_Player_StopFxFov( "stopFxFov" );
67 const idEventDef EV_Player_EnableWeapon( "enableWeapon" );
68 const idEventDef EV_Player_DisableWeapon( "disableWeapon" );
69 const idEventDef EV_Player_GetCurrentWeapon( "getCurrentWeapon", NULL, 's' );
70 const idEventDef EV_Player_GetPreviousWeapon( "getPreviousWeapon", NULL, 's' );
71 const idEventDef EV_Player_SelectWeapon( "selectWeapon", "s" );
72 const idEventDef EV_Player_GetWeaponEntity( "getWeaponEntity", NULL, 'e' );
73 const idEventDef EV_Player_OpenPDA( "openPDA" );
74 const idEventDef EV_Player_InPDA( "inPDA", NULL, 'd' );
75 const idEventDef EV_Player_ExitTeleporter( "exitTeleporter" );
76 const idEventDef EV_Player_StopAudioLog( "stopAudioLog" );
77 const idEventDef EV_Player_HideTip( "hideTip" );
78 const idEventDef EV_Player_LevelTrigger( "levelTrigger" );
79 const idEventDef EV_SpectatorTouch( "spectatorTouch", "et" );
80 const idEventDef EV_Player_GetIdealWeapon( "getIdealWeapon", NULL, 's' );
82 CLASS_DECLARATION( idActor, idPlayer )
83 EVENT( EV_Player_GetButtons, idPlayer::Event_GetButtons )
84 EVENT( EV_Player_GetMove, idPlayer::Event_GetMove )
85 EVENT( EV_Player_GetViewAngles, idPlayer::Event_GetViewAngles )
86 EVENT( EV_Player_StopFxFov, idPlayer::Event_StopFxFov )
87 EVENT( EV_Player_EnableWeapon, idPlayer::Event_EnableWeapon )
88 EVENT( EV_Player_DisableWeapon, idPlayer::Event_DisableWeapon )
89 EVENT( EV_Player_GetCurrentWeapon, idPlayer::Event_GetCurrentWeapon )
90 EVENT( EV_Player_GetPreviousWeapon, idPlayer::Event_GetPreviousWeapon )
91 EVENT( EV_Player_SelectWeapon, idPlayer::Event_SelectWeapon )
92 EVENT( EV_Player_GetWeaponEntity, idPlayer::Event_GetWeaponEntity )
93 EVENT( EV_Player_OpenPDA, idPlayer::Event_OpenPDA )
94 EVENT( EV_Player_InPDA, idPlayer::Event_InPDA )
95 EVENT( EV_Player_ExitTeleporter, idPlayer::Event_ExitTeleporter )
96 EVENT( EV_Player_StopAudioLog, idPlayer::Event_StopAudioLog )
97 EVENT( EV_Player_HideTip, idPlayer::Event_HideTip )
98 EVENT( EV_Player_LevelTrigger, idPlayer::Event_LevelTrigger )
99 EVENT( EV_Gibbed, idPlayer::Event_Gibbed )
100 EVENT( EV_Player_GetIdealWeapon, idPlayer::Event_GetIdealWeapon )
103 const int MAX_RESPAWN_TIME = 10000;
104 const int RAGDOLL_DEATH_TIME = 3000;
105 const int MAX_PDAS = 64;
106 const int MAX_PDA_ITEMS = 128;
107 const int STEPUP_TIME = 200;
108 const int MAX_INVENTORY_ITEMS = 20;
110 idVec3 idPlayer::colorBarTable[ 5 ] = {
111 idVec3( 0.25f, 0.25f, 0.25f ),
112 idVec3( 1.00f, 0.00f, 0.00f ),
113 idVec3( 0.00f, 0.80f, 0.10f ),
114 idVec3( 0.20f, 0.50f, 0.80f ),
115 idVec3( 1.00f, 0.80f, 0.10f )
123 void idInventory::Clear( void ) {
132 nextArmorDepleteTime = 0;
134 memset( ammo, 0, sizeof( ammo ) );
138 // 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
139 memset( clip, -1, sizeof( clip ) );
141 items.DeleteContents( true );
142 memset(pdasViewed, 0, 4 * sizeof( pdasViewed[0] ) );
153 levelTriggers.Clear();
158 pickupItemNames.Clear();
159 objectiveNames.Clear();
172 idInventory::GivePowerUp
175 void idInventory::GivePowerUp( idPlayer *player, int powerup, int msec ) {
177 // get the duration from the .def files
178 const idDeclEntityDef *def = NULL;
181 def = gameLocal.FindEntityDef( "powerup_berserk", false );
184 def = gameLocal.FindEntityDef( "powerup_invisibility", false );
187 def = gameLocal.FindEntityDef( "powerup_megahealth", false );
190 def = gameLocal.FindEntityDef( "powerup_adrenaline", false );
194 msec = def->dict.GetInt( "time" ) * 1000;
196 powerups |= 1 << powerup;
197 powerupEndTime[ powerup ] = gameLocal.time + msec;
202 idInventory::ClearPowerUps
205 void idInventory::ClearPowerUps( void ) {
207 for ( i = 0; i < MAX_POWERUPS; i++ ) {
208 powerupEndTime[ i ] = 0;
215 idInventory::GetPersistantData
218 void idInventory::GetPersistantData( idDict &dict ) {
223 const idKeyValue *kv;
227 dict.SetInt( "armor", armor );
229 // don't bother with powerups, maxhealth, maxarmor, or the clip
232 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
233 name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
235 dict.SetInt( name, ammo[ i ] );
241 for( i = 0; i < items.Num(); i++ ) {
244 // copy all keys with "inv_"
245 kv = item->MatchPrefix( "inv_" );
248 sprintf( key, "item_%i %s", num, kv->GetKey().c_str() );
249 dict.Set( key, kv->GetValue() );
250 kv = item->MatchPrefix( "inv_", kv );
255 dict.SetInt( "items", num );
258 for ( i = 0; i < 4; i++ ) {
259 dict.SetInt( va("pdasViewed_%i", i), pdasViewed[i] );
262 dict.SetInt( "selPDA", selPDA );
263 dict.SetInt( "selVideo", selVideo );
264 dict.SetInt( "selEmail", selEMail );
265 dict.SetInt( "selAudio", selAudio );
266 dict.SetInt( "pdaOpened", pdaOpened );
267 dict.SetInt( "turkeyScore", turkeyScore );
270 for ( i = 0; i < pdas.Num(); i++ ) {
271 sprintf( key, "pda_%i", i );
272 dict.Set( key, pdas[ i ] );
274 dict.SetInt( "pdas", pdas.Num() );
277 for ( i = 0; i < videos.Num(); i++ ) {
278 sprintf( key, "video_%i", i );
279 dict.Set( key, videos[ i ].c_str() );
281 dict.SetInt( "videos", videos.Num() );
284 for ( i = 0; i < emails.Num(); i++ ) {
285 sprintf( key, "email_%i", i );
286 dict.Set( key, emails[ i ].c_str() );
288 dict.SetInt( "emails", emails.Num() );
291 dict.SetInt( "weapon_bits", weapons );
293 dict.SetInt( "levelTriggers", levelTriggers.Num() );
294 for ( i = 0; i < levelTriggers.Num(); i++ ) {
295 sprintf( key, "levelTrigger_Level_%i", i );
296 dict.Set( key, levelTriggers[i].levelName );
297 sprintf( key, "levelTrigger_Trigger_%i", i );
298 dict.Set( key, levelTriggers[i].triggerName );
304 idInventory::RestoreInventory
307 void idInventory::RestoreInventory( idPlayer *owner, const idDict &dict ) {
313 const idKeyValue *kv;
319 maxHealth = dict.GetInt( "maxhealth", "100" );
320 armor = dict.GetInt( "armor", "50" );
321 maxarmor = dict.GetInt( "maxarmor", "100" );
322 deplete_armor = dict.GetInt( "deplete_armor", "0" );
323 deplete_rate = dict.GetFloat( "deplete_rate", "2.0" );
324 deplete_ammount = dict.GetInt( "deplete_ammount", "1" );
326 // the clip and powerups aren't restored
329 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
330 name = idWeapon::GetAmmoNameForNum( ( ammo_t )i );
332 ammo[ i ] = dict.GetInt( name );
337 num = dict.GetInt( "items" );
339 for( i = 0; i < num; i++ ) {
342 sprintf( itemname, "item_%i ", i );
343 kv = dict.MatchPrefix( itemname );
346 key.Strip( itemname );
347 item->Set( key, kv->GetValue() );
348 kv = dict.MatchPrefix( itemname, kv );
353 for ( i = 0; i < 4; i++ ) {
354 pdasViewed[i] = dict.GetInt(va("pdasViewed_%i", i));
357 selPDA = dict.GetInt( "selPDA" );
358 selEMail = dict.GetInt( "selEmail" );
359 selVideo = dict.GetInt( "selVideo" );
360 selAudio = dict.GetInt( "selAudio" );
361 pdaOpened = dict.GetBool( "pdaOpened" );
362 turkeyScore = dict.GetBool( "turkeyScore" );
365 num = dict.GetInt( "pdas" );
367 for ( i = 0; i < num; i++ ) {
368 sprintf( itemname, "pda_%i", i );
369 pdas[i] = dict.GetString( itemname, "default" );
373 num = dict.GetInt( "videos" );
374 videos.SetNum( num );
375 for ( i = 0; i < num; i++ ) {
376 sprintf( itemname, "video_%i", i );
377 videos[i] = dict.GetString( itemname, "default" );
381 num = dict.GetInt( "emails" );
382 emails.SetNum( num );
383 for ( i = 0; i < num; i++ ) {
384 sprintf( itemname, "email_%i", i );
385 emails[i] = dict.GetString( itemname, "default" );
388 // weapons are stored as a number for persistant data, but as strings in the entityDef
389 weapons = dict.GetInt( "weapon_bits", "0" );
392 Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false );
394 if ( g_skill.GetInteger() >= 3 ) {
395 Give( owner, dict, "weapon", dict.GetString( "weapon_nightmare" ), NULL, false );
397 Give( owner, dict, "weapon", dict.GetString( "weapon" ), NULL, false );
401 num = dict.GetInt( "levelTriggers" );
402 for ( i = 0; i < num; i++ ) {
403 sprintf( itemname, "levelTrigger_Level_%i", i );
404 idLevelTriggerInfo lti;
405 lti.levelName = dict.GetString( itemname );
406 sprintf( itemname, "levelTrigger_Trigger_%i", i );
407 lti.triggerName = dict.GetString( itemname );
408 levelTriggers.Append( lti );
418 void idInventory::Save( idSaveGame *savefile ) const {
421 savefile->WriteInt( maxHealth );
422 savefile->WriteInt( weapons );
423 savefile->WriteInt( powerups );
424 savefile->WriteInt( armor );
425 savefile->WriteInt( maxarmor );
426 savefile->WriteInt( ammoPredictTime );
427 savefile->WriteInt( deplete_armor );
428 savefile->WriteFloat( deplete_rate );
429 savefile->WriteInt( deplete_ammount );
430 savefile->WriteInt( nextArmorDepleteTime );
432 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
433 savefile->WriteInt( ammo[ i ] );
435 for( i = 0; i < MAX_WEAPONS; i++ ) {
436 savefile->WriteInt( clip[ i ] );
438 for( i = 0; i < MAX_POWERUPS; i++ ) {
439 savefile->WriteInt( powerupEndTime[ i ] );
442 savefile->WriteInt( items.Num() );
443 for( i = 0; i < items.Num(); i++ ) {
444 savefile->WriteDict( items[ i ] );
447 savefile->WriteInt( pdasViewed[0] );
448 savefile->WriteInt( pdasViewed[1] );
449 savefile->WriteInt( pdasViewed[2] );
450 savefile->WriteInt( pdasViewed[3] );
452 savefile->WriteInt( selPDA );
453 savefile->WriteInt( selVideo );
454 savefile->WriteInt( selEMail );
455 savefile->WriteInt( selAudio );
456 savefile->WriteBool( pdaOpened );
457 savefile->WriteBool( turkeyScore );
459 savefile->WriteInt( pdas.Num() );
460 for( i = 0; i < pdas.Num(); i++ ) {
461 savefile->WriteString( pdas[ i ] );
464 savefile->WriteInt( pdaSecurity.Num() );
465 for( i=0; i < pdaSecurity.Num(); i++ ) {
466 savefile->WriteString( pdaSecurity[ i ] );
469 savefile->WriteInt( videos.Num() );
470 for( i = 0; i < videos.Num(); i++ ) {
471 savefile->WriteString( videos[ i ] );
474 savefile->WriteInt( emails.Num() );
475 for ( i = 0; i < emails.Num(); i++ ) {
476 savefile->WriteString( emails[ i ] );
479 savefile->WriteInt( nextItemPickup );
480 savefile->WriteInt( nextItemNum );
481 savefile->WriteInt( onePickupTime );
483 savefile->WriteInt( pickupItemNames.Num() );
484 for( i = 0; i < pickupItemNames.Num(); i++ ) {
485 savefile->WriteString( pickupItemNames[i].icon );
486 savefile->WriteString( pickupItemNames[i].name );
489 savefile->WriteInt( objectiveNames.Num() );
490 for( i = 0; i < objectiveNames.Num(); i++ ) {
491 savefile->WriteString( objectiveNames[i].screenshot );
492 savefile->WriteString( objectiveNames[i].text );
493 savefile->WriteString( objectiveNames[i].title );
496 savefile->WriteInt( levelTriggers.Num() );
497 for ( i = 0; i < levelTriggers.Num(); i++ ) {
498 savefile->WriteString( levelTriggers[i].levelName );
499 savefile->WriteString( levelTriggers[i].triggerName );
502 savefile->WriteBool( ammoPulse );
503 savefile->WriteBool( weaponPulse );
504 savefile->WriteBool( armorPulse );
506 savefile->WriteInt( lastGiveTime );
514 void idInventory::Restore( idRestoreGame *savefile ) {
517 savefile->ReadInt( maxHealth );
518 savefile->ReadInt( weapons );
519 savefile->ReadInt( powerups );
520 savefile->ReadInt( armor );
521 savefile->ReadInt( maxarmor );
522 savefile->ReadInt( ammoPredictTime );
523 savefile->ReadInt( deplete_armor );
524 savefile->ReadFloat( deplete_rate );
525 savefile->ReadInt( deplete_ammount );
526 savefile->ReadInt( nextArmorDepleteTime );
528 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
529 savefile->ReadInt( ammo[ i ] );
531 for( i = 0; i < MAX_WEAPONS; i++ ) {
532 savefile->ReadInt( clip[ i ] );
534 for( i = 0; i < MAX_POWERUPS; i++ ) {
535 savefile->ReadInt( powerupEndTime[ i ] );
538 savefile->ReadInt( num );
539 for( i = 0; i < num; i++ ) {
540 idDict *itemdict = new idDict;
542 savefile->ReadDict( itemdict );
543 items.Append( itemdict );
547 savefile->ReadInt( pdasViewed[0] );
548 savefile->ReadInt( pdasViewed[1] );
549 savefile->ReadInt( pdasViewed[2] );
550 savefile->ReadInt( pdasViewed[3] );
552 savefile->ReadInt( selPDA );
553 savefile->ReadInt( selVideo );
554 savefile->ReadInt( selEMail );
555 savefile->ReadInt( selAudio );
556 savefile->ReadBool( pdaOpened );
557 savefile->ReadBool( turkeyScore );
559 savefile->ReadInt( num );
560 for( i = 0; i < num; i++ ) {
562 savefile->ReadString( strPda );
563 pdas.Append( strPda );
566 // pda security clearances
567 savefile->ReadInt( num );
568 for ( i = 0; i < num; i++ ) {
570 savefile->ReadString( invName );
571 pdaSecurity.Append( invName );
575 savefile->ReadInt( num );
576 for( i = 0; i < num; i++ ) {
578 savefile->ReadString( strVideo );
579 videos.Append( strVideo );
583 savefile->ReadInt( num );
584 for( i = 0; i < num; i++ ) {
586 savefile->ReadString( strEmail );
587 emails.Append( strEmail );
590 savefile->ReadInt( nextItemPickup );
591 savefile->ReadInt( nextItemNum );
592 savefile->ReadInt( onePickupTime );
593 savefile->ReadInt( num );
594 for( i = 0; i < num; i++ ) {
597 savefile->ReadString( info.icon );
598 savefile->ReadString( info.name );
600 pickupItemNames.Append( info );
603 savefile->ReadInt( num );
604 for( i = 0; i < num; i++ ) {
607 savefile->ReadString( obj.screenshot );
608 savefile->ReadString( obj.text );
609 savefile->ReadString( obj.title );
611 objectiveNames.Append( obj );
614 savefile->ReadInt( num );
615 for ( i = 0; i < num; i++ ) {
616 idLevelTriggerInfo lti;
617 savefile->ReadString( lti.levelName );
618 savefile->ReadString( lti.triggerName );
619 levelTriggers.Append( lti );
622 savefile->ReadBool( ammoPulse );
623 savefile->ReadBool( weaponPulse );
624 savefile->ReadBool( armorPulse );
626 savefile->ReadInt( lastGiveTime );
631 idInventory::AmmoIndexForAmmoClass
634 ammo_t idInventory::AmmoIndexForAmmoClass( const char *ammo_classname ) const {
635 return idWeapon::GetAmmoNumForName( ammo_classname );
640 idInventory::AmmoIndexForAmmoClass
643 int idInventory::MaxAmmoForAmmoClass( idPlayer *owner, const char *ammo_classname ) const {
644 return owner->spawnArgs.GetInt( va( "max_%s", ammo_classname ), "0" );
649 idInventory::AmmoPickupNameForIndex
652 const char *idInventory::AmmoPickupNameForIndex( ammo_t ammonum ) const {
653 return idWeapon::GetAmmoPickupNameForNum( ammonum );
658 idInventory::WeaponIndexForAmmoClass
659 mapping could be prepared in the constructor
662 int idInventory::WeaponIndexForAmmoClass( const idDict & spawnArgs, const char *ammo_classname ) const {
664 const char *weapon_classname;
665 for( i = 0; i < MAX_WEAPONS; i++ ) {
666 weapon_classname = spawnArgs.GetString( va( "def_weapon%d", i ) );
667 if ( !weapon_classname ) {
670 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
674 if ( !idStr::Icmp( ammo_classname, decl->dict.GetString( "ammoType" ) ) ) {
683 idInventory::AmmoIndexForWeaponClass
686 ammo_t idInventory::AmmoIndexForWeaponClass( const char *weapon_classname, int *ammoRequired ) {
687 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname, false );
689 gameLocal.Error( "Unknown weapon in decl '%s'", weapon_classname );
691 if ( ammoRequired ) {
692 *ammoRequired = decl->dict.GetInt( "ammoRequired" );
694 ammo_t ammo_i = AmmoIndexForAmmoClass( decl->dict.GetString( "ammoType" ) );
700 idInventory::AddPickupName
703 void idInventory::AddPickupName( const char *name, const char *icon ) {
706 num = pickupItemNames.Num();
707 if ( ( num == 0 ) || ( pickupItemNames[ num - 1 ].name.Icmp( name ) != 0 ) ) {
708 idItemInfo &info = pickupItemNames.Alloc();
710 if ( idStr::Cmpn( name, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
711 info.name = common->GetLanguageDict()->GetString( name );
724 bool idInventory::Give( idPlayer *owner, const idDict &spawnArgs, const char *statname, const char *value, int *idealWeapon, bool updateHud ) {
731 const idDeclEntityDef *weaponDecl;
737 if ( !idStr::Icmpn( statname, "ammo_", 5 ) ) {
738 i = AmmoIndexForAmmoClass( statname );
739 max = MaxAmmoForAmmoClass( owner, statname );
740 if ( ammo[ i ] >= max ) {
743 amount = atoi( value );
746 if ( ( max > 0 ) && ( ammo[ i ] > max ) ) {
751 name = AmmoPickupNameForIndex( i );
752 if ( idStr::Length( name ) ) {
753 AddPickupName( name, "" );
756 } else if ( !idStr::Icmp( statname, "armor" ) ) {
757 if ( armor >= maxarmor ) {
758 return false; // can't hold any more, so leave the item
760 amount = atoi( value );
763 if ( armor > maxarmor ) {
766 nextArmorDepleteTime = 0;
769 } else if ( idStr::FindText( statname, "inclip_" ) == 0 ) {
770 i = WeaponIndexForAmmoClass( spawnArgs, statname + 7 );
772 // set, don't add. not going over the clip size limit.
773 clip[ i ] = atoi( value );
775 } else if ( !idStr::Icmp( statname, "berserk" ) ) {
776 GivePowerUp( owner, BERSERK, SEC2MS( atof( value ) ) );
777 } else if ( !idStr::Icmp( statname, "mega" ) ) {
778 GivePowerUp( owner, MEGAHEALTH, SEC2MS( atof( value ) ) );
779 } else if ( !idStr::Icmp( statname, "weapon" ) ) {
781 for( pos = value; pos != NULL; pos = end ) {
782 end = strchr( pos, ',' );
790 idStr weaponName( pos, 0, len );
792 // find the number of the matching weapon name
793 for( i = 0; i < MAX_WEAPONS; i++ ) {
794 if ( weaponName == spawnArgs.GetString( va( "def_weapon%d", i ) ) ) {
799 if ( i >= MAX_WEAPONS ) {
800 gameLocal.Error( "Unknown weapon '%s'", weaponName.c_str() );
803 // cache the media for this weapon
804 weaponDecl = gameLocal.FindEntityDef( weaponName, false );
806 // don't pickup "no ammo" weapon types twice
807 // not for D3 SP .. there is only one case in the game where you can get a no ammo
808 // weapon when you might already have it, in that case it is more conistent to pick it up
809 if ( gameLocal.isMultiplayer && weaponDecl && ( weapons & ( 1 << i ) ) && !weaponDecl->dict.GetInt( "ammoRequired" ) ) {
813 if ( !gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || ( weaponName == "weapon_fists" ) || ( weaponName == "weapon_soulcube" ) ) {
814 if ( ( weapons & ( 1 << i ) ) == 0 || gameLocal.isMultiplayer ) {
815 if ( owner->GetUserInfo()->GetBool( "ui_autoSwitch" ) && idealWeapon ) {
816 assert( !gameLocal.isClient );
819 if ( owner->hud && updateHud && lastGiveTime + 1000 < gameLocal.time ) {
820 owner->hud->SetStateInt( "newWeapon", i );
821 owner->hud->HandleNamedEvent( "newWeapon" );
822 lastGiveTime = gameLocal.time;
825 weapons |= ( 1 << i );
831 } else if ( !idStr::Icmp( statname, "item" ) || !idStr::Icmp( statname, "icon" ) || !idStr::Icmp( statname, "name" ) ) {
832 // ignore these as they're handled elsewhere
836 gameLocal.Warning( "Unknown stat '%s' added to player's inventory", statname );
848 void idInventory::Drop( const idDict &spawnArgs, const char *weapon_classname, int weapon_index ) {
849 // remove the weapon bit
850 // also remove the ammo associated with the weapon as we pushed it in the item
851 assert( weapon_index != -1 || weapon_classname );
852 if ( weapon_index == -1 ) {
853 for( weapon_index = 0; weapon_index < MAX_WEAPONS; weapon_index++ ) {
854 if ( !idStr::Icmp( weapon_classname, spawnArgs.GetString( va( "def_weapon%d", weapon_index ) ) ) ) {
858 if ( weapon_index >= MAX_WEAPONS ) {
859 gameLocal.Error( "Unknown weapon '%s'", weapon_classname );
861 } else if ( !weapon_classname ) {
862 weapon_classname = spawnArgs.GetString( va( "def_weapon%d", weapon_index ) );
864 weapons &= ( 0xffffffff ^ ( 1 << weapon_index ) );
865 ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, NULL );
867 clip[ weapon_index ] = -1;
877 int idInventory::HasAmmo( ammo_t type, int amount ) {
878 if ( ( type == 0 ) || !amount ) {
879 // always allow weapons that don't use ammo to fire
883 // check if we have infinite ammo
884 if ( ammo[ type ] < 0 ) {
888 // return how many shots we can fire
889 return ammo[ type ] / amount;
897 int idInventory::HasAmmo( const char *weapon_classname ) {
899 ammo_t ammo_i = AmmoIndexForWeaponClass( weapon_classname, &ammoRequired );
900 return HasAmmo( ammo_i, ammoRequired );
908 bool idInventory::UseAmmo( ammo_t type, int amount ) {
909 if ( !HasAmmo( type, amount ) ) {
913 // take an ammo away if not infinite
914 if ( ammo[ type ] >= 0 ) {
915 ammo[ type ] -= amount;
916 ammoPredictTime = gameLocal.time; // mp client: we predict this. mark time so we're not confused by snapshots
924 idInventory::UpdateArmor
927 void idInventory::UpdateArmor( void ) {
928 if ( deplete_armor != 0.0f && deplete_armor < armor ) {
929 if ( !nextArmorDepleteTime ) {
930 nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
931 } else if ( gameLocal.time > nextArmorDepleteTime ) {
932 armor -= deplete_ammount;
933 if ( armor < deplete_armor ) {
934 armor = deplete_armor;
936 nextArmorDepleteTime = gameLocal.time + deplete_rate * 1000;
946 idPlayer::idPlayer() {
947 memset( &usercmd, 0, sizeof( usercmd ) );
952 spawnAnglesSet = false;
953 spawnAngles = ang_zero;
954 viewAngles = ang_zero;
955 cmdAngles = ang_zero;
963 lastSavingThrowTime = 0;
968 objectiveSystem = NULL;
969 objectiveSystemOpen = false;
971 heartRate = BASE_HEARTRATE;
972 heartInfo.Init( 0, 0, 0, 0 );
976 deathClearContentsTime = 0;
977 lastArmorPulse = -10000;
985 scoreBoardOpen = false;
986 forceScoreBoard = false;
987 forceRespawn = false;
990 colorBar = vec3_zero;
993 wantSpectate = false;
995 lastHitToggle = false;
1000 firstPersonViewOrigin = vec3_zero;
1001 firstPersonViewAxis = mat3_identity;
1003 hipJoint = INVALID_JOINT;
1004 chestJoint = INVALID_JOINT;
1005 headJoint = INVALID_JOINT;
1014 idealLegsYaw = 0.0f;
1018 viewBobAngles = ang_zero;
1019 viewBob = vec3_zero;
1025 previousWeapon = -1;
1026 weaponSwitchTime = 0;
1027 weaponEnabled = true;
1028 weapon_soulcube = -1;
1031 showWeaponViewModel = true;
1037 numProjectilesFired = 0;
1038 numProjectileHits = 0;
1045 gibsLaunched = false;
1046 gibsDir = vec3_zero;
1048 zoomFov.Init( 0, 0, 0, 0 );
1049 centerView.Init( 0, 0, 0, 0 );
1053 influenceActive = 0;
1054 influenceRadius = 0.0f;
1055 influenceEntity = NULL;
1056 influenceMaterial = NULL;
1057 influenceSkin = NULL;
1059 privateCameraView = NULL;
1061 memset( loggedViewAngles, 0, sizeof( loggedViewAngles ) );
1062 memset( loggedAccel, 0, sizeof( loggedAccel ) );
1063 currentLoggedAccel = 0;
1068 focusCharacter = NULL;
1070 focusVehicle = NULL;
1081 lastDamageDir = vec3_zero;
1082 lastDamageLocation = 0;
1084 smoothedOriginUpdated = false;
1085 smoothedOrigin = vec3_zero;
1086 smoothedAngles = ang_zero;
1088 fl.networkSync = true;
1091 doingDeathSkin = false;
1093 useInitialSpawns = false;
1095 lastSpectateTeleport = 0;
1097 hiddenWeapon = false;
1099 objectiveUp = false;
1100 teleportEntity = NULL;
1101 teleportKiller = -1;
1105 lastSpectateChange = 0;
1107 weaponCatchup = false;
1108 lastSnapshotSequence = 0;
1114 MPAimHighlight = false;
1117 lastManOver = false;
1118 lastManPlayAgain = false;
1119 lastManPresent = false;
1121 isTelefragged = false;
1131 idPlayer::LinkScriptVariables
1133 set up conditions for animation
1136 void idPlayer::LinkScriptVariables( void ) {
1137 AI_FORWARD.LinkTo( scriptObject, "AI_FORWARD" );
1138 AI_BACKWARD.LinkTo( scriptObject, "AI_BACKWARD" );
1139 AI_STRAFE_LEFT.LinkTo( scriptObject, "AI_STRAFE_LEFT" );
1140 AI_STRAFE_RIGHT.LinkTo( scriptObject, "AI_STRAFE_RIGHT" );
1141 AI_ATTACK_HELD.LinkTo( scriptObject, "AI_ATTACK_HELD" );
1142 AI_WEAPON_FIRED.LinkTo( scriptObject, "AI_WEAPON_FIRED" );
1143 AI_JUMP.LinkTo( scriptObject, "AI_JUMP" );
1144 AI_DEAD.LinkTo( scriptObject, "AI_DEAD" );
1145 AI_CROUCH.LinkTo( scriptObject, "AI_CROUCH" );
1146 AI_ONGROUND.LinkTo( scriptObject, "AI_ONGROUND" );
1147 AI_ONLADDER.LinkTo( scriptObject, "AI_ONLADDER" );
1148 AI_HARDLANDING.LinkTo( scriptObject, "AI_HARDLANDING" );
1149 AI_SOFTLANDING.LinkTo( scriptObject, "AI_SOFTLANDING" );
1150 AI_RUN.LinkTo( scriptObject, "AI_RUN" );
1151 AI_PAIN.LinkTo( scriptObject, "AI_PAIN" );
1152 AI_RELOAD.LinkTo( scriptObject, "AI_RELOAD" );
1153 AI_TELEPORT.LinkTo( scriptObject, "AI_TELEPORT" );
1154 AI_TURN_LEFT.LinkTo( scriptObject, "AI_TURN_LEFT" );
1155 AI_TURN_RIGHT.LinkTo( scriptObject, "AI_TURN_RIGHT" );
1160 idPlayer::SetupWeaponEntity
1163 void idPlayer::SetupWeaponEntity( void ) {
1167 if ( weapon.GetEntity() ) {
1168 // get rid of old weapon
1169 weapon.GetEntity()->Clear();
1171 } else if ( !gameLocal.isClient ) {
1172 weapon = static_cast<idWeapon *>( gameLocal.SpawnEntityType( idWeapon::Type, NULL ) );
1173 weapon.GetEntity()->SetOwner( this );
1177 for( w = 0; w < MAX_WEAPONS; w++ ) {
1178 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
1179 if ( weap && *weap ) {
1180 idWeapon::CacheWeapon( weap );
1190 void idPlayer::Init( void ) {
1192 const idKeyValue *kv;
1202 previousWeapon = -1;
1203 weaponSwitchTime = 0;
1204 weaponEnabled = true;
1205 weapon_soulcube = SlotForWeapon( "weapon_soulcube" );
1206 weapon_pda = SlotForWeapon( "weapon_pda" );
1207 weapon_fists = SlotForWeapon( "weapon_fists" );
1208 showWeaponViewModel = GetUserInfo()->GetBool( "ui_showGun" );
1212 lastArmorPulse = -10000;
1213 lastHeartAdjust = 0;
1215 heartInfo.Init( 0, 0, 0, 0 );
1221 zoomFov.Init( 0, 0, 0, 0 );
1222 centerView.Init( 0, 0, 0, 0 );
1226 influenceActive = 0;
1227 influenceRadius = 0.0f;
1228 influenceEntity = NULL;
1229 influenceMaterial = NULL;
1230 influenceSkin = NULL;
1232 currentLoggedAccel = 0;
1237 focusCharacter = NULL;
1239 focusVehicle = NULL;
1241 // remove any damage effects
1242 playerView.ClearEffects();
1245 fl.takedamage = true;
1248 // restore persistent data
1249 RestorePersistantInfo();
1254 nextHealthPulse = 0;
1255 healthPulse = false;
1259 SetupWeaponEntity();
1261 previousWeapon = -1;
1263 heartRate = BASE_HEARTRATE;
1264 AdjustHeartRate( BASE_HEARTRATE, 0.0f, 0.0f, true );
1266 idealLegsYaw = 0.0f;
1271 // set the pm_ cvars
1272 if ( !gameLocal.isMultiplayer || gameLocal.isServer ) {
1273 kv = spawnArgs.MatchPrefix( "pm_", NULL );
1275 cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
1276 kv = spawnArgs.MatchPrefix( "pm_", kv );
1280 // disable stamina on hell levels
1281 if ( gameLocal.world && gameLocal.world->spawnArgs.GetBool( "no_stamina" ) ) {
1282 pm_stamina.SetFloat( 0.0f );
1285 // stamina always initialized to maximum
1286 stamina = pm_stamina.GetFloat();
1288 // air always initialized to maximum too
1289 airTics = pm_airTics.GetFloat();
1293 gibsLaunched = false;
1297 physicsObj.SetGravity( gameLocal.GetGravity() );
1299 // start out standing
1300 SetEyeHeight( pm_normalviewheight.GetFloat() );
1304 viewBobAngles.Zero();
1307 value = spawnArgs.GetString( "model" );
1308 if ( value && ( *value != 0 ) ) {
1313 cursor->SetStateInt( "talkcursor", 0 );
1314 cursor->SetStateString( "combatcursor", "1" );
1315 cursor->SetStateString( "itemcursor", "0" );
1316 cursor->SetStateString( "guicursor", "0" );
1319 if ( ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) && skin ) {
1321 renderEntity.shaderParms[6] = 0.0f;
1322 } else if ( spawnArgs.GetString( "spawn_skin", NULL, &value ) ) {
1323 skin = declManager->FindSkin( value );
1325 renderEntity.shaderParms[6] = 0.0f;
1328 value = spawnArgs.GetString( "bone_hips", "" );
1329 hipJoint = animator.GetJointHandle( value );
1330 if ( hipJoint == INVALID_JOINT ) {
1331 gameLocal.Error( "Joint '%s' not found for 'bone_hips' on '%s'", value, name.c_str() );
1334 value = spawnArgs.GetString( "bone_chest", "" );
1335 chestJoint = animator.GetJointHandle( value );
1336 if ( chestJoint == INVALID_JOINT ) {
1337 gameLocal.Error( "Joint '%s' not found for 'bone_chest' on '%s'", value, name.c_str() );
1340 value = spawnArgs.GetString( "bone_head", "" );
1341 headJoint = animator.GetJointHandle( value );
1342 if ( headJoint == INVALID_JOINT ) {
1343 gameLocal.Error( "Joint '%s' not found for 'bone_head' on '%s'", value, name.c_str() );
1346 // initialize the script variables
1348 AI_BACKWARD = false;
1349 AI_STRAFE_LEFT = false;
1350 AI_STRAFE_RIGHT = false;
1351 AI_ATTACK_HELD = false;
1352 AI_WEAPON_FIRED = false;
1357 AI_ONLADDER = false;
1358 AI_HARDLANDING = false;
1359 AI_SOFTLANDING = false;
1363 AI_TELEPORT = false;
1364 AI_TURN_LEFT = false;
1365 AI_TURN_RIGHT = false;
1367 // reset the script object
1368 ConstructScriptObject();
1370 // execute the script so the script object's constructor takes effect immediately
1371 scriptThread->Execute();
1373 forceScoreBoard = false;
1374 forcedReady = false;
1376 privateCameraView = NULL;
1378 lastSpectateChange = 0;
1381 hiddenWeapon = false;
1383 objectiveUp = false;
1384 teleportEntity = NULL;
1385 teleportKiller = -1;
1388 SetPrivateCameraView( NULL );
1390 lastSnapshotSequence = 0;
1396 MPAimHighlight = false;
1399 hud->HandleNamedEvent( "aim_clear" );
1402 cvarSystem->SetCVarBool( "ui_chat", false );
1409 Prepare any resources used by the player.
1412 void idPlayer::Spawn( void ) {
1416 if ( entityNumber >= MAX_CLIENTS ) {
1417 gameLocal.Error( "entityNum > MAX_CLIENTS for player. Player may only be spawned with a client." );
1420 // allow thinking during cinematics
1423 if ( gameLocal.isMultiplayer ) {
1424 // always start in spectating state waiting to be spawned in
1425 // do this before SetClipModel to get the right bounding box
1429 // set our collision model
1430 physicsObj.SetSelf( this );
1432 physicsObj.SetMass( spawnArgs.GetFloat( "mass", "100" ) );
1433 physicsObj.SetContents( CONTENTS_BODY );
1434 physicsObj.SetClipMask( MASK_PLAYERSOLID );
1435 SetPhysics( &physicsObj );
1438 skin = renderEntity.customSkin;
1440 // only the local player needs guis
1441 if ( !gameLocal.isMultiplayer || entityNumber == gameLocal.localClientNum ) {
1444 if ( gameLocal.isMultiplayer ) {
1445 hud = uiManager->FindGui( "guis/mphud.gui", true, false, true );
1446 } else if ( spawnArgs.GetString( "hud", "", temp ) ) {
1447 hud = uiManager->FindGui( temp, true, false, true );
1450 hud->Activate( true, gameLocal.time );
1454 if ( spawnArgs.GetString( "cursor", "", temp ) ) {
1455 cursor = uiManager->FindGui( temp, true, gameLocal.isMultiplayer, gameLocal.isMultiplayer );
1458 cursor->Activate( true, gameLocal.time );
1461 objectiveSystem = uiManager->FindGui( "guis/pda.gui", true, false, true );
1462 objectiveSystemOpen = false;
1465 SetLastHitTime( 0 );
1467 // load the armor sound feedback
1468 declManager->FindSound( "player_sounds_hitArmor" );
1470 // set up conditions for animation
1471 LinkScriptVariables();
1473 animator.RemoveOriginOffset( true );
1475 // initialize user info related settings
1476 // on server, we wait for the userinfo broadcast, as this controls when the player is initially spawned in game
1477 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
1478 UserInfoChanged( false );
1481 // create combat collision hull for exact collision detection
1484 // init the damage effects
1485 playerView.SetPlayerEntity( this );
1487 // supress model in non-player views, but allow it in mirrors and remote views
1488 renderEntity.suppressSurfaceInViewID = entityNumber+1;
1490 // don't project shadow on self or weapon
1491 renderEntity.noSelfShadow = true;
1493 idAFAttachment *headEnt = head.GetEntity();
1495 headEnt->GetRenderEntity()->suppressSurfaceInViewID = entityNumber+1;
1496 headEnt->GetRenderEntity()->noSelfShadow = true;
1499 if ( gameLocal.isMultiplayer ) {
1501 Hide(); // properly hidden if starting as a spectator
1502 if ( !gameLocal.isClient ) {
1503 // set yourself ready to spawn. idMultiplayerGame will decide when/if appropriate and call SpawnFromSpawnSpot
1504 SetupWeaponEntity();
1505 SpawnFromSpawnSpot();
1506 forceRespawn = true;
1507 assert( spectating );
1510 SetupWeaponEntity();
1511 SpawnFromSpawnSpot();
1514 // trigger playtesting item gives, if we didn't get here from a previous level
1515 // the devmap key will be set on the first devmap, but cleared on any level
1517 if ( !gameLocal.isMultiplayer && gameLocal.serverInfo.FindKey( "devmap" ) ) {
1518 // fire a trigger with the name "devmap"
1519 idEntity *ent = gameLocal.FindEntity( "devmap" );
1521 ent->ActivateTargets( this );
1525 // We can spawn with a full soul cube, so we need to make sure the hud knows this
1526 if ( weapon_soulcube > 0 && ( inventory.weapons & ( 1 << weapon_soulcube ) ) ) {
1527 int max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
1528 if ( inventory.ammo[ idWeapon::GetAmmoNumForName( "ammo_souls" ) ] >= max_souls ) {
1529 hud->HandleNamedEvent( "soulCubeReady" );
1532 hud->HandleNamedEvent( "itemPickup" );
1536 // Add any emails from the inventory
1537 for ( int i = 0; i < inventory.emails.Num(); i++ ) {
1538 GetPDA()->AddEmail( inventory.emails[i] );
1540 GetPDA()->SetSecurity( common->GetLanguageDict()->GetString( "#str_00066" ) );
1543 if ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
1544 hiddenWeapon = true;
1545 if ( weapon.GetEntity() ) {
1546 weapon.GetEntity()->LowerWeapon();
1550 hiddenWeapon = false;
1555 hud->StateChanged( gameLocal.time );
1559 objectiveUp = false;
1561 if ( inventory.levelTriggers.Num() ) {
1562 PostEventMS( &EV_Player_LevelTrigger, 0 );
1565 inventory.pdaOpened = false;
1566 inventory.selPDA = 0;
1568 if ( !gameLocal.isMultiplayer ) {
1569 if ( g_skill.GetInteger() < 2 ) {
1570 if ( health < 25 ) {
1573 if ( g_useDynamicProtection.GetBool() ) {
1574 g_damageScale.SetFloat( 1.0f );
1577 g_damageScale.SetFloat( 1.0f );
1578 g_armorProtection.SetFloat( ( g_skill.GetInteger() < 2 ) ? 0.4f : 0.2f );
1579 #ifndef ID_DEMO_BUILD
1580 if ( g_skill.GetInteger() == 3 ) {
1582 nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
1591 idPlayer::~idPlayer()
1593 Release any resources used by the player.
1596 idPlayer::~idPlayer() {
1597 delete weapon.GetEntity();
1606 void idPlayer::Save( idSaveGame *savefile ) const {
1609 savefile->WriteUsercmd( usercmd );
1610 playerView.Save( savefile );
1612 savefile->WriteBool( noclip );
1613 savefile->WriteBool( godmode );
1615 // don't save spawnAnglesSet, since we'll have to reset them after loading the savegame
1616 savefile->WriteAngles( spawnAngles );
1617 savefile->WriteAngles( viewAngles );
1618 savefile->WriteAngles( cmdAngles );
1620 savefile->WriteInt( buttonMask );
1621 savefile->WriteInt( oldButtons );
1622 savefile->WriteInt( oldFlags );
1624 savefile->WriteInt( lastHitTime );
1625 savefile->WriteInt( lastSndHitTime );
1626 savefile->WriteInt( lastSavingThrowTime );
1628 // idBoolFields don't need to be saved, just re-linked in Restore
1630 inventory.Save( savefile );
1631 weapon.Save( savefile );
1633 savefile->WriteUserInterface( hud, false );
1634 savefile->WriteUserInterface( objectiveSystem, false );
1635 savefile->WriteBool( objectiveSystemOpen );
1637 savefile->WriteInt( weapon_soulcube );
1638 savefile->WriteInt( weapon_pda );
1639 savefile->WriteInt( weapon_fists );
1641 savefile->WriteInt( heartRate );
1643 savefile->WriteFloat( heartInfo.GetStartTime() );
1644 savefile->WriteFloat( heartInfo.GetDuration() );
1645 savefile->WriteFloat( heartInfo.GetStartValue() );
1646 savefile->WriteFloat( heartInfo.GetEndValue() );
1648 savefile->WriteInt( lastHeartAdjust );
1649 savefile->WriteInt( lastHeartBeat );
1650 savefile->WriteInt( lastDmgTime );
1651 savefile->WriteInt( deathClearContentsTime );
1652 savefile->WriteBool( doingDeathSkin );
1653 savefile->WriteInt( lastArmorPulse );
1654 savefile->WriteFloat( stamina );
1655 savefile->WriteFloat( healthPool );
1656 savefile->WriteInt( nextHealthPulse );
1657 savefile->WriteBool( healthPulse );
1658 savefile->WriteInt( nextHealthTake );
1659 savefile->WriteBool( healthTake );
1661 savefile->WriteBool( hiddenWeapon );
1662 soulCubeProjectile.Save( savefile );
1664 savefile->WriteInt( spectator );
1665 savefile->WriteVec3( colorBar );
1666 savefile->WriteInt( colorBarIndex );
1667 savefile->WriteBool( scoreBoardOpen );
1668 savefile->WriteBool( forceScoreBoard );
1669 savefile->WriteBool( forceRespawn );
1670 savefile->WriteBool( spectating );
1671 savefile->WriteInt( lastSpectateTeleport );
1672 savefile->WriteBool( lastHitToggle );
1673 savefile->WriteBool( forcedReady );
1674 savefile->WriteBool( wantSpectate );
1675 savefile->WriteBool( weaponGone );
1676 savefile->WriteBool( useInitialSpawns );
1677 savefile->WriteInt( latchedTeam );
1678 savefile->WriteInt( tourneyRank );
1679 savefile->WriteInt( tourneyLine );
1681 teleportEntity.Save( savefile );
1682 savefile->WriteInt( teleportKiller );
1684 savefile->WriteInt( minRespawnTime );
1685 savefile->WriteInt( maxRespawnTime );
1687 savefile->WriteVec3( firstPersonViewOrigin );
1688 savefile->WriteMat3( firstPersonViewAxis );
1690 // don't bother saving dragEntity since it's a dev tool
1692 savefile->WriteJoint( hipJoint );
1693 savefile->WriteJoint( chestJoint );
1694 savefile->WriteJoint( headJoint );
1696 savefile->WriteStaticObject( physicsObj );
1698 savefile->WriteInt( aasLocation.Num() );
1699 for( i = 0; i < aasLocation.Num(); i++ ) {
1700 savefile->WriteInt( aasLocation[ i ].areaNum );
1701 savefile->WriteVec3( aasLocation[ i ].pos );
1704 savefile->WriteInt( bobFoot );
1705 savefile->WriteFloat( bobFrac );
1706 savefile->WriteFloat( bobfracsin );
1707 savefile->WriteInt( bobCycle );
1708 savefile->WriteFloat( xyspeed );
1709 savefile->WriteInt( stepUpTime );
1710 savefile->WriteFloat( stepUpDelta );
1711 savefile->WriteFloat( idealLegsYaw );
1712 savefile->WriteFloat( legsYaw );
1713 savefile->WriteBool( legsForward );
1714 savefile->WriteFloat( oldViewYaw );
1715 savefile->WriteAngles( viewBobAngles );
1716 savefile->WriteVec3( viewBob );
1717 savefile->WriteInt( landChange );
1718 savefile->WriteInt( landTime );
1720 savefile->WriteInt( currentWeapon );
1721 savefile->WriteInt( idealWeapon );
1722 savefile->WriteInt( previousWeapon );
1723 savefile->WriteInt( weaponSwitchTime );
1724 savefile->WriteBool( weaponEnabled );
1725 savefile->WriteBool( showWeaponViewModel );
1727 savefile->WriteSkin( skin );
1728 savefile->WriteSkin( powerUpSkin );
1729 savefile->WriteString( baseSkinName );
1731 savefile->WriteInt( numProjectilesFired );
1732 savefile->WriteInt( numProjectileHits );
1734 savefile->WriteBool( airless );
1735 savefile->WriteInt( airTics );
1736 savefile->WriteInt( lastAirDamage );
1738 savefile->WriteBool( gibDeath );
1739 savefile->WriteBool( gibsLaunched );
1740 savefile->WriteVec3( gibsDir );
1742 savefile->WriteFloat( zoomFov.GetStartTime() );
1743 savefile->WriteFloat( zoomFov.GetDuration() );
1744 savefile->WriteFloat( zoomFov.GetStartValue() );
1745 savefile->WriteFloat( zoomFov.GetEndValue() );
1747 savefile->WriteFloat( centerView.GetStartTime() );
1748 savefile->WriteFloat( centerView.GetDuration() );
1749 savefile->WriteFloat( centerView.GetStartValue() );
1750 savefile->WriteFloat( centerView.GetEndValue() );
1752 savefile->WriteBool( fxFov );
1754 savefile->WriteFloat( influenceFov );
1755 savefile->WriteInt( influenceActive );
1756 savefile->WriteFloat( influenceRadius );
1757 savefile->WriteObject( influenceEntity );
1758 savefile->WriteMaterial( influenceMaterial );
1759 savefile->WriteSkin( influenceSkin );
1761 savefile->WriteObject( privateCameraView );
1763 for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
1764 savefile->WriteAngles( loggedViewAngles[ i ] );
1766 for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
1767 savefile->WriteInt( loggedAccel[ i ].time );
1768 savefile->WriteVec3( loggedAccel[ i ].dir );
1770 savefile->WriteInt( currentLoggedAccel );
1772 savefile->WriteObject( focusGUIent );
1773 // can't save focusUI
1774 savefile->WriteObject( focusCharacter );
1775 savefile->WriteInt( talkCursor );
1776 savefile->WriteInt( focusTime );
1777 savefile->WriteObject( focusVehicle );
1778 savefile->WriteUserInterface( cursor, false );
1780 savefile->WriteInt( oldMouseX );
1781 savefile->WriteInt( oldMouseY );
1783 savefile->WriteString( pdaAudio );
1784 savefile->WriteString( pdaVideo );
1785 savefile->WriteString( pdaVideoWave );
1787 savefile->WriteBool( tipUp );
1788 savefile->WriteBool( objectiveUp );
1790 savefile->WriteInt( lastDamageDef );
1791 savefile->WriteVec3( lastDamageDir );
1792 savefile->WriteInt( lastDamageLocation );
1793 savefile->WriteInt( smoothedFrame );
1794 savefile->WriteBool( smoothedOriginUpdated );
1795 savefile->WriteVec3( smoothedOrigin );
1796 savefile->WriteAngles( smoothedAngles );
1798 savefile->WriteBool( ready );
1799 savefile->WriteBool( respawning );
1800 savefile->WriteBool( leader );
1801 savefile->WriteInt( lastSpectateChange );
1802 savefile->WriteInt( lastTeleFX );
1804 savefile->WriteFloat( pm_stamina.GetFloat() );
1807 hud->SetStateString( "message", common->GetLanguageDict()->GetString( "#str_02916" ) );
1808 hud->HandleNamedEvent( "Message" );
1817 void idPlayer::Restore( idRestoreGame *savefile ) {
1822 savefile->ReadUsercmd( usercmd );
1823 playerView.Restore( savefile );
1825 savefile->ReadBool( noclip );
1826 savefile->ReadBool( godmode );
1828 savefile->ReadAngles( spawnAngles );
1829 savefile->ReadAngles( viewAngles );
1830 savefile->ReadAngles( cmdAngles );
1832 memset( usercmd.angles, 0, sizeof( usercmd.angles ) );
1833 SetViewAngles( viewAngles );
1834 spawnAnglesSet = true;
1836 savefile->ReadInt( buttonMask );
1837 savefile->ReadInt( oldButtons );
1838 savefile->ReadInt( oldFlags );
1843 savefile->ReadInt( lastHitTime );
1844 savefile->ReadInt( lastSndHitTime );
1845 savefile->ReadInt( lastSavingThrowTime );
1847 // Re-link idBoolFields to the scriptObject, values will be restored in scriptObject's restore
1848 LinkScriptVariables();
1850 inventory.Restore( savefile );
1851 weapon.Restore( savefile );
1853 for ( i = 0; i < inventory.emails.Num(); i++ ) {
1854 GetPDA()->AddEmail( inventory.emails[i] );
1857 savefile->ReadUserInterface( hud );
1858 savefile->ReadUserInterface( objectiveSystem );
1859 savefile->ReadBool( objectiveSystemOpen );
1861 savefile->ReadInt( weapon_soulcube );
1862 savefile->ReadInt( weapon_pda );
1863 savefile->ReadInt( weapon_fists );
1865 savefile->ReadInt( heartRate );
1867 savefile->ReadFloat( set );
1868 heartInfo.SetStartTime( set );
1869 savefile->ReadFloat( set );
1870 heartInfo.SetDuration( set );
1871 savefile->ReadFloat( set );
1872 heartInfo.SetStartValue( set );
1873 savefile->ReadFloat( set );
1874 heartInfo.SetEndValue( set );
1876 savefile->ReadInt( lastHeartAdjust );
1877 savefile->ReadInt( lastHeartBeat );
1878 savefile->ReadInt( lastDmgTime );
1879 savefile->ReadInt( deathClearContentsTime );
1880 savefile->ReadBool( doingDeathSkin );
1881 savefile->ReadInt( lastArmorPulse );
1882 savefile->ReadFloat( stamina );
1883 savefile->ReadFloat( healthPool );
1884 savefile->ReadInt( nextHealthPulse );
1885 savefile->ReadBool( healthPulse );
1886 savefile->ReadInt( nextHealthTake );
1887 savefile->ReadBool( healthTake );
1889 savefile->ReadBool( hiddenWeapon );
1890 soulCubeProjectile.Restore( savefile );
1892 savefile->ReadInt( spectator );
1893 savefile->ReadVec3( colorBar );
1894 savefile->ReadInt( colorBarIndex );
1895 savefile->ReadBool( scoreBoardOpen );
1896 savefile->ReadBool( forceScoreBoard );
1897 savefile->ReadBool( forceRespawn );
1898 savefile->ReadBool( spectating );
1899 savefile->ReadInt( lastSpectateTeleport );
1900 savefile->ReadBool( lastHitToggle );
1901 savefile->ReadBool( forcedReady );
1902 savefile->ReadBool( wantSpectate );
1903 savefile->ReadBool( weaponGone );
1904 savefile->ReadBool( useInitialSpawns );
1905 savefile->ReadInt( latchedTeam );
1906 savefile->ReadInt( tourneyRank );
1907 savefile->ReadInt( tourneyLine );
1909 teleportEntity.Restore( savefile );
1910 savefile->ReadInt( teleportKiller );
1912 savefile->ReadInt( minRespawnTime );
1913 savefile->ReadInt( maxRespawnTime );
1915 savefile->ReadVec3( firstPersonViewOrigin );
1916 savefile->ReadMat3( firstPersonViewAxis );
1918 // don't bother saving dragEntity since it's a dev tool
1921 savefile->ReadJoint( hipJoint );
1922 savefile->ReadJoint( chestJoint );
1923 savefile->ReadJoint( headJoint );
1925 savefile->ReadStaticObject( physicsObj );
1926 RestorePhysics( &physicsObj );
1928 savefile->ReadInt( num );
1929 aasLocation.SetGranularity( 1 );
1930 aasLocation.SetNum( num );
1931 for( i = 0; i < num; i++ ) {
1932 savefile->ReadInt( aasLocation[ i ].areaNum );
1933 savefile->ReadVec3( aasLocation[ i ].pos );
1936 savefile->ReadInt( bobFoot );
1937 savefile->ReadFloat( bobFrac );
1938 savefile->ReadFloat( bobfracsin );
1939 savefile->ReadInt( bobCycle );
1940 savefile->ReadFloat( xyspeed );
1941 savefile->ReadInt( stepUpTime );
1942 savefile->ReadFloat( stepUpDelta );
1943 savefile->ReadFloat( idealLegsYaw );
1944 savefile->ReadFloat( legsYaw );
1945 savefile->ReadBool( legsForward );
1946 savefile->ReadFloat( oldViewYaw );
1947 savefile->ReadAngles( viewBobAngles );
1948 savefile->ReadVec3( viewBob );
1949 savefile->ReadInt( landChange );
1950 savefile->ReadInt( landTime );
1952 savefile->ReadInt( currentWeapon );
1953 savefile->ReadInt( idealWeapon );
1954 savefile->ReadInt( previousWeapon );
1955 savefile->ReadInt( weaponSwitchTime );
1956 savefile->ReadBool( weaponEnabled );
1957 savefile->ReadBool( showWeaponViewModel );
1959 savefile->ReadSkin( skin );
1960 savefile->ReadSkin( powerUpSkin );
1961 savefile->ReadString( baseSkinName );
1963 savefile->ReadInt( numProjectilesFired );
1964 savefile->ReadInt( numProjectileHits );
1966 savefile->ReadBool( airless );
1967 savefile->ReadInt( airTics );
1968 savefile->ReadInt( lastAirDamage );
1970 savefile->ReadBool( gibDeath );
1971 savefile->ReadBool( gibsLaunched );
1972 savefile->ReadVec3( gibsDir );
1974 savefile->ReadFloat( set );
1975 zoomFov.SetStartTime( set );
1976 savefile->ReadFloat( set );
1977 zoomFov.SetDuration( set );
1978 savefile->ReadFloat( set );
1979 zoomFov.SetStartValue( set );
1980 savefile->ReadFloat( set );
1981 zoomFov.SetEndValue( set );
1983 savefile->ReadFloat( set );
1984 centerView.SetStartTime( set );
1985 savefile->ReadFloat( set );
1986 centerView.SetDuration( set );
1987 savefile->ReadFloat( set );
1988 centerView.SetStartValue( set );
1989 savefile->ReadFloat( set );
1990 centerView.SetEndValue( set );
1992 savefile->ReadBool( fxFov );
1994 savefile->ReadFloat( influenceFov );
1995 savefile->ReadInt( influenceActive );
1996 savefile->ReadFloat( influenceRadius );
1997 savefile->ReadObject( reinterpret_cast<idClass *&>( influenceEntity ) );
1998 savefile->ReadMaterial( influenceMaterial );
1999 savefile->ReadSkin( influenceSkin );
2001 savefile->ReadObject( reinterpret_cast<idClass *&>( privateCameraView ) );
2003 for( i = 0; i < NUM_LOGGED_VIEW_ANGLES; i++ ) {
2004 savefile->ReadAngles( loggedViewAngles[ i ] );
2006 for( i = 0; i < NUM_LOGGED_ACCELS; i++ ) {
2007 savefile->ReadInt( loggedAccel[ i ].time );
2008 savefile->ReadVec3( loggedAccel[ i ].dir );
2010 savefile->ReadInt( currentLoggedAccel );
2012 savefile->ReadObject( reinterpret_cast<idClass *&>( focusGUIent ) );
2013 // can't save focusUI
2015 savefile->ReadObject( reinterpret_cast<idClass *&>( focusCharacter ) );
2016 savefile->ReadInt( talkCursor );
2017 savefile->ReadInt( focusTime );
2018 savefile->ReadObject( reinterpret_cast<idClass *&>( focusVehicle ) );
2019 savefile->ReadUserInterface( cursor );
2021 savefile->ReadInt( oldMouseX );
2022 savefile->ReadInt( oldMouseY );
2024 savefile->ReadString( pdaAudio );
2025 savefile->ReadString( pdaVideo );
2026 savefile->ReadString( pdaVideoWave );
2028 savefile->ReadBool( tipUp );
2029 savefile->ReadBool( objectiveUp );
2031 savefile->ReadInt( lastDamageDef );
2032 savefile->ReadVec3( lastDamageDir );
2033 savefile->ReadInt( lastDamageLocation );
2034 savefile->ReadInt( smoothedFrame );
2035 savefile->ReadBool( smoothedOriginUpdated );
2036 savefile->ReadVec3( smoothedOrigin );
2037 savefile->ReadAngles( smoothedAngles );
2039 savefile->ReadBool( ready );
2040 savefile->ReadBool( respawning );
2041 savefile->ReadBool( leader );
2042 savefile->ReadInt( lastSpectateChange );
2043 savefile->ReadInt( lastTeleFX );
2045 // set the pm_ cvars
2046 const idKeyValue *kv;
2047 kv = spawnArgs.MatchPrefix( "pm_", NULL );
2049 cvarSystem->SetCVarString( kv->GetKey(), kv->GetValue() );
2050 kv = spawnArgs.MatchPrefix( "pm_", kv );
2053 savefile->ReadFloat( set );
2054 pm_stamina.SetFloat( set );
2056 // create combat collision hull for exact collision detection
2062 idPlayer::PrepareForRestart
2065 void idPlayer::PrepareForRestart( void ) {
2068 forceRespawn = true;
2070 // we will be restarting program, clear the client entities from program-related things first
2073 // the sound world is going to be cleared, don't keep references to emitters
2074 FreeSoundEmitter( false );
2082 void idPlayer::Restart( void ) {
2085 // client needs to setup the animation script object again
2086 if ( gameLocal.isClient ) {
2089 // choose a random spot and prepare the point of view in case player is left spectating
2090 assert( spectating );
2091 SpawnFromSpawnSpot();
2094 useInitialSpawns = true;
2095 UpdateSkinSetup( true );
2100 idPlayer::ServerSpectate
2103 void idPlayer::ServerSpectate( bool spectate ) {
2104 assert( !gameLocal.isClient );
2106 if ( spectating != spectate ) {
2107 Spectate( spectate );
2109 SetSpectateOrigin();
2111 if ( gameLocal.gameType == GAME_DM ) {
2112 // make sure the scores are reset so you can't exploit by spectating and entering the game back
2113 // other game types don't matter, as you either can't join back, or it's team scores
2114 gameLocal.mpGame.ClearFrags( entityNumber );
2119 SpawnFromSpawnSpot();
2125 idPlayer::SelectInitialSpawnPoint
2127 Try to find a spawn point marked 'initial', otherwise
2128 use normal spawn selection.
2131 void idPlayer::SelectInitialSpawnPoint( idVec3 &origin, idAngles &angles ) {
2135 spot = gameLocal.SelectInitialSpawnPoint( this );
2137 // set the player skin from the spawn location
2138 if ( spot->spawnArgs.GetString( "skin", NULL, skin ) ) {
2139 spawnArgs.Set( "spawn_skin", skin );
2142 // activate the spawn locations targets
2143 spot->PostEventMS( &EV_ActivateTargets, 0, this );
2145 origin = spot->GetPhysics()->GetOrigin();
2146 origin[2] += 4.0f + CM_BOX_EPSILON; // move up to make sure the player is at least an epsilon above the floor
2147 angles = spot->GetPhysics()->GetAxis().ToAngles();
2152 idPlayer::SpawnFromSpawnSpot
2154 Chooses a spawn location and spawns the player
2157 void idPlayer::SpawnFromSpawnSpot( void ) {
2158 idVec3 spawn_origin;
2159 idAngles spawn_angles;
2161 SelectInitialSpawnPoint( spawn_origin, spawn_angles );
2162 SpawnToPoint( spawn_origin, spawn_angles );
2167 idPlayer::SpawnToPoint
2169 Called every time a client is placed fresh in the world:
2170 after the first ClientBegin, and after each respawn
2171 Initializes all non-persistant parts of playerState
2173 when called here with spectating set to true, just place yourself and init
2176 void idPlayer::SpawnToPoint( const idVec3 &spawn_origin, const idAngles &spawn_angles ) {
2179 assert( !gameLocal.isClient );
2185 fl.noknockback = false;
2187 // stop any ragdolls being used
2190 // set back the player physics
2191 SetPhysics( &physicsObj );
2193 physicsObj.SetClipModelAxis();
2194 physicsObj.EnableClip();
2196 if ( !spectating ) {
2197 SetCombatContents( true );
2200 physicsObj.SetLinearVelocity( vec3_origin );
2202 // setup our initial view
2203 if ( !spectating ) {
2204 SetOrigin( spawn_origin );
2206 spec_origin = spawn_origin;
2207 spec_origin[ 2 ] += pm_normalheight.GetFloat();
2208 spec_origin[ 2 ] += SPECTATE_RAISE;
2209 SetOrigin( spec_origin );
2212 // if this is the first spawn of the map, we don't have a usercmd yet,
2213 // so the delta angles won't be correct. This will be fixed on the first think.
2214 viewAngles = ang_zero;
2215 SetDeltaViewAngles( ang_zero );
2216 SetViewAngles( spawn_angles );
2217 spawnAngles = spawn_angles;
2218 spawnAnglesSet = false;
2222 idealLegsYaw = 0.0f;
2223 oldViewYaw = viewAngles.yaw;
2231 if ( gameLocal.isMultiplayer ) {
2232 if ( !spectating ) {
2233 // we may be called twice in a row in some situations. avoid a double fx and 'fly to the roof'
2234 if ( lastTeleFX < gameLocal.time - 1000 ) {
2235 idEntityFx::StartFx( spawnArgs.GetString( "fx_spawn" ), &spawn_origin, NULL, this, true );
2236 lastTeleFX = gameLocal.time;
2241 AI_TELEPORT = false;
2244 // kill anything at the new position
2245 if ( !spectating ) {
2246 physicsObj.SetClipMask( MASK_PLAYERSOLID ); // the clip mask is usually maintained in Move(), but KillBox requires it
2247 gameLocal.KillBox( this );
2250 // don't allow full run speed for a bit
2251 physicsObj.SetKnockBack( 100 );
2253 // set our respawn time and buttons so that if we're killed we don't respawn immediately
2254 minRespawnTime = gameLocal.time;
2255 maxRespawnTime = gameLocal.time;
2256 if ( !spectating ) {
2257 forceRespawn = false;
2260 privateCameraView = NULL;
2262 BecomeActive( TH_THINK );
2264 // run a client frame to drop exactly to the floor,
2265 // initialize animations and other things
2269 lastManOver = false;
2270 lastManPlayAgain = false;
2271 isTelefragged = false;
2276 idPlayer::SavePersistantInfo
2278 Saves any inventory and player stats when changing levels.
2281 void idPlayer::SavePersistantInfo( void ) {
2282 idDict &playerInfo = gameLocal.persistentPlayerInfo[entityNumber];
2285 inventory.GetPersistantData( playerInfo );
2286 playerInfo.SetInt( "health", health );
2287 playerInfo.SetInt( "current_weapon", currentWeapon );
2292 idPlayer::RestorePersistantInfo
2294 Restores any inventory and player stats when changing levels.
2297 void idPlayer::RestorePersistantInfo( void ) {
2298 if ( gameLocal.isMultiplayer ) {
2299 gameLocal.persistentPlayerInfo[entityNumber].Clear();
2302 spawnArgs.Copy( gameLocal.persistentPlayerInfo[entityNumber] );
2304 inventory.RestoreInventory( this, spawnArgs );
2305 health = spawnArgs.GetInt( "health", "100" );
2306 if ( !gameLocal.isClient ) {
2307 idealWeapon = spawnArgs.GetInt( "current_weapon", "1" );
2313 idPlayer::GetUserInfo
2316 idDict *idPlayer::GetUserInfo( void ) {
2317 return &gameLocal.userInfo[ entityNumber ];
2322 idPlayer::UpdateSkinSetup
2325 void idPlayer::UpdateSkinSetup( bool restart ) {
2327 team = ( idStr::Icmp( GetUserInfo()->GetString( "ui_team" ), "Blue" ) == 0 );
2329 if ( gameLocal.gameType == GAME_TDM ) {
2331 baseSkinName = "skins/characters/player/marine_mp_blue";
2333 baseSkinName = "skins/characters/player/marine_mp_red";
2335 if ( !gameLocal.isClient && team != latchedTeam ) {
2336 gameLocal.mpGame.SwitchToTeam( entityNumber, latchedTeam, team );
2340 baseSkinName = GetUserInfo()->GetString( "ui_skin" );
2342 if ( !baseSkinName.Length() ) {
2343 baseSkinName = "skins/characters/player/marine_mp";
2345 skin = declManager->FindSkin( baseSkinName, false );
2347 // match the skin to a color band for scoreboard
2348 if ( baseSkinName.Find( "red" ) != -1 ) {
2350 } else if ( baseSkinName.Find( "green" ) != -1 ) {
2352 } else if ( baseSkinName.Find( "blue" ) != -1 ) {
2354 } else if ( baseSkinName.Find( "yellow" ) != -1 ) {
2359 colorBar = colorBarTable[ colorBarIndex ];
2360 if ( PowerUpActive( BERSERK ) ) {
2361 powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
2367 idPlayer::BalanceTDM
2370 bool idPlayer::BalanceTDM( void ) {
2371 int i, balanceTeam, teamCount[2];
2374 teamCount[ 0 ] = teamCount[ 1 ] = 0;
2375 for( i = 0; i < gameLocal.numClients; i++ ) {
2376 ent = gameLocal.entities[ i ];
2377 if ( ent && ent->IsType( idPlayer::Type ) ) {
2378 teamCount[ static_cast< idPlayer * >( ent )->team ]++;
2382 if ( teamCount[ 0 ] < teamCount[ 1 ] ) {
2384 } else if ( teamCount[ 0 ] > teamCount[ 1 ] ) {
2387 if ( balanceTeam != -1 && team != balanceTeam ) {
2388 common->DPrintf( "team balance: forcing player %d to %s team\n", entityNumber, balanceTeam ? "blue" : "red" );
2390 GetUserInfo()->Set( "ui_team", team ? "Blue" : "Red" );
2398 idPlayer::UserInfoChanged
2401 bool idPlayer::UserInfoChanged( bool canModify ) {
2407 userInfo = GetUserInfo();
2408 showWeaponViewModel = userInfo->GetBool( "ui_showGun" );
2410 if ( !gameLocal.isMultiplayer ) {
2414 modifiedInfo = false;
2416 spec = ( idStr::Icmp( userInfo->GetString( "ui_spectate" ), "Spectate" ) == 0 );
2417 if ( gameLocal.serverInfo.GetBool( "si_spectators" ) ) {
2418 // never let spectators go back to game while sudden death is on
2419 if ( canModify && gameLocal.mpGame.GetGameState() == idMultiplayerGame::SUDDENDEATH && !spec && wantSpectate == true ) {
2420 userInfo->Set( "ui_spectate", "Spectate" );
2421 modifiedInfo |= true;
2423 if ( spec != wantSpectate && !spec ) {
2424 // returning from spectate, set forceRespawn so we don't get stuck in spectate forever
2425 forceRespawn = true;
2427 wantSpectate = spec;
2430 if ( canModify && spec ) {
2431 userInfo->Set( "ui_spectate", "Play" );
2432 modifiedInfo |= true;
2433 } else if ( spectating ) {
2434 // allow player to leaving spectator mode if they were in it when si_spectators got turned off
2435 forceRespawn = true;
2437 wantSpectate = false;
2440 newready = ( idStr::Icmp( userInfo->GetString( "ui_ready" ), "Ready" ) == 0 );
2441 if ( ready != newready && gameLocal.mpGame.GetGameState() == idMultiplayerGame::WARMUP && !wantSpectate ) {
2442 gameLocal.mpGame.AddChatLine( common->GetLanguageDict()->GetString( "#str_07180" ), userInfo->GetString( "ui_name" ), newready ? common->GetLanguageDict()->GetString( "#str_04300" ) : common->GetLanguageDict()->GetString( "#str_04301" ) );
2445 team = ( idStr::Icmp( userInfo->GetString( "ui_team" ), "Blue" ) == 0 );
2446 // server maintains TDM balance
2447 if ( canModify && gameLocal.gameType == GAME_TDM && !gameLocal.mpGame.IsInGame( entityNumber ) && g_balanceTDM.GetBool() ) {
2448 modifiedInfo |= BalanceTDM( );
2450 UpdateSkinSetup( false );
2452 isChatting = userInfo->GetBool( "ui_chat", "0" );
2453 if ( canModify && isChatting && AI_DEAD ) {
2454 // if dead, always force chat icon off.
2456 userInfo->SetBool( "ui_chat", false );
2457 modifiedInfo |= true;
2460 return modifiedInfo;
2465 idPlayer::UpdateHudAmmo
2468 void idPlayer::UpdateHudAmmo( idUserInterface *_hud ) {
2472 assert( weapon.GetEntity() );
2475 inclip = weapon.GetEntity()->AmmoInClip();
2476 ammoamount = weapon.GetEntity()->AmmoAvailable();
2477 if ( ammoamount < 0 || !weapon.GetEntity()->IsReady() ) {
2478 // show infinite ammo
2479 _hud->SetStateString( "player_ammo", "" );
2480 _hud->SetStateString( "player_totalammo", "" );
2482 // show remaining ammo
2483 _hud->SetStateString( "player_totalammo", va( "%i", ammoamount - inclip ) );
2484 _hud->SetStateString( "player_ammo", weapon.GetEntity()->ClipSize() ? va( "%i", inclip ) : "--" ); // how much in the current clip
2485 _hud->SetStateString( "player_clips", weapon.GetEntity()->ClipSize() ? va( "%i", ammoamount / weapon.GetEntity()->ClipSize() ) : "--" );
2486 _hud->SetStateString( "player_allammo", va( "%i/%i", inclip, ammoamount - inclip ) );
2489 _hud->SetStateBool( "player_ammo_empty", ( ammoamount == 0 ) );
2490 _hud->SetStateBool( "player_clip_empty", ( weapon.GetEntity()->ClipSize() ? inclip == 0 : false ) );
2491 _hud->SetStateBool( "player_clip_low", ( weapon.GetEntity()->ClipSize() ? inclip <= weapon.GetEntity()->LowAmmo() : false ) );
2493 _hud->HandleNamedEvent( "updateAmmo" );
2498 idPlayer::UpdateHudStats
2501 void idPlayer::UpdateHudStats( idUserInterface *_hud ) {
2502 int staminapercentage;
2507 max_stamina = pm_stamina.GetFloat();
2508 if ( !max_stamina ) {
2509 // stamina disabled, so show full stamina bar
2510 staminapercentage = 100.0f;
2512 staminapercentage = idMath::FtoiFast( 100.0f * stamina / max_stamina );
2515 _hud->SetStateInt( "player_health", health );
2516 _hud->SetStateInt( "player_stamina", staminapercentage );
2517 _hud->SetStateInt( "player_armor", inventory.armor );
2518 _hud->SetStateInt( "player_hr", heartRate );
2519 _hud->SetStateInt( "player_nostamina", ( max_stamina == 0 ) ? 1 : 0 );
2521 _hud->HandleNamedEvent( "updateArmorHealthAir" );
2523 if ( healthPulse ) {
2524 _hud->HandleNamedEvent( "healthPulse" );
2525 StartSound( "snd_healthpulse", SND_CHANNEL_ITEM, 0, false, NULL );
2526 healthPulse = false;
2530 _hud->HandleNamedEvent( "healthPulse" );
2531 StartSound( "snd_healthtake", SND_CHANNEL_ITEM, 0, false, NULL );
2535 if ( inventory.ammoPulse ) {
2536 _hud->HandleNamedEvent( "ammoPulse" );
2537 inventory.ammoPulse = false;
2539 if ( inventory.weaponPulse ) {
2540 // We need to update the weapon hud manually, but not
2541 // the armor/ammo/health because they are updated every
2542 // frame no matter what
2544 _hud->HandleNamedEvent( "weaponPulse" );
2545 inventory.weaponPulse = false;
2547 if ( inventory.armorPulse ) {
2548 _hud->HandleNamedEvent( "armorPulse" );
2549 inventory.armorPulse = false;
2552 UpdateHudAmmo( _hud );
2557 idPlayer::UpdateHudWeapon
2560 void idPlayer::UpdateHudWeapon( bool flashWeapon ) {
2561 idUserInterface *hud = idPlayer::hud;
2563 // if updating the hud of a followed client
2564 if ( gameLocal.localClientNum >= 0 && gameLocal.entities[ gameLocal.localClientNum ] && gameLocal.entities[ gameLocal.localClientNum ]->IsType( idPlayer::Type ) ) {
2565 idPlayer *p = static_cast< idPlayer * >( gameLocal.entities[ gameLocal.localClientNum ] );
2566 if ( p->spectating && p->spectator == entityNumber ) {
2576 for ( int i = 0; i < MAX_WEAPONS; i++ ) {
2577 const char *weapnum = va( "def_weapon%d", i );
2578 const char *hudWeap = va( "weapon%d", i );
2580 if ( inventory.weapons & ( 1 << i ) ) {
2581 const char *weap = spawnArgs.GetString( weapnum );
2582 if ( weap && *weap ) {
2585 if ( idealWeapon == i ) {
2589 hud->SetStateInt( hudWeap, weapstate );
2591 if ( flashWeapon ) {
2592 hud->HandleNamedEvent( "weaponChange" );
2601 void idPlayer::DrawHUD( idUserInterface *_hud ) {
2603 if ( !weapon.GetEntity() || influenceActive != INFLUENCE_NONE || privateCameraView || gameLocal.GetCamera() || !_hud || !g_showHud.GetBool() ) {
2607 UpdateHudStats( _hud );
2609 _hud->SetStateString( "weapicon", weapon.GetEntity()->Icon() );
2611 // FIXME: this is temp to allow the sound meter to show up in the hud
2612 // it should be commented out before shipping but the code can remain
2613 // for mod developers to enable for the same functionality
2614 _hud->SetStateInt( "s_debug", cvarSystem->GetCVarInteger( "s_showLevelMeter" ) );
2616 weapon.GetEntity()->UpdateGUI();
2618 _hud->Redraw( gameLocal.realClientTime );
2620 // weapon targeting crosshair
2621 if ( !GuiActive() ) {
2622 if ( cursor && weapon.GetEntity()->ShowCrosshair() ) {
2623 cursor->Redraw( gameLocal.realClientTime );
2630 idPlayer::EnterCinematic
2633 void idPlayer::EnterCinematic( void ) {
2636 StopSound( SND_CHANNEL_PDA, false );
2638 hud->HandleNamedEvent( "radioChatterDown" );
2641 physicsObj.SetLinearVelocity( vec3_origin );
2643 SetState( "EnterCinematic" );
2646 if ( weaponEnabled && weapon.GetEntity() ) {
2647 weapon.GetEntity()->EnterCinematic();
2651 AI_BACKWARD = false;
2652 AI_STRAFE_LEFT = false;
2653 AI_STRAFE_RIGHT = false;
2655 AI_ATTACK_HELD = false;
2656 AI_WEAPON_FIRED = false;
2660 AI_ONLADDER = false;
2661 AI_DEAD = ( health <= 0 );
2664 AI_HARDLANDING = false;
2665 AI_SOFTLANDING = false;
2667 AI_TELEPORT = false;
2668 AI_TURN_LEFT = false;
2669 AI_TURN_RIGHT = false;
2674 idPlayer::ExitCinematic
2677 void idPlayer::ExitCinematic( void ) {
2680 if ( weaponEnabled && weapon.GetEntity() ) {
2681 weapon.GetEntity()->ExitCinematic();
2684 SetState( "ExitCinematic" );
2689 =====================
2690 idPlayer::UpdateConditions
2691 =====================
2693 void idPlayer::UpdateConditions( void ) {
2699 // minus the push velocity to avoid playing the walking animation and sounds when riding a mover
2700 velocity = physicsObj.GetLinearVelocity() - physicsObj.GetPushedLinearVelocity();
2701 fallspeed = velocity * physicsObj.GetGravityNormal();
2703 if ( influenceActive ) {
2705 AI_BACKWARD = false;
2706 AI_STRAFE_LEFT = false;
2707 AI_STRAFE_RIGHT = false;
2708 } else if ( gameLocal.time - lastDmgTime < 500 ) {
2709 forwardspeed = velocity * viewAxis[ 0 ];
2710 sidespeed = velocity * viewAxis[ 1 ];
2711 AI_FORWARD = AI_ONGROUND && ( forwardspeed > 20.01f );
2712 AI_BACKWARD = AI_ONGROUND && ( forwardspeed < -20.01f );
2713 AI_STRAFE_LEFT = AI_ONGROUND && ( sidespeed > 20.01f );
2714 AI_STRAFE_RIGHT = AI_ONGROUND && ( sidespeed < -20.01f );
2715 } else if ( xyspeed > MIN_BOB_SPEED ) {
2716 AI_FORWARD = AI_ONGROUND && ( usercmd.forwardmove > 0 );
2717 AI_BACKWARD = AI_ONGROUND && ( usercmd.forwardmove < 0 );
2718 AI_STRAFE_LEFT = AI_ONGROUND && ( usercmd.rightmove < 0 );
2719 AI_STRAFE_RIGHT = AI_ONGROUND && ( usercmd.rightmove > 0 );
2722 AI_BACKWARD = false;
2723 AI_STRAFE_LEFT = false;
2724 AI_STRAFE_RIGHT = false;
2727 AI_RUN = ( usercmd.buttons & BUTTON_RUN ) && ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) );
2728 AI_DEAD = ( health <= 0 );
2735 Called when a weapon fires, generates head twitches, etc
2738 void idPlayer::WeaponFireFeedback( const idDict *weaponDef ) {
2742 // play the fire animation
2743 AI_WEAPON_FIRED = true;
2745 // update view feedback
2746 playerView.WeaponFireFeedback( weaponDef );
2751 idPlayer::StopFiring
2754 void idPlayer::StopFiring( void ) {
2755 AI_ATTACK_HELD = false;
2756 AI_WEAPON_FIRED = false;
2758 if ( weapon.GetEntity() ) {
2759 weapon.GetEntity()->EndAttack();
2765 idPlayer::FireWeapon
2768 void idPlayer::FireWeapon( void ) {
2772 if ( privateCameraView ) {
2776 if ( g_editEntityMode.GetInteger() ) {
2777 GetViewPos( muzzle, axis );
2778 if ( gameLocal.editEntities->SelectEntity( muzzle, axis[0], this ) ) {
2783 if ( !hiddenWeapon && weapon.GetEntity()->IsReady() ) {
2784 if ( weapon.GetEntity()->AmmoInClip() || weapon.GetEntity()->AmmoAvailable() ) {
2785 AI_ATTACK_HELD = true;
2786 weapon.GetEntity()->BeginAttack();
2787 if ( ( weapon_soulcube >= 0 ) && ( currentWeapon == weapon_soulcube ) ) {
2789 hud->HandleNamedEvent( "soulCubeNotReady" );
2791 SelectWeapon( previousWeapon, false );
2802 // may want to track with with a bool as well
2803 // keep from looking up named events so often
2804 if ( objectiveUp ) {
2812 idPlayer::CacheWeapons
2815 void idPlayer::CacheWeapons( void ) {
2819 // check if we have any weapons
2820 if ( !inventory.weapons ) {
2824 for( w = 0; w < MAX_WEAPONS; w++ ) {
2825 if ( inventory.weapons & ( 1 << w ) ) {
2826 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
2828 idWeapon::CacheWeapon( weap );
2830 inventory.weapons &= ~( 1 << w );
2841 bool idPlayer::Give( const char *statname, const char *value ) {
2848 if ( !idStr::Icmp( statname, "health" ) ) {
2849 if ( health >= inventory.maxHealth ) {
2852 amount = atoi( value );
2855 if ( health > inventory.maxHealth ) {
2856 health = inventory.maxHealth;
2859 hud->HandleNamedEvent( "healthPulse" );
2863 } else if ( !idStr::Icmp( statname, "stamina" ) ) {
2864 if ( stamina >= 100 ) {
2867 stamina += atof( value );
2868 if ( stamina > 100 ) {
2872 } else if ( !idStr::Icmp( statname, "heartRate" ) ) {
2873 heartRate += atoi( value );
2874 if ( heartRate > MAX_HEARTRATE ) {
2875 heartRate = MAX_HEARTRATE;
2878 } else if ( !idStr::Icmp( statname, "air" ) ) {
2879 if ( airTics >= pm_airTics.GetInteger() ) {
2882 airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger();
2883 if ( airTics > pm_airTics.GetInteger() ) {
2884 airTics = pm_airTics.GetInteger();
2887 return inventory.Give( this, spawnArgs, statname, value, &idealWeapon, true );
2895 idPlayer::GiveHealthPool
2897 adds health to the player health pool
2900 void idPlayer::GiveHealthPool( float amt ) {
2908 if ( healthPool > inventory.maxHealth - health ) {
2909 healthPool = inventory.maxHealth - health;
2911 nextHealthPulse = gameLocal.time;
2919 Returns false if the item shouldn't be picked up
2922 bool idPlayer::GiveItem( idItem *item ) {
2924 const idKeyValue *arg;
2929 if ( gameLocal.isMultiplayer && spectating ) {
2933 item->GetAttributes( attr );
2936 numPickup = inventory.pickupItemNames.Num();
2937 for( i = 0; i < attr.GetNumKeyVals(); i++ ) {
2938 arg = attr.GetKeyVal( i );
2939 if ( Give( arg->GetKey(), arg->GetValue() ) ) {
2944 arg = item->spawnArgs.MatchPrefix( "inv_weapon", NULL );
2946 // We need to update the weapon hud manually, but not
2947 // the armor/ammo/health because they are updated every
2948 // frame no matter what
2949 UpdateHudWeapon( false );
2950 hud->HandleNamedEvent( "weaponPulse" );
2953 // display the pickup feedback on the hud
2954 if ( gave && ( numPickup == inventory.pickupItemNames.Num() ) ) {
2955 inventory.AddPickupName( item->spawnArgs.GetString( "inv_name" ), item->spawnArgs.GetString( "inv_icon" ) );
2963 idPlayer::PowerUpModifier
2966 float idPlayer::PowerUpModifier( int type ) {
2969 if ( PowerUpActive( BERSERK ) ) {
2975 case PROJECTILE_DAMAGE: {
2979 case MELEE_DAMAGE: {
2983 case MELEE_DISTANCE: {
2990 if ( gameLocal.isMultiplayer && !gameLocal.isClient ) {
2991 if ( PowerUpActive( MEGAHEALTH ) ) {
2992 if ( healthPool <= 0 ) {
2993 GiveHealthPool( 100 );
3005 idPlayer::PowerUpActive
3008 bool idPlayer::PowerUpActive( int powerup ) const {
3009 return ( inventory.powerups & ( 1 << powerup ) ) != 0;
3014 idPlayer::GivePowerUp
3017 bool idPlayer::GivePowerUp( int powerup, int time ) {
3021 if ( powerup >= 0 && powerup < MAX_POWERUPS ) {
3023 if ( gameLocal.isServer ) {
3025 byte msgBuf[MAX_EVENT_PARAM_SIZE];
3027 msg.Init( msgBuf, sizeof( msgBuf ) );
3028 msg.WriteShort( powerup );
3029 msg.WriteBits( 1, 1 );
3030 ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3033 if ( powerup != MEGAHEALTH ) {
3034 inventory.GivePowerUp( this, powerup, time );
3037 const idDeclEntityDef *def = NULL;
3041 if ( spawnArgs.GetString( "snd_berserk_third", "", &sound ) ) {
3042 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_DEMONIC, 0, false, NULL );
3044 if ( baseSkinName.Length() ) {
3045 powerUpSkin = declManager->FindSkin( baseSkinName + "_berserk" );
3047 if ( !gameLocal.isClient ) {
3052 case INVISIBILITY: {
3053 spawnArgs.GetString( "skin_invisibility", "", &skin );
3054 powerUpSkin = declManager->FindSkin( skin );
3055 // remove any decals from the model
3056 if ( modelDefHandle != -1 ) {
3057 gameRenderWorld->RemoveDecals( modelDefHandle );
3059 if ( weapon.GetEntity() ) {
3060 weapon.GetEntity()->UpdateSkin();
3062 if ( spawnArgs.GetString( "snd_invisibility", "", &sound ) ) {
3063 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3072 if ( spawnArgs.GetString( "snd_megahealth", "", &sound ) ) {
3073 StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_ANY, 0, false, NULL );
3075 def = gameLocal.FindEntityDef( "powerup_megahealth", false );
3077 health = def->dict.GetInt( "inv_health" );
3084 hud->HandleNamedEvent( "itemPickup" );
3089 gameLocal.Warning( "Player given power up %i\n which is out of range", powerup );
3096 idPlayer::ClearPowerup
3099 void idPlayer::ClearPowerup( int i ) {
3101 if ( gameLocal.isServer ) {
3103 byte msgBuf[MAX_EVENT_PARAM_SIZE];
3105 msg.Init( msgBuf, sizeof( msgBuf ) );
3106 msg.WriteShort( i );
3107 msg.WriteBits( 0, 1 );
3108 ServerSendEvent( EVENT_POWERUP, &msg, false, -1 );
3112 inventory.powerups &= ~( 1 << i );
3113 inventory.powerupEndTime[ i ] = 0;
3116 StopSound( SND_CHANNEL_DEMONIC, false );
3119 case INVISIBILITY: {
3120 if ( weapon.GetEntity() ) {
3121 weapon.GetEntity()->UpdateSkin();
3130 idPlayer::UpdatePowerUps
3133 void idPlayer::UpdatePowerUps( void ) {
3136 if ( !gameLocal.isClient ) {
3137 for ( i = 0; i < MAX_POWERUPS; i++ ) {
3138 if ( PowerUpActive( i ) && inventory.powerupEndTime[i] <= gameLocal.time ) {
3145 if ( powerUpSkin ) {
3146 renderEntity.customSkin = powerUpSkin;
3148 renderEntity.customSkin = skin;
3152 if ( healthPool && gameLocal.time > nextHealthPulse && !AI_DEAD && health > 0 ) {
3153 assert( !gameLocal.isClient ); // healthPool never be set on client
3154 int amt = ( healthPool > 5 ) ? 5 : healthPool;
3156 if ( health > inventory.maxHealth ) {
3157 health = inventory.maxHealth;
3162 nextHealthPulse = gameLocal.time + HEALTHPULSE_TIME;
3165 #ifndef ID_DEMO_BUILD
3166 if ( !gameLocal.inCinematic && influenceActive == 0 && g_skill.GetInteger() == 3 && gameLocal.time > nextHealthTake && !AI_DEAD && health > g_healthTakeLimit.GetInteger() ) {
3167 assert( !gameLocal.isClient ); // healthPool never be set on client
3168 health -= g_healthTakeAmt.GetInteger();
3169 if ( health < g_healthTakeLimit.GetInteger() ) {
3170 health = g_healthTakeLimit.GetInteger();
3172 nextHealthTake = gameLocal.time + g_healthTakeTime.GetInteger() * 1000;
3180 idPlayer::ClearPowerUps
3183 void idPlayer::ClearPowerUps( void ) {
3185 for ( i = 0; i < MAX_POWERUPS; i++ ) {
3186 if ( PowerUpActive( i ) ) {
3190 inventory.ClearPowerUps();
3195 idPlayer::GiveInventoryItem
3198 bool idPlayer::GiveInventoryItem( idDict *item ) {
3199 if ( gameLocal.isMultiplayer && spectating ) {
3202 inventory.items.Append( new idDict( *item ) );
3204 const char* itemName = item->GetString( "inv_name" );
3205 if ( idStr::Cmpn( itemName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) {
3206 info.name = common->GetLanguageDict()->GetString( itemName );
3208 info.name = itemName;
3210 info.icon = item->GetString( "inv_icon" );
3211 inventory.pickupItemNames.Append( info );
3213 hud->SetStateString( "itemicon", info.icon );
3214 hud->HandleNamedEvent( "invPickup" );
3221 idPlayer::UpdateObjectiveInfo
3224 void idPlayer::UpdateObjectiveInfo( void ) {
3225 if ( objectiveSystem == NULL ) {
3228 objectiveSystem->SetStateString( "objective1", "" );
3229 objectiveSystem->SetStateString( "objective2", "" );
3230 objectiveSystem->SetStateString( "objective3", "" );
3231 for ( int i = 0; i < inventory.objectiveNames.Num(); i++ ) {
3232 objectiveSystem->SetStateString( va( "objective%i", i+1 ), "1" );
3233 objectiveSystem->SetStateString( va( "objectivetitle%i", i+1 ), inventory.objectiveNames[i].title.c_str() );
3234 objectiveSystem->SetStateString( va( "objectivetext%i", i+1 ), inventory.objectiveNames[i].text.c_str() );
3235 objectiveSystem->SetStateString( va( "objectiveshot%i", i+1 ), inventory.objectiveNames[i].screenshot.c_str() );
3237 objectiveSystem->StateChanged( gameLocal.time );
3242 idPlayer::GiveObjective
3245 void idPlayer::GiveObjective( const char *title, const char *text, const char *screenshot ) {
3246 idObjectiveInfo info;
3249 info.screenshot = screenshot;
3250 inventory.objectiveNames.Append( info );
3251 ShowObjective( "newObjective" );
3253 hud->HandleNamedEvent( "newObjective" );
3259 idPlayer::CompleteObjective
3262 void idPlayer::CompleteObjective( const char *title ) {
3263 int c = inventory.objectiveNames.Num();
3264 for ( int i = 0; i < c; i++ ) {
3265 if ( idStr::Icmp(inventory.objectiveNames[i].title, title) == 0 ) {
3266 inventory.objectiveNames.RemoveIndex( i );
3270 ShowObjective( "newObjectiveComplete" );
3273 hud->HandleNamedEvent( "newObjectiveComplete" );
3282 void idPlayer::GiveVideo( const char *videoName, idDict *item ) {
3284 if ( videoName == NULL || *videoName == NULL ) {
3288 inventory.videos.AddUnique( videoName );
3292 info.name = item->GetString( "inv_name" );
3293 info.icon = item->GetString( "inv_icon" );
3294 inventory.pickupItemNames.Append( info );
3297 hud->HandleNamedEvent( "videoPickup" );
3303 idPlayer::GiveSecurity
3306 void idPlayer::GiveSecurity( const char *security ) {
3307 GetPDA()->SetSecurity( security );
3309 hud->SetStateString( "pda_security", "1" );
3310 hud->HandleNamedEvent( "securityPickup" );
3319 void idPlayer::GiveEmail( const char *emailName ) {
3321 if ( emailName == NULL || *emailName == NULL ) {
3325 inventory.emails.AddUnique( emailName );
3326 GetPDA()->AddEmail( emailName );
3329 hud->HandleNamedEvent( "emailPickup" );
3338 void idPlayer::GivePDA( const char *pdaName, idDict *item )
3340 if ( gameLocal.isMultiplayer && spectating ) {
3345 inventory.pdaSecurity.AddUnique( item->GetString( "inv_name" ) );
3348 if ( pdaName == NULL || *pdaName == NULL ) {
3349 pdaName = "personal";
3352 const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, pdaName ) );
3354 inventory.pdas.AddUnique( pdaName );
3356 // Copy any videos over
3357 for ( int i = 0; i < pda->GetNumVideos(); i++ ) {
3358 const idDeclVideo *video = pda->GetVideoByIndex( i );
3360 inventory.videos.AddUnique( video->GetName() );
3364 // This is kind of a hack, but it works nicely
3365 // We don't want to display the 'you got a new pda' message during a map load
3366 if ( gameLocal.GetFrameNum() > 10 ) {
3368 idStr pdaName = pda->GetPdaName();
3369 pdaName.RemoveColors();
3370 hud->SetStateString( "pda", "1" );
3371 hud->SetStateString( "pda_text", pdaName );
3372 const char *sec = pda->GetSecurity();
3373 hud->SetStateString( "pda_security", ( sec && *sec ) ? "1" : "0" );
3374 hud->HandleNamedEvent( "pdaPickup" );
3377 if ( inventory.pdas.Num() == 1 ) {
3378 GetPDA()->RemoveAddedEmailsAndVideos();
3379 if ( !objectiveSystemOpen ) {
3382 objectiveSystem->HandleNamedEvent( "showPDATip" );
3383 //ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_firstPDA" ), true );
3386 if ( inventory.pdas.Num() > 1 && pda->GetNumVideos() > 0 && hud ) {
3387 hud->HandleNamedEvent( "videoPickup" );
3394 idPlayer::FindInventoryItem
3397 idDict *idPlayer::FindInventoryItem( const char *name ) {
3398 for ( int i = 0; i < inventory.items.Num(); i++ ) {
3399 const char *iname = inventory.items[i]->GetString( "inv_name" );
3400 if ( iname && *iname ) {
3401 if ( idStr::Icmp( name, iname ) == 0 ) {
3402 return inventory.items[i];
3411 idPlayer::RemoveInventoryItem
3414 void idPlayer::RemoveInventoryItem( const char *name ) {
3415 idDict *item = FindInventoryItem(name);
3417 RemoveInventoryItem( item );
3423 idPlayer::RemoveInventoryItem
3426 void idPlayer::RemoveInventoryItem( idDict *item ) {
3427 inventory.items.Remove( item );
3436 void idPlayer::GiveItem( const char *itemname ) {
3439 args.Set( "classname", itemname );
3440 args.Set( "owner", name.c_str() );
3441 gameLocal.SpawnEntityDef( args );
3443 hud->HandleNamedEvent( "itemPickup" );
3449 idPlayer::SlotForWeapon
3452 int idPlayer::SlotForWeapon( const char *weaponName ) {
3455 for( i = 0; i < MAX_WEAPONS; i++ ) {
3456 const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
3457 if ( !idStr::Cmp( weap, weaponName ) ) {
3471 void idPlayer::Reload( void ) {
3472 if ( gameLocal.isClient ) {
3476 if ( spectating || gameLocal.inCinematic || influenceActive ) {
3480 if ( weapon.GetEntity() && weapon.GetEntity()->IsLinked() ) {
3481 weapon.GetEntity()->Reload();
3487 idPlayer::NextBestWeapon
3490 void idPlayer::NextBestWeapon( void ) {
3492 int w = MAX_WEAPONS;
3494 if ( gameLocal.isClient || !weaponEnabled ) {
3500 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3501 if ( !weap[ 0 ] || ( ( inventory.weapons & ( 1 << w ) ) == 0 ) || ( !inventory.HasAmmo( weap ) ) ) {
3504 if ( !spawnArgs.GetBool( va( "weapon%d_best", w ) ) ) {
3510 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3516 idPlayer::NextWeapon
3519 void idPlayer::NextWeapon( void ) {
3523 if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3527 if ( gameLocal.isClient ) {
3531 // check if we have any weapons
3532 if ( !inventory.weapons ) {
3539 if ( w >= MAX_WEAPONS ) {
3542 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3543 if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3549 if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3552 if ( inventory.HasAmmo( weap ) ) {
3557 if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3559 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3566 idPlayer::PrevWeapon
3569 void idPlayer::PrevWeapon( void ) {
3573 if ( !weaponEnabled || spectating || hiddenWeapon || gameLocal.inCinematic || gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) || health < 0 ) {
3577 if ( gameLocal.isClient ) {
3581 // check if we have any weapons
3582 if ( !inventory.weapons ) {
3590 w = MAX_WEAPONS - 1;
3592 weap = spawnArgs.GetString( va( "def_weapon%d", w ) );
3593 if ( !spawnArgs.GetBool( va( "weapon%d_cycle", w ) ) ) {
3599 if ( ( inventory.weapons & ( 1 << w ) ) == 0 ) {
3602 if ( inventory.HasAmmo( weap ) ) {
3607 if ( ( w != currentWeapon ) && ( w != idealWeapon ) ) {
3609 weaponSwitchTime = gameLocal.time + WEAPON_SWITCH_DELAY;
3616 idPlayer::SelectWeapon
3619 void idPlayer::SelectWeapon( int num, bool force ) {
3622 if ( !weaponEnabled || spectating || gameLocal.inCinematic || health < 0 ) {
3626 if ( ( num < 0 ) || ( num >= MAX_WEAPONS ) ) {
3630 if ( gameLocal.isClient ) {
3634 if ( ( num != weapon_pda ) && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
3637 if ( hiddenWeapon && weapon.GetEntity() ) {
3638 weapon.GetEntity()->LowerWeapon();
3640 weapon.GetEntity()->RaiseWeapon();
3644 weap = spawnArgs.GetString( va( "def_weapon%d", num ) );
3646 gameLocal.Printf( "Invalid weapon\n" );
3650 if ( force || ( inventory.weapons & ( 1 << num ) ) ) {
3651 if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", num ) ) ) {
3654 if ( ( previousWeapon >= 0 ) && ( idealWeapon == num ) && ( spawnArgs.GetBool( va( "weapon%d_toggle", num ) ) ) ) {
3655 weap = spawnArgs.GetString( va( "def_weapon%d", previousWeapon ) );
3656 if ( !inventory.HasAmmo( weap ) && !spawnArgs.GetBool( va( "weapon%d_allowempty", previousWeapon ) ) ) {
3659 idealWeapon = previousWeapon;
3660 } else if ( ( weapon_pda >= 0 ) && ( num == weapon_pda ) && ( inventory.pdas.Num() == 0 ) ) {
3661 ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
3672 idPlayer::DropWeapon
3675 void idPlayer::DropWeapon( bool died ) {
3677 int inclip, ammoavailable;
3679 assert( !gameLocal.isClient );
3681 if ( spectating || weaponGone || weapon.GetEntity() == NULL ) {
3685 if ( ( !died && !weapon.GetEntity()->IsReady() ) || weapon.GetEntity()->IsReloading() ) {
3688 // ammoavailable is how many shots we can fire
3689 // inclip is which amount is in clip right now
3690 ammoavailable = weapon.GetEntity()->AmmoAvailable();
3691 inclip = weapon.GetEntity()->AmmoInClip();
3693 // don't drop a grenade if we have none left
3694 if ( !idStr::Icmp( idWeapon::GetAmmoNameForNum( weapon.GetEntity()->GetAmmoType() ), "ammo_grenades" ) && ( ammoavailable - inclip <= 0 ) ) {
3698 // expect an ammo setup that makes sense before doing any dropping
3699 // ammoavailable is -1 for infinite ammo, and weapons like chainsaw
3700 // a bad ammo config usually indicates a bad weapon state, so we should not drop
3701 // used to be an assertion check, but it still happens in edge cases
3702 if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3703 common->DPrintf( "idPlayer::DropWeapon: bad ammo setup\n" );
3706 idEntity *item = NULL;
3708 // ain't gonna throw you no weapon if I'm dead
3709 item = weapon.GetEntity()->DropItem( vec3_origin, 0, WEAPON_DROP_TIME, died );
3711 viewAngles.ToVectors( &forward, NULL, &up );
3712 item = weapon.GetEntity()->DropItem( 250.0f * forward + 150.0f * up, 500, WEAPON_DROP_TIME, died );
3717 // set the appropriate ammo in the dropped object
3718 const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" );
3720 item->spawnArgs.SetInt( keyval->GetKey(), ammoavailable );
3721 idStr inclipKey = keyval->GetKey();
3722 inclipKey.Insert( "inclip_", 4 );
3723 item->spawnArgs.SetInt( inclipKey, inclip );
3726 // remove from our local inventory completely
3727 inventory.Drop( spawnArgs, item->spawnArgs.GetString( "inv_weapon" ), -1 );
3728 weapon.GetEntity()->ResetAmmoClip();
3730 weapon.GetEntity()->WeaponStolen();
3737 idPlayer::StealWeapon
3738 steal the target player's current weapon
3741 void idPlayer::StealWeapon( idPlayer *player ) {
3742 assert( !gameLocal.isClient );
3744 // make sure there's something to steal
3745 idWeapon *player_weapon = static_cast< idWeapon * >( player->weapon.GetEntity() );
3746 if ( !player_weapon || !player_weapon->CanDrop() || weaponGone ) {
3749 // steal - we need to effectively force the other player to abandon his weapon
3750 int newweap = player->currentWeapon;
3751 if ( newweap == -1 ) {
3754 // might be just dropped - check inventory
3755 if ( ! ( player->inventory.weapons & ( 1 << newweap ) ) ) {
3758 const char *weapon_classname = spawnArgs.GetString( va( "def_weapon%d", newweap ) );
3759 assert( weapon_classname );
3760 int ammoavailable = player->weapon.GetEntity()->AmmoAvailable();
3761 int inclip = player->weapon.GetEntity()->AmmoInClip();
3762 if ( ( ammoavailable != -1 ) && ( ammoavailable - inclip < 0 ) ) {
3764 common->DPrintf( "idPlayer::StealWeapon: bad ammo setup\n" );
3765 // we still steal the weapon, so let's use the default ammo levels
3767 const idDeclEntityDef *decl = gameLocal.FindEntityDef( weapon_classname );
3769 const idKeyValue *keypair = decl->dict.MatchPrefix( "inv_ammo_" );
3771 ammoavailable = atoi( keypair->GetValue() );
3774 player->weapon.GetEntity()->WeaponStolen();
3775 player->inventory.Drop( player->spawnArgs, NULL, newweap );
3776 player->SelectWeapon( weapon_fists, false );
3777 // in case the robbed player is firing rounds with a continuous fire weapon like the chaingun/plasma etc.
3778 // this will ensure the firing actually stops
3779 player->weaponGone = true;
3781 // give weapon, setup the ammo count
3782 Give( "weapon", weapon_classname );
3783 ammo_t ammo_i = player->inventory.AmmoIndexForWeaponClass( weapon_classname, NULL );
3784 idealWeapon = newweap;
3785 inventory.ammo[ ammo_i ] += ammoavailable;
3786 inventory.clip[ newweap ] = inclip;
3794 idUserInterface *idPlayer::ActiveGui( void ) {
3795 if ( objectiveSystemOpen ) {
3796 return objectiveSystem;
3804 idPlayer::Weapon_Combat
3807 void idPlayer::Weapon_Combat( void ) {
3808 if ( influenceActive || !weaponEnabled || gameLocal.inCinematic || privateCameraView ) {
3812 weapon.GetEntity()->RaiseWeapon();
3813 if ( weapon.GetEntity()->IsReloading() ) {
3816 SetState( "ReloadWeapon" );
3823 if ( idealWeapon == weapon_soulcube && soulCubeProjectile.GetEntity() != NULL ) {
3824 idealWeapon = currentWeapon;
3827 if ( idealWeapon != currentWeapon ) {
3828 if ( weaponCatchup ) {
3829 assert( gameLocal.isClient );
3831 currentWeapon = idealWeapon;
3833 animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3834 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3835 animPrefix.Strip( "weapon_" );
3837 weapon.GetEntity()->NetCatchup();
3838 const function_t *newstate = GetScriptFunction( "NetCatchup" );
3840 SetState( newstate );
3843 weaponCatchup = false;
3845 if ( weapon.GetEntity()->IsReady() ) {
3846 weapon.GetEntity()->PutAway();
3849 if ( weapon.GetEntity()->IsHolstered() ) {
3850 assert( idealWeapon >= 0 );
3851 assert( idealWeapon < MAX_WEAPONS );
3853 if ( currentWeapon != weapon_pda && !spawnArgs.GetBool( va( "weapon%d_toggle", currentWeapon ) ) ) {
3854 previousWeapon = currentWeapon;
3856 currentWeapon = idealWeapon;
3858 animPrefix = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
3859 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ currentWeapon ] );
3860 animPrefix.Strip( "weapon_" );
3862 weapon.GetEntity()->Raise();
3866 weaponGone = false; // if you drop and re-get weap, you may miss the = false above
3867 if ( weapon.GetEntity()->IsHolstered() ) {
3868 if ( !weapon.GetEntity()->AmmoAvailable() ) {
3869 // weapons can switch automatically if they have no more ammo
3872 weapon.GetEntity()->Raise();
3873 state = GetScriptFunction( "RaiseWeapon" );
3882 AI_WEAPON_FIRED = false;
3883 if ( !influenceActive ) {
3884 if ( ( usercmd.buttons & BUTTON_ATTACK ) && !weaponGone ) {
3886 } else if ( oldButtons & BUTTON_ATTACK ) {
3887 AI_ATTACK_HELD = false;
3888 weapon.GetEntity()->EndAttack();
3892 // update our ammo clip in our inventory
3893 if ( ( currentWeapon >= 0 ) && ( currentWeapon < MAX_WEAPONS ) ) {
3894 inventory.clip[ currentWeapon ] = weapon.GetEntity()->AmmoInClip();
3895 if ( hud && ( currentWeapon == idealWeapon ) ) {
3896 UpdateHudAmmo( hud );
3903 idPlayer::Weapon_NPC
3906 void idPlayer::Weapon_NPC( void ) {
3907 if ( idealWeapon != currentWeapon ) {
3911 weapon.GetEntity()->LowerWeapon();
3913 if ( ( usercmd.buttons & BUTTON_ATTACK ) && !( oldButtons & BUTTON_ATTACK ) ) {
3914 buttonMask |= BUTTON_ATTACK;
3915 focusCharacter->TalkTo( this );
3921 idPlayer::LowerWeapon
3924 void idPlayer::LowerWeapon( void ) {
3925 if ( weapon.GetEntity() && !weapon.GetEntity()->IsHidden() ) {
3926 weapon.GetEntity()->LowerWeapon();
3932 idPlayer::RaiseWeapon
3935 void idPlayer::RaiseWeapon( void ) {
3936 if ( weapon.GetEntity() && weapon.GetEntity()->IsHidden() ) {
3937 weapon.GetEntity()->RaiseWeapon();
3943 idPlayer::WeaponLoweringCallback
3946 void idPlayer::WeaponLoweringCallback( void ) {
3947 SetState( "LowerWeapon" );
3953 idPlayer::WeaponRisingCallback
3956 void idPlayer::WeaponRisingCallback( void ) {
3957 SetState( "RaiseWeapon" );
3963 idPlayer::Weapon_GUI
3966 void idPlayer::Weapon_GUI( void ) {
3968 if ( !objectiveSystemOpen ) {
3969 if ( idealWeapon != currentWeapon ) {
3973 weapon.GetEntity()->LowerWeapon();
3976 // disable click prediction for the GUIs. handy to check the state sync does the right thing
3977 if ( gameLocal.isClient && !net_clientPredictGUI.GetBool() ) {
3981 if ( ( oldButtons ^ usercmd.buttons ) & BUTTON_ATTACK ) {
3983 const char *command = NULL;
3984 bool updateVisuals = false;
3986 idUserInterface *ui = ActiveGui();
3988 ev = sys->GenerateMouseButtonEvent( 1, ( usercmd.buttons & BUTTON_ATTACK ) != 0 );
3989 command = ui->HandleEvent( &ev, gameLocal.time, &updateVisuals );
3990 if ( updateVisuals && focusGUIent && ui == focusUI ) {
3991 focusGUIent->UpdateVisuals();
3994 if ( gameLocal.isClient ) {
3995 // we predict enough, but don't want to execute commands
3998 if ( focusGUIent ) {
3999 HandleGuiCommands( focusGUIent, command );
4001 HandleGuiCommands( this, command );
4008 idPlayer::UpdateWeapon
4011 void idPlayer::UpdateWeapon( void ) {
4012 if ( health <= 0 ) {
4016 assert( !spectating );
4018 if ( gameLocal.isClient ) {
4019 // clients need to wait till the weapon and it's world model entity
4020 // are present and synchronized ( weapon.worldModel idEntityPtr to idAnimatedEntity )
4021 if ( !weapon.GetEntity()->IsWorldModelReady() ) {
4026 // always make sure the weapon is correctly setup before accessing it
4027 if ( !weapon.GetEntity()->IsLinked() ) {
4028 if ( idealWeapon != -1 ) {
4029 animPrefix = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
4030 weapon.GetEntity()->GetWeaponDef( animPrefix, inventory.clip[ idealWeapon ] );
4031 assert( weapon.GetEntity()->IsLinked() );
4037 if ( hiddenWeapon && tipUp && usercmd.buttons & BUTTON_ATTACK ) {
4041 if ( g_dragEntity.GetBool() ) {
4043 weapon.GetEntity()->LowerWeapon();
4044 dragEntity.Update( this );
4045 } else if ( ActiveGui() ) {
4046 // gui handling overrides weapon use
4048 } else if ( focusCharacter && ( focusCharacter->health > 0 ) ) {
4054 if ( hiddenWeapon ) {
4055 weapon.GetEntity()->LowerWeapon();
4058 // update weapon state, particles, dlights, etc
4059 weapon.GetEntity()->PresentWeapon( showWeaponViewModel );
4064 idPlayer::SpectateFreeFly
4067 void idPlayer::SpectateFreeFly( bool force ) {
4070 idVec3 spawn_origin;
4071 idAngles spawn_angles;
4073 player = gameLocal.GetClientByNum( spectator );
4074 if ( force || gameLocal.time > lastSpectateChange ) {
4075 spectator = entityNumber;
4076 if ( player && player != this && !player->spectating && !player->IsInTeleport() ) {
4077 newOrig = player->GetPhysics()->GetOrigin();
4078 if ( player->physicsObj.IsCrouching() ) {
4079 newOrig[ 2 ] += pm_crouchviewheight.GetFloat();
4081 newOrig[ 2 ] += pm_normalviewheight.GetFloat();
4083 newOrig[ 2 ] += SPECTATE_RAISE;
4084 idBounds b = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
4085 idVec3 start = player->GetPhysics()->GetOrigin();
4086 start[2] += pm_spectatebbox.GetFloat() * 0.5f;
4088 // assuming spectate bbox is inside stand or crouch box
4089 gameLocal.clip.TraceBounds( t, start, newOrig, b, MASK_PLAYERSOLID, player );
4090 newOrig.Lerp( start, newOrig, t.fraction );
4091 SetOrigin( newOrig );
4092 idAngles angle = player->viewAngles;
4094 SetViewAngles( angle );
4096 SelectInitialSpawnPoint( spawn_origin, spawn_angles );
4097 spawn_origin[ 2 ] += pm_normalviewheight.GetFloat();
4098 spawn_origin[ 2 ] += SPECTATE_RAISE;
4099 SetOrigin( spawn_origin );
4100 SetViewAngles( spawn_angles );
4102 lastSpectateChange = gameLocal.time + 500;
4108 idPlayer::SpectateCycle
4111 void idPlayer::SpectateCycle( void ) {
4114 if ( gameLocal.time > lastSpectateChange ) {
4115 int latchedSpectator = spectator;
4116 spectator = gameLocal.GetNextClientNum( spectator );
4117 player = gameLocal.GetClientByNum( spectator );
4118 assert( player ); // never call here when the current spectator is wrong
4119 // ignore other spectators
4120 while ( latchedSpectator != spectator && player->spectating ) {
4121 spectator = gameLocal.GetNextClientNum( spectator );
4122 player = gameLocal.GetClientByNum( spectator );
4124 lastSpectateChange = gameLocal.time + 500;
4130 idPlayer::UpdateSpectating
4133 void idPlayer::UpdateSpectating( void ) {
4134 assert( spectating );
4135 assert( !gameLocal.isClient );
4136 assert( IsHidden() );
4138 if ( !gameLocal.isMultiplayer ) {
4141 player = gameLocal.GetClientByNum( spectator );
4142 if ( !player || ( player->spectating && player != this ) ) {
4143 SpectateFreeFly( true );
4144 } else if ( usercmd.upmove > 0 ) {
4145 SpectateFreeFly( false );
4146 } else if ( usercmd.buttons & BUTTON_ATTACK ) {
4153 idPlayer::HandleSingleGuiCommand
4156 bool idPlayer::HandleSingleGuiCommand( idEntity *entityGui, idLexer *src ) {
4159 if ( !src->ReadToken( &token ) ) {
4163 if ( token == ";" ) {
4167 if ( token.Icmp( "addhealth" ) == 0 ) {
4168 if ( entityGui && health < 100 ) {
4169 int _health = entityGui->spawnArgs.GetInt( "gui_parm1" );
4170 int amt = ( _health >= HEALTH_PER_DOSE ) ? HEALTH_PER_DOSE : _health;
4172 entityGui->spawnArgs.SetInt( "gui_parm1", _health );
4173 if ( entityGui->GetRenderEntity() && entityGui->GetRenderEntity()->gui[ 0 ] ) {
4174 entityGui->GetRenderEntity()->gui[ 0 ]->SetStateInt( "gui_parm1", _health );
4177 if ( health > 100 ) {
4184 if ( token.Icmp( "ready" ) == 0 ) {
4185 PerformImpulse( IMPULSE_17 );
4189 if ( token.Icmp( "updatepda" ) == 0 ) {
4190 UpdatePDAInfo( true );
4194 if ( token.Icmp( "updatepda2" ) == 0 ) {
4195 UpdatePDAInfo( false );
4199 if ( token.Icmp( "stoppdavideo" ) == 0 ) {
4200 if ( objectiveSystem && objectiveSystemOpen && pdaVideoWave.Length() > 0 ) {
4201 StopSound( SND_CHANNEL_PDA, false );
4206 if ( token.Icmp( "close" ) == 0 ) {
4207 if ( objectiveSystem && objectiveSystemOpen ) {
4212 if ( token.Icmp( "playpdavideo" ) == 0 ) {
4213 if ( objectiveSystem && objectiveSystemOpen && pdaVideo.Length() > 0 ) {
4214 const idMaterial *mat = declManager->FindMaterial( pdaVideo );
4216 int c = mat->GetNumStages();
4217 for ( int i = 0; i < c; i++ ) {
4218 const shaderStage_t *stage = mat->GetStage(i);
4219 if ( stage && stage->texture.cinematic ) {
4220 stage->texture.cinematic->ResetTime( gameLocal.time );
4223 if ( pdaVideoWave.Length() ) {
4224 const idSoundShader *shader = declManager->FindSound( pdaVideoWave );
4225 StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, NULL );
4231 if ( token.Icmp( "playpdaaudio" ) == 0 ) {
4232 if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4233 const idSoundShader *shader = declManager->FindSound( pdaAudio );
4235 StartSoundShader( shader, SND_CHANNEL_PDA, 0, false, &ms );
4237 CancelEvents( &EV_Player_StopAudioLog );
4238 PostEventMS( &EV_Player_StopAudioLog, ms + 150 );
4243 if ( token.Icmp( "stoppdaaudio" ) == 0 ) {
4244 if ( objectiveSystem && objectiveSystemOpen && pdaAudio.Length() > 0 ) {
4245 // idSoundShader *shader = declManager->FindSound( pdaAudio );
4247 StopSound( SND_CHANNEL_PDA, false );
4252 src->UnreadToken( &token );
4261 bool idPlayer::Collide( const trace_t &collision, const idVec3 &velocity ) {
4264 if ( gameLocal.isClient ) {
4268 other = gameLocal.entities[ collision.c.entityNum ];
4270 other->Signal( SIG_TOUCH );
4271 if ( !spectating ) {
4272 if ( other->RespondsTo( EV_Touch ) ) {
4273 other->ProcessEvent( &EV_Touch, this, &collision );
4276 if ( other->RespondsTo( EV_SpectatorTouch ) ) {
4277 other->ProcessEvent( &EV_SpectatorTouch, this, &collision );
4287 idPlayer::UpdateLocation
4289 Searches nearby locations
4292 void idPlayer::UpdateLocation( void ) {
4294 idLocationEntity *locationEntity = gameLocal.LocationForPoint( GetEyePosition() );
4295 if ( locationEntity ) {
4296 hud->SetStateString( "location", locationEntity->GetLocation() );
4298 hud->SetStateString( "location", common->GetLanguageDict()->GetString( "#str_02911" ) );
4305 idPlayer::ClearFocus
4307 Clears the focus cursor
4310 void idPlayer::ClearFocus( void ) {
4311 focusCharacter = NULL;
4314 focusVehicle = NULL;
4320 idPlayer::UpdateFocus
4322 Searches nearby entities for interactive guis, possibly making one of them
4323 the focus and sending it a mouse move event
4326 void idPlayer::UpdateFocus( void ) {
4327 idClipModel *clipModelList[ MAX_GENTITIES ];
4329 int listedClipModels;
4332 idUserInterface *oldUI;
4335 idAFEntity_Vehicle *oldVehicle;
4339 const char *command;
4342 const idKeyValue *kv;
4344 idUserInterface *ui;
4346 if ( gameLocal.inCinematic ) {
4350 // only update the focus character when attack button isn't pressed so players
4351 // can still chainsaw NPC's
4352 if ( gameLocal.isMultiplayer || ( !focusCharacter && ( usercmd.buttons & BUTTON_ATTACK ) ) ) {
4358 oldFocus = focusGUIent;
4360 oldChar = focusCharacter;
4361 oldTalkCursor = talkCursor;
4362 oldVehicle = focusVehicle;
4364 if ( focusTime <= gameLocal.time ) {
4368 // don't let spectators interact with GUIs
4373 start = GetEyePosition();
4374 end = start + viewAngles.ToForward() * 80.0f;
4376 // player identification -> names to the hud
4377 if ( gameLocal.isMultiplayer && entityNumber == gameLocal.localClientNum ) {
4378 idVec3 end = start + viewAngles.ToForward() * 768.0f;
4379 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_BOUNDINGBOX, this );
4381 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum < MAX_CLIENTS ) ) {
4382 iclient = trace.c.entityNum;
4384 if ( MPAim != iclient ) {
4387 lastMPAimTime = gameLocal.realClientTime;
4391 idBounds bounds( start );
4392 bounds.AddPoint( end );
4394 listedClipModels = gameLocal.clip.ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
4396 // no pretense at sorting here, just assume that there will only be one active
4397 // gui within range along the trace
4398 for ( i = 0; i < listedClipModels; i++ ) {
4399 clip = clipModelList[ i ];
4400 ent = clip->GetEntity();
4402 if ( ent->IsHidden() ) {
4407 if ( ent->IsType( idAFAttachment::Type ) ) {
4408 idEntity *body = static_cast<idAFAttachment *>( ent )->GetBody();
4409 if ( body && body->IsType( idAI::Type ) && ( static_cast<idAI *>( body )->GetTalkState() >= TALK_OK ) ) {
4410 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4411 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4413 focusCharacter = static_cast<idAI *>( body );
4415 focusTime = gameLocal.time + FOCUS_TIME;
4422 if ( ent->IsType( idAI::Type ) ) {
4423 if ( static_cast<idAI *>( ent )->GetTalkState() >= TALK_OK ) {
4424 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4425 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4427 focusCharacter = static_cast<idAI *>( ent );
4429 focusTime = gameLocal.time + FOCUS_TIME;
4436 if ( ent->IsType( idAFEntity_Vehicle::Type ) ) {
4437 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
4438 if ( ( trace.fraction < 1.0f ) && ( trace.c.entityNum == ent->entityNumber ) ) {
4440 focusVehicle = static_cast<idAFEntity_Vehicle *>( ent );
4441 focusTime = gameLocal.time + FOCUS_TIME;
4448 if ( !ent->GetRenderEntity() || !ent->GetRenderEntity()->gui[ 0 ] || !ent->GetRenderEntity()->gui[ 0 ]->IsInteractive() ) {
4452 if ( ent->spawnArgs.GetBool( "inv_item" ) ) {
4453 // don't allow guis on pickup items focus
4457 pt = gameRenderWorld->GuiTrace( ent->GetModelDefHandle(), start, end );
4460 renderEntity_t *focusGUIrenderEntity = ent->GetRenderEntity();
4461 if ( !focusGUIrenderEntity ) {
4465 if ( pt.guiId == 1 ) {
4466 ui = focusGUIrenderEntity->gui[ 0 ];
4467 } else if ( pt.guiId == 2 ) {
4468 ui = focusGUIrenderEntity->gui[ 1 ];
4470 ui = focusGUIrenderEntity->gui[ 2 ];
4481 if ( oldFocus != ent ) {
4483 // going to see if we have anything in inventory a gui might be interested in
4484 // need to enumerate inventory items
4485 focusUI->SetStateInt( "inv_count", inventory.items.Num() );
4486 for ( j = 0; j < inventory.items.Num(); j++ ) {
4487 idDict *item = inventory.items[ j ];
4488 const char *iname = item->GetString( "inv_name" );
4489 const char *iicon = item->GetString( "inv_icon" );
4490 const char *itext = item->GetString( "inv_text" );
4492 focusUI->SetStateString( va( "inv_name_%i", j), iname );
4493 focusUI->SetStateString( va( "inv_icon_%i", j), iicon );
4494 focusUI->SetStateString( va( "inv_text_%i", j), itext );
4495 kv = item->MatchPrefix("inv_id", NULL);
4497 focusUI->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
4499 focusUI->SetStateInt( iname, 1 );
4503 for( j = 0; j < inventory.pdaSecurity.Num(); j++ ) {
4504 const char *p = inventory.pdaSecurity[ j ];
4506 focusUI->SetStateInt( p, 1 );
4510 int staminapercentage = ( int )( 100.0f * stamina / pm_stamina.GetFloat() );
4511 focusUI->SetStateString( "player_health", va("%i", health ) );
4512 focusUI->SetStateString( "player_stamina", va( "%i%%", staminapercentage ) );
4513 focusUI->SetStateString( "player_armor", va( "%i%%", inventory.armor ) );
4515 kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", NULL );
4517 focusUI->SetStateString( kv->GetKey(), kv->GetValue() );
4518 kv = focusGUIent->spawnArgs.MatchPrefix( "gui_parm", kv );
4522 // clamp the mouse to the corner
4523 ev = sys->GenerateMouseMoveEvent( -2000, -2000 );
4524 command = focusUI->HandleEvent( &ev, gameLocal.time );
4525 HandleGuiCommands( focusGUIent, command );
4527 // move to an absolute position
4528 ev = sys->GenerateMouseMoveEvent( pt.x * SCREEN_WIDTH, pt.y * SCREEN_HEIGHT );
4529 command = focusUI->HandleEvent( &ev, gameLocal.time );
4530 HandleGuiCommands( focusGUIent, command );
4531 focusTime = gameLocal.time + FOCUS_GUI_TIME;
4536 if ( focusGUIent && focusUI ) {
4537 if ( !oldFocus || oldFocus != focusGUIent ) {
4538 command = focusUI->Activate( true, gameLocal.time );
4539 HandleGuiCommands( focusGUIent, command );
4540 StartSound( "snd_guienter", SND_CHANNEL_ANY, 0, false, NULL );
4544 } else if ( oldFocus && oldUI ) {
4545 command = oldUI->Activate( false, gameLocal.time );
4546 HandleGuiCommands( oldFocus, command );
4547 StartSound( "snd_guiexit", SND_CHANNEL_ANY, 0, false, NULL );
4550 if ( cursor && ( oldTalkCursor != talkCursor ) ) {
4551 cursor->SetStateInt( "talkcursor", talkCursor );
4554 if ( oldChar != focusCharacter && hud ) {
4555 if ( focusCharacter ) {
4556 hud->SetStateString( "npc", focusCharacter->spawnArgs.GetString( "npc_name", "Joe" ) );
4557 hud->HandleNamedEvent( "showNPC" );
4561 hud->SetStateString( "npc", "" );
4562 hud->HandleNamedEvent( "hideNPC" );
4571 Check for hard landings that generate sound events
4574 void idPlayer::CrashLand( const idVec3 &oldOrigin, const idVec3 &oldVelocity ) {
4575 idVec3 origin, velocity;
4576 idVec3 gravityVector, gravityNormal;
4578 float hardDelta, fatalDelta;
4583 waterLevel_t waterLevel;
4586 AI_SOFTLANDING = false;
4587 AI_HARDLANDING = false;
4589 // if the player is not on the ground
4590 if ( !physicsObj.HasGroundContacts() ) {
4594 gravityNormal = physicsObj.GetGravityNormal();
4596 // if the player wasn't going down
4597 if ( ( oldVelocity * -gravityNormal ) >= 0.0f ) {
4601 waterLevel = physicsObj.GetWaterLevel();
4603 // never take falling damage if completely underwater
4604 if ( waterLevel == WATERLEVEL_HEAD ) {
4608 // no falling damage if touching a nodamage surface
4610 for ( int i = 0; i < physicsObj.GetNumContacts(); i++ ) {
4611 const contactInfo_t &contact = physicsObj.GetContact( i );
4612 if ( contact.material->GetSurfaceFlags() & SURF_NODAMAGE ) {
4614 StartSound( "snd_land_hard", SND_CHANNEL_ANY, 0, false, NULL );
4619 origin = GetPhysics()->GetOrigin();
4620 gravityVector = physicsObj.GetGravity();
4622 // calculate the exact velocity on landing
4623 dist = ( origin - oldOrigin ) * -gravityNormal;
4624 vel = oldVelocity * -gravityNormal;
4625 acc = -gravityVector.Length();
4631 den = b * b - 4.0f * a * c;
4635 t = ( -b - idMath::Sqrt( den ) ) / ( 2.0f * a );
4637 delta = vel + t * acc;
4638 delta = delta * delta * 0.0001;
4640 // reduce falling damage if there is standing water
4641 if ( waterLevel == WATERLEVEL_WAIST ) {
4644 if ( waterLevel == WATERLEVEL_FEET ) {
4648 if ( delta < 1.0f ) {
4652 // allow falling a bit further for multiplayer
4653 if ( gameLocal.isMultiplayer ) {
4661 if ( delta > fatalDelta ) {
4662 AI_HARDLANDING = true;
4664 landTime = gameLocal.time;
4666 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4667 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_fatalfall", 1.0f, 0 );
4669 } else if ( delta > hardDelta ) {
4670 AI_HARDLANDING = true;
4672 landTime = gameLocal.time;
4674 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4675 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_hardfall", 1.0f, 0 );
4677 } else if ( delta > 30 ) {
4678 AI_HARDLANDING = true;
4680 landTime = gameLocal.time;
4682 pain_debounce_time = gameLocal.time + pain_delay + 1; // ignore pain since we'll play our landing anim
4683 Damage( NULL, NULL, idVec3( 0, 0, -1 ), "damage_softfall", 1.0f, 0 );
4685 } else if ( delta > 7 ) {
4686 AI_SOFTLANDING = true;
4688 landTime = gameLocal.time;
4689 } else if ( delta > 3 ) {
4699 void idPlayer::BobCycle( const idVec3 &pushVelocity ) {
4702 idVec3 vel, gravityDir, velocity;
4710 // calculate speed and cycle to be used for
4711 // all cyclic walking effects
4713 velocity = physicsObj.GetLinearVelocity() - pushVelocity;
4715 gravityDir = physicsObj.GetGravityNormal();
4716 vel = velocity - ( velocity * gravityDir ) * gravityDir;
4717 xyspeed = vel.LengthFast();
4719 // do not evaluate the bob for other clients
4720 // when doing a spectate follow, don't do any weapon bobbing
4721 if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
4722 viewBobAngles.Zero();
4727 if ( !physicsObj.HasGroundContacts() || influenceActive == INFLUENCE_LEVEL2 || ( gameLocal.isMultiplayer && spectating ) ) {
4732 } else if ( ( !usercmd.forwardmove && !usercmd.rightmove ) || ( xyspeed <= MIN_BOB_SPEED ) ) {
4733 // start at beginning of cycle again
4738 if ( physicsObj.IsCrouching() ) {
4739 bobmove = pm_crouchbob.GetFloat();
4740 // ducked characters never play footsteps
4742 // vary the bobbing based on the speed of the player
4743 bobmove = pm_walkbob.GetFloat() * ( 1.0f - bobFrac ) + pm_runbob.GetFloat() * bobFrac;
4746 // check for footstep / splash sounds
4748 bobCycle = (int)( old + bobmove * gameLocal.msec ) & 255;
4749 bobFoot = ( bobCycle & 128 ) >> 7;
4750 bobfracsin = idMath::Fabs( sin( ( bobCycle & 127 ) / 127.0 * idMath::PI ) );
4753 // calculate angles for view bobbing
4754 viewBobAngles.Zero();
4756 viewaxis = viewAngles.ToMat3() * physicsObj.GetGravityAxis();
4758 // add angles based on velocity
4759 delta = velocity * viewaxis[0];
4760 viewBobAngles.pitch += delta * pm_runpitch.GetFloat();
4762 delta = velocity * viewaxis[1];
4763 viewBobAngles.roll -= delta * pm_runroll.GetFloat();
4765 // add angles based on bob
4766 // make sure the bob is visible even at low speeds
4767 speed = xyspeed > 200 ? xyspeed : 200;
4769 delta = bobfracsin * pm_bobpitch.GetFloat() * speed;
4770 if ( physicsObj.IsCrouching() ) {
4771 delta *= 3; // crouching
4773 viewBobAngles.pitch += delta;
4774 delta = bobfracsin * pm_bobroll.GetFloat() * speed;
4775 if ( physicsObj.IsCrouching() ) {
4776 delta *= 3; // crouching accentuates roll
4778 if ( bobFoot & 1 ) {
4781 viewBobAngles.roll += delta;
4783 // calculate position for view bobbing
4786 if ( physicsObj.HasSteppedUp() ) {
4788 // check for stepping up before a previous step is completed
4789 deltaTime = gameLocal.time - stepUpTime;
4790 if ( deltaTime < STEPUP_TIME ) {
4791 stepUpDelta = stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME + physicsObj.GetStepUp();
4793 stepUpDelta = physicsObj.GetStepUp();
4795 if ( stepUpDelta > 2.0f * pm_stepsize.GetFloat() ) {
4796 stepUpDelta = 2.0f * pm_stepsize.GetFloat();
4798 stepUpTime = gameLocal.time;
4801 idVec3 gravity = physicsObj.GetGravityNormal();
4803 // if the player stepped up recently
4804 deltaTime = gameLocal.time - stepUpTime;
4805 if ( deltaTime < STEPUP_TIME ) {
4806 viewBob += gravity * ( stepUpDelta * ( STEPUP_TIME - deltaTime ) / STEPUP_TIME );
4809 // add bob height after any movement smoothing
4810 bob = bobfracsin * xyspeed * pm_bobup.GetFloat();
4817 delta = gameLocal.time - landTime;
4818 if ( delta < LAND_DEFLECT_TIME ) {
4819 f = delta / LAND_DEFLECT_TIME;
4820 viewBob -= gravity * ( landChange * f );
4821 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
4822 delta -= LAND_DEFLECT_TIME;
4823 f = 1.0 - ( delta / LAND_RETURN_TIME );
4824 viewBob -= gravity * ( landChange * f );
4830 idPlayer::UpdateDeltaViewAngles
4833 void idPlayer::UpdateDeltaViewAngles( const idAngles &angles ) {
4834 // set the delta angle
4836 for( int i = 0; i < 3; i++ ) {
4837 delta[ i ] = angles[ i ] - SHORT2ANGLE( usercmd.angles[ i ] );
4839 SetDeltaViewAngles( delta );
4844 idPlayer::SetViewAngles
4847 void idPlayer::SetViewAngles( const idAngles &angles ) {
4848 UpdateDeltaViewAngles( angles );
4849 viewAngles = angles;
4854 idPlayer::UpdateViewAngles
4857 void idPlayer::UpdateViewAngles( void ) {
4861 if ( !noclip && ( gameLocal.inCinematic || privateCameraView || gameLocal.GetCamera() || influenceActive == INFLUENCE_LEVEL2 || objectiveSystemOpen ) ) {
4862 // no view changes at all, but we still want to update the deltas or else when
4863 // we get out of this mode, our view will snap to a kind of random angle
4864 UpdateDeltaViewAngles( viewAngles );
4869 if ( health <= 0 ) {
4870 if ( pm_thirdPersonDeath.GetBool() ) {
4871 viewAngles.roll = 0.0f;
4872 viewAngles.pitch = 30.0f;
4874 viewAngles.roll = 40.0f;
4875 viewAngles.pitch = -15.0f;
4880 // circularly clamp the angles with deltas
4881 for ( i = 0; i < 3; i++ ) {
4882 cmdAngles[i] = SHORT2ANGLE( usercmd.angles[i] );
4883 if ( influenceActive == INFLUENCE_LEVEL3 ) {
4884 viewAngles[i] += idMath::ClampFloat( -1.0f, 1.0f, idMath::AngleDelta( idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] ) , viewAngles[i] ) );
4886 viewAngles[i] = idMath::AngleNormalize180( SHORT2ANGLE( usercmd.angles[i]) + deltaViewAngles[i] );
4889 if ( !centerView.IsDone( gameLocal.time ) ) {
4890 viewAngles.pitch = centerView.GetCurrentValue(gameLocal.time);
4895 if ( viewAngles.pitch > 89.0f ) {
4896 // don't let the player look down more than 89 degrees while noclipping
4897 viewAngles.pitch = 89.0f;
4898 } else if ( viewAngles.pitch < -89.0f ) {
4899 // don't let the player look up more than 89 degrees while noclipping
4900 viewAngles.pitch = -89.0f;
4903 if ( viewAngles.pitch > pm_maxviewpitch.GetFloat() ) {
4904 // don't let the player look down enough to see the shadow of his (non-existant) feet
4905 viewAngles.pitch = pm_maxviewpitch.GetFloat();
4906 } else if ( viewAngles.pitch < pm_minviewpitch.GetFloat() ) {
4907 // don't let the player look up more than 89 degrees
4908 viewAngles.pitch = pm_minviewpitch.GetFloat();
4912 UpdateDeltaViewAngles( viewAngles );
4914 // orient the model towards the direction we're looking
4915 SetAngles( idAngles( 0, viewAngles.yaw, 0 ) );
4917 // save in the log for analyzing weapon angle offsets
4918 loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ] = viewAngles;
4923 idPlayer::AdjustHeartRate
4925 Player heartrate works as follows
4927 DEF_HEARTRATE is resting heartrate
4929 Taking damage when health is above 75 adjusts heart rate by 1 beat per second
4930 Taking damage when health is below 75 adjusts heart rate by 5 beats per second
4931 Maximum heartrate from damage is MAX_HEARTRATE
4933 Firing a weapon adds 1 beat per second up to a maximum of COMBAT_HEARTRATE
4935 Being at less than 25% stamina adds 5 beats per second up to ZEROSTAMINA_HEARTRATE
4937 All heartrates are target rates.. the heart rate will start falling as soon as there have been no adjustments for 5 seconds
4938 Once it starts falling it always tries to get to DEF_HEARTRATE
4940 The exception to the above rule is upon death at which point the rate is set to DYING_HEARTRATE and starts falling
4943 Heart rate volumes go from zero ( -40 db for DEF_HEARTRATE to 5 db for MAX_HEARTRATE ) the volume is
4944 scaled linearly based on the actual rate
4946 Exception to the above rule is once the player is dead, the dying heart rate starts at either the current volume if
4947 it is audible or -10db and scales to 8db on the last few beats
4950 void idPlayer::AdjustHeartRate( int target, float timeInSecs, float delay, bool force ) {
4952 if ( heartInfo.GetEndValue() == target ) {
4956 if ( AI_DEAD && !force ) {
4960 lastHeartAdjust = gameLocal.time;
4962 heartInfo.Init( gameLocal.time + delay * 1000, timeInSecs * 1000, heartRate, target );
4967 idPlayer::GetBaseHeartRate
4970 int idPlayer::GetBaseHeartRate( void ) {
4971 int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float)health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
4972 int rate = idMath::FtoiFast( base + ( ZEROSTAMINA_HEARTRATE - base ) * ( 1.0f - stamina / pm_stamina.GetFloat() ) );
4973 int diff = ( lastDmgTime ) ? gameLocal.time - lastDmgTime : 99999;
4974 rate += ( diff < 5000 ) ? ( diff < 2500 ) ? ( diff < 1000 ) ? 15 : 10 : 5 : 0;
4980 idPlayer::SetCurrentHeartRate
4983 void idPlayer::SetCurrentHeartRate( void ) {
4985 int base = idMath::FtoiFast( ( BASE_HEARTRATE + LOWHEALTH_HEARTRATE_ADJ ) - ( (float) health / 100.0f ) * LOWHEALTH_HEARTRATE_ADJ );
4987 if ( PowerUpActive( ADRENALINE )) {
4990 heartRate = idMath::FtoiFast( heartInfo.GetCurrentValue( gameLocal.time ) );
4991 int currentRate = GetBaseHeartRate();
4992 if ( health >= 0 && gameLocal.time > lastHeartAdjust + 2500 ) {
4993 AdjustHeartRate( currentRate, 2.5f, 0.0f, false );
4997 int bps = idMath::FtoiFast( 60.0f / heartRate * 1000.0f );
4998 if ( gameLocal.time - lastHeartBeat > bps ) {
4999 int dmgVol = DMG_VOLUME;
5000 int deathVol = DEATH_VOLUME;
5001 int zeroVol = ZERO_VOLUME;
5003 if ( heartRate > BASE_HEARTRATE && health > 0 ) {
5004 pct = (float)(heartRate - base) / (MAX_HEARTRATE - base);
5005 pct *= ((float)dmgVol - (float)zeroVol);
5006 } else if ( health <= 0 ) {
5007 pct = (float)(heartRate - DYING_HEARTRATE) / (BASE_HEARTRATE - DYING_HEARTRATE);
5010 } else if (pct < 0.0f) {
5013 pct *= ((float)deathVol - (float)zeroVol);
5016 pct += (float)zeroVol;
5018 if ( pct != zeroVol ) {
5019 StartSound( "snd_heartbeat", SND_CHANNEL_HEART, SSF_PRIVATE_SOUND, false, NULL );
5020 // modify just this channel to a custom volume
5021 soundShaderParms_t parms;
5022 memset( &parms, 0, sizeof( parms ) );
5024 refSound.referenceSound->ModifySound( SND_CHANNEL_HEART, &parms );
5027 lastHeartBeat = gameLocal.time;
5036 void idPlayer::UpdateAir( void ) {
5037 if ( health <= 0 ) {
5041 // see if the player is connected to the info_vacuum
5042 bool newAirless = false;
5044 if ( gameLocal.vacuumAreaNum != -1 ) {
5045 int num = GetNumPVSAreas();
5049 // if the player box spans multiple areas, get the area from the origin point instead,
5050 // otherwise a rotating player box may poke into an outside area
5052 const int *pvsAreas = GetPVSAreas();
5053 areaNum = pvsAreas[0];
5055 areaNum = gameRenderWorld->PointInArea( this->GetPhysics()->GetOrigin() );
5057 newAirless = gameRenderWorld->AreasAreConnected( gameLocal.vacuumAreaNum, areaNum, PS_BLOCK_AIR );
5063 StartSound( "snd_decompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5064 StartSound( "snd_noAir", SND_CHANNEL_BODY2, 0, false, NULL );
5066 hud->HandleNamedEvent( "noAir" );
5070 if ( airTics < 0 ) {
5073 const idDict *damageDef = gameLocal.FindEntityDefDict( "damage_noair", false );
5074 int dmgTiming = 1000 * ((damageDef) ? damageDef->GetFloat( "delay", "3.0" ) : 3.0f );
5075 if ( gameLocal.time > lastAirDamage + dmgTiming ) {
5076 Damage( NULL, NULL, vec3_origin, "damage_noair", 1.0f, 0 );
5077 lastAirDamage = gameLocal.time;
5083 StartSound( "snd_recompress", SND_CHANNEL_ANY, SSF_GLOBAL, false, NULL );
5084 StopSound( SND_CHANNEL_BODY2, false );
5086 hud->HandleNamedEvent( "Air" );
5089 airTics+=2; // regain twice as fast as lose
5090 if ( airTics > pm_airTics.GetInteger() ) {
5091 airTics = pm_airTics.GetInteger();
5095 airless = newAirless;
5098 hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() );
5104 idPlayer::AddGuiPDAData
5107 int idPlayer::AddGuiPDAData( const declType_t dataType, const char *listName, const idDeclPDA *src, idUserInterface *gui ) {
5110 if ( dataType == DECL_EMAIL ) {
5111 c = src->GetNumEmails();
5112 for ( i = 0; i < c; i++ ) {
5113 const idDeclEmail *email = src->GetEmailByIndex( i );
5114 if ( email == NULL ) {
5115 work = va( "-\tEmail %d not found\t-", i );
5117 work = email->GetFrom();
5119 work += email->GetSubject();
5121 work += email->GetDate();
5123 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5126 } else if ( dataType == DECL_AUDIO ) {
5127 c = src->GetNumAudios();
5128 for ( i = 0; i < c; i++ ) {
5129 const idDeclAudio *audio = src->GetAudioByIndex( i );
5130 if ( audio == NULL ) {
5131 work = va( "Audio Log %d not found", i );
5133 work = audio->GetAudioName();
5135 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5138 } else if ( dataType == DECL_VIDEO ) {
5139 c = inventory.videos.Num();
5140 for ( i = 0; i < c; i++ ) {
5141 const idDeclVideo *video = GetVideo( i );
5142 if ( video == NULL ) {
5143 work = va( "Video CD %s not found", inventory.videos[i].c_str() );
5145 work = video->GetVideoName();
5147 gui->SetStateString( va( "%s_item_%i", listName, i ), work );
5159 const idDeclPDA *idPlayer::GetPDA( void ) const {
5160 if ( inventory.pdas.Num() ) {
5161 return static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[ 0 ] ) );
5173 const idDeclVideo *idPlayer::GetVideo( int index ) {
5174 if ( index >= 0 && index < inventory.videos.Num() ) {
5175 return static_cast< const idDeclVideo* >( declManager->FindType( DECL_VIDEO, inventory.videos[index], false ) );
5183 idPlayer::UpdatePDAInfo
5186 void idPlayer::UpdatePDAInfo( bool updatePDASel ) {
5189 if ( objectiveSystem == NULL ) {
5195 int currentPDA = objectiveSystem->State().GetInt( "listPDA_sel_0", "0" );
5196 if ( currentPDA == -1 ) {
5200 if ( updatePDASel ) {
5201 objectiveSystem->SetStateInt( "listPDAVideo_sel_0", 0 );
5202 objectiveSystem->SetStateInt( "listPDAEmail_sel_0", 0 );
5203 objectiveSystem->SetStateInt( "listPDAAudio_sel_0", 0 );
5206 if ( currentPDA > 0 ) {
5207 currentPDA = inventory.pdas.Num() - currentPDA;
5210 // Mark in the bit array that this pda has been read
5211 if ( currentPDA < 128 ) {
5212 inventory.pdasViewed[currentPDA >> 5] |= 1 << (currentPDA & 31);
5218 idStr name, data, preview, info, wave;
5219 for ( j = 0; j < MAX_PDAS; j++ ) {
5220 objectiveSystem->SetStateString( va( "listPDA_item_%i", j ), "" );
5222 for ( j = 0; j < MAX_PDA_ITEMS; j++ ) {
5223 objectiveSystem->SetStateString( va( "listPDAVideo_item_%i", j ), "" );
5224 objectiveSystem->SetStateString( va( "listPDAAudio_item_%i", j ), "" );
5225 objectiveSystem->SetStateString( va( "listPDAEmail_item_%i", j ), "" );
5226 objectiveSystem->SetStateString( va( "listPDASecurity_item_%i", j ), "" );
5228 for ( j = 0; j < inventory.pdas.Num(); j++ ) {
5230 const idDeclPDA *pda = static_cast< const idDeclPDA* >( declManager->FindType( DECL_PDA, inventory.pdas[j], false ) );
5232 if ( pda == NULL ) {
5236 int index = inventory.pdas.Num() - j;
5238 // Special case for the first PDA
5242 if ( j != currentPDA && j < 128 && inventory.pdasViewed[j >> 5] & (1 << (j & 31)) ) {
5243 // This pda has been read already, mark in gray
5244 objectiveSystem->SetStateString( va( "listPDA_item_%i", index), va(S_COLOR_GRAY "%s", pda->GetPdaName()) );
5246 // This pda has not been read yet
5247 objectiveSystem->SetStateString( va( "listPDA_item_%i", index), pda->GetPdaName() );
5250 const char *security = pda->GetSecurity();
5251 if ( j == currentPDA || (currentPDA == 0 && security && *security ) ) {
5252 if ( *security == NULL ) {
5253 security = common->GetLanguageDict()->GetString( "#str_00066" );
5255 objectiveSystem->SetStateString( "PDASecurityClearance", security );
5258 if ( j == currentPDA ) {
5260 objectiveSystem->SetStateString( "pda_icon", pda->GetIcon() );
5261 objectiveSystem->SetStateString( "pda_id", pda->GetID() );
5262 objectiveSystem->SetStateString( "pda_title", pda->GetTitle() );
5265 // Selected, personal pda
5267 if ( updatePDASel || !inventory.pdaOpened ) {
5268 objectiveSystem->HandleNamedEvent( "playerPDAActive" );
5269 objectiveSystem->SetStateString( "pda_personal", "1" );
5270 inventory.pdaOpened = true;
5272 objectiveSystem->SetStateString( "pda_location", hud->State().GetString("location") );
5273 objectiveSystem->SetStateString( "pda_name", cvarSystem->GetCVarString( "ui_name") );
5274 AddGuiPDAData( DECL_VIDEO, "listPDAVideo", pda, objectiveSystem );
5275 sel = objectiveSystem->State().GetInt( "listPDAVideo_sel_0", "0" );
5276 const idDeclVideo *vid = NULL;
5277 if ( sel >= 0 && sel < inventory.videos.Num() ) {
5278 vid = static_cast< const idDeclVideo * >( declManager->FindType( DECL_VIDEO, inventory.videos[ sel ], false ) );
5281 pdaVideo = vid->GetRoq();
5282 pdaVideoWave = vid->GetWave();
5283 objectiveSystem->SetStateString( "PDAVideoTitle", vid->GetVideoName() );
5284 objectiveSystem->SetStateString( "PDAVideoVid", vid->GetRoq() );
5285 objectiveSystem->SetStateString( "PDAVideoIcon", vid->GetPreview() );
5286 objectiveSystem->SetStateString( "PDAVideoInfo", vid->GetInfo() );
5288 //FIXME: need to precache these in the player def
5289 objectiveSystem->SetStateString( "PDAVideoVid", "sound/vo/video/welcome.tga" );
5290 objectiveSystem->SetStateString( "PDAVideoIcon", "sound/vo/video/welcome.tga" );
5291 objectiveSystem->SetStateString( "PDAVideoTitle", "" );
5292 objectiveSystem->SetStateString( "PDAVideoInfo", "" );
5295 // Selected, non-personal pda
5297 if ( updatePDASel ) {
5298 objectiveSystem->HandleNamedEvent( "playerPDANotActive" );
5299 objectiveSystem->SetStateString( "pda_personal", "0" );
5300 inventory.pdaOpened = true;
5302 objectiveSystem->SetStateString( "pda_location", pda->GetPost() );
5303 objectiveSystem->SetStateString( "pda_name", pda->GetFullName() );
5304 int audioCount = AddGuiPDAData( DECL_AUDIO, "listPDAAudio", pda, objectiveSystem );
5305 objectiveSystem->SetStateInt( "audioLogCount", audioCount );
5306 sel = objectiveSystem->State().GetInt( "listPDAAudio_sel_0", "0" );
5307 const idDeclAudio *aud = NULL;
5309 aud = pda->GetAudioByIndex( sel );
5312 pdaAudio = aud->GetWave();
5313 objectiveSystem->SetStateString( "PDAAudioTitle", aud->GetAudioName() );
5314 objectiveSystem->SetStateString( "PDAAudioIcon", aud->GetPreview() );
5315 objectiveSystem->SetStateString( "PDAAudioInfo", aud->GetInfo() );
5317 objectiveSystem->SetStateString( "PDAAudioIcon", "sound/vo/video/welcome.tga" );
5318 objectiveSystem->SetStateString( "PDAAutioTitle", "" );
5319 objectiveSystem->SetStateString( "PDAAudioInfo", "" );
5325 int numEmails = pda->GetNumEmails();
5326 if ( numEmails > 0 ) {
5327 AddGuiPDAData( DECL_EMAIL, "listPDAEmail", pda, objectiveSystem );
5328 sel = objectiveSystem->State().GetInt( "listPDAEmail_sel_0", "-1" );
5329 if ( sel >= 0 && sel < numEmails ) {
5330 const idDeclEmail *email = pda->GetEmailByIndex( sel );
5331 name = email->GetSubject();
5332 data = email->GetBody();
5335 objectiveSystem->SetStateString( "PDAEmailTitle", name );
5336 objectiveSystem->SetStateString( "PDAEmailText", data );
5339 if ( objectiveSystem->State().GetInt( "listPDA_sel_0", "-1" ) == -1 ) {
5340 objectiveSystem->SetStateInt( "listPDA_sel_0", 0 );
5342 objectiveSystem->StateChanged( gameLocal.time );
5350 void idPlayer::TogglePDA( void ) {
5351 if ( objectiveSystem == NULL ) {
5355 if ( inventory.pdas.Num() == 0 ) {
5356 ShowTip( spawnArgs.GetString( "text_infoTitle" ), spawnArgs.GetString( "text_noPDA" ), true );
5362 if ( !objectiveSystemOpen ) {
5363 int j, c = inventory.items.Num();
5364 objectiveSystem->SetStateInt( "inv_count", c );
5365 for ( j = 0; j < MAX_INVENTORY_ITEMS; j++ ) {
5366 objectiveSystem->SetStateString( va( "inv_name_%i", j ), "" );
5367 objectiveSystem->SetStateString( va( "inv_icon_%i", j ), "" );
5368 objectiveSystem->SetStateString( va( "inv_text_%i", j ), "" );
5370 for ( j = 0; j < c; j++ ) {
5371 idDict *item = inventory.items[j];
5372 if ( !item->GetBool( "inv_pda" ) ) {
5373 const char *iname = item->GetString( "inv_name" );
5374 const char *iicon = item->GetString( "inv_icon" );
5375 const char *itext = item->GetString( "inv_text" );
5376 objectiveSystem->SetStateString( va( "inv_name_%i", j ), iname );
5377 objectiveSystem->SetStateString( va( "inv_icon_%i", j ), iicon );
5378 objectiveSystem->SetStateString( va( "inv_text_%i", j ), itext );
5379 const idKeyValue *kv = item->MatchPrefix( "inv_id", NULL );
5381 objectiveSystem->SetStateString( va( "inv_id_%i", j ), kv->GetValue() );
5386 for ( j = 0; j < MAX_WEAPONS; j++ ) {
5387 const char *weapnum = va( "def_weapon%d", j );
5388 const char *hudWeap = va( "weapon%d", j );
5390 if ( inventory.weapons & ( 1 << j ) ) {
5391 const char *weap = spawnArgs.GetString( weapnum );
5392 if ( weap && *weap ) {
5396 objectiveSystem->SetStateInt( hudWeap, weapstate );
5399 objectiveSystem->SetStateInt( "listPDA_sel_0", inventory.selPDA );
5400 objectiveSystem->SetStateInt( "listPDAVideo_sel_0", inventory.selVideo );
5401 objectiveSystem->SetStateInt( "listPDAAudio_sel_0", inventory.selAudio );
5402 objectiveSystem->SetStateInt( "listPDAEmail_sel_0", inventory.selEMail );
5403 UpdatePDAInfo( false );
5404 UpdateObjectiveInfo();
5405 objectiveSystem->Activate( true, gameLocal.time );
5406 hud->HandleNamedEvent( "pdaPickupHide" );
5407 hud->HandleNamedEvent( "videoPickupHide" );
5409 inventory.selPDA = objectiveSystem->State().GetInt( "listPDA_sel_0" );
5410 inventory.selVideo = objectiveSystem->State().GetInt( "listPDAVideo_sel_0" );
5411 inventory.selAudio = objectiveSystem->State().GetInt( "listPDAAudio_sel_0" );
5412 inventory.selEMail = objectiveSystem->State().GetInt( "listPDAEmail_sel_0" );
5413 objectiveSystem->Activate( false, gameLocal.time );
5415 objectiveSystemOpen ^= 1;
5420 idPlayer::ToggleScoreboard
5423 void idPlayer::ToggleScoreboard( void ) {
5424 scoreBoardOpen ^= 1;
5432 void idPlayer::Spectate( bool spectate ) {
5434 byte msgBuf[MAX_EVENT_PARAM_SIZE];
5436 // track invisible player bug
5437 // all hiding and showing should be performed through Spectate calls
5438 // except for the private camera view, which is used for teleports
5439 assert( ( teleportEntity.GetEntity() != NULL ) || ( IsHidden() == spectating ) );
5441 if ( spectating == spectate ) {
5445 spectating = spectate;
5447 if ( gameLocal.isServer ) {
5448 msg.Init( msgBuf, sizeof( msgBuf ) );
5449 msg.WriteBits( spectating, 1 );
5450 ServerSendEvent( EVENT_SPECTATE, &msg, false, -1 );
5454 // join the spectators
5456 spectator = this->entityNumber;
5459 SetPhysics( &physicsObj );
5460 physicsObj.DisableClip();
5462 Event_DisableWeapon();
5464 hud->HandleNamedEvent( "aim_clear" );
5468 // put everything back together again
5469 currentWeapon = -1; // to make sure the def will be loaded if necessary
5471 Event_EnableWeapon();
5478 idPlayer::SetClipModel
5481 void idPlayer::SetClipModel( void ) {
5485 bounds = idBounds( vec3_origin ).Expand( pm_spectatebbox.GetFloat() * 0.5f );
5487 bounds[0].Set( -pm_bboxwidth.GetFloat() * 0.5f, -pm_bboxwidth.GetFloat() * 0.5f, 0 );
5488 bounds[1].Set( pm_bboxwidth.GetFloat() * 0.5f, pm_bboxwidth.GetFloat() * 0.5f, pm_normalheight.GetFloat() );
5490 // the origin of the clip model needs to be set before calling SetClipModel
5491 // otherwise our physics object's current origin value gets reset to 0
5492 idClipModel *newClip;
5493 if ( pm_usecylinder.GetBool() ) {
5494 newClip = new idClipModel( idTraceModel( bounds, 8 ) );
5495 newClip->Translate( physicsObj.PlayerGetOrigin() );
5496 physicsObj.SetClipModel( newClip, 1.0f );
5498 newClip = new idClipModel( idTraceModel( bounds ) );
5499 newClip->Translate( physicsObj.PlayerGetOrigin() );
5500 physicsObj.SetClipModel( newClip, 1.0f );
5506 idPlayer::UseVehicle
5509 void idPlayer::UseVehicle( void ) {
5514 if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5516 static_cast<idAFEntity_Vehicle*>(GetBindMaster())->Use( this );
5518 start = GetEyePosition();
5519 end = start + viewAngles.ToForward() * 80.0f;
5520 gameLocal.clip.TracePoint( trace, start, end, MASK_SHOT_RENDERMODEL, this );
5521 if ( trace.fraction < 1.0f ) {
5522 ent = gameLocal.entities[ trace.c.entityNum ];
5523 if ( ent && ent->IsType( idAFEntity_Vehicle::Type ) ) {
5525 static_cast<idAFEntity_Vehicle*>(ent)->Use( this );
5533 idPlayer::PerformImpulse
5536 void idPlayer::PerformImpulse( int impulse ) {
5538 if ( gameLocal.isClient ) {
5540 byte msgBuf[MAX_EVENT_PARAM_SIZE];
5542 assert( entityNumber == gameLocal.localClientNum );
5543 msg.Init( msgBuf, sizeof( msgBuf ) );
5545 msg.WriteBits( impulse, 6 );
5546 ClientSendEvent( EVENT_IMPULSE, &msg );
5549 if ( impulse >= IMPULSE_0 && impulse <= IMPULSE_12 ) {
5550 SelectWeapon( impulse, false );
5568 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5569 gameLocal.mpGame.ToggleReady();
5574 centerView.Init(gameLocal.time, 200, viewAngles.pitch, 0);
5578 // when we're not in single player, IMPULSE_19 is used for showScores
5579 // otherwise it opens the pda
5580 if ( !gameLocal.isMultiplayer ) {
5581 if ( objectiveSystemOpen ) {
5583 } else if ( weapon_pda >= 0 ) {
5584 SelectWeapon( weapon_pda, true );
5590 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5591 gameLocal.mpGame.ToggleTeam();
5596 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5597 gameLocal.mpGame.ToggleSpectate();
5602 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5603 gameLocal.mpGame.CastVote( gameLocal.localClientNum, true );
5608 if ( gameLocal.isClient || entityNumber == gameLocal.localClientNum ) {
5609 gameLocal.mpGame.CastVote( gameLocal.localClientNum, false );
5620 bool idPlayer::HandleESC( void ) {
5621 if ( gameLocal.inCinematic ) {
5622 return SkipCinematic();
5625 if ( objectiveSystemOpen ) {
5633 bool idPlayer::SkipCinematic( void ) {
5634 StartSound( "snd_skipcinematic", SND_CHANNEL_ANY, 0, false, NULL );
5635 return gameLocal.SkipCinematic();
5640 idPlayer::EvaluateControls
5643 void idPlayer::EvaluateControls( void ) {
5644 // check for respawning
5645 if ( health <= 0 ) {
5646 if ( ( gameLocal.time > minRespawnTime ) && ( usercmd.buttons & BUTTON_ATTACK ) ) {
5647 forceRespawn = true;
5648 } else if ( gameLocal.time > maxRespawnTime ) {
5649 forceRespawn = true;
5653 // in MP, idMultiplayerGame decides spawns
5654 if ( forceRespawn && !gameLocal.isMultiplayer && !g_testDeath.GetBool() ) {
5655 // in single player, we let the session handle restarting the level or loading a game
5656 gameLocal.sessionCommand = "died";
5659 if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
5660 PerformImpulse( usercmd.impulse );
5663 scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
5665 oldFlags = usercmd.flags;
5669 // update the viewangles
5675 idPlayer::AdjustSpeed
5678 void idPlayer::AdjustSpeed( void ) {
5683 speed = pm_spectatespeed.GetFloat();
5685 } else if ( noclip ) {
5686 speed = pm_noclipspeed.GetFloat();
5688 } else if ( !physicsObj.OnLadder() && ( usercmd.buttons & BUTTON_RUN ) && ( usercmd.forwardmove || usercmd.rightmove ) && ( usercmd.upmove >= 0 ) ) {
5689 if ( !gameLocal.isMultiplayer && !physicsObj.IsCrouching() && !PowerUpActive( ADRENALINE ) ) {
5690 stamina -= MS2SEC( gameLocal.msec );
5692 if ( stamina < 0 ) {
5695 if ( ( !pm_stamina.GetFloat() ) || ( stamina > pm_staminathreshold.GetFloat() ) ) {
5697 } else if ( pm_staminathreshold.GetFloat() <= 0.0001f ) {
5700 bobFrac = stamina / pm_staminathreshold.GetFloat();
5702 speed = pm_walkspeed.GetFloat() * ( 1.0f - bobFrac ) + pm_runspeed.GetFloat() * bobFrac;
5704 rate = pm_staminarate.GetFloat();
5706 // increase 25% faster when not moving
5707 if ( ( usercmd.forwardmove == 0 ) && ( usercmd.rightmove == 0 ) && ( !physicsObj.OnLadder() || ( usercmd.upmove == 0 ) ) ) {
5711 stamina += rate * MS2SEC( gameLocal.msec );
5712 if ( stamina > pm_stamina.GetFloat() ) {
5713 stamina = pm_stamina.GetFloat();
5715 speed = pm_walkspeed.GetFloat();
5719 speed *= PowerUpModifier(SPEED);
5721 if ( influenceActive == INFLUENCE_LEVEL3 ) {
5725 physicsObj.SetSpeed( speed, pm_crouchspeed.GetFloat() );
5730 idPlayer::AdjustBodyAngles
5733 void idPlayer::AdjustBodyAngles( void ) {
5749 if ( !physicsObj.HasGroundContacts() ) {
5750 idealLegsYaw = 0.0f;
5752 } else if ( usercmd.forwardmove < 0 ) {
5753 idealLegsYaw = idMath::AngleNormalize180( idVec3( -usercmd.forwardmove, usercmd.rightmove, 0.0f ).ToYaw() );
5754 legsForward = false;
5755 } else if ( usercmd.forwardmove > 0 ) {
5756 idealLegsYaw = idMath::AngleNormalize180( idVec3( usercmd.forwardmove, -usercmd.rightmove, 0.0f ).ToYaw() );
5758 } else if ( ( usercmd.rightmove != 0 ) && physicsObj.IsCrouching() ) {
5759 if ( !legsForward ) {
5760 idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), usercmd.rightmove, 0.0f ).ToYaw() );
5762 idealLegsYaw = idMath::AngleNormalize180( idVec3( idMath::Abs( usercmd.rightmove ), -usercmd.rightmove, 0.0f ).ToYaw() );
5764 } else if ( usercmd.rightmove != 0 ) {
5765 idealLegsYaw = 0.0f;
5769 diff = idMath::Fabs( idealLegsYaw - legsYaw );
5770 idealLegsYaw = idealLegsYaw - idMath::AngleNormalize180( viewAngles.yaw - oldViewYaw );
5771 if ( diff < 0.1f ) {
5772 legsYaw = idealLegsYaw;
5777 if ( !physicsObj.IsCrouching() ) {
5781 oldViewYaw = viewAngles.yaw;
5783 AI_TURN_LEFT = false;
5784 AI_TURN_RIGHT = false;
5785 if ( idealLegsYaw < -45.0f ) {
5787 AI_TURN_RIGHT = true;
5789 } else if ( idealLegsYaw > 45.0f ) {
5791 AI_TURN_LEFT = true;
5796 legsYaw = legsYaw * 0.9f + idealLegsYaw * 0.1f;
5798 legsAxis = idAngles( 0.0f, legsYaw, 0.0f ).ToMat3();
5799 animator.SetJointAxis( hipJoint, JOINTMOD_WORLD, legsAxis );
5801 // calculate the blending between down, straight, and up
5802 frac = viewAngles.pitch / 90.0f;
5803 if ( frac > 0.0f ) {
5805 forwardBlend = 1.0f - frac;
5809 forwardBlend = 1.0f + frac;
5813 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 0, downBlend );
5814 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 1, forwardBlend );
5815 animator.CurrentAnim( ANIMCHANNEL_TORSO )->SetSyncedAnimWeight( 2, upBlend );
5817 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 0, downBlend );
5818 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 1, forwardBlend );
5819 animator.CurrentAnim( ANIMCHANNEL_LEGS )->SetSyncedAnimWeight( 2, upBlend );
5824 idPlayer::InitAASLocation
5827 void idPlayer::InitAASLocation( void ) {
5835 GetFloorPos( 64.0f, origin );
5837 num = gameLocal.NumAAS();
5838 aasLocation.SetGranularity( 1 );
5839 aasLocation.SetNum( num );
5840 for( i = 0; i < aasLocation.Num(); i++ ) {
5841 aasLocation[ i ].areaNum = 0;
5842 aasLocation[ i ].pos = origin;
5843 aas = gameLocal.GetAAS( i );
5844 if ( aas && aas->GetSettings() ) {
5845 size = aas->GetSettings()->boundingBoxes[0][1];
5850 aasLocation[ i ].areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5857 idPlayer::SetAASLocation
5860 void idPlayer::SetAASLocation( void ) {
5868 if ( !GetFloorPos( 64.0f, origin ) ) {
5872 for( i = 0; i < aasLocation.Num(); i++ ) {
5873 aas = gameLocal.GetAAS( i );
5878 size = aas->GetSettings()->boundingBoxes[0][1];
5883 areaNum = aas->PointReachableAreaNum( origin, bounds, AREA_REACHABLE_WALK );
5885 aasLocation[ i ].pos = origin;
5886 aasLocation[ i ].areaNum = areaNum;
5893 idPlayer::GetAASLocation
5896 void idPlayer::GetAASLocation( idAAS *aas, idVec3 &pos, int &areaNum ) const {
5899 if ( aas != NULL ) {
5900 for( i = 0; i < aasLocation.Num(); i++ ) {
5901 if ( aas == gameLocal.GetAAS( i ) ) {
5902 areaNum = aasLocation[ i ].areaNum;
5903 pos = aasLocation[ i ].pos;
5910 pos = physicsObj.GetOrigin();
5918 void idPlayer::Move( void ) {
5922 idVec3 pushVelocity;
5924 // save old origin and velocity for crashlanding
5925 oldOrigin = physicsObj.GetOrigin();
5926 oldVelocity = physicsObj.GetLinearVelocity();
5927 pushVelocity = physicsObj.GetPushedLinearVelocity();
5929 // set physics variables
5930 physicsObj.SetMaxStepHeight( pm_stepsize.GetFloat() );
5931 physicsObj.SetMaxJumpHeight( pm_jumpheight.GetFloat() );
5934 physicsObj.SetContents( 0 );
5935 physicsObj.SetMovementType( PM_NOCLIP );
5936 } else if ( spectating ) {
5937 physicsObj.SetContents( 0 );
5938 physicsObj.SetMovementType( PM_SPECTATOR );
5939 } else if ( health <= 0 ) {
5940 physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
5941 physicsObj.SetMovementType( PM_DEAD );
5942 } else if ( gameLocal.inCinematic || gameLocal.GetCamera() || privateCameraView || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
5943 physicsObj.SetContents( CONTENTS_BODY );
5944 physicsObj.SetMovementType( PM_FREEZE );
5946 physicsObj.SetContents( CONTENTS_BODY );
5947 physicsObj.SetMovementType( PM_NORMAL );
5951 physicsObj.SetClipMask( MASK_DEADSOLID );
5952 } else if ( health <= 0 ) {
5953 physicsObj.SetClipMask( MASK_DEADSOLID );
5955 physicsObj.SetClipMask( MASK_PLAYERSOLID );
5958 physicsObj.SetDebugLevel( g_debugMove.GetBool() );
5959 physicsObj.SetPlayerInput( usercmd, viewAngles );
5961 // FIXME: physics gets disabled somehow
5962 BecomeActive( TH_PHYSICS );
5965 // update our last valid AAS location for the AI
5969 newEyeOffset = 0.0f;
5970 } else if ( health <= 0 ) {
5971 newEyeOffset = pm_deadviewheight.GetFloat();
5972 } else if ( physicsObj.IsCrouching() ) {
5973 newEyeOffset = pm_crouchviewheight.GetFloat();
5974 } else if ( GetBindMaster() && GetBindMaster()->IsType( idAFEntity_Vehicle::Type ) ) {
5975 newEyeOffset = 0.0f;
5977 newEyeOffset = pm_normalviewheight.GetFloat();
5980 if ( EyeHeight() != newEyeOffset ) {
5982 SetEyeHeight( newEyeOffset );
5984 // smooth out duck height changes
5985 SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) );
5989 if ( noclip || gameLocal.inCinematic || ( influenceActive == INFLUENCE_LEVEL2 ) ) {
5991 AI_ONGROUND = ( influenceActive == INFLUENCE_LEVEL2 );
5992 AI_ONLADDER = false;
5995 AI_CROUCH = physicsObj.IsCrouching();
5996 AI_ONGROUND = physicsObj.HasGroundContacts();
5997 AI_ONLADDER = physicsObj.OnLadder();
5998 AI_JUMP = physicsObj.HasJumped();
6000 // check if we're standing on top of a monster and give a push if we are
6001 idEntity *groundEnt = physicsObj.GetGroundEntity();
6002 if ( groundEnt && groundEnt->IsType( idAI::Type ) ) {
6003 idVec3 vel = physicsObj.GetLinearVelocity();
6004 if ( vel.ToVec2().LengthSqr() < 0.1f ) {
6005 vel.ToVec2() = physicsObj.GetOrigin().ToVec2() - groundEnt->GetPhysics()->GetAbsBounds().GetCenter().ToVec2();
6006 vel.ToVec2().NormalizeFast();
6007 vel.ToVec2() *= pm_walkspeed.GetFloat();
6009 // give em a push in the direction they're going
6012 physicsObj.SetLinearVelocity( vel );
6017 // bounce the view weapon
6018 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6019 currentLoggedAccel++;
6020 acc->time = gameLocal.time;
6022 acc->dir[0] = acc->dir[1] = 0;
6025 if ( AI_ONLADDER ) {
6026 int old_rung = oldOrigin.z / LADDER_RUNG_DISTANCE;
6027 int new_rung = physicsObj.GetOrigin().z / LADDER_RUNG_DISTANCE;
6029 if ( old_rung != new_rung ) {
6030 StartSound( "snd_stepladder", SND_CHANNEL_ANY, 0, false, NULL );
6034 BobCycle( pushVelocity );
6035 CrashLand( oldOrigin, oldVelocity );
6043 void idPlayer::UpdateHud( void ) {
6050 if ( entityNumber != gameLocal.localClientNum ) {
6054 int c = inventory.pickupItemNames.Num();
6056 if ( gameLocal.time > inventory.nextItemPickup ) {
6057 if ( inventory.nextItemPickup && gameLocal.time - inventory.nextItemPickup > 2000 ) {
6058 inventory.nextItemNum = 1;
6061 for ( i = 0; i < 5, i < c; i++ ) {
6062 hud->SetStateString( va( "itemtext%i", inventory.nextItemNum ), inventory.pickupItemNames[0].name );
6063 hud->SetStateString( va( "itemicon%i", inventory.nextItemNum ), inventory.pickupItemNames[0].icon );
6064 hud->HandleNamedEvent( va( "itemPickup%i", inventory.nextItemNum++ ) );
6065 inventory.pickupItemNames.RemoveIndex( 0 );
6066 if (inventory.nextItemNum == 1 ) {
6067 inventory.onePickupTime = gameLocal.time;
6068 } else if ( inventory.nextItemNum > 5 ) {
6069 inventory.nextItemNum = 1;
6070 inventory.nextItemPickup = inventory.onePickupTime + 2000;
6072 inventory.nextItemPickup = gameLocal.time + 400;
6078 if ( gameLocal.realClientTime == lastMPAimTime ) {
6079 if ( MPAim != -1 && gameLocal.gameType == GAME_TDM
6080 && gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type )
6081 && static_cast< idPlayer * >( gameLocal.entities[ MPAim ] )->team == team ) {
6082 aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
6083 hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
6084 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
6085 hud->HandleNamedEvent( "aim_flash" );
6086 MPAimHighlight = true;
6087 MPAimFadeTime = 0; // no fade till loosing focus
6088 } else if ( MPAimHighlight ) {
6089 hud->HandleNamedEvent( "aim_fade" );
6090 MPAimFadeTime = gameLocal.realClientTime;
6091 MPAimHighlight = false;
6094 if ( MPAimFadeTime ) {
6095 assert( !MPAimHighlight );
6096 if ( gameLocal.realClientTime - MPAimFadeTime > 2000 ) {
6101 hud->SetStateInt( "g_showProjectilePct", g_showProjectilePct.GetInteger() );
6102 if ( numProjectilesFired ) {
6103 hud->SetStateString( "projectilepct", va( "Hit %% %.1f", ( (float) numProjectileHits / numProjectilesFired ) * 100 ) );
6105 hud->SetStateString( "projectilepct", "Hit % 0.0" );
6108 if ( isLagged && gameLocal.isMultiplayer && gameLocal.localClientNum == entityNumber ) {
6109 hud->SetStateString( "hudLag", "1" );
6111 hud->SetStateString( "hudLag", "0" );
6117 idPlayer::UpdateDeathSkin
6120 void idPlayer::UpdateDeathSkin( bool state_hitch ) {
6121 if ( !( gameLocal.isMultiplayer || g_testDeath.GetBool() ) ) {
6124 if ( health <= 0 ) {
6125 if ( !doingDeathSkin ) {
6126 deathClearContentsTime = spawnArgs.GetInt( "deathSkinTime" );
6127 doingDeathSkin = true;
6128 renderEntity.noShadow = true;
6129 if ( state_hitch ) {
6130 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f - 2.0f;
6132 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f;
6137 // wait a bit before switching off the content
6138 if ( deathClearContentsTime && gameLocal.time > deathClearContentsTime ) {
6139 SetCombatContents( false );
6140 deathClearContentsTime = 0;
6143 renderEntity.noShadow = false;
6144 renderEntity.shaderParms[ SHADERPARM_TIME_OF_DEATH ] = 0.0f;
6146 doingDeathSkin = false;
6152 idPlayer::StartFxOnBone
6155 void idPlayer::StartFxOnBone( const char *fx, const char *bone ) {
6158 jointHandle_t jointHandle = GetAnimator()->GetJointHandle( bone );
6160 if ( jointHandle == INVALID_JOINT ) {
6161 gameLocal.Printf( "Cannot find bone %s\n", bone );
6165 if ( GetAnimator()->GetJointTransform( jointHandle, gameLocal.time, offset, axis ) ) {
6166 offset = GetPhysics()->GetOrigin() + offset * GetPhysics()->GetAxis();
6167 axis = axis * GetPhysics()->GetAxis();
6170 idEntityFx::StartFx( fx, &offset, &axis, this, true );
6177 Called every tic for each player
6180 void idPlayer::Think( void ) {
6181 renderEntity_t *headRenderEnt;
6183 UpdatePlayerIcons();
6185 // latch button actions
6186 oldButtons = usercmd.buttons;
6189 usercmd_t oldCmd = usercmd;
6190 usercmd = gameLocal.usercmds[ entityNumber ];
6191 buttonMask &= usercmd.buttons;
6192 usercmd.buttons &= ~buttonMask;
6194 if ( gameLocal.inCinematic && gameLocal.skipCinematic ) {
6198 // clear the ik before we do anything else so the skeleton doesn't get updated twice
6199 walkIK.ClearJointMods();
6201 // if this is the very first frame of the map, set the delta view angles
6202 // based on the usercmd angles
6203 if ( !spawnAnglesSet && ( gameLocal.GameState() != GAMESTATE_STARTUP ) ) {
6204 spawnAnglesSet = true;
6205 SetViewAngles( spawnAngles );
6206 oldFlags = usercmd.flags;
6209 if ( objectiveSystemOpen || gameLocal.inCinematic || influenceActive ) {
6210 if ( objectiveSystemOpen && AI_PAIN ) {
6213 usercmd.forwardmove = 0;
6214 usercmd.rightmove = 0;
6218 // log movement changes for weapon bobbing effects
6219 if ( usercmd.forwardmove != oldCmd.forwardmove ) {
6220 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6221 currentLoggedAccel++;
6222 acc->time = gameLocal.time;
6223 acc->dir[0] = usercmd.forwardmove - oldCmd.forwardmove;
6224 acc->dir[1] = acc->dir[2] = 0;
6227 if ( usercmd.rightmove != oldCmd.rightmove ) {
6228 loggedAccel_t *acc = &loggedAccel[currentLoggedAccel&(NUM_LOGGED_ACCELS-1)];
6229 currentLoggedAccel++;
6230 acc->time = gameLocal.time;
6231 acc->dir[1] = usercmd.rightmove - oldCmd.rightmove;
6232 acc->dir[0] = acc->dir[2] = 0;
6235 // freelook centering
6236 if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_MLOOK ) {
6237 centerView.Init( gameLocal.time, 200, viewAngles.pitch, 0 );
6241 if ( ( usercmd.buttons ^ oldCmd.buttons ) & BUTTON_ZOOM ) {
6242 if ( ( usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ) {
6243 zoomFov.Init( gameLocal.time, 200.0f, CalcFov( false ), weapon.GetEntity()->GetZoomFov() );
6245 zoomFov.Init( gameLocal.time, 200.0f, zoomFov.GetCurrentValue( gameLocal.time ), DefaultFov() );
6249 // if we have an active gui, we will unrotate the view angles as
6250 // we turn the mouse movements into gui events
6251 idUserInterface *gui = ActiveGui();
6252 if ( gui && gui != focusUI ) {
6253 RouteGuiMouse( gui );
6256 // set the push velocity on the weapon before running the physics
6257 if ( weapon.GetEntity() ) {
6258 weapon.GetEntity()->SetPushVelocity( physicsObj.GetPushedLinearVelocity() );
6263 if ( !af.IsActive() ) {
6265 CopyJointsFromBodyToHead();
6270 if ( !g_stopTime.GetBool() ) {
6272 if ( !noclip && !spectating && ( health > 0 ) && !IsHidden() ) {
6276 // not done on clients for various reasons. don't do it on server and save the sound channel for other things
6277 if ( !gameLocal.isMultiplayer ) {
6278 SetCurrentHeartRate();
6279 float scale = g_damageScale.GetFloat();
6280 if ( g_useDynamicProtection.GetBool() && scale < 1.0f && gameLocal.time - lastDmgTime > 500 ) {
6281 if ( scale < 1.0f ) {
6284 if ( scale > 1.0f ) {
6287 g_damageScale.SetFloat( scale );
6291 // update GUIs, Items, and character interactions
6296 // update player script
6299 // service animations
6300 if ( !spectating && !af.IsActive() && !gameLocal.inCinematic ) {
6306 // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
6310 // calculate the exact bobbed view position, which is used to
6311 // position the view weapon, among other things
6312 CalculateFirstPersonView();
6314 // this may use firstPersonView, or a thirdPeroson / camera view
6315 CalculateRenderView();
6317 inventory.UpdateArmor();
6321 } else if ( health > 0 ) {
6331 UpdateDeathSkin( false );
6333 if ( gameLocal.isMultiplayer ) {
6337 if ( head.GetEntity() ) {
6338 headRenderEnt = head.GetEntity()->GetRenderEntity();
6340 headRenderEnt = NULL;
6343 if ( headRenderEnt ) {
6344 if ( influenceSkin ) {
6345 headRenderEnt->customSkin = influenceSkin;
6347 headRenderEnt->customSkin = NULL;
6351 if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
6352 renderEntity.suppressShadowInViewID = 0;
6353 if ( headRenderEnt ) {
6354 headRenderEnt->suppressShadowInViewID = 0;
6357 renderEntity.suppressShadowInViewID = entityNumber+1;
6358 if ( headRenderEnt ) {
6359 headRenderEnt->suppressShadowInViewID = entityNumber+1;
6362 // never cast shadows from our first-person muzzle flashes
6363 renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6364 if ( headRenderEnt ) {
6365 headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
6368 if ( !g_stopTime.GetBool() ) {
6373 UpdateDamageEffects();
6377 playerView.CalculateShake();
6380 if ( !( thinkFlags & TH_THINK ) ) {
6381 gameLocal.Printf( "player %d not thinking?\n", entityNumber );
6384 if ( g_showEnemies.GetBool() ) {
6387 for( ent = enemyList.Next(); ent != NULL; ent = ent->enemyNode.Next() ) {
6388 gameLocal.Printf( "enemy (%d)'%s'\n", ent->entityNumber, ent->name.c_str() );
6389 gameRenderWorld->DebugBounds( colorRed, ent->GetPhysics()->GetBounds().Expand( 2 ), ent->GetPhysics()->GetOrigin() );
6392 gameLocal.Printf( "%d: enemies\n", num );
6398 idPlayer::RouteGuiMouse
6401 void idPlayer::RouteGuiMouse( idUserInterface *gui ) {
6403 const char *command;
6405 if ( usercmd.mx != oldMouseX || usercmd.my != oldMouseY ) {
6406 ev = sys->GenerateMouseMoveEvent( usercmd.mx - oldMouseX, usercmd.my - oldMouseY );
6407 command = gui->HandleEvent( &ev, gameLocal.time );
6408 oldMouseX = usercmd.mx;
6409 oldMouseY = usercmd.my;
6415 idPlayer::LookAtKiller
6418 void idPlayer::LookAtKiller( idEntity *inflictor, idEntity *attacker ) {
6421 if ( attacker && attacker != this ) {
6422 dir = attacker->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6423 } else if ( inflictor && inflictor != this ) {
6424 dir = inflictor->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin();
6426 dir = viewAxis[ 0 ];
6429 idAngles ang( 0, dir.ToYaw(), 0 );
6430 SetViewAngles( ang );
6438 void idPlayer::Kill( bool delayRespawn, bool nodamage ) {
6440 SpectateFreeFly( false );
6441 } else if ( health > 0 ) {
6444 ServerSpectate( true );
6445 forceRespawn = true;
6447 Damage( this, this, vec3_origin, "damage_suicide", 1.0f, INVALID_JOINT );
6448 if ( delayRespawn ) {
6449 forceRespawn = false;
6450 int delay = spawnArgs.GetFloat( "respawn_delay" );
6451 minRespawnTime = gameLocal.time + SEC2MS( delay );
6452 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6463 void idPlayer::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
6466 assert( !gameLocal.isClient );
6468 // stop taking knockback once dead
6469 fl.noknockback = true;
6470 if ( health < -999 ) {
6479 heartInfo.Init( 0, 0, 0, BASE_HEARTRATE );
6480 AdjustHeartRate( DEAD_HEARTRATE, 10.0f, 0.0f, true );
6482 if ( !g_testDeath.GetBool() ) {
6483 playerView.Fade( colorBlack, 12000 );
6487 SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
6488 SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
6491 animator.ClearAllJoints();
6493 if ( StartRagdoll() ) {
6494 pm_modelView.SetInteger( 0 );
6495 minRespawnTime = gameLocal.time + RAGDOLL_DEATH_TIME;
6496 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6498 // don't allow respawn until the death anim is done
6499 // g_forcerespawn may force spawning at some later time
6500 delay = spawnArgs.GetFloat( "respawn_delay" );
6501 minRespawnTime = gameLocal.time + SEC2MS( delay );
6502 maxRespawnTime = minRespawnTime + MAX_RESPAWN_TIME;
6505 physicsObj.SetMovementType( PM_DEAD );
6506 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
6507 StopSound( SND_CHANNEL_BODY2, false );
6509 fl.takedamage = true; // can still be gibbed
6511 // get rid of weapon
6512 weapon.GetEntity()->OwnerDied();
6514 // drop the weapon as an item
6517 if ( !g_testDeath.GetBool() ) {
6518 LookAtKiller( inflictor, attacker );
6521 if ( gameLocal.isMultiplayer || g_testDeath.GetBool() ) {
6522 idPlayer *killer = NULL;
6523 // no gibbing in MP. Event_Gib will early out in MP
6524 if ( attacker->IsType( idPlayer::Type ) ) {
6525 killer = static_cast<idPlayer*>(attacker);
6526 if ( health < -20 || killer->PowerUpActive( BERSERK ) ) {
6529 gibsLaunched = false;
6532 gameLocal.mpGame.PlayerDeath( this, killer, isTelefragged );
6534 physicsObj.SetContents( CONTENTS_CORPSE | CONTENTS_MONSTERCLIP );
6545 =====================
6546 idPlayer::GetAIAimTargets
6548 Returns positions for the AI to aim at.
6549 =====================
6551 void idPlayer::GetAIAimTargets( const idVec3 &lastSightPos, idVec3 &headPos, idVec3 &chestPos ) {
6556 origin = lastSightPos - physicsObj.GetOrigin();
6558 GetJointWorldTransform( chestJoint, gameLocal.time, offset, axis );
6559 headPos = offset + origin;
6561 GetJointWorldTransform( headJoint, gameLocal.time, offset, axis );
6562 chestPos = offset + origin;
6567 idPlayer::DamageFeedback
6569 callback function for when another entity received damage from this entity. damage can be adjusted and returned to the caller.
6572 void idPlayer::DamageFeedback( idEntity *victim, idEntity *inflictor, int &damage ) {
6573 assert( !gameLocal.isClient );
6574 damage *= PowerUpModifier( BERSERK );
6575 if ( damage && ( victim != this ) && victim->IsType( idActor::Type ) ) {
6576 SetLastHitTime( gameLocal.time );
6582 idPlayer::CalcDamagePoints
6584 Calculates how many health and armor points will be inflicted, but
6585 doesn't actually do anything with them. This is used to tell when an attack
6586 would have killed the player, possibly allowing a "saving throw"
6589 void idPlayer::CalcDamagePoints( idEntity *inflictor, idEntity *attacker, const idDict *damageDef,
6590 const float damageScale, const int location, int *health, int *armor ) {
6594 damageDef->GetInt( "damage", "20", damage );
6595 damage = GetDamageForLocation( damage, location );
6597 idPlayer *player = attacker->IsType( idPlayer::Type ) ? static_cast<idPlayer*>(attacker) : NULL;
6598 if ( !gameLocal.isMultiplayer ) {
6599 if ( inflictor != gameLocal.world ) {
6600 switch ( g_skill.GetInteger() ) {
6619 damage *= damageScale;
6621 // always give half damage if hurting self
6622 if ( attacker == this ) {
6623 if ( gameLocal.isMultiplayer ) {
6624 // only do this in mp so single player plasma and rocket splash is very dangerous in close quarters
6625 damage *= damageDef->GetFloat( "selfDamageScale", "0.5" );
6627 damage *= damageDef->GetFloat( "selfDamageScale", "1" );
6631 // check for completely getting out of the damage
6632 if ( !damageDef->GetBool( "noGod" ) ) {
6633 // check for godmode
6639 // inform the attacker that they hit someone
6640 attacker->DamageFeedback( this, inflictor, damage );
6642 // save some from armor
6643 if ( !damageDef->GetBool( "noArmor" ) ) {
6644 float armor_protection;
6646 armor_protection = ( gameLocal.isMultiplayer ) ? g_armorProtectionMP.GetFloat() : g_armorProtection.GetFloat();
6648 armorSave = ceil( damage * armor_protection );
6649 if ( armorSave >= inventory.armor ) {
6650 armorSave = inventory.armor;
6655 } else if ( armorSave >= damage ) {
6656 armorSave = damage - 1;
6659 damage -= armorSave;
6665 // check for team damage
6666 if ( gameLocal.gameType == GAME_TDM
6667 && !gameLocal.serverInfo.GetBool( "si_teamDamage" )
6668 && !damageDef->GetBool( "noTeam" )
6670 && player != this // you get self damage no matter what
6671 && player->team == team ) {
6683 this entity that is being damaged
6684 inflictor entity that is causing the damage
6685 attacker entity that caused the inflictor to damage targ
6686 example: this=monster, inflictor=rocket, attacker=player
6688 dir direction of the attack for knockback in global space
6690 damageDef an idDict with all the options for damage effects
6692 inflictor, attacker, dir, and point can be NULL for environmental effects
6695 void idPlayer::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir,
6696 const char *damageDefName, const float damageScale, const int location ) {
6702 idVec3 localDamageVector;
6703 float attackerPushScale;
6705 // damage is only processed on server
6706 if ( gameLocal.isClient ) {
6710 if ( !fl.takedamage || noclip || spectating || gameLocal.inCinematic ) {
6715 inflictor = gameLocal.world;
6718 attacker = gameLocal.world;
6721 if ( attacker->IsType( idAI::Type ) ) {
6722 if ( PowerUpActive( BERSERK ) ) {
6725 // don't take damage from monsters during influences
6726 if ( influenceActive != 0 ) {
6731 const idDeclEntityDef *damageDef = gameLocal.FindEntityDef( damageDefName, false );
6733 gameLocal.Warning( "Unknown damageDef '%s'", damageDefName );
6737 if ( damageDef->dict.GetBool( "ignore_player" ) ) {
6741 CalcDamagePoints( inflictor, attacker, &damageDef->dict, damageScale, location, &damage, &armorSave );
6743 // determine knockback
6744 damageDef->dict.GetInt( "knockback", "20", knockback );
6746 if ( knockback != 0 && !fl.noknockback ) {
6747 if ( attacker == this ) {
6748 damageDef->dict.GetFloat( "attackerPushScale", "0", attackerPushScale );
6750 attackerPushScale = 1.0f;
6755 kick *= g_knockback.GetFloat() * knockback * attackerPushScale / 200.0f;
6756 physicsObj.SetLinearVelocity( physicsObj.GetLinearVelocity() + kick );
6758 // set the timer so that the player can't cancel out the movement immediately
6759 physicsObj.SetKnockBack( idMath::ClampInt( 50, 200, knockback * 2 ) );
6762 // give feedback on the player view and audibly when armor is helping
6764 inventory.armor -= armorSave;
6766 if ( gameLocal.time > lastArmorPulse + 200 ) {
6767 StartSound( "snd_hitArmor", SND_CHANNEL_ITEM, 0, false, NULL );
6769 lastArmorPulse = gameLocal.time;
6772 if ( damageDef->dict.GetBool( "burn" ) ) {
6773 StartSound( "snd_burn", SND_CHANNEL_BODY3, 0, false, NULL );
6774 } else if ( damageDef->dict.GetBool( "no_air" ) ) {
6775 if ( !armorSave && health > 0 ) {
6776 StartSound( "snd_airGasp", SND_CHANNEL_ITEM, 0, false, NULL );
6780 if ( g_debugDamage.GetInteger() ) {
6781 gameLocal.Printf( "client:%i health:%i damage:%i armor:%i\n",
6782 entityNumber, health, damage, armorSave );
6785 // move the world direction vector to local coordinates
6787 damage_from.Normalize();
6789 viewAxis.ProjectVector( damage_from, localDamageVector );
6791 // add to the damage inflicted on a player this frame
6792 // the total will be turned into screen blends and view angle kicks
6793 // at the end of the frame
6795 playerView.DamageImpulse( localDamageVector, &damageDef->dict );
6801 if ( !gameLocal.isMultiplayer ) {
6802 float scale = g_damageScale.GetFloat();
6803 if ( g_useDynamicProtection.GetBool() && g_skill.GetInteger() < 2 ) {
6804 if ( gameLocal.time > lastDmgTime + 500 && scale > 0.25f ) {
6806 g_damageScale.SetFloat( scale );
6810 if ( scale > 0.0f ) {
6819 int oldHealth = health;
6822 if ( health <= 0 ) {
6824 if ( health < -999 ) {
6828 isTelefragged = damageDef->dict.GetBool( "telefrag" );
6830 lastDmgTime = gameLocal.time;
6831 Killed( inflictor, attacker, damage, dir, location );
6837 // let the anim script know we took damage
6838 AI_PAIN = Pain( inflictor, attacker, damage, dir, location );
6839 if ( !g_testDeath.GetBool() ) {
6840 lastDmgTime = gameLocal.time;
6844 // don't accumulate impulses
6845 if ( af.IsLoaded() ) {
6849 // physics is turned off by calling af.Rest()
6850 BecomeActive( TH_PHYSICS );
6854 lastDamageDef = damageDef->Index();
6855 lastDamageDir = damage_from;
6856 lastDamageLocation = location;
6864 void idPlayer::Teleport( const idVec3 &origin, const idAngles &angles, idEntity *destination ) {
6867 if ( weapon.GetEntity() ) {
6868 weapon.GetEntity()->LowerWeapon();
6871 SetOrigin( origin + idVec3( 0, 0, CM_CLIP_EPSILON ) );
6872 if ( !gameLocal.isMultiplayer && GetFloorPos( 16.0f, org ) ) {
6876 // clear the ik heights so model doesn't appear in the wrong place
6879 GetPhysics()->SetLinearVelocity( vec3_origin );
6881 SetViewAngles( angles );
6884 idealLegsYaw = 0.0f;
6885 oldViewYaw = viewAngles.yaw;
6887 if ( gameLocal.isMultiplayer ) {
6888 playerView.Flash( colorWhite, 140 );
6893 teleportEntity = destination;
6895 if ( !gameLocal.isClient && !noclip ) {
6896 if ( gameLocal.isMultiplayer ) {
6897 // kill anything at the new position or mark for kill depending on immediate or delayed teleport
6898 gameLocal.KillBox( this, destination != NULL );
6900 // kill anything at the new position
6901 gameLocal.KillBox( this, true );
6907 ====================
6908 idPlayer::SetPrivateCameraView
6909 ====================
6911 void idPlayer::SetPrivateCameraView( idCamera *camView ) {
6912 privateCameraView = camView;
6917 if ( !spectating ) {
6924 ====================
6925 idPlayer::DefaultFov
6927 Returns the base FOV
6928 ====================
6930 float idPlayer::DefaultFov( void ) const {
6933 fov = g_fov.GetFloat();
6934 if ( gameLocal.isMultiplayer ) {
6935 if ( fov < 90.0f ) {
6937 } else if ( fov > 110.0f ) {
6946 ====================
6949 Fixed fov at intermissions, otherwise account for fov variable and zooms.
6950 ====================
6952 float idPlayer::CalcFov( bool honorZoom ) {
6956 return DefaultFov() + 10.0f + cos( ( gameLocal.time + 2000 ) * 0.01 ) * 10.0f;
6959 if ( influenceFov ) {
6960 return influenceFov;
6963 if ( zoomFov.IsDone( gameLocal.time ) ) {
6964 fov = ( honorZoom && usercmd.buttons & BUTTON_ZOOM ) && weapon.GetEntity() ? weapon.GetEntity()->GetZoomFov() : DefaultFov();
6966 fov = zoomFov.GetCurrentValue( gameLocal.time );
6969 // bound normal viewsize
6972 } else if ( fov > 179 ) {
6981 idPlayer::GunTurningOffset
6983 generate a rotational offset for the gun based on the view angle
6984 history in loggedViewAngles
6987 idAngles idPlayer::GunTurningOffset( void ) {
6992 if ( gameLocal.framenum < NUM_LOGGED_VIEW_ANGLES ) {
6996 idAngles current = loggedViewAngles[ gameLocal.framenum & (NUM_LOGGED_VIEW_ANGLES-1) ];
6999 int weaponAngleOffsetAverages;
7000 float weaponAngleOffsetScale, weaponAngleOffsetMax;
7002 weapon.GetEntity()->GetWeaponAngleOffsets( &weaponAngleOffsetAverages, &weaponAngleOffsetScale, &weaponAngleOffsetMax );
7006 // calcualte this so the wrap arounds work properly
7007 for ( int j = 1 ; j < weaponAngleOffsetAverages ; j++ ) {
7008 idAngles a2 = loggedViewAngles[ ( gameLocal.framenum - j ) & (NUM_LOGGED_VIEW_ANGLES-1) ];
7010 idAngles delta = a2 - current;
7012 if ( delta[1] > 180 ) {
7014 } else if ( delta[1] < -180 ) {
7018 av += delta * ( 1.0f / weaponAngleOffsetAverages );
7021 a = ( av - current ) * weaponAngleOffsetScale;
7023 for ( int i = 0 ; i < 3 ; i++ ) {
7024 if ( a[i] < -weaponAngleOffsetMax ) {
7025 a[i] = -weaponAngleOffsetMax;
7026 } else if ( a[i] > weaponAngleOffsetMax ) {
7027 a[i] = weaponAngleOffsetMax;
7036 idPlayer::GunAcceleratingOffset
7038 generate a positional offset for the gun based on the movement
7039 history in loggedAccelerations
7042 idVec3 idPlayer::GunAcceleratingOffset( void ) {
7045 float weaponOffsetTime, weaponOffsetScale;
7049 weapon.GetEntity()->GetWeaponTimeOffsets( &weaponOffsetTime, &weaponOffsetScale );
7051 int stop = currentLoggedAccel - NUM_LOGGED_ACCELS;
7055 for ( int i = currentLoggedAccel-1 ; i > stop ; i-- ) {
7056 loggedAccel_t *acc = &loggedAccel[i&(NUM_LOGGED_ACCELS-1)];
7059 float t = gameLocal.time - acc->time;
7060 if ( t >= weaponOffsetTime ) {
7061 break; // remainder are too old to care about
7064 f = t / weaponOffsetTime;
7065 f = ( cos( f * 2.0f * idMath::PI ) - 1.0f ) * 0.5f;
7066 ofs += f * weaponOffsetScale * acc->dir;
7074 idPlayer::CalculateViewWeaponPos
7076 Calculate the bobbing position of the view weapon
7079 void idPlayer::CalculateViewWeaponPos( idVec3 &origin, idMat3 &axis ) {
7085 // CalculateRenderView must have been called first
7086 const idVec3 &viewOrigin = firstPersonViewOrigin;
7087 const idMat3 &viewAxis = firstPersonViewAxis;
7089 // these cvars are just for hand tweaking before moving a value to the weapon def
7090 idVec3 gunpos( g_gun_x.GetFloat(), g_gun_y.GetFloat(), g_gun_z.GetFloat() );
7092 // as the player changes direction, the gun will take a small lag
7093 idVec3 gunOfs = GunAcceleratingOffset();
7094 origin = viewOrigin + ( gunpos + gunOfs ) * viewAxis;
7096 // on odd legs, invert some angles
7097 if ( bobCycle & 128 ) {
7103 // gun angles from bobbing
7104 angles.roll = scale * bobfracsin * 0.005f;
7105 angles.yaw = scale * bobfracsin * 0.01f;
7106 angles.pitch = xyspeed * bobfracsin * 0.005f;
7108 // gun angles from turning
7109 if ( gameLocal.isMultiplayer ) {
7110 idAngles offset = GunTurningOffset();
7111 offset *= g_mpWeaponAngleScale.GetFloat();
7114 angles += GunTurningOffset();
7117 idVec3 gravity = physicsObj.GetGravityNormal();
7119 // drop the weapon when landing after a jump / fall
7120 delta = gameLocal.time - landTime;
7121 if ( delta < LAND_DEFLECT_TIME ) {
7122 origin -= gravity * ( landChange*0.25f * delta / LAND_DEFLECT_TIME );
7123 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
7124 origin -= gravity * ( landChange*0.25f * (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME );
7127 // speed sensitive idle drift
7128 scale = xyspeed + 40.0f;
7129 fracsin = scale * sin( MS2SEC( gameLocal.time ) ) * 0.01f;
7130 angles.roll += fracsin;
7131 angles.yaw += fracsin;
7132 angles.pitch += fracsin;
7134 axis = angles.ToMat3() * viewAxis;
7139 idPlayer::OffsetThirdPersonView
7142 void idPlayer::OffsetThirdPersonView( float angle, float range, float height, bool clip ) {
7148 float forwardScale, sideScale;
7154 angles = viewAngles;
7155 GetViewPos( origin, axis );
7158 angles.pitch = 0.0f;
7161 if ( angles.pitch > 45.0f ) {
7162 angles.pitch = 45.0f; // don't go too far overhead
7165 focusPoint = origin + angles.ToForward() * THIRD_PERSON_FOCUS_DISTANCE;
7166 focusPoint.z += height;
7168 view.z += 8 + height;
7170 angles.pitch *= 0.5f;
7171 renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7173 idMath::SinCos( DEG2RAD( angle ), sideScale, forwardScale );
7174 view -= range * forwardScale * renderView->viewaxis[ 0 ];
7175 view += range * sideScale * renderView->viewaxis[ 1 ];
7178 // trace a ray from the origin to the viewpoint to make sure the view isn't
7179 // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
7180 bounds = idBounds( idVec3( -4, -4, -4 ), idVec3( 4, 4, 4 ) );
7181 gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7182 if ( trace.fraction != 1.0f ) {
7183 view = trace.endpos;
7184 view.z += ( 1.0f - trace.fraction ) * 32.0f;
7186 // try another trace to this position, because a tunnel may have the ceiling
7187 // close enough that this is poking out
7188 gameLocal.clip.TraceBounds( trace, origin, view, bounds, MASK_SOLID, this );
7189 view = trace.endpos;
7193 // select pitch to look at focus point from vieword
7195 focusDist = idMath::Sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
7196 if ( focusDist < 1.0f ) {
7197 focusDist = 1.0f; // should never happen
7200 angles.pitch = - RAD2DEG( atan2( focusPoint.z, focusDist ) );
7201 angles.yaw -= angle;
7203 renderView->vieworg = view;
7204 renderView->viewaxis = angles.ToMat3() * physicsObj.GetGravityAxis();
7205 renderView->viewID = 0;
7210 idPlayer::GetEyePosition
7213 idVec3 idPlayer::GetEyePosition( void ) const {
7216 // use the smoothed origin if spectating another player in multiplayer
7217 if ( gameLocal.isClient && entityNumber != gameLocal.localClientNum ) {
7218 org = smoothedOrigin;
7220 org = GetPhysics()->GetOrigin();
7222 return org + ( GetPhysics()->GetGravityNormal() * -eyeOffset.z );
7227 idPlayer::GetViewPos
7230 void idPlayer::GetViewPos( idVec3 &origin, idMat3 &axis ) const {
7233 // if dead, fix the angle and don't add any kick
7234 if ( health <= 0 ) {
7235 angles.yaw = viewAngles.yaw;
7238 axis = angles.ToMat3();
7239 origin = GetEyePosition();
7241 origin = GetEyePosition() + viewBob;
7242 angles = viewAngles + viewBobAngles + playerView.AngleOffset();
7244 axis = angles.ToMat3() * physicsObj.GetGravityAxis();
7246 // adjust the origin based on the camera nodal distance (eye distance from neck)
7247 origin += physicsObj.GetGravityNormal() * g_viewNodalZ.GetFloat();
7248 origin += axis[0] * g_viewNodalX.GetFloat() + axis[2] * g_viewNodalZ.GetFloat();
7254 idPlayer::CalculateFirstPersonView
7257 void idPlayer::CalculateFirstPersonView( void ) {
7258 if ( ( pm_modelView.GetInteger() == 1 ) || ( ( pm_modelView.GetInteger() == 2 ) && ( health <= 0 ) ) ) {
7259 // Displays the view from the point of view of the "camera" joint in the player model
7265 ang = viewBobAngles + playerView.AngleOffset();
7266 ang.yaw += viewAxis[ 0 ].ToYaw();
7268 jointHandle_t joint = animator.GetJointHandle( "camera" );
7269 animator.GetJointTransform( joint, gameLocal.time, origin, axis );
7270 firstPersonViewOrigin = ( origin + modelOffset ) * ( viewAxis * physicsObj.GetGravityAxis() ) + physicsObj.GetOrigin() + viewBob;
7271 firstPersonViewAxis = axis * ang.ToMat3() * physicsObj.GetGravityAxis();
7273 // offset for local bobbing and kicks
7274 GetViewPos( firstPersonViewOrigin, firstPersonViewAxis );
7276 // shakefrom sound stuff only happens in first person
7277 firstPersonViewAxis = firstPersonViewAxis * playerView.ShakeAxis();
7284 idPlayer::GetRenderView
7286 Returns the renderView that was calculated for this tic
7289 renderView_t *idPlayer::GetRenderView( void ) {
7295 idPlayer::CalculateRenderView
7297 create the renderView for the current tic
7300 void idPlayer::CalculateRenderView( void ) {
7304 if ( !renderView ) {
7305 renderView = new renderView_t;
7307 memset( renderView, 0, sizeof( *renderView ) );
7309 // copy global shader parms
7310 for( i = 0; i < MAX_GLOBAL_SHADER_PARMS; i++ ) {
7311 renderView->shaderParms[ i ] = gameLocal.globalShaderParms[ i ];
7313 renderView->globalMaterial = gameLocal.GetGlobalMaterial();
7314 renderView->time = gameLocal.time;
7316 // calculate size of 3D view
7319 renderView->width = SCREEN_WIDTH;
7320 renderView->height = SCREEN_HEIGHT;
7321 renderView->viewID = 0;
7323 // check if we should be drawing from a camera's POV
7324 if ( !noclip && (gameLocal.GetCamera() || privateCameraView) ) {
7325 // get origin, axis, and fov
7326 if ( privateCameraView ) {
7327 privateCameraView->GetViewParms( renderView );
7329 gameLocal.GetCamera()->GetViewParms( renderView );
7332 if ( g_stopTime.GetBool() ) {
7333 renderView->vieworg = firstPersonViewOrigin;
7334 renderView->viewaxis = firstPersonViewAxis;
7336 if ( !pm_thirdPerson.GetBool() ) {
7337 // set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7338 // allow the right player view weapons
7339 renderView->viewID = entityNumber + 1;
7341 } else if ( pm_thirdPerson.GetBool() ) {
7342 OffsetThirdPersonView( pm_thirdPersonAngle.GetFloat(), pm_thirdPersonRange.GetFloat(), pm_thirdPersonHeight.GetFloat(), pm_thirdPersonClip.GetBool() );
7343 } else if ( pm_thirdPersonDeath.GetBool() ) {
7344 range = gameLocal.time < minRespawnTime ? ( gameLocal.time + RAGDOLL_DEATH_TIME - minRespawnTime ) * ( 120.0f / RAGDOLL_DEATH_TIME ) : 120.0f;
7345 OffsetThirdPersonView( 0.0f, 20.0f + range, 0.0f, false );
7347 renderView->vieworg = firstPersonViewOrigin;
7348 renderView->viewaxis = firstPersonViewAxis;
7350 // set the viewID to the clientNum + 1, so we can suppress the right player bodies and
7351 // allow the right player view weapons
7352 renderView->viewID = entityNumber + 1;
7356 gameLocal.CalcFov( CalcFov( true ), renderView->fov_x, renderView->fov_y );
7359 if ( renderView->fov_y == 0 ) {
7360 common->Error( "renderView->fov_y == 0" );
7363 if ( g_showviewpos.GetBool() ) {
7364 gameLocal.Printf( "%s : %s\n", renderView->vieworg.ToString(), renderView->viewaxis.ToAngles().ToString() );
7373 void idPlayer::AddAIKill( void ) {
7377 if ( ( weapon_soulcube < 0 ) || ( inventory.weapons & ( 1 << weapon_soulcube ) ) == 0 ) {
7383 ammo_souls = idWeapon::GetAmmoNumForName( "ammo_souls" );
7384 max_souls = inventory.MaxAmmoForAmmoClass( this, "ammo_souls" );
7385 if ( inventory.ammo[ ammo_souls ] < max_souls ) {
7386 inventory.ammo[ ammo_souls ]++;
7387 if ( inventory.ammo[ ammo_souls ] >= max_souls ) {
7388 hud->HandleNamedEvent( "soulCubeReady" );
7389 StartSound( "snd_soulcube_ready", SND_CHANNEL_ANY, 0, false, NULL );
7396 idPlayer::SetSoulCubeProjectile
7399 void idPlayer::SetSoulCubeProjectile( idProjectile *projectile ) {
7400 soulCubeProjectile = projectile;
7405 idPlayer::AddProjectilesFired
7408 void idPlayer::AddProjectilesFired( int count ) {
7409 numProjectilesFired += count;
7414 idPlayer::AddProjectileHites
7417 void idPlayer::AddProjectileHits( int count ) {
7418 numProjectileHits += count;
7423 idPlayer::SetLastHitTime
7426 void idPlayer::SetLastHitTime( int time ) {
7427 idPlayer *aimed = NULL;
7429 if ( time && lastHitTime != time ) {
7434 // level start and inits
7437 if ( gameLocal.isMultiplayer && ( time - lastSndHitTime ) > 10 ) {
7438 lastSndHitTime = time;
7439 StartSound( "snd_hit_feedback", SND_CHANNEL_ANY, SSF_PRIVATE_SOUND, false, NULL );
7442 cursor->HandleNamedEvent( "hitTime" );
7445 if ( MPAim != -1 ) {
7446 if ( gameLocal.entities[ MPAim ] && gameLocal.entities[ MPAim ]->IsType( idPlayer::Type ) ) {
7447 aimed = static_cast< idPlayer * >( gameLocal.entities[ MPAim ] );
7450 // full highlight, no fade till loosing aim
7451 hud->SetStateString( "aim_text", gameLocal.userInfo[ MPAim ].GetString( "ui_name" ) );
7453 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7455 hud->HandleNamedEvent( "aim_flash" );
7456 MPAimHighlight = true;
7458 } else if ( lastMPAim != -1 ) {
7459 if ( gameLocal.entities[ lastMPAim ] && gameLocal.entities[ lastMPAim ]->IsType( idPlayer::Type ) ) {
7460 aimed = static_cast< idPlayer * >( gameLocal.entities[ lastMPAim ] );
7463 // start fading right away
7464 hud->SetStateString( "aim_text", gameLocal.userInfo[ lastMPAim ].GetString( "ui_name" ) );
7466 hud->SetStateFloat( "aim_color", aimed->colorBarIndex );
7468 hud->HandleNamedEvent( "aim_flash" );
7469 hud->HandleNamedEvent( "aim_fade" );
7470 MPAimHighlight = false;
7471 MPAimFadeTime = gameLocal.realClientTime;
7478 idPlayer::SetInfluenceLevel
7481 void idPlayer::SetInfluenceLevel( int level ) {
7482 if ( level != influenceActive ) {
7484 for ( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
7485 if ( ent->IsType( idProjectile::Type ) ) {
7486 // remove all projectiles
7487 ent->PostEventMS( &EV_Remove, 0 );
7490 if ( weaponEnabled && weapon.GetEntity() ) {
7491 weapon.GetEntity()->EnterCinematic();
7494 physicsObj.SetLinearVelocity( vec3_origin );
7495 if ( weaponEnabled && weapon.GetEntity() ) {
7496 weapon.GetEntity()->ExitCinematic();
7499 influenceActive = level;
7505 idPlayer::SetInfluenceView
7508 void idPlayer::SetInfluenceView( const char *mtr, const char *skinname, float radius, idEntity *ent ) {
7509 influenceMaterial = NULL;
7510 influenceEntity = NULL;
7511 influenceSkin = NULL;
7512 if ( mtr && *mtr ) {
7513 influenceMaterial = declManager->FindMaterial( mtr );
7515 if ( skinname && *skinname ) {
7516 influenceSkin = declManager->FindSkin( skinname );
7517 if ( head.GetEntity() ) {
7518 head.GetEntity()->GetRenderEntity()->shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time );
7522 influenceRadius = radius;
7523 if ( radius > 0.0f ) {
7524 influenceEntity = ent;
7530 idPlayer::SetInfluenceFov
7533 void idPlayer::SetInfluenceFov( float fov ) {
7542 bool idPlayer::OnLadder( void ) const {
7543 return physicsObj.OnLadder();
7548 idPlayer::Event_GetButtons
7551 void idPlayer::Event_GetButtons( void ) {
7552 idThread::ReturnInt( usercmd.buttons );
7557 idPlayer::Event_GetMove
7560 void idPlayer::Event_GetMove( void ) {
7561 idVec3 move( usercmd.forwardmove, usercmd.rightmove, usercmd.upmove );
7562 idThread::ReturnVector( move );
7567 idPlayer::Event_GetViewAngles
7570 void idPlayer::Event_GetViewAngles( void ) {
7571 idThread::ReturnVector( idVec3( viewAngles[0], viewAngles[1], viewAngles[2] ) );
7576 idPlayer::Event_StopFxFov
7579 void idPlayer::Event_StopFxFov( void ) {
7585 idPlayer::StartFxFov
7588 void idPlayer::StartFxFov( float duration ) {
7590 PostEventSec( &EV_Player_StopFxFov, duration );
7595 idPlayer::Event_EnableWeapon
7598 void idPlayer::Event_EnableWeapon( void ) {
7599 hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7600 weaponEnabled = true;
7601 if ( weapon.GetEntity() ) {
7602 weapon.GetEntity()->ExitCinematic();
7608 idPlayer::Event_DisableWeapon
7611 void idPlayer::Event_DisableWeapon( void ) {
7612 hiddenWeapon = gameLocal.world->spawnArgs.GetBool( "no_Weapons" );
7613 weaponEnabled = false;
7614 if ( weapon.GetEntity() ) {
7615 weapon.GetEntity()->EnterCinematic();
7621 idPlayer::Event_GetCurrentWeapon
7624 void idPlayer::Event_GetCurrentWeapon( void ) {
7627 if ( currentWeapon >= 0 ) {
7628 weapon = spawnArgs.GetString( va( "def_weapon%d", currentWeapon ) );
7629 idThread::ReturnString( weapon );
7631 idThread::ReturnString( "" );
7637 idPlayer::Event_GetPreviousWeapon
7640 void idPlayer::Event_GetPreviousWeapon( void ) {
7643 if ( previousWeapon >= 0 ) {
7644 int pw = ( gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) ? 0 : previousWeapon;
7645 weapon = spawnArgs.GetString( va( "def_weapon%d", pw) );
7646 idThread::ReturnString( weapon );
7648 idThread::ReturnString( spawnArgs.GetString( "def_weapon0" ) );
7654 idPlayer::Event_SelectWeapon
7657 void idPlayer::Event_SelectWeapon( const char *weaponName ) {
7661 if ( gameLocal.isClient ) {
7662 gameLocal.Warning( "Cannot switch weapons from script in multiplayer" );
7666 if ( hiddenWeapon && gameLocal.world->spawnArgs.GetBool( "no_Weapons" ) ) {
7667 idealWeapon = weapon_fists;
7668 weapon.GetEntity()->HideWeapon();
7673 for( i = 0; i < MAX_WEAPONS; i++ ) {
7674 if ( inventory.weapons & ( 1 << i ) ) {
7675 const char *weap = spawnArgs.GetString( va( "def_weapon%d", i ) );
7676 if ( !idStr::Cmp( weap, weaponName ) ) {
7683 if ( weaponNum < 0 ) {
7684 gameLocal.Warning( "%s is not carrying weapon '%s'", name.c_str(), weaponName );
7688 hiddenWeapon = false;
7689 idealWeapon = weaponNum;
7696 idPlayer::Event_GetWeaponEntity
7699 void idPlayer::Event_GetWeaponEntity( void ) {
7700 idThread::ReturnEntity( weapon.GetEntity() );
7705 idPlayer::Event_OpenPDA
7708 void idPlayer::Event_OpenPDA( void ) {
7709 if ( !gameLocal.isMultiplayer ) {
7716 idPlayer::Event_InPDA
7719 void idPlayer::Event_InPDA( void ) {
7720 idThread::ReturnInt( objectiveSystemOpen );
7725 idPlayer::TeleportDeath
7728 void idPlayer::TeleportDeath( int killer ) {
7729 teleportKiller = killer;
7734 idPlayer::Event_ExitTeleporter
7737 void idPlayer::Event_ExitTeleporter( void ) {
7742 exitEnt = teleportEntity.GetEntity();
7744 common->DPrintf( "Event_ExitTeleporter player %d while not being teleported\n", entityNumber );
7748 pushVel = exitEnt->spawnArgs.GetFloat( "push", "300" );
7750 if ( gameLocal.isServer ) {
7751 ServerSendEvent( EVENT_EXIT_TELEPORTER, NULL, false, -1 );
7754 SetPrivateCameraView( NULL );
7755 // setup origin and push according to the exit target
7756 SetOrigin( exitEnt->GetPhysics()->GetOrigin() + idVec3( 0, 0, CM_CLIP_EPSILON ) );
7757 SetViewAngles( exitEnt->GetPhysics()->GetAxis().ToAngles() );
7758 physicsObj.SetLinearVelocity( exitEnt->GetPhysics()->GetAxis()[ 0 ] * pushVel );
7759 physicsObj.ClearPushedVelocity();
7761 playerView.Flash( colorWhite, 120 );
7763 // clear the ik heights so model doesn't appear in the wrong place
7768 StartSound( "snd_teleport_exit", SND_CHANNEL_ANY, 0, false, NULL );
7770 if ( teleportKiller != -1 ) {
7771 // we got killed while being teleported
7772 Damage( gameLocal.entities[ teleportKiller ], gameLocal.entities[ teleportKiller ], vec3_origin, "damage_telefrag", 1.0f, INVALID_JOINT );
7773 teleportKiller = -1;
7775 // kill anything that would have waited at teleport exit
7776 gameLocal.KillBox( this );
7778 teleportEntity = NULL;
7783 idPlayer::ClientPredictionThink
7786 void idPlayer::ClientPredictionThink( void ) {
7787 renderEntity_t *headRenderEnt;
7789 oldFlags = usercmd.flags;
7790 oldButtons = usercmd.buttons;
7792 usercmd = gameLocal.usercmds[ entityNumber ];
7794 if ( entityNumber != gameLocal.localClientNum ) {
7795 // ignore attack button of other clients. that's no good for predictions
7796 usercmd.buttons &= ~BUTTON_ATTACK;
7799 buttonMask &= usercmd.buttons;
7800 usercmd.buttons &= ~buttonMask;
7802 if ( objectiveSystemOpen ) {
7803 usercmd.forwardmove = 0;
7804 usercmd.rightmove = 0;
7808 // clear the ik before we do anything else so the skeleton doesn't get updated twice
7809 walkIK.ClearJointMods();
7811 if ( gameLocal.isNewFrame ) {
7812 if ( ( usercmd.flags & UCF_IMPULSE_SEQUENCE ) != ( oldFlags & UCF_IMPULSE_SEQUENCE ) ) {
7813 PerformImpulse( usercmd.impulse );
7817 scoreBoardOpen = ( ( usercmd.buttons & BUTTON_SCORES ) != 0 || forceScoreBoard );
7823 // update the smoothed view angles
7824 if ( gameLocal.framenum >= smoothedFrame && entityNumber != gameLocal.localClientNum ) {
7825 idAngles anglesDiff = viewAngles - smoothedAngles;
7826 anglesDiff.Normalize180();
7827 if ( idMath::Fabs( anglesDiff.yaw ) < 90.0f && idMath::Fabs( anglesDiff.pitch ) < 90.0f ) {
7828 // smoothen by pushing back to the previous angles
7829 viewAngles -= gameLocal.clientSmoothing * anglesDiff;
7830 viewAngles.Normalize180();
7832 smoothedAngles = viewAngles;
7834 smoothedOriginUpdated = false;
7836 if ( !af.IsActive() ) {
7841 // don't allow client to move when lagged
7845 // update GUIs, Items, and character interactions
7848 // service animations
7849 if ( !spectating && !af.IsActive() ) {
7855 // clear out our pain flag so we can tell if we recieve any damage between now and the next time we think
7858 // calculate the exact bobbed view position, which is used to
7859 // position the view weapon, among other things
7860 CalculateFirstPersonView();
7862 // this may use firstPersonView, or a thirdPerson / camera view
7863 CalculateRenderView();
7865 if ( !gameLocal.inCinematic && weapon.GetEntity() && ( health > 0 ) && !( gameLocal.isMultiplayer && spectating ) ) {
7871 if ( gameLocal.isNewFrame ) {
7875 UpdateDeathSkin( false );
7877 if ( head.GetEntity() ) {
7878 headRenderEnt = head.GetEntity()->GetRenderEntity();
7880 headRenderEnt = NULL;
7883 if ( headRenderEnt ) {
7884 if ( influenceSkin ) {
7885 headRenderEnt->customSkin = influenceSkin;
7887 headRenderEnt->customSkin = NULL;
7891 if ( gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() ) {
7892 renderEntity.suppressShadowInViewID = 0;
7893 if ( headRenderEnt ) {
7894 headRenderEnt->suppressShadowInViewID = 0;
7897 renderEntity.suppressShadowInViewID = entityNumber+1;
7898 if ( headRenderEnt ) {
7899 headRenderEnt->suppressShadowInViewID = entityNumber+1;
7902 // never cast shadows from our first-person muzzle flashes
7903 renderEntity.suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7904 if ( headRenderEnt ) {
7905 headRenderEnt->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + entityNumber;
7908 if ( !gameLocal.inCinematic ) {
7912 if ( gameLocal.isMultiplayer ) {
7918 UpdateDamageEffects();
7922 if ( gameLocal.isNewFrame && entityNumber == gameLocal.localClientNum ) {
7923 playerView.CalculateShake();
7929 idPlayer::GetPhysicsToVisualTransform
7932 bool idPlayer::GetPhysicsToVisualTransform( idVec3 &origin, idMat3 &axis ) {
7933 if ( af.IsActive() ) {
7934 af.GetPhysicsToVisualTransform( origin, axis );
7938 // smoothen the rendered origin and angles of other clients
7939 // smooth self origin if snapshots are telling us prediction is off
7940 if ( gameLocal.isClient && gameLocal.framenum >= smoothedFrame && ( entityNumber != gameLocal.localClientNum || selfSmooth ) ) {
7941 // render origin and axis
7942 idMat3 renderAxis = viewAxis * GetPhysics()->GetAxis();
7943 idVec3 renderOrigin = GetPhysics()->GetOrigin() + modelOffset * renderAxis;
7945 // update the smoothed origin
7946 if ( !smoothedOriginUpdated ) {
7947 idVec2 originDiff = renderOrigin.ToVec2() - smoothedOrigin.ToVec2();
7948 if ( originDiff.LengthSqr() < Square( 100.0f ) ) {
7949 // smoothen by pushing back to the previous position
7951 assert( entityNumber == gameLocal.localClientNum );
7952 renderOrigin.ToVec2() -= net_clientSelfSmoothing.GetFloat() * originDiff;
7954 renderOrigin.ToVec2() -= gameLocal.clientSmoothing * originDiff;
7957 smoothedOrigin = renderOrigin;
7959 smoothedFrame = gameLocal.framenum;
7960 smoothedOriginUpdated = true;
7963 axis = idAngles( 0.0f, smoothedAngles.yaw, 0.0f ).ToMat3();
7964 origin = ( smoothedOrigin - GetPhysics()->GetOrigin() ) * axis.Transpose();
7969 origin = modelOffset;
7976 idPlayer::GetPhysicsToSoundTransform
7979 bool idPlayer::GetPhysicsToSoundTransform( idVec3 &origin, idMat3 &axis ) {
7982 if ( privateCameraView ) {
7983 camera = privateCameraView;
7985 camera = gameLocal.GetCamera();
7991 memset( &view, 0, sizeof( view ) );
7992 camera->GetViewParms( &view );
7993 origin = view.vieworg;
7994 axis = view.viewaxis;
7997 return idActor::GetPhysicsToSoundTransform( origin, axis );
8003 idPlayer::WriteToSnapshot
8006 void idPlayer::WriteToSnapshot( idBitMsgDelta &msg ) const {
8007 physicsObj.WriteToSnapshot( msg );
8008 WriteBindToSnapshot( msg );
8009 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[0] );
8010 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[1] );
8011 msg.WriteDeltaFloat( 0.0f, deltaViewAngles[2] );
8012 msg.WriteShort( health );
8013 msg.WriteBits( gameLocal.ServerRemapDecl( -1, DECL_ENTITYDEF, lastDamageDef ), gameLocal.entityDefBits );
8014 msg.WriteDir( lastDamageDir, 9 );
8015 msg.WriteShort( lastDamageLocation );
8016 msg.WriteBits( idealWeapon, idMath::BitsForInteger( MAX_WEAPONS ) );
8017 msg.WriteBits( inventory.weapons, MAX_WEAPONS );
8018 msg.WriteBits( weapon.GetSpawnId(), 32 );
8019 msg.WriteBits( spectator, idMath::BitsForInteger( MAX_CLIENTS ) );
8020 msg.WriteBits( lastHitToggle, 1 );
8021 msg.WriteBits( weaponGone, 1 );
8022 msg.WriteBits( isLagged, 1 );
8023 msg.WriteBits( isChatting, 1 );
8028 idPlayer::ReadFromSnapshot
8031 void idPlayer::ReadFromSnapshot( const idBitMsgDelta &msg ) {
8032 int i, oldHealth, newIdealWeapon, weaponSpawnId;
8033 bool newHitToggle, stateHitch;
8035 if ( snapshotSequence - lastSnapshotSequence > 1 ) {
8040 lastSnapshotSequence = snapshotSequence;
8044 physicsObj.ReadFromSnapshot( msg );
8045 ReadBindFromSnapshot( msg );
8046 deltaViewAngles[0] = msg.ReadDeltaFloat( 0.0f );
8047 deltaViewAngles[1] = msg.ReadDeltaFloat( 0.0f );
8048 deltaViewAngles[2] = msg.ReadDeltaFloat( 0.0f );
8049 health = msg.ReadShort();
8050 lastDamageDef = gameLocal.ClientRemapDecl( DECL_ENTITYDEF, msg.ReadBits( gameLocal.entityDefBits ) );
8051 lastDamageDir = msg.ReadDir( 9 );
8052 lastDamageLocation = msg.ReadShort();
8053 newIdealWeapon = msg.ReadBits( idMath::BitsForInteger( MAX_WEAPONS ) );
8054 inventory.weapons = msg.ReadBits( MAX_WEAPONS );
8055 weaponSpawnId = msg.ReadBits( 32 );
8056 spectator = msg.ReadBits( idMath::BitsForInteger( MAX_CLIENTS ) );
8057 newHitToggle = msg.ReadBits( 1 ) != 0;
8058 weaponGone = msg.ReadBits( 1 ) != 0;
8059 isLagged = msg.ReadBits( 1 ) != 0;
8060 isChatting = msg.ReadBits( 1 ) != 0;
8062 // no msg reading below this
8064 if ( weapon.SetSpawnId( weaponSpawnId ) ) {
8065 if ( weapon.GetEntity() ) {
8066 // maintain ownership locally
8067 weapon.GetEntity()->SetOwner( this );
8071 // if not a local client assume the client has all ammo types
8072 if ( entityNumber != gameLocal.localClientNum ) {
8073 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8074 inventory.ammo[ i ] = 999;
8078 if ( oldHealth > 0 && health <= 0 ) {
8080 // so we just hide and don't show a death skin
8081 UpdateDeathSkin( true );
8086 SetAnimState( ANIMCHANNEL_LEGS, "Legs_Death", 4 );
8087 SetAnimState( ANIMCHANNEL_TORSO, "Torso_Death", 4 );
8089 animator.ClearAllJoints();
8090 if ( entityNumber == gameLocal.localClientNum ) {
8091 playerView.Fade( colorBlack, 12000 );
8094 physicsObj.SetMovementType( PM_DEAD );
8095 if ( !stateHitch ) {
8096 StartSound( "snd_death", SND_CHANNEL_VOICE, 0, false, NULL );
8098 if ( weapon.GetEntity() ) {
8099 weapon.GetEntity()->OwnerDied();
8101 } else if ( oldHealth <= 0 && health > 0 ) {
8105 SetPhysics( &physicsObj );
8106 physicsObj.EnableClip();
8107 SetCombatContents( true );
8108 } else if ( health < oldHealth && health > 0 ) {
8110 lastDmgTime = gameLocal.time;
8113 const idDeclEntityDef *def = static_cast<const idDeclEntityDef *>( declManager->DeclByIndex( DECL_ENTITYDEF, lastDamageDef, false ) );
8115 playerView.DamageImpulse( lastDamageDir * viewAxis.Transpose(), &def->dict );
8116 AI_PAIN = Pain( NULL, NULL, oldHealth - health, lastDamageDir, lastDamageLocation );
8117 lastDmgTime = gameLocal.time;
8119 common->Warning( "NET: no damage def for damage feedback '%d'\n", lastDamageDef );
8122 } else if ( health > oldHealth && PowerUpActive( MEGAHEALTH ) && !stateHitch ) {
8123 // just pulse, for any health raise
8127 // If the player is alive, restore proper physics object
8128 if ( health > 0 && IsActiveAF() ) {
8130 SetPhysics( &physicsObj );
8131 physicsObj.EnableClip();
8132 SetCombatContents( true );
8135 if ( idealWeapon != newIdealWeapon ) {
8137 weaponCatchup = true;
8139 idealWeapon = newIdealWeapon;
8143 if ( lastHitToggle != newHitToggle ) {
8144 SetLastHitTime( gameLocal.realClientTime );
8147 if ( msg.HasChanged() ) {
8154 idPlayer::WritePlayerStateToSnapshot
8157 void idPlayer::WritePlayerStateToSnapshot( idBitMsgDelta &msg ) const {
8160 msg.WriteByte( bobCycle );
8161 msg.WriteLong( stepUpTime );
8162 msg.WriteFloat( stepUpDelta );
8163 msg.WriteShort( inventory.weapons );
8164 msg.WriteByte( inventory.armor );
8166 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8167 msg.WriteBits( inventory.ammo[i], ASYNC_PLAYER_INV_AMMO_BITS );
8169 for( i = 0; i < MAX_WEAPONS; i++ ) {
8170 msg.WriteBits( inventory.clip[i], ASYNC_PLAYER_INV_CLIP_BITS );
8176 idPlayer::ReadPlayerStateFromSnapshot
8179 void idPlayer::ReadPlayerStateFromSnapshot( const idBitMsgDelta &msg ) {
8182 bobCycle = msg.ReadByte();
8183 stepUpTime = msg.ReadLong();
8184 stepUpDelta = msg.ReadFloat();
8185 inventory.weapons = msg.ReadShort();
8186 inventory.armor = msg.ReadByte();
8188 for( i = 0; i < AMMO_NUMTYPES; i++ ) {
8189 ammo = msg.ReadBits( ASYNC_PLAYER_INV_AMMO_BITS );
8190 if ( gameLocal.time >= inventory.ammoPredictTime ) {
8191 inventory.ammo[ i ] = ammo;
8194 for( i = 0; i < MAX_WEAPONS; i++ ) {
8195 inventory.clip[i] = msg.ReadBits( ASYNC_PLAYER_INV_CLIP_BITS );
8201 idPlayer::ServerReceiveEvent
8204 bool idPlayer::ServerReceiveEvent( int event, int time, const idBitMsg &msg ) {
8206 if ( idEntity::ServerReceiveEvent( event, time, msg ) ) {
8210 // client->server events
8212 case EVENT_IMPULSE: {
8213 PerformImpulse( msg.ReadBits( 6 ) );
8224 idPlayer::ClientReceiveEvent
8227 bool idPlayer::ClientReceiveEvent( int event, int time, const idBitMsg &msg ) {
8232 case EVENT_EXIT_TELEPORTER:
8233 Event_ExitTeleporter();
8235 case EVENT_ABORT_TELEPORTER:
8236 SetPrivateCameraView( NULL );
8238 case EVENT_POWERUP: {
8239 powerup = msg.ReadShort();
8240 start = msg.ReadBits( 1 ) != 0;
8242 GivePowerUp( powerup, 0 );
8244 ClearPowerup( powerup );
8248 case EVENT_SPECTATE: {
8249 bool spectate = ( msg.ReadBits( 1 ) != 0 );
8250 Spectate( spectate );
8253 case EVENT_ADD_DAMAGE_EFFECT: {
8255 // if we're spectating, ignore
8256 // happens if the event and the spectate change are written on the server during the same frame (fraglimit)
8259 return idActor::ClientReceiveEvent( event, time, msg );
8262 return idActor::ClientReceiveEvent( event, time, msg );
8273 void idPlayer::Hide( void ) {
8277 weap = weapon.GetEntity();
8279 weap->HideWorldModel();
8288 void idPlayer::Show( void ) {
8292 weap = weapon.GetEntity();
8294 weap->ShowWorldModel();
8300 idPlayer::StartAudioLog
8303 void idPlayer::StartAudioLog( void ) {
8305 hud->HandleNamedEvent( "audioLogUp" );
8311 idPlayer::StopAudioLog
8314 void idPlayer::StopAudioLog( void ) {
8316 hud->HandleNamedEvent( "audioLogDown" );
8325 void idPlayer::ShowTip( const char *title, const char *tip, bool autoHide ) {
8329 hud->SetStateString( "tip", tip );
8330 hud->SetStateString( "tiptitle", title );
8331 hud->HandleNamedEvent( "tipWindowUp" );
8333 PostEventSec( &EV_Player_HideTip, 5.0f );
8343 void idPlayer::HideTip( void ) {
8344 hud->HandleNamedEvent( "tipWindowDown" );
8350 idPlayer::Event_HideTip
8353 void idPlayer::Event_HideTip( void ) {
8359 idPlayer::ShowObjective
8362 void idPlayer::ShowObjective( const char *obj ) {
8363 hud->HandleNamedEvent( obj );
8369 idPlayer::HideObjective
8372 void idPlayer::HideObjective( void ) {
8373 hud->HandleNamedEvent( "closeObjective" );
8374 objectiveUp = false;
8379 idPlayer::Event_StopAudioLog
8382 void idPlayer::Event_StopAudioLog( void ) {
8388 idPlayer::SetSpectateOrigin
8391 void idPlayer::SetSpectateOrigin( void ) {
8394 neworig = GetPhysics()->GetOrigin();
8395 neworig[ 2 ] += EyeHeight();
8397 SetOrigin( neworig );
8402 idPlayer::RemoveWeapon
8405 void idPlayer::RemoveWeapon( const char *weap ) {
8406 if ( weap && *weap ) {
8407 inventory.Drop( spawnArgs, spawnArgs.GetString( weap ), -1 );
8413 idPlayer::CanShowWeaponViewmodel
8416 bool idPlayer::CanShowWeaponViewmodel( void ) const {
8417 return showWeaponViewModel;
8422 idPlayer::SetLevelTrigger
8425 void idPlayer::SetLevelTrigger( const char *levelName, const char *triggerName ) {
8426 if ( levelName && *levelName && triggerName && *triggerName ) {
8427 idLevelTriggerInfo lti;
8428 lti.levelName = levelName;
8429 lti.triggerName = triggerName;
8430 inventory.levelTriggers.Append( lti );
8436 idPlayer::Event_LevelTrigger
8439 void idPlayer::Event_LevelTrigger( void ) {
8440 idStr mapName = gameLocal.GetMapName();
8441 mapName.StripPath();
8442 mapName.StripFileExtension();
8443 for ( int i = inventory.levelTriggers.Num() - 1; i >= 0; i-- ) {
8444 if ( idStr::Icmp( mapName, inventory.levelTriggers[i].levelName) == 0 ){
8445 idEntity *ent = gameLocal.FindEntity( inventory.levelTriggers[i].triggerName );
8447 ent->PostEventMS( &EV_Activate, 1, this );
8455 idPlayer::Event_Gibbed
8458 void idPlayer::Event_Gibbed( void ) {
8463 idPlayer::Event_GetIdealWeapon
8466 void idPlayer::Event_GetIdealWeapon( void ) {
8469 if ( idealWeapon >= 0 ) {
8470 weapon = spawnArgs.GetString( va( "def_weapon%d", idealWeapon ) );
8471 idThread::ReturnString( weapon );
8473 idThread::ReturnString( "" );
8479 idPlayer::UpdatePlayerIcons
8482 void idPlayer::UpdatePlayerIcons( void ) {
8483 int time = networkSystem->ServerGetClientTimeSinceLastPacket( entityNumber );
8484 if ( time > cvarSystem->GetCVarInteger( "net_clientMaxPrediction" ) ) {
8493 idPlayer::DrawPlayerIcons
8496 void idPlayer::DrawPlayerIcons( void ) {
8497 if ( !NeedsIcon() ) {
8498 playerIcon.FreeIcon();
8501 playerIcon.Draw( this, headJoint );
8506 idPlayer::HidePlayerIcons
8509 void idPlayer::HidePlayerIcons( void ) {
8510 playerIcon.FreeIcon();
8518 bool idPlayer::NeedsIcon( void ) {
8519 // local clients don't render their own icons... they're only info for other clients
8520 return entityNumber != gameLocal.localClientNum && ( isLagged || isChatting );